diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..94250d3b --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]] +} \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 357716f0..20b1ac0d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,20 +12,20 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Docker Build id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build image and push to Docker Hub and GitHub Container Registry - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v5 with: context: . tags: sandermertens/flecs.explorer:latest diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..0f68e8e2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Flecs Hub + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 9f6d61ef..4edb6694 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Flecs Explorer Web-based UI for monitoring Flecs applications, trying out queries & learning ECS -Screen Shot 2021-11-07 at 10 20 51 PM +Screenshot 2025-09-12 at 5 35 23 PM + +Screenshot 2025-09-12 at 5 48 15 PM A live version of the explorer is running @ https://flecs.dev/explorer @@ -16,7 +18,7 @@ In C: ecs_singleton_set(world, EcsRest, {0}); ``` -In C with the flecs app addon: +In C with the app addon: ```c ecs_app_run(world, &(ecs_app_desc_t) { .enable_rest = true @@ -28,6 +30,13 @@ In C++: world.set({}); ``` +In C++ with the app addon: +```cpp +world.app() + .enable_rest() + .run(); +``` + When the application is running, verify that the server works by going to: http://localhost:27750/entity/flecs @@ -72,6 +81,35 @@ docker run --name=explorer -p 80:80 --restart=unless-stopped -d sandermertens/fl You can now go to http://localhost to open the explorer. +### Statistics +The explorer can visualize statistics from Flecs applications. Statistics collection is disabled by default as it is not free. To enable it, import the `flecs.monitor` module: + +In C: +```c +ECS_IMPORT(world, FlecsMonitor); +``` + +In C++: +```c +world.import(); +``` + +Note that the statistics collection systems run as part of the default Flecs pipeline. If your application does not use systems, manually runs systems or uses a custom pipeline statistics collection will not run. To make sure the collection systems are ran, call this once in the main loop of your game: + +In C: +```c +ecs_progress(world, 0); +``` + +In C++ +```c +world.progress(); +``` + +You should now be able to see statistics in the explorer, which should look like this: + +Screenshot 2025-09-12 at 5 49 11 PM + ### URL options The following options can be added to the URL: diff --git a/deps/cglm.c b/deps/cglm.c new file mode 100644 index 00000000..d84c3229 --- /dev/null +++ b/deps/cglm.c @@ -0,0 +1,4689 @@ +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#include "cglm.h" +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_call_h +#define cglm_call_h +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_vec2_h +#define cglmc_vec2_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_vec2(float * __restrict v, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_copy(vec2 a, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_zero(vec2 v); + +CGLM_EXPORT +void +glmc_vec2_one(vec2 v); + +CGLM_EXPORT +float +glmc_vec2_dot(vec2 a, vec2 b); + +CGLM_EXPORT +float +glmc_vec2_cross(vec2 a, vec2 b); + +CGLM_EXPORT +float +glmc_vec2_norm2(vec2 v); + +CGLM_EXPORT +float +glmc_vec2_norm(vec2 v); + +CGLM_EXPORT +void +glmc_vec2_add(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_adds(vec2 v, float s, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_sub(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_subs(vec2 v, float s, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_mul(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_scale(vec2 v, float s, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_scale_as(vec2 v, float s, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_div(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_divs(vec2 v, float s, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_addadd(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_subadd(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_muladd(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_muladds(vec2 a, float s, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_maxadd(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_minadd(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_negate_to(vec2 v, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_negate(vec2 v); + +CGLM_EXPORT +void +glmc_vec2_normalize(vec2 v); + +CGLM_EXPORT +void +glmc_vec2_normalize_to(vec2 v, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_rotate(vec2 v, float angle, vec2 dest); + +CGLM_EXPORT +float +glmc_vec2_distance2(vec2 a, vec2 b); + +CGLM_EXPORT +float +glmc_vec2_distance(vec2 a, vec2 b); + +CGLM_EXPORT +void +glmc_vec2_maxv(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_minv(vec2 a, vec2 b, vec2 dest); + +CGLM_EXPORT +void +glmc_vec2_clamp(vec2 v, float minval, float maxval); + +CGLM_EXPORT +void +glmc_vec2_lerp(vec2 from, vec2 to, float t, vec2 dest); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_vec2_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_vec3_h +#define cglmc_vec3_h +#ifdef __cplusplus +extern "C" { +#endif + + +/* DEPRECATED! use _copy, _ucopy versions */ +#define glmc_vec_dup(v, dest) glmc_vec3_copy(v, dest) +#define glmc_vec3_flipsign(v) glmc_vec3_negate(v) +#define glmc_vec3_flipsign_to(v, dest) glmc_vec3_negate_to(v, dest) +#define glmc_vec3_inv(v) glmc_vec3_negate(v) +#define glmc_vec3_inv_to(v, dest) glmc_vec3_negate_to(v, dest) + +CGLM_EXPORT +void +glmc_vec3(vec4 v4, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_copy(vec3 a, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_zero(vec3 v); + +CGLM_EXPORT +void +glmc_vec3_one(vec3 v); + +CGLM_EXPORT +float +glmc_vec3_dot(vec3 a, vec3 b); + +CGLM_EXPORT +void +glmc_vec3_cross(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_crossn(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +float +glmc_vec3_norm(vec3 v); + +CGLM_EXPORT +float +glmc_vec3_norm2(vec3 v); + +CGLM_EXPORT +float +glmc_vec3_norm_one(vec3 v); + +CGLM_EXPORT +float +glmc_vec3_norm_inf(vec3 v); + +CGLM_EXPORT +void +glmc_vec3_normalize_to(vec3 v, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_normalize(vec3 v); + +CGLM_EXPORT +void +glmc_vec3_add(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_adds(vec3 v, float s, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_sub(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_subs(vec3 v, float s, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_mul(vec3 a, vec3 b, vec3 d); + +CGLM_EXPORT +void +glmc_vec3_scale(vec3 v, float s, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_scale_as(vec3 v, float s, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_div(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_divs(vec3 a, float s, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_addadd(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_subadd(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_muladd(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_muladds(vec3 a, float s, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_maxadd(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_minadd(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_negate(vec3 v); + +CGLM_EXPORT +void +glmc_vec3_negate_to(vec3 v, vec3 dest); + +CGLM_EXPORT +float +glmc_vec3_angle(vec3 a, vec3 b); + +CGLM_EXPORT +void +glmc_vec3_rotate(vec3 v, float angle, vec3 axis); + +CGLM_EXPORT +void +glmc_vec3_rotate_m4(mat4 m, vec3 v, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_rotate_m3(mat3 m, vec3 v, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_proj(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_center(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +float +glmc_vec3_distance2(vec3 a, vec3 b); + +CGLM_EXPORT +float +glmc_vec3_distance(vec3 a, vec3 b); + +CGLM_EXPORT +void +glmc_vec3_maxv(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_minv(vec3 a, vec3 b, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_clamp(vec3 v, float minVal, float maxVal); + +CGLM_EXPORT +void +glmc_vec3_ortho(vec3 v, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_lerp(vec3 from, vec3 to, float t, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_lerpc(vec3 from, vec3 to, float t, vec3 dest); + +CGLM_INLINE +void +glmc_vec3_mix(vec3 from, vec3 to, float t, vec3 dest) { + glmc_vec3_lerp(from, to, t, dest); +} + +CGLM_INLINE +void +glmc_vec3_mixc(vec3 from, vec3 to, float t, vec3 dest) { + glmc_vec3_lerpc(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_vec3_step_uni(float edge, vec3 x, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_step(vec3 edge, vec3 x, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_smoothstep_uni(float edge0, float edge1, vec3 x, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_smoothstep(vec3 edge0, vec3 edge1, vec3 x, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_smoothinterp(vec3 from, vec3 to, float t, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_smoothinterpc(vec3 from, vec3 to, float t, vec3 dest); + +/* ext */ + +CGLM_EXPORT +void +glmc_vec3_mulv(vec3 a, vec3 b, vec3 d); + +CGLM_EXPORT +void +glmc_vec3_broadcast(float val, vec3 d); + +CGLM_EXPORT +void +glmc_vec3_fill(vec3 v, float val); + +CGLM_EXPORT +bool +glmc_vec3_eq(vec3 v, float val); + +CGLM_EXPORT +bool +glmc_vec3_eq_eps(vec3 v, float val); + +CGLM_EXPORT +bool +glmc_vec3_eq_all(vec3 v); + +CGLM_EXPORT +bool +glmc_vec3_eqv(vec3 a, vec3 b); + +CGLM_EXPORT +bool +glmc_vec3_eqv_eps(vec3 a, vec3 b); + +CGLM_EXPORT +float +glmc_vec3_max(vec3 v); + +CGLM_EXPORT +float +glmc_vec3_min(vec3 v); + +CGLM_EXPORT +bool +glmc_vec3_isnan(vec3 v); + +CGLM_EXPORT +bool +glmc_vec3_isinf(vec3 v); + +CGLM_EXPORT +bool +glmc_vec3_isvalid(vec3 v); + +CGLM_EXPORT +void +glmc_vec3_sign(vec3 v, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_abs(vec3 v, vec3 dest); + +CGLM_EXPORT +void +glmc_vec3_fract(vec3 v, vec3 dest); + +CGLM_EXPORT +float +glmc_vec3_hadd(vec3 v); + +CGLM_EXPORT +void +glmc_vec3_sqrt(vec3 v, vec3 dest); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_vec3_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_vec4_h +#define cglmc_vec4_h +#ifdef __cplusplus +extern "C" { +#endif + + +/* DEPRECATED! use _copy, _ucopy versions */ +#define glmc_vec4_dup3(v, dest) glmc_vec4_copy3(v, dest) +#define glmc_vec4_dup(v, dest) glmc_vec4_copy(v, dest) +#define glmc_vec4_flipsign(v) glmc_vec4_negate(v) +#define glmc_vec4_flipsign_to(v, dest) glmc_vec4_negate_to(v, dest) +#define glmc_vec4_inv(v) glmc_vec4_negate(v) +#define glmc_vec4_inv_to(v, dest) glmc_vec4_negate_to(v, dest) + +CGLM_EXPORT +void +glmc_vec4(vec3 v3, float last, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_zero(vec4 v); + +CGLM_EXPORT +void +glmc_vec4_one(vec4 v); + +CGLM_EXPORT +void +glmc_vec4_copy3(vec4 v, vec3 dest); + +CGLM_EXPORT +void +glmc_vec4_copy(vec4 v, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_ucopy(vec4 v, vec4 dest); + +CGLM_EXPORT +float +glmc_vec4_dot(vec4 a, vec4 b); + +CGLM_EXPORT +float +glmc_vec4_norm(vec4 v); + +CGLM_EXPORT +float +glmc_vec4_norm2(vec4 v); + +CGLM_EXPORT +float +glmc_vec4_norm_one(vec4 v); + +CGLM_EXPORT +float +glmc_vec4_norm_inf(vec4 v); + +CGLM_EXPORT +void +glmc_vec4_normalize_to(vec4 v, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_normalize(vec4 v); + +CGLM_EXPORT +void +glmc_vec4_add(vec4 a, vec4 b, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_adds(vec4 v, float s, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_sub(vec4 a, vec4 b, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_subs(vec4 v, float s, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_mul(vec4 a, vec4 b, vec4 d); + +CGLM_EXPORT +void +glmc_vec4_scale(vec4 v, float s, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_scale_as(vec3 v, float s, vec3 dest); + +CGLM_EXPORT +void +glmc_vec4_div(vec4 a, vec4 b, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_divs(vec4 v, float s, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_addadd(vec4 a, vec4 b, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_subadd(vec4 a, vec4 b, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_muladd(vec4 a, vec4 b, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_muladds(vec4 a, float s, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_maxadd(vec4 a, vec4 b, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_minadd(vec4 a, vec4 b, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_negate(vec4 v); + +CGLM_EXPORT +void +glmc_vec4_negate_to(vec4 v, vec4 dest); + +CGLM_EXPORT +float +glmc_vec4_distance(vec4 a, vec4 b); + +CGLM_EXPORT +float +glmc_vec4_distance2(vec4 a, vec4 b); + +CGLM_EXPORT +void +glmc_vec4_maxv(vec4 a, vec4 b, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_minv(vec4 a, vec4 b, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_clamp(vec4 v, float minVal, float maxVal); + +CGLM_EXPORT +void +glmc_vec4_lerp(vec4 from, vec4 to, float t, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_lerpc(vec4 from, vec4 to, float t, vec4 dest); + +CGLM_INLINE +void +glmc_vec4_mix(vec4 from, vec4 to, float t, vec4 dest) { + glmc_vec4_lerp(from, to, t, dest); +} + +CGLM_INLINE +void +glmc_vec4_mixc(vec4 from, vec4 to, float t, vec4 dest) { + glmc_vec4_lerpc(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_vec4_step_uni(float edge, vec4 x, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_step(vec4 edge, vec4 x, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_smoothstep_uni(float edge0, float edge1, vec4 x, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_smoothstep(vec4 edge0, vec4 edge1, vec4 x, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_smoothinterp(vec4 from, vec4 to, float t, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_smoothinterpc(vec4 from, vec4 to, float t, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_cubic(float s, vec4 dest); + +/* ext */ + +CGLM_EXPORT +void +glmc_vec4_mulv(vec4 a, vec4 b, vec4 d); + +CGLM_EXPORT +void +glmc_vec4_broadcast(float val, vec4 d); + +CGLM_EXPORT +void +glmc_vec4_fill(vec4 v, float val); + +CGLM_EXPORT +bool +glmc_vec4_eq(vec4 v, float val); + +CGLM_EXPORT +bool +glmc_vec4_eq_eps(vec4 v, float val); + +CGLM_EXPORT +bool +glmc_vec4_eq_all(vec4 v); + +CGLM_EXPORT +bool +glmc_vec4_eqv(vec4 a, vec4 b); + +CGLM_EXPORT +bool +glmc_vec4_eqv_eps(vec4 a, vec4 b); + +CGLM_EXPORT +float +glmc_vec4_max(vec4 v); + +CGLM_EXPORT +float +glmc_vec4_min(vec4 v); + +CGLM_EXPORT +bool +glmc_vec4_isnan(vec4 v); + +CGLM_EXPORT +bool +glmc_vec4_isinf(vec4 v); + +CGLM_EXPORT +bool +glmc_vec4_isvalid(vec4 v); + +CGLM_EXPORT +void +glmc_vec4_sign(vec4 v, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_abs(vec4 v, vec4 dest); + +CGLM_EXPORT +void +glmc_vec4_fract(vec4 v, vec4 dest); + +CGLM_EXPORT +float +glmc_vec4_hadd(vec4 v); + +CGLM_EXPORT +void +glmc_vec4_sqrt(vec4 v, vec4 dest); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_vec4_h */ + + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_mat2_h +#define cglmc_mat2_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_mat2_copy(mat2 mat, mat2 dest); + +CGLM_EXPORT +void +glmc_mat2_identity(mat2 mat); + +CGLM_EXPORT +void +glmc_mat2_identity_array(mat2 * __restrict mat, size_t count); + +CGLM_EXPORT +void +glmc_mat2_zero(mat2 mat); + +CGLM_EXPORT +void +glmc_mat2_mul(mat2 m1, mat2 m2, mat2 dest); + +CGLM_EXPORT +void +glmc_mat2_transpose_to(mat2 m, mat2 dest); + +CGLM_EXPORT +void +glmc_mat2_transpose(mat2 m); + +CGLM_EXPORT +void +glmc_mat2_mulv(mat2 m, vec2 v, vec2 dest); + +CGLM_EXPORT +float +glmc_mat2_trace(mat2 m); + +CGLM_EXPORT +void +glmc_mat2_scale(mat2 m, float s); + +CGLM_EXPORT +float +glmc_mat2_det(mat2 mat); + +CGLM_EXPORT +void +glmc_mat2_inv(mat2 mat, mat2 dest); + +CGLM_EXPORT +void +glmc_mat2_swap_col(mat2 mat, int col1, int col2); + +CGLM_EXPORT +void +glmc_mat2_swap_row(mat2 mat, int row1, int row2); + +CGLM_EXPORT +float +glmc_mat2_rmc(vec2 r, mat2 m, vec2 c); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_mat2_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_mat3_h +#define cglmc_mat3_h +#ifdef __cplusplus +extern "C" { +#endif + + +/* DEPRECATED! use _copy, _ucopy versions */ +#define glmc_mat3_dup(mat, dest) glmc_mat3_copy(mat, dest) + +CGLM_EXPORT +void +glmc_mat3_copy(mat3 mat, mat3 dest); + +CGLM_EXPORT +void +glmc_mat3_identity(mat3 mat); + +CGLM_EXPORT +void +glmc_mat3_zero(mat3 mat); + +CGLM_EXPORT +void +glmc_mat3_identity_array(mat3 * __restrict mat, size_t count); + +CGLM_EXPORT +void +glmc_mat3_mul(mat3 m1, mat3 m2, mat3 dest); + +CGLM_EXPORT +void +glmc_mat3_transpose_to(mat3 m, mat3 dest); + +CGLM_EXPORT +void +glmc_mat3_transpose(mat3 m); + +CGLM_EXPORT +void +glmc_mat3_mulv(mat3 m, vec3 v, vec3 dest); + +CGLM_EXPORT +float +glmc_mat3_trace(mat3 m); + +CGLM_EXPORT +void +glmc_mat3_quat(mat3 m, versor dest); + +CGLM_EXPORT +void +glmc_mat3_scale(mat3 m, float s); + +CGLM_EXPORT +float +glmc_mat3_det(mat3 mat); + +CGLM_EXPORT +void +glmc_mat3_inv(mat3 mat, mat3 dest); + +CGLM_EXPORT +void +glmc_mat3_swap_col(mat3 mat, int col1, int col2); + +CGLM_EXPORT +void +glmc_mat3_swap_row(mat3 mat, int row1, int row2); + +CGLM_EXPORT +float +glmc_mat3_rmc(vec3 r, mat3 m, vec3 c); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_mat3_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_mat_h +#define cglmc_mat_h +#ifdef __cplusplus +extern "C" { +#endif + + +/* DEPRECATED! use _copy, _ucopy versions */ +#define glmc_mat4_udup(mat, dest) glmc_mat4_ucopy(mat, dest) +#define glmc_mat4_dup(mat, dest) glmc_mat4_copy(mat, dest) + +CGLM_EXPORT +void +glmc_mat4_ucopy(mat4 mat, mat4 dest); + +CGLM_EXPORT +void +glmc_mat4_copy(mat4 mat, mat4 dest); + +CGLM_EXPORT +void +glmc_mat4_identity(mat4 mat); + +CGLM_EXPORT +void +glmc_mat4_identity_array(mat4 * __restrict mat, size_t count); + +CGLM_EXPORT +void +glmc_mat4_zero(mat4 mat); + +CGLM_EXPORT +void +glmc_mat4_pick3(mat4 mat, mat3 dest); + +CGLM_EXPORT +void +glmc_mat4_pick3t(mat4 mat, mat3 dest); + +CGLM_EXPORT +void +glmc_mat4_ins3(mat3 mat, mat4 dest); + +CGLM_EXPORT +void +glmc_mat4_mul(mat4 m1, mat4 m2, mat4 dest); + +CGLM_EXPORT +void +glmc_mat4_mulN(mat4 * __restrict matrices[], uint32_t len, mat4 dest); + +CGLM_EXPORT +void +glmc_mat4_mulv(mat4 m, vec4 v, vec4 dest); + +CGLM_EXPORT +void +glmc_mat4_mulv3(mat4 m, vec3 v, float last, vec3 dest); + +CGLM_EXPORT +float +glmc_mat4_trace(mat4 m); + +CGLM_EXPORT +float +glmc_mat4_trace3(mat4 m); + +CGLM_EXPORT +void +glmc_mat4_quat(mat4 m, versor dest); + +CGLM_EXPORT +void +glmc_mat4_transpose_to(mat4 m, mat4 dest); + +CGLM_EXPORT +void +glmc_mat4_transpose(mat4 m); + +CGLM_EXPORT +void +glmc_mat4_scale_p(mat4 m, float s); + +CGLM_EXPORT +void +glmc_mat4_scale(mat4 m, float s); + +CGLM_EXPORT +float +glmc_mat4_det(mat4 mat); + +CGLM_EXPORT +void +glmc_mat4_inv(mat4 mat, mat4 dest); + +CGLM_EXPORT +void +glmc_mat4_inv_precise(mat4 mat, mat4 dest); + +CGLM_EXPORT +void +glmc_mat4_inv_fast(mat4 mat, mat4 dest); + +CGLM_EXPORT +void +glmc_mat4_swap_col(mat4 mat, int col1, int col2); + +CGLM_EXPORT +void +glmc_mat4_swap_row(mat4 mat, int row1, int row2); + +CGLM_EXPORT +float +glmc_mat4_rmc(vec4 r, mat4 m, vec4 c); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_mat_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_affine_h +#define cglmc_affine_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_translate_make(mat4 m, vec3 v); + +CGLM_EXPORT +void +glmc_translate_to(mat4 m, vec3 v, mat4 dest); + +CGLM_EXPORT +void +glmc_translate(mat4 m, vec3 v); + +CGLM_EXPORT +void +glmc_translate_x(mat4 m, float to); + +CGLM_EXPORT +void +glmc_translate_y(mat4 m, float to); + +CGLM_EXPORT +void +glmc_translate_z(mat4 m, float to); + +CGLM_EXPORT +void +glmc_scale_make(mat4 m, vec3 v); + +CGLM_EXPORT +void +glmc_scale_to(mat4 m, vec3 v, mat4 dest); + +CGLM_EXPORT +void +glmc_scale(mat4 m, vec3 v); + +CGLM_EXPORT +void +glmc_scale_uni(mat4 m, float s); + +CGLM_EXPORT +void +glmc_rotate_x(mat4 m, float rad, mat4 dest); + +CGLM_EXPORT +void +glmc_rotate_y(mat4 m, float rad, mat4 dest); + +CGLM_EXPORT +void +glmc_rotate_z(mat4 m, float rad, mat4 dest); + +CGLM_EXPORT +void +glmc_rotate_make(mat4 m, float angle, vec3 axis); + +CGLM_EXPORT +void +glmc_rotate(mat4 m, float angle, vec3 axis); + +CGLM_EXPORT +void +glmc_rotate_at(mat4 m, vec3 pivot, float angle, vec3 axis); + +CGLM_EXPORT +void +glmc_rotate_atm(mat4 m, vec3 pivot, float angle, vec3 axis); + +CGLM_EXPORT +void +glmc_decompose_scalev(mat4 m, vec3 s); + +CGLM_EXPORT +bool +glmc_uniscaled(mat4 m); + +CGLM_EXPORT +void +glmc_decompose_rs(mat4 m, mat4 r, vec3 s); + +CGLM_EXPORT +void +glmc_decompose(mat4 m, vec4 t, mat4 r, vec3 s); + +/* affine-mat */ + +CGLM_EXPORT +void +glmc_mul(mat4 m1, mat4 m2, mat4 dest); + +CGLM_EXPORT +void +glmc_mul_rot(mat4 m1, mat4 m2, mat4 dest); + +CGLM_EXPORT +void +glmc_inv_tr(mat4 mat); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_affine_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_cam_h +#define cglmc_cam_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_frustum(float left, + float right, + float bottom, + float top, + float nearVal, + float farVal, + mat4 dest); + +CGLM_EXPORT +void +glmc_ortho(float left, + float right, + float bottom, + float top, + float nearVal, + float farVal, + mat4 dest); + +CGLM_EXPORT +void +glmc_ortho_aabb(vec3 box[2], mat4 dest); + +CGLM_EXPORT +void +glmc_ortho_aabb_p(vec3 box[2], float padding, mat4 dest); + +CGLM_EXPORT +void +glmc_ortho_aabb_pz(vec3 box[2], float padding, mat4 dest); + +CGLM_EXPORT +void +glmc_ortho_default(float aspect, mat4 dest); + +CGLM_EXPORT +void +glmc_ortho_default_s(float aspect, float size, mat4 dest); + +CGLM_EXPORT +void +glmc_perspective(float fovy, + float aspect, + float nearVal, + float farVal, + mat4 dest); + +CGLM_EXPORT +void +glmc_persp_move_far(mat4 proj, float deltaFar); + +CGLM_EXPORT +void +glmc_perspective_default(float aspect, mat4 dest); + +CGLM_EXPORT +void +glmc_perspective_resize(float aspect, mat4 proj); + +CGLM_EXPORT +void +glmc_lookat(vec3 eye, vec3 center, vec3 up, mat4 dest); + +CGLM_EXPORT +void +glmc_look(vec3 eye, vec3 dir, vec3 up, mat4 dest); + +CGLM_EXPORT +void +glmc_look_anyup(vec3 eye, vec3 dir, mat4 dest); + +CGLM_EXPORT +void +glmc_persp_decomp(mat4 proj, + float * __restrict nearVal, + float * __restrict farVal, + float * __restrict top, + float * __restrict bottom, + float * __restrict left, + float * __restrict right); + +CGLM_EXPORT +void +glmc_persp_decompv(mat4 proj, float dest[6]); + +CGLM_EXPORT +void +glmc_persp_decomp_x(mat4 proj, + float * __restrict left, + float * __restrict right); + +CGLM_EXPORT +void +glmc_persp_decomp_y(mat4 proj, + float * __restrict top, + float * __restrict bottom); + +CGLM_EXPORT +void +glmc_persp_decomp_z(mat4 proj, + float * __restrict nearVal, + float * __restrict farVal); + +CGLM_EXPORT +void +glmc_persp_decomp_far(mat4 proj, float * __restrict farVal); + +CGLM_EXPORT +void +glmc_persp_decomp_near(mat4 proj, float * __restrict nearVal); + +CGLM_EXPORT +float +glmc_persp_fovy(mat4 proj); + +CGLM_EXPORT +float +glmc_persp_aspect(mat4 proj); + +CGLM_EXPORT +void +glmc_persp_sizes(mat4 proj, float fovy, vec4 dest); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_cam_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_quat_h +#define cglmc_quat_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_quat_identity(versor q); + +CGLM_EXPORT +void +glmc_quat_identity_array(versor * __restrict q, size_t count); + +CGLM_EXPORT +void +glmc_quat_init(versor q, float x, float y, float z, float w); + +CGLM_EXPORT +void +glmc_quat(versor q, float angle, float x, float y, float z); + +CGLM_EXPORT +void +glmc_quatv(versor q, float angle, vec3 axis); + +CGLM_EXPORT +void +glmc_quat_copy(versor q, versor dest); + +CGLM_EXPORT +float +glmc_quat_norm(versor q); + +CGLM_EXPORT +void +glmc_quat_normalize_to(versor q, versor dest); + +CGLM_EXPORT +void +glmc_quat_normalize(versor q); + +CGLM_EXPORT +float +glmc_quat_dot(versor p, versor q); + +CGLM_EXPORT +void +glmc_quat_conjugate(versor q, versor dest); + +CGLM_EXPORT +void +glmc_quat_inv(versor q, versor dest); + +CGLM_EXPORT +void +glmc_quat_add(versor p, versor q, versor dest); + +CGLM_EXPORT +void +glmc_quat_sub(versor p, versor q, versor dest); + +CGLM_EXPORT +float +glmc_quat_real(versor q); + +CGLM_EXPORT +void +glmc_quat_imag(versor q, vec3 dest); + +CGLM_EXPORT +void +glmc_quat_imagn(versor q, vec3 dest); + +CGLM_EXPORT +float +glmc_quat_imaglen(versor q); + +CGLM_EXPORT +float +glmc_quat_angle(versor q); + +CGLM_EXPORT +void +glmc_quat_axis(versor q, vec3 dest); + +CGLM_EXPORT +void +glmc_quat_mul(versor p, versor q, versor dest); + +CGLM_EXPORT +void +glmc_quat_mat4(versor q, mat4 dest); + +CGLM_EXPORT +void +glmc_quat_mat4t(versor q, mat4 dest); + +CGLM_EXPORT +void +glmc_quat_mat3(versor q, mat3 dest); + +CGLM_EXPORT +void +glmc_quat_mat3t(versor q, mat3 dest); + +CGLM_EXPORT +void +glmc_quat_lerp(versor from, versor to, float t, versor dest); + +CGLM_EXPORT +void +glmc_quat_lerpc(versor from, versor to, float t, versor dest); + +CGLM_EXPORT +void +glmc_quat_slerp(versor q, versor r, float t, versor dest); + +CGLM_EXPORT +void +glmc_quat_look(vec3 eye, versor ori, mat4 dest); + +CGLM_EXPORT +void +glmc_quat_for(vec3 dir, vec3 up, versor dest); + +CGLM_EXPORT +void +glmc_quat_forp(vec3 from, vec3 to, vec3 up, versor dest); + +CGLM_EXPORT +void +glmc_quat_rotatev(versor from, vec3 to, vec3 dest); + +CGLM_EXPORT +void +glmc_quat_rotate(mat4 m, versor q, mat4 dest); + +CGLM_EXPORT +void +glmc_quat_rotate_at(mat4 model, versor q, vec3 pivot); + +CGLM_EXPORT +void +glmc_quat_rotate_atm(mat4 m, versor q, vec3 pivot); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_quat_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_euler_h +#define cglmc_euler_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_euler_angles(mat4 m, vec3 dest); + +CGLM_EXPORT +void +glmc_euler(vec3 angles, mat4 dest); + +CGLM_EXPORT +void +glmc_euler_xyz(vec3 angles, mat4 dest); + +CGLM_EXPORT +void +glmc_euler_zyx(vec3 angles, mat4 dest); + +CGLM_EXPORT +void +glmc_euler_zxy(vec3 angles, mat4 dest); + +CGLM_EXPORT +void +glmc_euler_xzy(vec3 angles, mat4 dest); + +CGLM_EXPORT +void +glmc_euler_yzx(vec3 angles, mat4 dest); + +CGLM_EXPORT +void +glmc_euler_yxz(vec3 angles, mat4 dest); + +CGLM_EXPORT +void +glmc_euler_by_order(vec3 angles, glm_euler_seq axis, mat4 dest); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_euler_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_plane_h +#define cglmc_plane_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_plane_normalize(vec4 plane); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_plane_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_frustum_h +#define cglmc_frustum_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_frustum_planes(mat4 m, vec4 dest[6]); + +CGLM_EXPORT +void +glmc_frustum_corners(mat4 invMat, vec4 dest[8]); + +CGLM_EXPORT +void +glmc_frustum_center(vec4 corners[8], vec4 dest); + +CGLM_EXPORT +void +glmc_frustum_box(vec4 corners[8], mat4 m, vec3 box[2]); + +CGLM_EXPORT +void +glmc_frustum_corners_at(vec4 corners[8], + float splitDist, + float farDist, + vec4 planeCorners[4]); +#ifdef __cplusplus +} +#endif +#endif /* cglmc_frustum_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_box_h +#define cglmc_box_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_aabb_transform(vec3 box[2], mat4 m, vec3 dest[2]); + +CGLM_EXPORT +void +glmc_aabb_merge(vec3 box1[2], vec3 box2[2], vec3 dest[2]); + +CGLM_EXPORT +void +glmc_aabb_crop(vec3 box[2], vec3 cropBox[2], vec3 dest[2]); + +CGLM_EXPORT +void +glmc_aabb_crop_until(vec3 box[2], + vec3 cropBox[2], + vec3 clampBox[2], + vec3 dest[2]); + +CGLM_EXPORT +bool +glmc_aabb_frustum(vec3 box[2], vec4 planes[6]); + +CGLM_EXPORT +void +glmc_aabb_invalidate(vec3 box[2]); + +CGLM_EXPORT +bool +glmc_aabb_isvalid(vec3 box[2]); + +CGLM_EXPORT +float +glmc_aabb_size(vec3 box[2]); + +CGLM_EXPORT +float +glmc_aabb_radius(vec3 box[2]); + +CGLM_EXPORT +void +glmc_aabb_center(vec3 box[2], vec3 dest); + +CGLM_EXPORT +bool +glmc_aabb_aabb(vec3 box[2], vec3 other[2]); + +CGLM_EXPORT +bool +glmc_aabb_point(vec3 box[2], vec3 point); + +CGLM_EXPORT +bool +glmc_aabb_contains(vec3 box[2], vec3 other[2]); + +CGLM_EXPORT +bool +glmc_aabb_sphere(vec3 box[2], vec4 s); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_box_h */ + + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_io_h +#define cglmc_io_h + +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_mat4_print(mat4 matrix, + FILE * __restrict ostream); + +CGLM_EXPORT +void +glmc_mat3_print(mat3 matrix, + FILE * __restrict ostream); + +CGLM_EXPORT +void +glmc_vec4_print(vec4 vec, + FILE * __restrict ostream); + +CGLM_EXPORT +void +glmc_vec3_print(vec3 vec, + FILE * __restrict ostream); + +CGLM_EXPORT +void +glmc_versor_print(versor vec, + FILE * __restrict ostream); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_io_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_project_h +#define cglmc_project_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_unprojecti(vec3 pos, mat4 invMat, vec4 vp, vec3 dest); + +CGLM_EXPORT +void +glmc_unproject(vec3 pos, mat4 m, vec4 vp, vec3 dest); + +CGLM_EXPORT +void +glmc_project(vec3 pos, mat4 m, vec4 vp, vec3 dest); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_project_h */ + + + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_sphere_h +#define cglmc_sphere_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +float +glmc_sphere_radii(vec4 s); + +CGLM_EXPORT +void +glmc_sphere_transform(vec4 s, mat4 m, vec4 dest); + +CGLM_EXPORT +void +glmc_sphere_merge(vec4 s1, vec4 s2, vec4 dest); + +CGLM_EXPORT +bool +glmc_sphere_sphere(vec4 s1, vec4 s2); + +CGLM_EXPORT +bool +glmc_sphere_point(vec4 s, vec3 point); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_sphere_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_ease_h +#define cglmc_ease_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +float +glmc_ease_linear(float t); + +CGLM_EXPORT +float +glmc_ease_sine_in(float t); + +CGLM_EXPORT +float +glmc_ease_sine_out(float t); + +CGLM_EXPORT +float +glmc_ease_sine_inout(float t); + +CGLM_EXPORT +float +glmc_ease_quad_in(float t); + +CGLM_EXPORT +float +glmc_ease_quad_out(float t); + +CGLM_EXPORT +float +glmc_ease_quad_inout(float t); + +CGLM_EXPORT +float +glmc_ease_cubic_in(float t); + +CGLM_EXPORT +float +glmc_ease_cubic_out(float t); + +CGLM_EXPORT +float +glmc_ease_cubic_inout(float t); + +CGLM_EXPORT +float +glmc_ease_quart_in(float t); + +CGLM_EXPORT +float +glmc_ease_quart_out(float t); + +CGLM_EXPORT +float +glmc_ease_quart_inout(float t); + +CGLM_EXPORT +float +glmc_ease_quint_in(float t); + +CGLM_EXPORT +float +glmc_ease_quint_out(float t); + +CGLM_EXPORT +float +glmc_ease_quint_inout(float t); + +CGLM_EXPORT +float +glmc_ease_exp_in(float t); + +CGLM_EXPORT +float +glmc_ease_exp_out(float t); + +CGLM_EXPORT +float +glmc_ease_exp_inout(float t); + +CGLM_EXPORT +float +glmc_ease_circ_in(float t); + +CGLM_EXPORT +float +glmc_ease_circ_out(float t); + +CGLM_EXPORT +float +glmc_ease_circ_inout(float t); + +CGLM_EXPORT +float +glmc_ease_back_in(float t); + +CGLM_EXPORT +float +glmc_ease_back_out(float t); + +CGLM_EXPORT +float +glmc_ease_back_inout(float t); + +CGLM_EXPORT +float +glmc_ease_elast_in(float t); + +CGLM_EXPORT +float +glmc_ease_elast_out(float t); + +CGLM_EXPORT +float +glmc_ease_elast_inout(float t); + +CGLM_EXPORT +float +glmc_ease_bounce_out(float t); + +CGLM_EXPORT +float +glmc_ease_bounce_in(float t); + +CGLM_EXPORT +float +glmc_ease_bounce_inout(float t); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_ease_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_curve_h +#define cglmc_curve_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +float +glmc_smc(float s, mat4 m, vec4 c); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_curve_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_bezier_h +#define cglmc_bezier_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +float +glmc_bezier(float s, float p0, float c0, float c1, float p1); + +CGLM_EXPORT +float +glmc_hermite(float s, float p0, float t0, float t1, float p1); + +CGLM_EXPORT +float +glmc_decasteljau(float prm, float p0, float c0, float c1, float p1); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_bezier_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_ray_h +#define cglmc_ray_h +#ifdef __cplusplus +extern "C" { +#endif + +CGLM_EXPORT +bool +glmc_ray_triangle(vec3 origin, + vec3 direction, + vec3 v0, + vec3 v1, + vec3 v2, + float *d); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_ray_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglmc_affine2d_h +#define cglmc_affine2d_h +#ifdef __cplusplus +extern "C" { +#endif + + +CGLM_EXPORT +void +glmc_translate2d_make(mat3 m, vec2 v); + +CGLM_EXPORT +void +glmc_translate2d_to(mat3 m, vec2 v, mat3 dest); + +CGLM_EXPORT +void +glmc_translate2d(mat3 m, vec2 v); + +CGLM_EXPORT +void +glmc_translate2d_x(mat3 m, float to); + +CGLM_EXPORT +void +glmc_translate2d_y(mat3 m, float to); + +CGLM_EXPORT +void +glmc_scale2d_to(mat3 m, vec2 v, mat3 dest); + +CGLM_EXPORT +void +glmc_scale2d_make(mat3 m, vec2 v); + +CGLM_EXPORT +void +glmc_scale2d(mat3 m, vec2 v); + +CGLM_EXPORT +void +glmc_scale2d_uni(mat3 m, float s); + +CGLM_EXPORT +void +glmc_rotate2d_make(mat3 m, float angle); + +CGLM_EXPORT +void +glmc_rotate2d(mat3 m, float angle); + +CGLM_EXPORT +void +glmc_rotate2d_to(mat3 m, float angle, mat3 dest); + +#ifdef __cplusplus +} +#endif +#endif /* cglmc_affine2d_h */ + + +#ifdef __cplusplus +} +#endif +#endif /* cglm_call_h */ + + +CGLM_EXPORT +void +glmc_translate_make(mat4 m, vec3 v) { + glm_translate_make(m, v); +} + +CGLM_EXPORT +void +glmc_translate_to(mat4 m, vec3 v, mat4 dest) { + glm_translate_to(m, v, dest); +} + +CGLM_EXPORT +void +glmc_translate(mat4 m, vec3 v) { + glm_translate(m, v); +} + +CGLM_EXPORT +void +glmc_translate_x(mat4 m, float to) { + glm_translate_x(m, to); +} + +CGLM_EXPORT +void +glmc_translate_y(mat4 m, float to) { + glm_translate_y(m, to); +} + +CGLM_EXPORT +void +glmc_translate_z(mat4 m, float to) { + glm_translate_z(m, to); +} + +CGLM_EXPORT +void +glmc_scale_make(mat4 m, vec3 v) { + glm_scale_make(m, v); +} + +CGLM_EXPORT +void +glmc_scale_to(mat4 m, vec3 v, mat4 dest) { + glm_scale_to(m, v, dest); +} + +CGLM_EXPORT +void +glmc_scale(mat4 m, vec3 v) { + glm_scale(m, v); +} + +CGLM_EXPORT +void +glmc_scale_uni(mat4 m, float s) { + glm_scale_uni(m, s); +} + +CGLM_EXPORT +void +glmc_rotate_x(mat4 m, float rad, mat4 dest) { + glm_rotate_x(m, rad, dest); +} + +CGLM_EXPORT +void +glmc_rotate_y(mat4 m, float rad, mat4 dest) { + glm_rotate_y(m, rad, dest); +} + +CGLM_EXPORT +void +glmc_rotate_z(mat4 m, float rad, mat4 dest) { + glm_rotate_z(m, rad, dest); +} + +CGLM_EXPORT +void +glmc_rotate_make(mat4 m, float angle, vec3 axis) { + glm_rotate_make(m, angle, axis); +} + +CGLM_EXPORT +void +glmc_rotate(mat4 m, float angle, vec3 axis) { + glm_rotate(m, angle, axis); +} + +CGLM_EXPORT +void +glmc_rotate_at(mat4 m, vec3 pivot, float angle, vec3 axis) { + glm_rotate_at(m, pivot, angle, axis); +} + +CGLM_EXPORT +void +glmc_rotate_atm(mat4 m, vec3 pivot, float angle, vec3 axis) { + glm_rotate_atm(m, pivot, angle, axis); +} + +CGLM_EXPORT +void +glmc_decompose_scalev(mat4 m, vec3 s) { + glm_decompose_scalev(m, s); +} + +CGLM_EXPORT +bool +glmc_uniscaled(mat4 m) { + return glm_uniscaled(m); +} + +CGLM_EXPORT +void +glmc_decompose_rs(mat4 m, mat4 r, vec3 s) { + glm_decompose_rs(m, r, s); +} + +CGLM_EXPORT +void +glmc_decompose(mat4 m, vec4 t, mat4 r, vec3 s) { + glm_decompose(m, t, r, s); +} + +CGLM_EXPORT +void +glmc_mul(mat4 m1, mat4 m2, mat4 dest) { + glm_mul(m1, m2, dest); +} + +CGLM_EXPORT +void +glmc_mul_rot(mat4 m1, mat4 m2, mat4 dest) { + glm_mul_rot(m1, m2, dest); +} + +CGLM_EXPORT +void +glmc_inv_tr(mat4 mat) { + glm_inv_tr(mat); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_translate2d_make(mat3 m, vec2 v) { + glm_translate2d_make(m, v); +} + +CGLM_EXPORT +void +glmc_translate2d_to(mat3 m, vec2 v, mat3 dest) { + glm_translate2d_to(m, v, dest); +} + +CGLM_EXPORT +void +glmc_translate2d(mat3 m, vec2 v) { + glm_translate2d(m, v); +} + +CGLM_EXPORT +void +glmc_translate2d_x(mat3 m, float to) { + glm_translate2d_x(m, to); +} + +CGLM_EXPORT +void +glmc_translate2d_y(mat3 m, float to) { + glm_translate2d_y(m, to); +} + +CGLM_EXPORT +void +glmc_scale2d_to(mat3 m, vec2 v, mat3 dest) { + glm_scale2d_to(m, v, dest); +} + +CGLM_EXPORT +void +glmc_scale2d_make(mat3 m, vec2 v) { + glm_scale2d_make(m, v); +} + +CGLM_EXPORT +void +glmc_scale2d(mat3 m, vec2 v) { + glm_scale2d(m, v); +} + +CGLM_EXPORT +void +glmc_scale2d_uni(mat3 m, float s) { + glm_scale2d_uni(m, s); +} + +CGLM_EXPORT +void +glmc_rotate2d_make(mat3 m, float angle) { + glm_rotate2d_make(m, angle); +} + +CGLM_EXPORT +void +glmc_rotate2d(mat3 m, float angle) { + glm_rotate2d(m, angle); +} + +CGLM_EXPORT +void +glmc_rotate2d_to(mat3 m, float angle, mat3 dest) { + glm_rotate2d_to(m, angle, dest); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +float +glmc_bezier(float s, float p0, float c0, float c1, float p1) { + return glm_bezier(s, p0, c0, c1, p1); +} + +CGLM_EXPORT +float +glmc_hermite(float s, float p0, float t0, float t1, float p1) { + return glm_hermite(s, p0, t0, t1, p1); +} + +CGLM_EXPORT +float +glmc_decasteljau(float prm, float p0, float c0, float c1, float p1) { + return glm_decasteljau(prm, p0, c0, c1, p1); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_aabb_transform(vec3 box[2], mat4 m, vec3 dest[2]) { + glm_aabb_transform(box, m, dest); +} + +CGLM_EXPORT +void +glmc_aabb_merge(vec3 box1[2], vec3 box2[2], vec3 dest[2]) { + glm_aabb_merge(box1, box2, dest); +} + +CGLM_EXPORT +void +glmc_aabb_crop(vec3 box[2], vec3 cropBox[2], vec3 dest[2]) { + glm_aabb_crop(box, cropBox, dest); +} + +CGLM_EXPORT +void +glmc_aabb_crop_until(vec3 box[2], + vec3 cropBox[2], + vec3 clampBox[2], + vec3 dest[2]) { + glm_aabb_crop_until(box, cropBox, clampBox, dest); +} + +CGLM_EXPORT +bool +glmc_aabb_frustum(vec3 box[2], vec4 planes[6]) { + return glm_aabb_frustum(box, planes); +} + +CGLM_EXPORT +void +glmc_aabb_invalidate(vec3 box[2]) { + glm_aabb_invalidate(box); +} + +CGLM_EXPORT +bool +glmc_aabb_isvalid(vec3 box[2]) { + return glm_aabb_isvalid(box); +} + +CGLM_EXPORT +float +glmc_aabb_size(vec3 box[2]) { + return glm_aabb_size(box); +} + +CGLM_EXPORT +float +glmc_aabb_radius(vec3 box[2]) { + return glm_aabb_radius(box); +} + +CGLM_EXPORT +void +glmc_aabb_center(vec3 box[2], vec3 dest) { + glm_aabb_center(box, dest); +} + +CGLM_EXPORT +bool +glmc_aabb_aabb(vec3 box[2], vec3 other[2]) { + return glm_aabb_aabb(box, other); +} + +CGLM_EXPORT +bool +glmc_aabb_point(vec3 box[2], vec3 point) { + return glm_aabb_point(box, point); +} + +CGLM_EXPORT +bool +glmc_aabb_contains(vec3 box[2], vec3 other[2]) { + return glm_aabb_contains(box, other); +} + +CGLM_EXPORT +bool +glmc_aabb_sphere(vec3 box[2], vec4 s) { + return glm_aabb_sphere(box, s); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_frustum(float left, + float right, + float bottom, + float top, + float nearVal, + float farVal, + mat4 dest) { + glm_frustum(left, + right, + bottom, + top, + nearVal, + farVal, + dest); +} + +CGLM_EXPORT +void +glmc_ortho(float left, + float right, + float bottom, + float top, + float nearVal, + float farVal, + mat4 dest) { + glm_ortho(left, + right, + bottom, + top, + nearVal, + farVal, + dest); +} + +CGLM_EXPORT +void +glmc_ortho_aabb(vec3 box[2], mat4 dest) { + glm_ortho_aabb(box, dest); +} + +CGLM_EXPORT +void +glmc_ortho_aabb_p(vec3 box[2], float padding, mat4 dest) { + glm_ortho_aabb_p(box, padding, dest); +} + +CGLM_EXPORT +void +glmc_ortho_aabb_pz(vec3 box[2], float padding, mat4 dest) { + glm_ortho_aabb_pz(box, padding, dest); +} + +CGLM_EXPORT +void +glmc_ortho_default(float aspect, mat4 dest) { + glm_ortho_default(aspect, dest); +} + +CGLM_EXPORT +void +glmc_ortho_default_s(float aspect, float size, mat4 dest) { + glm_ortho_default_s(aspect, size, dest); +} + +CGLM_EXPORT +void +glmc_perspective(float fovy, + float aspect, + float nearVal, + float farVal, + mat4 dest) { + glm_perspective(fovy, + aspect, + nearVal, + farVal, + dest); +} + +CGLM_EXPORT +void +glmc_persp_move_far(mat4 proj, float deltaFar) { + glm_persp_move_far(proj, deltaFar); +} + +CGLM_EXPORT +void +glmc_perspective_default(float aspect, mat4 dest) { + glm_perspective_default(aspect, dest); +} + +CGLM_EXPORT +void +glmc_perspective_resize(float aspect, mat4 proj) { + glm_perspective_resize(aspect, proj); +} + +CGLM_EXPORT +void +glmc_lookat(vec3 eye, + vec3 center, + vec3 up, + mat4 dest) { + glm_lookat(eye, center, up, dest); +} + +CGLM_EXPORT +void +glmc_look(vec3 eye, vec3 dir, vec3 up, mat4 dest) { + glm_look(eye, dir, up, dest); +} + +CGLM_EXPORT +void +glmc_look_anyup(vec3 eye, vec3 dir, mat4 dest) { + glm_look_anyup(eye, dir, dest); +} + +CGLM_EXPORT +void +glmc_persp_decomp(mat4 proj, + float * __restrict nearVal, + float * __restrict farVal, + float * __restrict top, + float * __restrict bottom, + float * __restrict left, + float * __restrict right) { + glm_persp_decomp(proj, nearVal, farVal, top, bottom, left, right); +} + +CGLM_EXPORT +void +glmc_persp_decompv(mat4 proj, float dest[6]) { + glm_persp_decompv(proj, dest); +} + +CGLM_EXPORT +void +glmc_persp_decomp_x(mat4 proj, + float * __restrict left, + float * __restrict right) { + glm_persp_decomp_x(proj, left, right); +} + +CGLM_EXPORT +void +glmc_persp_decomp_y(mat4 proj, + float * __restrict top, + float * __restrict bottom) { + glm_persp_decomp_y(proj, top, bottom); +} + +CGLM_EXPORT +void +glmc_persp_decomp_z(mat4 proj, + float * __restrict nearVal, + float * __restrict farVal) { + glm_persp_decomp_z(proj, nearVal, farVal); +} + +CGLM_EXPORT +void +glmc_persp_decomp_far(mat4 proj, float * __restrict farVal) { + glm_persp_decomp_far(proj, farVal); +} + +CGLM_EXPORT +void +glmc_persp_decomp_near(mat4 proj, float * __restrict nearVal) { + glm_persp_decomp_near(proj, nearVal); +} + +CGLM_EXPORT +float +glmc_persp_fovy(mat4 proj) { + return glm_persp_fovy(proj); +} + +CGLM_EXPORT +float +glmc_persp_aspect(mat4 proj) { + return glm_persp_aspect(proj); +} + +CGLM_EXPORT +void +glmc_persp_sizes(mat4 proj, float fovy, vec4 dest) { + glm_persp_sizes(proj, fovy, dest); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +float +glmc_smc(float s, mat4 m, vec4 c) { + return glm_smc(s, m, c); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +float +glmc_ease_linear(float t) { + return glm_ease_linear(t); +} + +CGLM_EXPORT +float +glmc_ease_sine_in(float t) { + return glm_ease_sine_in(t); +} + +CGLM_EXPORT +float +glmc_ease_sine_out(float t) { + return glm_ease_sine_out(t); +} + +CGLM_EXPORT +float +glmc_ease_sine_inout(float t) { + return glm_ease_sine_inout(t); +} + +CGLM_EXPORT +float +glmc_ease_quad_in(float t) { + return glm_ease_quad_in(t); +} + +CGLM_EXPORT +float +glmc_ease_quad_out(float t) { + return glm_ease_quad_out(t); +} + +CGLM_EXPORT +float +glmc_ease_quad_inout(float t) { + return glm_ease_quad_inout(t); +} + +CGLM_EXPORT +float +glmc_ease_cubic_in(float t) { + return glm_ease_cubic_in(t); +} + +CGLM_EXPORT +float +glmc_ease_cubic_out(float t) { + return glm_ease_cubic_out(t); +} + +CGLM_EXPORT +float +glmc_ease_cubic_inout(float t) { + return glm_ease_cubic_inout(t); +} + +CGLM_EXPORT +float +glmc_ease_quart_in(float t) { + return glm_ease_quart_in(t); +} + +CGLM_EXPORT +float +glmc_ease_quart_out(float t) { + return glm_ease_quart_out(t); +} + +CGLM_EXPORT +float +glmc_ease_quart_inout(float t) { + return glm_ease_quart_inout(t); +} + +CGLM_EXPORT +float +glmc_ease_quint_in(float t) { + return glm_ease_quint_in(t); +} + +CGLM_EXPORT +float +glmc_ease_quint_out(float t) { + return glm_ease_quint_out(t); +} + +CGLM_EXPORT +float +glmc_ease_quint_inout(float t) { + return glm_ease_quint_inout(t); +} + +CGLM_EXPORT +float +glmc_ease_exp_in(float t) { + return glm_ease_exp_in(t); +} + +CGLM_EXPORT +float +glmc_ease_exp_out(float t) { + return glm_ease_exp_out(t); +} + +CGLM_EXPORT +float +glmc_ease_exp_inout(float t) { + return glm_ease_exp_inout(t); +} + +CGLM_EXPORT +float +glmc_ease_circ_in(float t) { + return glm_ease_circ_in(t); +} + +CGLM_EXPORT +float +glmc_ease_circ_out(float t) { + return glm_ease_circ_out(t); +} + +CGLM_EXPORT +float +glmc_ease_circ_inout(float t) { + return glm_ease_circ_inout(t); +} + +CGLM_EXPORT +float +glmc_ease_back_in(float t) { + return glm_ease_back_in(t); +} + +CGLM_EXPORT +float +glmc_ease_back_out(float t) { + return glm_ease_back_out(t); +} + +CGLM_EXPORT +float +glmc_ease_back_inout(float t) { + return glm_ease_back_inout(t); +} + +CGLM_EXPORT +float +glmc_ease_elast_in(float t) { + return glm_ease_elast_in(t); +} + +CGLM_EXPORT +float +glmc_ease_elast_out(float t) { + return glm_ease_elast_out(t); +} + +CGLM_EXPORT +float +glmc_ease_elast_inout(float t) { + return glm_ease_elast_inout(t); +} + +CGLM_EXPORT +float +glmc_ease_bounce_out(float t) { + return glm_ease_bounce_out(t); +} + +CGLM_EXPORT +float +glmc_ease_bounce_in(float t) { + return glm_ease_bounce_in(t); +} + +CGLM_EXPORT +float +glmc_ease_bounce_inout(float t) { + return glm_ease_bounce_inout(t); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_euler_angles(mat4 m, vec3 dest) { + glm_euler_angles(m, dest); +} + +CGLM_EXPORT +void +glmc_euler(vec3 angles, mat4 dest) { + glm_euler(angles, dest); +} + +CGLM_EXPORT +void +glmc_euler_xyz(vec3 angles, mat4 dest) { + glm_euler_xyz(angles, dest); +} + +CGLM_EXPORT +void +glmc_euler_zyx(vec3 angles, mat4 dest) { + glm_euler_zyx(angles, dest); +} + +CGLM_EXPORT +void +glmc_euler_zxy(vec3 angles, mat4 dest) { + glm_euler_zxy(angles, dest); +} + +CGLM_EXPORT +void +glmc_euler_xzy(vec3 angles, mat4 dest) { + glm_euler_xzy(angles, dest); +} + +CGLM_EXPORT +void +glmc_euler_yzx(vec3 angles, mat4 dest) { + glm_euler_yzx(angles, dest); +} + +CGLM_EXPORT +void +glmc_euler_yxz(vec3 angles, mat4 dest) { + glm_euler_yxz(angles, dest); +} + +CGLM_EXPORT +void +glmc_euler_by_order(vec3 angles, glm_euler_seq axis, mat4 dest) { + glm_euler_by_order(angles, axis, dest); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_frustum_planes(mat4 m, vec4 dest[6]) { + glm_frustum_planes(m, dest); +} + +CGLM_EXPORT +void +glmc_frustum_corners(mat4 invMat, vec4 dest[8]) { + glm_frustum_corners(invMat, dest); +} + +CGLM_EXPORT +void +glmc_frustum_center(vec4 corners[8], vec4 dest) { + glm_frustum_center(corners, dest); +} + +CGLM_EXPORT +void +glmc_frustum_box(vec4 corners[8], mat4 m, vec3 box[2]) { + glm_frustum_box(corners, m, box); +} + +CGLM_EXPORT +void +glmc_frustum_corners_at(vec4 corners[8], + float splitDist, + float farDist, + vec4 planeCorners[4]) { + glm_frustum_corners_at(corners, splitDist, farDist, planeCorners); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#define CGLM_LIB_SRC + + +CGLM_EXPORT +void +glmc_mat4_print(mat4 matrix, + FILE * __restrict ostream) { + glm_mat4_print(matrix, ostream); +} + +CGLM_EXPORT +void +glmc_mat3_print(mat3 matrix, + FILE * __restrict ostream) { + glm_mat3_print(matrix, ostream); +} + +CGLM_EXPORT +void +glmc_vec4_print(vec4 vec, + FILE * __restrict ostream) { + glm_vec4_print(vec, ostream); +} + +CGLM_EXPORT +void +glmc_vec3_print(vec3 vec, + FILE * __restrict ostream) { + glm_vec3_print(vec, ostream); +} + +CGLM_EXPORT +void +glmc_versor_print(versor vec, + FILE * __restrict ostream) { + glm_versor_print(vec, ostream); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_mat2_copy(mat2 mat, mat2 dest) { + glm_mat2_copy(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat2_identity(mat2 mat) { + glm_mat2_identity(mat); +} + +CGLM_EXPORT +void +glmc_mat2_identity_array(mat2 * __restrict mat, size_t count) { + glm_mat2_identity_array(mat, count); +} + +CGLM_EXPORT +void +glmc_mat2_zero(mat2 mat) { + glm_mat2_zero(mat); +} + +CGLM_EXPORT +void +glmc_mat2_mul(mat2 m1, mat2 m2, mat2 dest) { + glm_mat2_mul(m1, m2, dest); +} + +CGLM_EXPORT +void +glmc_mat2_transpose_to(mat2 m, mat2 dest) { + glm_mat2_transpose_to(m, dest); +} + +CGLM_EXPORT +void +glmc_mat2_transpose(mat2 m) { + glm_mat2_transpose(m); +} + +CGLM_EXPORT +void +glmc_mat2_mulv(mat2 m, vec2 v, vec2 dest) { + glm_mat2_mulv(m, v, dest); +} + +CGLM_EXPORT +float +glmc_mat2_trace(mat2 m) { + return glm_mat2_trace(m); +} + +CGLM_EXPORT +void +glmc_mat2_scale(mat2 m, float s) { + glm_mat2_scale(m, s); +} + +CGLM_EXPORT +float +glmc_mat2_det(mat2 mat) { + return glm_mat2_det(mat); +} + +CGLM_EXPORT +void +glmc_mat2_inv(mat2 mat, mat2 dest) { + glm_mat2_inv(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat2_swap_col(mat2 mat, int col1, int col2) { + glm_mat2_swap_col(mat, col1, col2); +} + +CGLM_EXPORT +void +glmc_mat2_swap_row(mat2 mat, int row1, int row2) { + glm_mat2_swap_row(mat, row1, row2); +} + +CGLM_EXPORT +float +glmc_mat2_rmc(vec2 r, mat2 m, vec2 c) { + return glm_mat2_rmc(r, m, c); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_mat3_copy(mat3 mat, mat3 dest) { + glm_mat3_copy(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat3_identity(mat3 mat) { + glm_mat3_identity(mat); +} + +CGLM_EXPORT +void +glmc_mat3_zero(mat3 mat) { + glm_mat3_zero(mat); +} + +CGLM_EXPORT +void +glmc_mat3_identity_array(mat3 * __restrict mat, size_t count) { + glm_mat3_identity_array(mat, count); +} + +CGLM_EXPORT +void +glmc_mat3_mul(mat3 m1, mat3 m2, mat3 dest) { + glm_mat3_mul(m1, m2, dest); +} + +CGLM_EXPORT +void +glmc_mat3_transpose_to(mat3 m, mat3 dest) { + glm_mat3_transpose_to(m, dest); +} + +CGLM_EXPORT +void +glmc_mat3_transpose(mat3 m) { + glm_mat3_transpose(m); +} + +CGLM_EXPORT +void +glmc_mat3_mulv(mat3 m, vec3 v, vec3 dest) { + glm_mat3_mulv(m, v, dest); +} + +CGLM_EXPORT +float +glmc_mat3_trace(mat3 m) { + return glm_mat3_trace(m); +} + +CGLM_EXPORT +void +glmc_mat3_quat(mat3 m, versor dest) { + glm_mat3_quat(m, dest); +} + +CGLM_EXPORT +void +glmc_mat3_scale(mat3 m, float s) { + glm_mat3_scale(m, s); +} + +CGLM_EXPORT +float +glmc_mat3_det(mat3 mat) { + return glm_mat3_det(mat); +} + +CGLM_EXPORT +void +glmc_mat3_inv(mat3 mat, mat3 dest) { + glm_mat3_inv(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat3_swap_col(mat3 mat, int col1, int col2) { + glm_mat3_swap_col(mat, col1, col2); +} + +CGLM_EXPORT +void +glmc_mat3_swap_row(mat3 mat, int row1, int row2) { + glm_mat3_swap_row(mat, row1, row2); +} + +CGLM_EXPORT +float +glmc_mat3_rmc(vec3 r, mat3 m, vec3 c) { + return glm_mat3_rmc(r, m, c); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_mat4_ucopy(mat4 mat, mat4 dest) { + glm_mat4_copy(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat4_copy(mat4 mat, mat4 dest) { + glm_mat4_copy(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat4_identity(mat4 mat) { + glm_mat4_identity(mat); +} + +CGLM_EXPORT +void +glmc_mat4_identity_array(mat4 * __restrict mat, size_t count) { + glm_mat4_identity_array(mat, count); +} + +CGLM_EXPORT +void +glmc_mat4_zero(mat4 mat) { + glm_mat4_zero(mat); +} + +CGLM_EXPORT +void +glmc_mat4_pick3(mat4 mat, mat3 dest) { + glm_mat4_pick3(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat4_pick3t(mat4 mat, mat3 dest) { + glm_mat4_pick3t(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat4_ins3(mat3 mat, mat4 dest) { + glm_mat4_ins3(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat4_mul(mat4 m1, mat4 m2, mat4 dest) { + glm_mat4_mul(m1, m2, dest); +} + +CGLM_EXPORT +void +glmc_mat4_mulN(mat4 * __restrict matrices[], uint32_t len, mat4 dest) { + glm_mat4_mulN(matrices, len, dest); +} + +CGLM_EXPORT +void +glmc_mat4_mulv(mat4 m, vec4 v, vec4 dest) { + glm_mat4_mulv(m, v, dest); +} + +CGLM_EXPORT +void +glmc_mat4_mulv3(mat4 m, vec3 v, float last, vec3 dest) { + glm_mat4_mulv3(m, v, last, dest); +} + +CGLM_EXPORT +float +glmc_mat4_trace(mat4 m) { + return glm_mat4_trace(m); +} + +CGLM_EXPORT +float +glmc_mat4_trace3(mat4 m) { + return glm_mat4_trace3(m); +} + +CGLM_EXPORT +void +glmc_mat4_quat(mat4 m, versor dest) { + glm_mat4_quat(m, dest); +} + +CGLM_EXPORT +void +glmc_mat4_transpose_to(mat4 m, mat4 dest) { + glm_mat4_transpose_to(m, dest); +} + +CGLM_EXPORT +void +glmc_mat4_transpose(mat4 m) { + glm_mat4_transpose(m); +} + +CGLM_EXPORT +void +glmc_mat4_scale_p(mat4 m, float s) { + glm_mat4_scale_p(m, s); +} + +CGLM_EXPORT +void +glmc_mat4_scale(mat4 m, float s) { + glm_mat4_scale(m, s); +} + +CGLM_EXPORT +float +glmc_mat4_det(mat4 mat) { + return glm_mat4_det(mat); +} + +CGLM_EXPORT +void +glmc_mat4_inv(mat4 mat, mat4 dest) { + glm_mat4_inv(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat4_inv_precise(mat4 mat, mat4 dest) { + glm_mat4_inv_precise(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat4_inv_fast(mat4 mat, mat4 dest) { + glm_mat4_inv_fast(mat, dest); +} + +CGLM_EXPORT +void +glmc_mat4_swap_col(mat4 mat, int col1, int col2) { + glm_mat4_swap_col(mat, col1, col2); +} + +CGLM_EXPORT +void +glmc_mat4_swap_row(mat4 mat, int row1, int row2) { + glm_mat4_swap_row(mat, row1, row2); +} + +CGLM_EXPORT +float +glmc_mat4_rmc(vec4 r, mat4 m, vec4 c) { + return glm_mat4_rmc(r, m, c); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_plane_normalize(vec4 plane) { + glm_plane_normalize(plane); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_unprojecti(vec3 pos, mat4 invMat, vec4 vp, vec3 dest) { + glm_unprojecti(pos, invMat, vp, dest); +} + +CGLM_EXPORT +void +glmc_unproject(vec3 pos, mat4 m, vec4 vp, vec3 dest) { + glm_unproject(pos, m, vp, dest); +} + +CGLM_EXPORT +void +glmc_project(vec3 pos, mat4 m, vec4 vp, vec3 dest) { + glm_project(pos, m, vp, dest); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_quat_identity(versor q) { + glm_quat_identity(q); +} + +CGLM_EXPORT +void +glmc_quat_identity_array(versor * __restrict q, size_t count) { + glm_quat_identity_array(q, count); +} + +CGLM_EXPORT +void +glmc_quat_init(versor q, float x, float y, float z, float w) { + glm_quat_init(q, x, y, z, w); +} + +CGLM_EXPORT +void +glmc_quat(versor q, float angle, float x, float y, float z) { + glm_quat(q, angle, x, y, z); +} + +CGLM_EXPORT +void +glmc_quatv(versor q, float angle, vec3 axis) { + glm_quatv(q, angle, axis); +} + +CGLM_EXPORT +void +glmc_quat_copy(versor q, versor dest) { + glm_quat_copy(q, dest); +} + +CGLM_EXPORT +float +glmc_quat_norm(versor q) { + return glm_quat_norm(q); +} + +CGLM_EXPORT +void +glmc_quat_normalize_to(versor q, versor dest) { + glm_quat_normalize_to(q, dest); +} + +CGLM_EXPORT +void +glmc_quat_normalize(versor q) { + glm_quat_normalize(q); +} + +CGLM_EXPORT +float +glmc_quat_dot(versor p, versor q) { + return glm_quat_dot(p, q); +} + +CGLM_EXPORT +void +glmc_quat_conjugate(versor q, versor dest) { + glm_quat_conjugate(q, dest); +} + +CGLM_EXPORT +void +glmc_quat_inv(versor q, versor dest) { + glm_quat_inv(q, dest); +} + +CGLM_EXPORT +void +glmc_quat_add(versor p, versor q, versor dest) { + glm_quat_add(p, q, dest); +} + +CGLM_EXPORT +void +glmc_quat_sub(versor p, versor q, versor dest) { + glm_quat_sub(p, q, dest); +} + +CGLM_EXPORT +float +glmc_quat_real(versor q) { + return glm_quat_real(q); +} + +CGLM_EXPORT +void +glmc_quat_imag(versor q, vec3 dest) { + glm_quat_imag(q, dest); +} + +CGLM_EXPORT +void +glmc_quat_imagn(versor q, vec3 dest) { + glm_quat_imagn(q, dest); +} + +CGLM_EXPORT +float +glmc_quat_imaglen(versor q) { + return glm_quat_imaglen(q); +} + +CGLM_EXPORT +float +glmc_quat_angle(versor q) { + return glm_quat_angle(q); +} + +CGLM_EXPORT +void +glmc_quat_axis(versor q, vec3 dest) { + glm_quat_axis(q, dest); +} + +CGLM_EXPORT +void +glmc_quat_mul(versor p, versor q, versor dest) { + glm_quat_mul(p, q, dest); +} + +CGLM_EXPORT +void +glmc_quat_mat4(versor q, mat4 dest) { + glm_quat_mat4(q, dest); +} + +CGLM_EXPORT +void +glmc_quat_mat4t(versor q, mat4 dest) { + glm_quat_mat4t(q, dest); +} + +CGLM_EXPORT +void +glmc_quat_mat3(versor q, mat3 dest) { + glm_quat_mat3(q, dest); +} + +CGLM_EXPORT +void +glmc_quat_mat3t(versor q, mat3 dest) { + glm_quat_mat3t(q, dest); +} + +CGLM_EXPORT +void +glmc_quat_lerp(versor from, versor to, float t, versor dest) { + glm_quat_lerp(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_quat_lerpc(versor from, versor to, float t, versor dest) { + glm_quat_lerpc(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_quat_slerp(versor from, versor to, float t, versor dest) { + glm_quat_slerp(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_quat_look(vec3 eye, versor ori, mat4 dest) { + glm_quat_look(eye, ori, dest); +} + +CGLM_EXPORT +void +glmc_quat_for(vec3 dir, vec3 up, versor dest) { + glm_quat_for(dir, up, dest); +} + +CGLM_EXPORT +void +glmc_quat_forp(vec3 from, vec3 to, vec3 up, versor dest) { + glm_quat_forp(from, to, up, dest); +} + +CGLM_EXPORT +void +glmc_quat_rotatev(versor q, vec3 v, vec3 dest) { + glm_quat_rotatev(q, v, dest); +} + +CGLM_EXPORT +void +glmc_quat_rotate(mat4 m, versor q, mat4 dest) { + glm_quat_rotate(m, q, dest); +} + +CGLM_EXPORT +void +glmc_quat_rotate_at(mat4 model, versor q, vec3 pivot) { + glm_quat_rotate_at(model, q, pivot); +} + +CGLM_EXPORT +void +glmc_quat_rotate_atm(mat4 m, versor q, vec3 pivot) { + glm_quat_rotate_atm(m, q, pivot); +} + + +CGLM_EXPORT +bool +glmc_ray_triangle(vec3 origin, + vec3 direction, + vec3 v0, + vec3 v1, + vec3 v2, + float *d) { + return glm_ray_triangle(origin, direction, v0, v1, v2, d); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +float +glmc_sphere_radii(vec4 s) { + return glm_sphere_radii(s); +} + +CGLM_EXPORT +void +glmc_sphere_transform(vec4 s, mat4 m, vec4 dest) { + glm_sphere_transform(s, m, dest); +} + +CGLM_EXPORT +void +glmc_sphere_merge(vec4 s1, vec4 s2, vec4 dest) { + glm_sphere_merge(s1, s2, dest); +} + +CGLM_EXPORT +bool +glmc_sphere_sphere(vec4 s1, vec4 s2) { + return glm_sphere_sphere(s1, s2); +} + +CGLM_EXPORT +bool +glmc_sphere_point(vec4 s, vec3 point) { + return glm_sphere_point(s, point); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_vec2(float * __restrict v, vec2 dest) { + glm_vec2(v, dest); +} + +CGLM_EXPORT +void +glmc_vec2_copy(vec2 a, vec2 dest) { + glm_vec2_copy(a, dest); +} + +CGLM_EXPORT +void +glmc_vec2_zero(vec2 v) { + glm_vec2_zero(v); +} + +CGLM_EXPORT +void +glmc_vec2_one(vec2 v) { + glm_vec2_one(v); +} + +CGLM_EXPORT +float +glmc_vec2_dot(vec2 a, vec2 b) { + return glm_vec2_dot(a, b); +} + +CGLM_EXPORT +float +glmc_vec2_cross(vec2 a, vec2 b) { + return glm_vec2_cross(a, b); +} + +CGLM_EXPORT +float +glmc_vec2_norm2(vec2 v) { + return glm_vec2_norm2(v); +} + +CGLM_EXPORT +float +glmc_vec2_norm(vec2 v) { + return glm_vec2_norm(v); +} + +CGLM_EXPORT +void +glmc_vec2_add(vec2 a, vec2 b, vec2 dest) { + glm_vec2_add(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_adds(vec2 v, float s, vec2 dest) { + glm_vec2_adds(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec2_sub(vec2 a, vec2 b, vec2 dest) { + glm_vec2_sub(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_subs(vec2 v, float s, vec2 dest) { + glm_vec2_subs(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec2_mul(vec2 a, vec2 b, vec2 dest) { + glm_vec2_mul(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_scale(vec2 v, float s, vec2 dest) { + glm_vec2_scale(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec2_scale_as(vec2 v, float s, vec2 dest) { + glm_vec2_scale_as(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec2_div(vec2 a, vec2 b, vec2 dest) { + glm_vec2_div(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_divs(vec2 v, float s, vec2 dest) { + glm_vec2_divs(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec2_addadd(vec2 a, vec2 b, vec2 dest) { + glm_vec2_addadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_subadd(vec2 a, vec2 b, vec2 dest) { + glm_vec2_subadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_muladd(vec2 a, vec2 b, vec2 dest) { + glm_vec2_muladd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_muladds(vec2 a, float s, vec2 dest) { + glm_vec2_muladds(a, s, dest); +} + +CGLM_EXPORT +void +glmc_vec2_maxadd(vec2 a, vec2 b, vec2 dest) { + glm_vec2_maxadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_minadd(vec2 a, vec2 b, vec2 dest) { + glm_vec2_minadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_negate_to(vec2 v, vec2 dest) { + glm_vec2_negate_to(v, dest); +} + +CGLM_EXPORT +void +glmc_vec2_negate(vec2 v) { + glm_vec2_negate(v); +} + +CGLM_EXPORT +void +glmc_vec2_normalize(vec2 v) { + glm_vec2_normalize(v); +} + +CGLM_EXPORT +void +glmc_vec2_normalize_to(vec2 v, vec2 dest) { + glm_vec2_normalize_to(v, dest); +} + +CGLM_EXPORT +void +glmc_vec2_rotate(vec2 v, float angle, vec2 dest) { + glm_vec2_rotate(v, angle, dest); +} + +CGLM_EXPORT +float +glmc_vec2_distance2(vec2 a, vec2 b) { + return glm_vec2_distance2(a, b); +} + +CGLM_EXPORT +float +glmc_vec2_distance(vec2 a, vec2 b) { + return glm_vec2_distance(a, b); +} + +CGLM_EXPORT +void +glmc_vec2_maxv(vec2 a, vec2 b, vec2 dest) { + glm_vec2_maxv(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_minv(vec2 a, vec2 b, vec2 dest) { + glm_vec2_minv(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec2_clamp(vec2 v, float minval, float maxval) { + glm_vec2_clamp(v, minval, maxval); +} + +CGLM_EXPORT +void +glmc_vec2_lerp(vec2 from, vec2 to, float t, vec2 dest) { + glm_vec2_lerp(from, to, t, dest); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_vec3(vec4 v4, vec3 dest) { + glm_vec3(v4, dest); +} + +CGLM_EXPORT +void +glmc_vec3_copy(vec3 a, vec3 dest) { + glm_vec3_copy(a, dest); +} + +CGLM_EXPORT +void +glmc_vec3_zero(vec3 v) { + glm_vec3_zero(v); +} + +CGLM_EXPORT +void +glmc_vec3_one(vec3 v) { + glm_vec3_one(v); +} + +CGLM_EXPORT +float +glmc_vec3_dot(vec3 a, vec3 b) { + return glm_vec3_dot(a, b); +} + +CGLM_EXPORT +void +glmc_vec3_cross(vec3 a, vec3 b, vec3 dest) { + glm_vec3_cross(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_crossn(vec3 a, vec3 b, vec3 dest) { + glm_vec3_crossn(a, b, dest); +} + +CGLM_EXPORT +float +glmc_vec3_norm(vec3 v) { + return glm_vec3_norm(v); +} + +CGLM_EXPORT +void +glmc_vec3_normalize_to(vec3 v, vec3 dest) { + glm_vec3_normalize_to(v, dest); +} + +CGLM_EXPORT +void +glmc_vec3_normalize(vec3 v) { + glm_vec3_normalize(v); +} + +CGLM_EXPORT +float +glmc_vec3_norm2(vec3 v) { + return glm_vec3_norm2(v); +} + +CGLM_EXPORT +float +glmc_vec3_norm_one(vec3 v) { + return glm_vec3_norm_one(v); +} + +CGLM_EXPORT +float +glmc_vec3_norm_inf(vec3 v) { + return glm_vec3_norm_inf(v); +} + +CGLM_EXPORT +void +glmc_vec3_add(vec3 a, vec3 b, vec3 dest) { + glm_vec3_add(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_adds(vec3 v, float s, vec3 dest) { + glm_vec3_adds(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec3_sub(vec3 a, vec3 b, vec3 dest) { + glm_vec3_sub(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_subs(vec3 v, float s, vec3 dest) { + glm_vec3_subs(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec3_mul(vec3 a, vec3 b, vec3 d) { + glm_vec3_mul(a, b, d); +} + +CGLM_EXPORT +void +glmc_vec3_scale(vec3 v, float s, vec3 dest) { + glm_vec3_scale(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec3_scale_as(vec3 v, float s, vec3 dest) { + glm_vec3_scale_as(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec3_div(vec3 a, vec3 b, vec3 dest) { + glm_vec3_div(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_divs(vec3 a, float s, vec3 dest) { + glm_vec3_divs(a, s, dest); +} + +CGLM_EXPORT +void +glmc_vec3_addadd(vec3 a, vec3 b, vec3 dest) { + glm_vec3_addadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_subadd(vec3 a, vec3 b, vec3 dest) { + glm_vec3_subadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_muladd(vec3 a, vec3 b, vec3 dest) { + glm_vec3_muladd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_muladds(vec3 a, float s, vec3 dest) { + glm_vec3_muladds(a, s, dest); +} + +CGLM_EXPORT +void +glmc_vec3_maxadd(vec3 a, vec3 b, vec3 dest) { + glm_vec3_maxadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_minadd(vec3 a, vec3 b, vec3 dest) { + glm_vec3_minadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_negate(vec3 v) { + glm_vec3_negate(v); +} + +CGLM_EXPORT +void +glmc_vec3_negate_to(vec3 v, vec3 dest) { + glm_vec3_negate_to(v, dest); +} + +CGLM_EXPORT +float +glmc_vec3_angle(vec3 a, vec3 b) { + return glm_vec3_angle(a, b); +} + +CGLM_EXPORT +void +glmc_vec3_rotate(vec3 v, float angle, vec3 axis) { + glm_vec3_rotate(v, angle, axis); +} + +CGLM_EXPORT +void +glmc_vec3_rotate_m4(mat4 m, vec3 v, vec3 dest) { + glm_vec3_rotate_m4(m, v, dest); +} + +CGLM_EXPORT +void +glmc_vec3_rotate_m3(mat3 m, vec3 v, vec3 dest) { + glm_vec3_rotate_m3(m, v, dest); +} + +CGLM_EXPORT +void +glmc_vec3_proj(vec3 a, vec3 b, vec3 dest) { + glm_vec3_proj(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_center(vec3 a, vec3 b, vec3 dest) { + glm_vec3_center(a, b, dest); +} + +CGLM_EXPORT +float +glmc_vec3_distance(vec3 a, vec3 b) { + return glm_vec3_distance(a, b); +} + +CGLM_EXPORT +float +glmc_vec3_distance2(vec3 a, vec3 b) { + return glm_vec3_distance2(a, b); +} + +CGLM_EXPORT +void +glmc_vec3_maxv(vec3 a, vec3 b, vec3 dest) { + glm_vec3_maxv(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_minv(vec3 a, vec3 b, vec3 dest) { + glm_vec3_minv(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec3_clamp(vec3 v, float minVal, float maxVal) { + glm_vec3_clamp(v, minVal, maxVal); +} + +CGLM_EXPORT +void +glmc_vec3_ortho(vec3 v, vec3 dest) { + glm_vec3_ortho(v, dest); +} + +CGLM_EXPORT +void +glmc_vec3_lerp(vec3 from, vec3 to, float t, vec3 dest) { + glm_vec3_lerp(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_vec3_lerpc(vec3 from, vec3 to, float t, vec3 dest) { + glm_vec3_lerpc(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_vec3_step_uni(float edge, vec3 x, vec3 dest) { + glm_vec3_step_uni(edge, x, dest); +} + +CGLM_EXPORT +void +glmc_vec3_step(vec3 edge, vec3 x, vec3 dest) { + glm_vec3_step(edge, x, dest); +} + +CGLM_EXPORT +void +glmc_vec3_smoothstep_uni(float edge0, float edge1, vec3 x, vec3 dest) { + glm_vec3_smoothstep_uni(edge0, edge1, x, dest); +} + +CGLM_EXPORT +void +glmc_vec3_smoothstep(vec3 edge0, vec3 edge1, vec3 x, vec3 dest) { + glm_vec3_smoothstep(edge0, edge1, x, dest); +} + +CGLM_EXPORT +void +glmc_vec3_smoothinterp(vec3 from, vec3 to, float t, vec3 dest) { + glm_vec3_smoothinterp(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_vec3_smoothinterpc(vec3 from, vec3 to, float t, vec3 dest) { + glm_vec3_smoothinterpc(from, to, t, dest); +} + +/* ext */ + +CGLM_EXPORT +void +glmc_vec3_mulv(vec3 a, vec3 b, vec3 d) { + glm_vec3_mulv(a, b, d); +} + +CGLM_EXPORT +void +glmc_vec3_broadcast(float val, vec3 d) { + glm_vec3_broadcast(val, d); +} + +CGLM_EXPORT +void +glmc_vec3_fill(vec3 v, float val) { + glm_vec3_fill(v, val); +} + +CGLM_EXPORT +bool +glmc_vec3_eq(vec3 v, float val) { + return glm_vec3_eq(v, val); +} + +CGLM_EXPORT +bool +glmc_vec3_eq_eps(vec3 v, float val) { + return glm_vec3_eq_eps(v, val); +} + +CGLM_EXPORT +bool +glmc_vec3_eq_all(vec3 v) { + return glm_vec3_eq_all(v); +} + +CGLM_EXPORT +bool +glmc_vec3_eqv(vec3 a, vec3 b) { + return glm_vec3_eqv(a, b); +} + +CGLM_EXPORT +bool +glmc_vec3_eqv_eps(vec3 a, vec3 b) { + return glm_vec3_eqv_eps(a, b); +} + +CGLM_EXPORT +float +glmc_vec3_max(vec3 v) { + return glm_vec3_max(v); +} + +CGLM_EXPORT +float +glmc_vec3_min(vec3 v) { + return glm_vec3_min(v); +} + +CGLM_EXPORT +bool +glmc_vec3_isnan(vec3 v) { + return glm_vec3_isnan(v); +} + +CGLM_EXPORT +bool +glmc_vec3_isinf(vec3 v) { + return glm_vec3_isinf(v); +} + +CGLM_EXPORT +bool +glmc_vec3_isvalid(vec3 v) { + return glm_vec3_isvalid(v); +} + +CGLM_EXPORT +void +glmc_vec3_sign(vec3 v, vec3 dest) { + glm_vec3_sign(v, dest); +} + +CGLM_EXPORT +void +glmc_vec3_abs(vec3 v, vec3 dest) { + glm_vec3_abs(v, dest); +} + +CGLM_EXPORT +void +glmc_vec3_fract(vec3 v, vec3 dest) { + glm_vec3_fract(v, dest); +} + +CGLM_EXPORT +float +glmc_vec3_hadd(vec3 v) { + return glm_vec3_hadd(v); +} + +CGLM_EXPORT +void +glmc_vec3_sqrt(vec3 v, vec3 dest) { + glm_vec3_sqrt(v, dest); +} + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + + +CGLM_EXPORT +void +glmc_vec4(vec3 v3, float last, vec4 dest) { + glm_vec4(v3, last, dest); +} + +CGLM_EXPORT +void +glmc_vec4_zero(vec4 v) { + glm_vec4_zero(v); +} + +CGLM_EXPORT +void +glmc_vec4_one(vec4 v) { + glm_vec4_one(v); +} + +CGLM_EXPORT +void +glmc_vec4_copy3(vec4 v, vec3 dest) { + glm_vec4_copy3(v, dest); +} + +CGLM_EXPORT +void +glmc_vec4_copy(vec4 v, vec4 dest) { + glm_vec4_copy(v, dest); +} + +CGLM_EXPORT +void +glmc_vec4_ucopy(vec4 v, vec4 dest) { + glm_vec4_ucopy(v, dest); +} + +CGLM_EXPORT +float +glmc_vec4_dot(vec4 a, vec4 b) { + return glm_vec4_dot(a, b); +} + +CGLM_EXPORT +float +glmc_vec4_norm(vec4 v) { + return glm_vec4_norm(v); +} + +CGLM_EXPORT +void +glmc_vec4_normalize_to(vec4 v, vec4 dest) { + glm_vec4_normalize_to(v, dest); +} + +CGLM_EXPORT +void +glmc_vec4_normalize(vec4 v) { + glm_vec4_normalize(v); +} + +CGLM_EXPORT +float +glmc_vec4_norm2(vec4 v) { + return glm_vec4_norm2(v); +} + +CGLM_EXPORT +float +glmc_vec4_norm_one(vec4 v) { + return glm_vec4_norm_one(v); +} + +CGLM_EXPORT +float +glmc_vec4_norm_inf(vec4 v) { + return glm_vec4_norm_inf(v); +} + +CGLM_EXPORT +void +glmc_vec4_add(vec4 a, vec4 b, vec4 dest) { + glm_vec4_add(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec4_adds(vec4 v, float s, vec4 dest) { + glm_vec4_adds(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec4_sub(vec4 a, vec4 b, vec4 dest) { + glm_vec4_sub(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec4_subs(vec4 v, float s, vec4 dest) { + glm_vec4_subs(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec4_mul(vec4 a, vec4 b, vec4 d) { + glm_vec4_mul(a, b, d); +} + +CGLM_EXPORT +void +glmc_vec4_scale(vec4 v, float s, vec4 dest) { + glm_vec4_scale(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec4_scale_as(vec4 v, float s, vec4 dest) { + glm_vec4_scale_as(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec4_div(vec4 a, vec4 b, vec4 dest) { + glm_vec4_div(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec4_divs(vec4 v, float s, vec4 dest) { + glm_vec4_divs(v, s, dest); +} + +CGLM_EXPORT +void +glmc_vec4_addadd(vec4 a, vec4 b, vec4 dest) { + glm_vec4_addadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec4_subadd(vec4 a, vec4 b, vec4 dest) { + glm_vec4_subadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec4_muladd(vec4 a, vec4 b, vec4 dest) { + glm_vec4_muladd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec4_muladds(vec4 a, float s, vec4 dest) { + glm_vec4_muladds(a, s, dest); +} + +CGLM_EXPORT +void +glmc_vec4_maxadd(vec4 a, vec4 b, vec4 dest) { + glm_vec4_maxadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec4_minadd(vec4 a, vec4 b, vec4 dest) { + glm_vec4_minadd(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec4_negate(vec4 v) { + glm_vec4_negate(v); +} + +CGLM_EXPORT +void +glmc_vec4_negate_to(vec4 v, vec4 dest) { + glm_vec4_negate_to(v, dest); +} + +CGLM_EXPORT +float +glmc_vec4_distance(vec4 a, vec4 b) { + return glm_vec4_distance(a, b); +} + +CGLM_EXPORT +float +glmc_vec4_distance2(vec4 a, vec4 b) { + return glm_vec4_distance2(a, b); +} + +CGLM_EXPORT +void +glmc_vec4_maxv(vec4 a, vec4 b, vec4 dest) { + glm_vec4_maxv(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec4_minv(vec4 a, vec4 b, vec4 dest) { + glm_vec4_minv(a, b, dest); +} + +CGLM_EXPORT +void +glmc_vec4_clamp(vec4 v, float minVal, float maxVal) { + glm_vec4_clamp(v, minVal, maxVal); +} + +CGLM_EXPORT +void +glmc_vec4_lerp(vec4 from, vec4 to, float t, vec4 dest) { + glm_vec4_lerp(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_vec4_lerpc(vec4 from, vec4 to, float t, vec4 dest) { + glm_vec4_lerpc(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_vec4_step_uni(float edge, vec4 x, vec4 dest) { + glm_vec4_step_uni(edge, x, dest); +} + +CGLM_EXPORT +void +glmc_vec4_step(vec4 edge, vec4 x, vec4 dest) { + glm_vec4_step(edge, x, dest); +} + +CGLM_EXPORT +void +glmc_vec4_smoothstep_uni(float edge0, float edge1, vec4 x, vec4 dest) { + glm_vec4_smoothstep_uni(edge0, edge1, x, dest); +} + +CGLM_EXPORT +void +glmc_vec4_smoothstep(vec4 edge0, vec4 edge1, vec4 x, vec4 dest) { + glm_vec4_smoothstep(edge0, edge1, x, dest); +} + +CGLM_EXPORT +void +glmc_vec4_smoothinterp(vec4 from, vec4 to, float t, vec4 dest) { + glm_vec4_smoothinterp(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_vec4_smoothinterpc(vec4 from, vec4 to, float t, vec4 dest) { + glm_vec4_smoothinterpc(from, to, t, dest); +} + +CGLM_EXPORT +void +glmc_vec4_cubic(float s, vec4 dest) { + glm_vec4_cubic(s, dest); +} + +/* ext */ + +CGLM_EXPORT +void +glmc_vec4_mulv(vec4 a, vec4 b, vec4 d) { + glm_vec4_mulv(a, b, d); +} + +CGLM_EXPORT +void +glmc_vec4_broadcast(float val, vec4 d) { + glm_vec4_broadcast(val, d); +} + +CGLM_EXPORT +void +glmc_vec4_fill(vec4 v, float val) { + glm_vec4_fill(v, val); +} + +CGLM_EXPORT +bool +glmc_vec4_eq(vec4 v, float val) { + return glm_vec4_eq(v, val); +} + +CGLM_EXPORT +bool +glmc_vec4_eq_eps(vec4 v, float val) { + return glm_vec4_eq_eps(v, val); +} + +CGLM_EXPORT +bool +glmc_vec4_eq_all(vec4 v) { + return glm_vec4_eq_all(v); +} + +CGLM_EXPORT +bool +glmc_vec4_eqv(vec4 a, vec4 b) { + return glm_vec4_eqv(a, b); +} + +CGLM_EXPORT +bool +glmc_vec4_eqv_eps(vec4 a, vec4 b) { + return glm_vec4_eqv_eps(a, b); +} + +CGLM_EXPORT +float +glmc_vec4_max(vec4 v) { + return glm_vec4_max(v); +} + +CGLM_EXPORT +float +glmc_vec4_min(vec4 v) { + return glm_vec4_min(v); +} + +CGLM_EXPORT +bool +glmc_vec4_isnan(vec4 v) { + return glm_vec4_isnan(v); +} + +CGLM_EXPORT +bool +glmc_vec4_isinf(vec4 v) { + return glm_vec4_isinf(v); +} + +CGLM_EXPORT +bool +glmc_vec4_isvalid(vec4 v) { + return glm_vec4_isvalid(v); +} + +CGLM_EXPORT +void +glmc_vec4_sign(vec4 v, vec4 dest) { + glm_vec4_sign(v, dest); +} + +CGLM_EXPORT +void +glmc_vec4_abs(vec4 v, vec4 dest) { + glm_vec4_abs(v, dest); +} + +CGLM_EXPORT +void +glmc_vec4_fract(vec4 v, vec4 dest) { + glm_vec4_fract(v, dest); +} + +CGLM_EXPORT +float +glmc_vec4_hadd(vec4 v) { + return glm_vec4_hadd(v); +} + +CGLM_EXPORT +void +glmc_vec4_sqrt(vec4 v, vec4 dest) { + glm_vec4_sqrt(v, dest); +} + +// This empty file is needed to trick swiftpm to build the header-only version of cglm as swiftpm itself does not support C targets that have no source code files diff --git a/deps/cglm.h b/deps/cglm.h new file mode 100644 index 00000000..7dcc6535 --- /dev/null +++ b/deps/cglm.h @@ -0,0 +1,11236 @@ +// Comment out this line when using as DLL +#define cglm_STATIC +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_h +#define cglm_h + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_common_h +#define cglm_common_h + +#ifndef _USE_MATH_DEFINES +# define _USE_MATH_DEFINES /* for windows */ +#endif + +#ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS /* for windows */ +#endif + +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +# ifdef CGLM_STATIC +# define CGLM_EXPORT +# elif defined(CGLM_EXPORTS) +# define CGLM_EXPORT __declspec(dllexport) +# else +# define CGLM_EXPORT __declspec(dllimport) +# endif +# define CGLM_INLINE __forceinline +#else +# define CGLM_EXPORT __attribute__((visibility("default"))) +# define CGLM_INLINE static inline __attribute((always_inline)) +#endif + +#define GLM_SHUFFLE4(z, y, x, w) (((z) << 6) | ((y) << 4) | ((x) << 2) | (w)) +#define GLM_SHUFFLE3(z, y, x) (((z) << 4) | ((y) << 2) | (x)) + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_types_h +#define cglm_types_h + +#if defined(_MSC_VER) +/* do not use alignment for older visual studio versions */ +# if _MSC_VER < 1913 /* Visual Studio 2017 version 15.6 */ +# define CGLM_ALL_UNALIGNED +# define CGLM_ALIGN(X) /* no alignment */ +# else +# define CGLM_ALIGN(X) __declspec(align(X)) +# endif +#else +# define CGLM_ALIGN(X) __attribute((aligned(X))) +#endif + +#ifndef CGLM_ALL_UNALIGNED +# define CGLM_ALIGN_IF(X) CGLM_ALIGN(X) +#else +# define CGLM_ALIGN_IF(X) /* no alignment */ +#endif + +#ifdef __AVX__ +# define CGLM_ALIGN_MAT CGLM_ALIGN(32) +#else +# define CGLM_ALIGN_MAT CGLM_ALIGN(16) +#endif + +#ifdef __GNUC__ +# define CGLM_ASSUME_ALIGNED(expr, alignment) \ + __builtin_assume_aligned((expr), (alignment)) +#else +# define CGLM_ASSUME_ALIGNED(expr, alignment) (expr) +#endif + +#define CGLM_CASTPTR_ASSUME_ALIGNED(expr, type) \ + ((type*)CGLM_ASSUME_ALIGNED((expr), __alignof__(type))) + +typedef float vec2[2]; +typedef float vec3[3]; +typedef int ivec3[3]; +typedef CGLM_ALIGN_IF(16) float vec4[4]; +typedef vec4 versor; /* |x, y, z, w| -> w is the last */ +typedef vec3 mat3[3]; +typedef CGLM_ALIGN_IF(16) vec2 mat2[2]; +typedef CGLM_ALIGN_MAT vec4 mat4[4]; + +/* + Important: cglm stores quaternion as [x, y, z, w] in memory since v0.4.0 + it was [w, x, y, z] before v0.4.0 ( v0.3.5 and earlier ). w is real part. +*/ + +#define GLM_E 2.71828182845904523536028747135266250 /* e */ +#define GLM_LOG2E 1.44269504088896340735992468100189214 /* log2(e) */ +#define GLM_LOG10E 0.434294481903251827651128918916605082 /* log10(e) */ +#define GLM_LN2 0.693147180559945309417232121458176568 /* loge(2) */ +#define GLM_LN10 2.30258509299404568401799145468436421 /* loge(10) */ +#define GLM_PI 3.14159265358979323846264338327950288 /* pi */ +#define GLM_PI_2 1.57079632679489661923132169163975144 /* pi/2 */ +#define GLM_PI_4 0.785398163397448309615660845819875721 /* pi/4 */ +#define GLM_1_PI 0.318309886183790671537767526745028724 /* 1/pi */ +#define GLM_2_PI 0.636619772367581343075535053490057448 /* 2/pi */ +#define GLM_2_SQRTPI 1.12837916709551257389615890312154517 /* 2/sqrt(pi) */ +#define GLM_SQRT2 1.41421356237309504880168872420969808 /* sqrt(2) */ +#define GLM_SQRT1_2 0.707106781186547524400844362104849039 /* 1/sqrt(2) */ + +#define GLM_Ef ((float)GLM_E) +#define GLM_LOG2Ef ((float)GLM_LOG2E) +#define GLM_LOG10Ef ((float)GLM_LOG10E) +#define GLM_LN2f ((float)GLM_LN2) +#define GLM_LN10f ((float)GLM_LN10) +#define GLM_PIf ((float)GLM_PI) +#define GLM_PI_2f ((float)GLM_PI_2) +#define GLM_PI_4f ((float)GLM_PI_4) +#define GLM_1_PIf ((float)GLM_1_PI) +#define GLM_2_PIf ((float)GLM_2_PI) +#define GLM_2_SQRTPIf ((float)GLM_2_SQRTPI) +#define GLM_SQRT2f ((float)GLM_SQRT2) +#define GLM_SQRT1_2f ((float)GLM_SQRT1_2) + +/* DEPRECATED! use GLM_PI and friends */ +#define CGLM_PI GLM_PIf +#define CGLM_PI_2 GLM_PI_2f +#define CGLM_PI_4 GLM_PI_4f + +#endif /* cglm_types_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_intrin_h +#define cglm_intrin_h + +#if defined( _MSC_VER ) +# if (defined(_M_AMD64) || defined(_M_X64)) || _M_IX86_FP == 2 +# ifndef __SSE2__ +# define __SSE2__ +# endif +# elif _M_IX86_FP == 1 +# ifndef __SSE__ +# define __SSE__ +# endif +# endif +/* do not use alignment for older visual studio versions */ +# if _MSC_VER < 1913 /* Visual Studio 2017 version 15.6 */ +# define CGLM_ALL_UNALIGNED +# endif +#endif + +#if defined( __SSE__ ) || defined( __SSE2__ ) +# include +# include +# define CGLM_SSE_FP 1 +# ifndef CGLM_SIMD_x86 +# define CGLM_SIMD_x86 +# endif +#endif + +#if defined(__SSE3__) +# include +# ifndef CGLM_SIMD_x86 +# define CGLM_SIMD_x86 +# endif +#endif + +#if defined(__SSE4_1__) +# include +# ifndef CGLM_SIMD_x86 +# define CGLM_SIMD_x86 +# endif +#endif + +#if defined(__SSE4_2__) +# include +# ifndef CGLM_SIMD_x86 +# define CGLM_SIMD_x86 +# endif +#endif + +#ifdef __AVX__ +# include +# define CGLM_AVX_FP 1 +# ifndef CGLM_SIMD_x86 +# define CGLM_SIMD_x86 +# endif +#endif + +/* ARM Neon */ +#if defined(__ARM_NEON) +# include +# if defined(__ARM_NEON_FP) +# define CGLM_NEON_FP 1 +# ifndef CGLM_SIMD_ARM +# define CGLM_SIMD_ARM +# endif +# endif +#endif + +#if defined(CGLM_SIMD_x86) || defined(CGLM_NEON_FP) +# ifndef CGLM_SIMD +# define CGLM_SIMD +# endif +#endif + +#if defined(CGLM_SIMD_x86) +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_simd_x86_h +#define cglm_simd_x86_h +#ifdef CGLM_SIMD_x86 + +#ifdef CGLM_ALL_UNALIGNED +# define glmm_load(p) _mm_loadu_ps(p) +# define glmm_store(p, a) _mm_storeu_ps(p, a) +#else +# define glmm_load(p) _mm_load_ps(p) +# define glmm_store(p, a) _mm_store_ps(p, a) +#endif + +#ifdef CGLM_USE_INT_DOMAIN +# define glmm_shuff1(xmm, z, y, x, w) \ + _mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(xmm), \ + _MM_SHUFFLE(z, y, x, w))) +#else +# define glmm_shuff1(xmm, z, y, x, w) \ + _mm_shuffle_ps(xmm, xmm, _MM_SHUFFLE(z, y, x, w)) +#endif + +#define glmm_shuff1x(xmm, x) glmm_shuff1(xmm, x, x, x, x) +#define glmm_shuff2(a, b, z0, y0, x0, w0, z1, y1, x1, w1) \ + glmm_shuff1(_mm_shuffle_ps(a, b, _MM_SHUFFLE(z0, y0, x0, w0)), \ + z1, y1, x1, w1) + +#ifdef __AVX__ +# ifdef CGLM_ALL_UNALIGNED +# define glmm_load256(p) _mm256_loadu_ps(p) +# define glmm_store256(p, a) _mm256_storeu_ps(p, a) +# else +# define glmm_load256(p) _mm256_load_ps(p) +# define glmm_store256(p, a) _mm256_store_ps(p, a) +# endif +#endif + +static inline +__m128 +glmm_abs(__m128 x) { + return _mm_andnot_ps(_mm_set1_ps(-0.0f), x); +} + +static inline +__m128 +glmm_vhadds(__m128 v) { +#if defined(__SSE3__) + __m128 shuf, sums; + shuf = _mm_movehdup_ps(v); + sums = _mm_add_ps(v, shuf); + shuf = _mm_movehl_ps(shuf, sums); + sums = _mm_add_ss(sums, shuf); + return sums; +#else + __m128 shuf, sums; + shuf = glmm_shuff1(v, 2, 3, 0, 1); + sums = _mm_add_ps(v, shuf); + shuf = _mm_movehl_ps(shuf, sums); + sums = _mm_add_ss(sums, shuf); + return sums; +#endif +} + +static inline +float +glmm_hadd(__m128 v) { + return _mm_cvtss_f32(glmm_vhadds(v)); +} + +static inline +__m128 +glmm_vhmin(__m128 v) { + __m128 x0, x1, x2; + x0 = _mm_movehl_ps(v, v); /* [2, 3, 2, 3] */ + x1 = _mm_min_ps(x0, v); /* [0|2, 1|3, 2|2, 3|3] */ + x2 = glmm_shuff1x(x1, 1); /* [1|3, 1|3, 1|3, 1|3] */ + return _mm_min_ss(x1, x2); +} + +static inline +float +glmm_hmin(__m128 v) { + return _mm_cvtss_f32(glmm_vhmin(v)); +} + +static inline +__m128 +glmm_vhmax(__m128 v) { + __m128 x0, x1, x2; + x0 = _mm_movehl_ps(v, v); /* [2, 3, 2, 3] */ + x1 = _mm_max_ps(x0, v); /* [0|2, 1|3, 2|2, 3|3] */ + x2 = glmm_shuff1x(x1, 1); /* [1|3, 1|3, 1|3, 1|3] */ + return _mm_max_ss(x1, x2); +} + +static inline +float +glmm_hmax(__m128 v) { + return _mm_cvtss_f32(glmm_vhmax(v)); +} + +static inline +__m128 +glmm_vdots(__m128 a, __m128 b) { +#if (defined(__SSE4_1__) || defined(__SSE4_2__)) && defined(CGLM_SSE4_DOT) + return _mm_dp_ps(a, b, 0xFF); +#elif defined(__SSE3__) && defined(CGLM_SSE3_DOT) + __m128 x0, x1; + x0 = _mm_mul_ps(a, b); + x1 = _mm_hadd_ps(x0, x0); + return _mm_hadd_ps(x1, x1); +#else + return glmm_vhadds(_mm_mul_ps(a, b)); +#endif +} + +static inline +__m128 +glmm_vdot(__m128 a, __m128 b) { +#if (defined(__SSE4_1__) || defined(__SSE4_2__)) && defined(CGLM_SSE4_DOT) + return _mm_dp_ps(a, b, 0xFF); +#elif defined(__SSE3__) && defined(CGLM_SSE3_DOT) + __m128 x0, x1; + x0 = _mm_mul_ps(a, b); + x1 = _mm_hadd_ps(x0, x0); + return _mm_hadd_ps(x1, x1); +#else + __m128 x0; + x0 = _mm_mul_ps(a, b); + x0 = _mm_add_ps(x0, glmm_shuff1(x0, 1, 0, 3, 2)); + return _mm_add_ps(x0, glmm_shuff1(x0, 0, 1, 0, 1)); +#endif +} + +static inline +float +glmm_dot(__m128 a, __m128 b) { + return _mm_cvtss_f32(glmm_vdots(a, b)); +} + +static inline +float +glmm_norm(__m128 a) { + return _mm_cvtss_f32(_mm_sqrt_ss(glmm_vhadds(_mm_mul_ps(a, a)))); +} + +static inline +float +glmm_norm2(__m128 a) { + return _mm_cvtss_f32(glmm_vhadds(_mm_mul_ps(a, a))); +} + +static inline +float +glmm_norm_one(__m128 a) { + return _mm_cvtss_f32(glmm_vhadds(glmm_abs(a))); +} + +static inline +float +glmm_norm_inf(__m128 a) { + return _mm_cvtss_f32(glmm_vhmax(glmm_abs(a))); +} + +static inline +__m128 +glmm_load3(float v[3]) { + __m128i xy; + __m128 z; + + xy = _mm_loadl_epi64(CGLM_CASTPTR_ASSUME_ALIGNED(v, const __m128i)); + z = _mm_load_ss(&v[2]); + + return _mm_movelh_ps(_mm_castsi128_ps(xy), z); +} + +static inline +void +glmm_store3(float v[3], __m128 vx) { + _mm_storel_pi(CGLM_CASTPTR_ASSUME_ALIGNED(v, __m64), vx); + _mm_store_ss(&v[2], glmm_shuff1(vx, 2, 2, 2, 2)); +} + +#endif +#endif /* cglm_simd_x86_h */ + +#endif + +#if defined(CGLM_SIMD_ARM) +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_simd_arm_h +#define cglm_simd_arm_h +#ifdef CGLM_SIMD_ARM + +#define glmm_load(p) vld1q_f32(p) +#define glmm_store(p, a) vst1q_f32(p, a) + +static inline +float32x4_t +glmm_abs(float32x4_t v) { + return vabsq_f32(v); +} + +static inline +float +glmm_hadd(float32x4_t v) { +#if defined(__aarch64__) + return vaddvq_f32(v); +#else + v = vaddq_f32(v, vrev64q_f32(v)); + v = vaddq_f32(v, vcombine_f32(vget_high_f32(v), vget_low_f32(v))); + return vgetq_lane_f32(v, 0); +#endif +} + +static inline +float +glmm_hmin(float32x4_t v) { + float32x2_t t; + t = vpmin_f32(vget_low_f32(v), vget_high_f32(v)); + t = vpmin_f32(t, t); + return vget_lane_f32(t, 0); +} + +static inline +float +glmm_hmax(float32x4_t v) { + float32x2_t t; + t = vpmax_f32(vget_low_f32(v), vget_high_f32(v)); + t = vpmax_f32(t, t); + return vget_lane_f32(t, 0); +} + +static inline +float +glmm_dot(float32x4_t a, float32x4_t b) { + return glmm_hadd(vmulq_f32(a, b)); +} + +static inline +float +glmm_norm(float32x4_t a) { + return sqrtf(glmm_dot(a, a)); +} + +static inline +float +glmm_norm2(float32x4_t a) { + return glmm_dot(a, a); +} + +static inline +float +glmm_norm_one(float32x4_t a) { + return glmm_hadd(glmm_abs(a)); +} + +static inline +float +glmm_norm_inf(float32x4_t a) { + return glmm_hmax(glmm_abs(a)); +} + +#endif +#endif /* cglm_simd_arm_h */ + +#endif + +#endif /* cglm_intrin_h */ + + +#ifndef CGLM_USE_DEFAULT_EPSILON +# ifndef GLM_FLT_EPSILON +# define GLM_FLT_EPSILON 1e-6 +# endif +#else +# define GLM_FLT_EPSILON FLT_EPSILON +#endif + +#endif /* cglm_common_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Macros: + GLM_VEC2_ONE_INIT + GLM_VEC2_ZERO_INIT + GLM_VEC2_ONE + GLM_VEC2_ZERO + + Functions: + CGLM_INLINE void glm_vec2(float * __restrict v, vec2 dest) + CGLM_INLINE void glm_vec2_copy(vec2 a, vec2 dest) + CGLM_INLINE void glm_vec2_zero(vec2 v) + CGLM_INLINE void glm_vec2_one(vec2 v) + CGLM_INLINE float glm_vec2_dot(vec2 a, vec2 b) + CGLM_INLINE float glm_vec2_cross(vec2 a, vec2 b) + CGLM_INLINE float glm_vec2_norm2(vec2 v) + CGLM_INLINE float glm_vec2_norm(vec2 vec) + CGLM_INLINE void glm_vec2_add(vec2 a, vec2 b, vec2 dest) + CGLM_INLINE void glm_vec2_adds(vec2 v, float s, vec2 dest) + CGLM_INLINE void glm_vec2_sub(vec2 a, vec2 b, vec2 dest) + CGLM_INLINE void glm_vec2_subs(vec2 v, float s, vec2 dest) + CGLM_INLINE void glm_vec2_mul(vec2 a, vec2 b, vec2 d) + CGLM_INLINE void glm_vec2_scale(vec2 v, float s, vec2 dest) + CGLM_INLINE void glm_vec2_scale_as(vec2 v, float s, vec2 dest) + CGLM_INLINE void glm_vec2_div(vec2 a, vec2 b, vec2 dest) + CGLM_INLINE void glm_vec2_divs(vec2 v, float s, vec2 dest) + CGLM_INLINE void glm_vec2_addadd(vec2 a, vec2 b, vec2 dest) + CGLM_INLINE void glm_vec2_subadd(vec2 a, vec2 b, vec2 dest) + CGLM_INLINE void glm_vec2_muladd(vec2 a, vec2 b, vec2 dest) + CGLM_INLINE void glm_vec2_muladds(vec2 a, float s, vec2 dest) + CGLM_INLINE void glm_vec2_maxadd(vec2 a, vec2 b, vec2 dest) + CGLM_INLINE void glm_vec2_minadd(vec2 a, vec2 b, vec2 dest) + CGLM_INLINE void glm_vec2_negate_to(vec2 v, vec2 dest) + CGLM_INLINE void glm_vec2_negate(vec2 v) + CGLM_INLINE void glm_vec2_normalize(vec2 v) + CGLM_INLINE void glm_vec2_normalize_to(vec2 vec, vec2 dest) + CGLM_INLINE void glm_vec2_rotate(vec2 v, float angle, vec2 dest) + CGLM_INLINE float glm_vec2_distance2(vec2 a, vec2 b) + CGLM_INLINE float glm_vec2_distance(vec2 a, vec2 b) + CGLM_INLINE void glm_vec2_maxv(vec2 v1, vec2 v2, vec2 dest) + CGLM_INLINE void glm_vec2_minv(vec2 v1, vec2 v2, vec2 dest) + CGLM_INLINE void glm_vec2_clamp(vec2 v, float minVal, float maxVal) + CGLM_INLINE void glm_vec2_lerp(vec2 from, vec2 to, float t, vec2 dest) + + */ + +#ifndef cglm_vec2_h +#define cglm_vec2_h + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Functions: + CGLM_INLINE int glm_sign(int val); + CGLM_INLINE float glm_signf(float val); + CGLM_INLINE float glm_rad(float deg); + CGLM_INLINE float glm_deg(float rad); + CGLM_INLINE void glm_make_rad(float *deg); + CGLM_INLINE void glm_make_deg(float *rad); + CGLM_INLINE float glm_pow2(float x); + CGLM_INLINE float glm_min(float a, float b); + CGLM_INLINE float glm_max(float a, float b); + CGLM_INLINE float glm_clamp(float val, float minVal, float maxVal); + CGLM_INLINE float glm_clamp_zo(float val, float minVal, float maxVal); + CGLM_INLINE float glm_lerp(float from, float to, float t); + CGLM_INLINE float glm_lerpc(float from, float to, float t); + CGLM_INLINE float glm_step(float edge, float x); + CGLM_INLINE float glm_smooth(float t); + CGLM_INLINE float glm_smoothstep(float edge0, float edge1, float x); + CGLM_INLINE float glm_smoothinterp(float from, float to, float t); + CGLM_INLINE float glm_smoothinterpc(float from, float to, float t); + CGLM_INLINE bool glm_eq(float a, float b); + CGLM_INLINE float glm_percent(float from, float to, float current); + CGLM_INLINE float glm_percentc(float from, float to, float current); + */ + +#ifndef cglm_util_h +#define cglm_util_h + + +#define GLM_MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) +#define GLM_MAX(X, Y) (((X) > (Y)) ? (X) : (Y)) + +/*! + * @brief get sign of 32 bit integer as +1, -1, 0 + * + * Important: It returns 0 for zero input + * + * @param val integer value + */ +CGLM_INLINE +int +glm_sign(int val) { + return ((val >> 31) - (-val >> 31)); +} + +/*! + * @brief get sign of 32 bit float as +1, -1, 0 + * + * Important: It returns 0 for zero/NaN input + * + * @param val float value + */ +CGLM_INLINE +float +glm_signf(float val) { + return (float)((val > 0.0f) - (val < 0.0f)); +} + +/*! + * @brief convert degree to radians + * + * @param[in] deg angle in degrees + */ +CGLM_INLINE +float +glm_rad(float deg) { + return deg * GLM_PIf / 180.0f; +} + +/*! + * @brief convert radians to degree + * + * @param[in] rad angle in radians + */ +CGLM_INLINE +float +glm_deg(float rad) { + return rad * 180.0f / GLM_PIf; +} + +/*! + * @brief convert exsisting degree to radians. this will override degrees value + * + * @param[in, out] deg pointer to angle in degrees + */ +CGLM_INLINE +void +glm_make_rad(float *deg) { + *deg = *deg * GLM_PIf / 180.0f; +} + +/*! + * @brief convert exsisting radians to degree. this will override radians value + * + * @param[in, out] rad pointer to angle in radians + */ +CGLM_INLINE +void +glm_make_deg(float *rad) { + *rad = *rad * 180.0f / GLM_PIf; +} + +/*! + * @brief multiplies given parameter with itself = x * x or powf(x, 2) + * + * @param[in] x x + */ +CGLM_INLINE +float +glm_pow2(float x) { + return x * x; +} + +/*! + * @brief find minimum of given two values + * + * @param[in] a number 1 + * @param[in] b number 2 + */ +CGLM_INLINE +float +glm_min(float a, float b) { + if (a < b) + return a; + return b; +} + +/*! + * @brief find maximum of given two values + * + * @param[in] a number 1 + * @param[in] b number 2 + */ +CGLM_INLINE +float +glm_max(float a, float b) { + if (a > b) + return a; + return b; +} + +/*! + * @brief clamp a number between min and max + * + * @param[in] val value to clamp + * @param[in] minVal minimum value + * @param[in] maxVal maximum value + */ +CGLM_INLINE +float +glm_clamp(float val, float minVal, float maxVal) { + return glm_min(glm_max(val, minVal), maxVal); +} + +/*! + * @brief clamp a number to zero and one + * + * @param[in] val value to clamp + */ +CGLM_INLINE +float +glm_clamp_zo(float val) { + return glm_clamp(val, 0.0f, 1.0f); +} + +/*! + * @brief linear interpolation between two numbers + * + * formula: from + t * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) + */ +CGLM_INLINE +float +glm_lerp(float from, float to, float t) { + return from + t * (to - from); +} + +/*! + * @brief clamped linear interpolation between two numbers + * + * formula: from + t * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) clamped between 0 and 1 + */ +CGLM_INLINE +float +glm_lerpc(float from, float to, float t) { + return glm_lerp(from, to, glm_clamp_zo(t)); +} + +/*! + * @brief threshold function + * + * @param[in] edge threshold + * @param[in] x value to test against threshold + * @return returns 0.0 if x < edge, else 1.0 + */ +CGLM_INLINE +float +glm_step(float edge, float x) { + /* branching - no type conversion */ + return (x < edge) ? 0.0f : 1.0f; + /* + * An alternative implementation without branching + * but with type conversion could be: + * return !(x < edge); + */ +} + +/*! + * @brief smooth Hermite interpolation + * + * formula: t^2 * (3-2t) + * + * @param[in] t interpolant (amount) + */ +CGLM_INLINE +float +glm_smooth(float t) { + return t * t * (3.0f - 2.0f * t); +} + +/*! + * @brief threshold function with a smooth transition (according to OpenCL specs) + * + * formula: t^2 * (3-2t) + * + * @param[in] edge0 low threshold + * @param[in] edge1 high threshold + * @param[in] x interpolant (amount) + */ +CGLM_INLINE +float +glm_smoothstep(float edge0, float edge1, float x) { + float t; + t = glm_clamp_zo((x - edge0) / (edge1 - edge0)); + return glm_smooth(t); +} + +/*! + * @brief smoothstep interpolation between two numbers + * + * formula: from + smoothstep(t) * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) + */ +CGLM_INLINE +float +glm_smoothinterp(float from, float to, float t) { + return from + glm_smooth(t) * (to - from); +} + +/*! + * @brief clamped smoothstep interpolation between two numbers + * + * formula: from + smoothstep(t) * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) clamped between 0 and 1 + */ +CGLM_INLINE +float +glm_smoothinterpc(float from, float to, float t) { + return glm_smoothinterp(from, to, glm_clamp_zo(t)); +} + +/*! + * @brief check if two float equal with using EPSILON + * + * @param[in] a a + * @param[in] b b + */ +CGLM_INLINE +bool +glm_eq(float a, float b) { + return fabsf(a - b) <= GLM_FLT_EPSILON; +} + +/*! + * @brief percentage of current value between start and end value + * + * maybe fraction could be alternative name. + * + * @param[in] from from value + * @param[in] to to value + * @param[in] current current value + */ +CGLM_INLINE +float +glm_percent(float from, float to, float current) { + float t; + + if ((t = to - from) == 0.0f) + return 1.0f; + + return (current - from) / t; +} + +/*! + * @brief clamped percentage of current value between start and end value + * + * @param[in] from from value + * @param[in] to to value + * @param[in] current current value + */ +CGLM_INLINE +float +glm_percentc(float from, float to, float current) { + return glm_clamp_zo(glm_percent(from, to, current)); +} + +/*! +* @brief swap two float values +* +* @param[in] a float value 1 (pointer) +* @param[in] b float value 2 (pointer) +*/ +CGLM_INLINE +void +glm_swapf(float * __restrict a, float * __restrict b) { + float t; + t = *a; + *a = *b; + *b = t; +} + +#endif /* cglm_util_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Functions: + CGLM_INLINE void glm_vec2_fill(vec2 v, float val) + CGLM_INLINE bool glm_vec2_eq(vec2 v, float val); + CGLM_INLINE bool glm_vec2_eq_eps(vec2 v, float val); + CGLM_INLINE bool glm_vec2_eq_all(vec2 v); + CGLM_INLINE bool glm_vec2_eqv(vec2 a, vec2 b); + CGLM_INLINE bool glm_vec2_eqv_eps(vec2 a, vec2 b); + CGLM_INLINE float glm_vec2_max(vec2 v); + CGLM_INLINE float glm_vec2_min(vec2 v); + CGLM_INLINE bool glm_vec2_isnan(vec2 v); + CGLM_INLINE bool glm_vec2_isinf(vec2 v); + CGLM_INLINE bool glm_vec2_isvalid(vec2 v); + CGLM_INLINE void glm_vec2_sign(vec2 v, vec2 dest); + CGLM_INLINE void glm_vec2_sqrt(vec2 v, vec2 dest); + */ + +#ifndef cglm_vec2_ext_h +#define cglm_vec2_ext_h + + +/*! + * @brief fill a vector with specified value + * + * @param[out] v dest + * @param[in] val value + */ +CGLM_INLINE +void +glm_vec2_fill(vec2 v, float val) { + v[0] = v[1] = val; +} + +/*! + * @brief check if vector is equal to value (without epsilon) + * + * @param[in] v vector + * @param[in] val value + */ +CGLM_INLINE +bool +glm_vec2_eq(vec2 v, float val) { + return v[0] == val && v[0] == v[1]; +} + +/*! + * @brief check if vector is equal to value (with epsilon) + * + * @param[in] v vector + * @param[in] val value + */ +CGLM_INLINE +bool +glm_vec2_eq_eps(vec2 v, float val) { + return fabsf(v[0] - val) <= GLM_FLT_EPSILON + && fabsf(v[1] - val) <= GLM_FLT_EPSILON; +} + +/*! + * @brief check if vectors members are equal (without epsilon) + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec2_eq_all(vec2 v) { + return glm_vec2_eq_eps(v, v[0]); +} + +/*! + * @brief check if vector is equal to another (without epsilon) + * + * @param[in] a vector + * @param[in] b vector + */ +CGLM_INLINE +bool +glm_vec2_eqv(vec2 a, vec2 b) { + return a[0] == b[0] && a[1] == b[1]; +} + +/*! + * @brief check if vector is equal to another (with epsilon) + * + * @param[in] a vector + * @param[in] b vector + */ +CGLM_INLINE +bool +glm_vec2_eqv_eps(vec2 a, vec2 b) { + return fabsf(a[0] - b[0]) <= GLM_FLT_EPSILON + && fabsf(a[1] - b[1]) <= GLM_FLT_EPSILON; +} + +/*! + * @brief max value of vector + * + * @param[in] v vector + */ +CGLM_INLINE +float +glm_vec2_max(vec2 v) { + return glm_max(v[0], v[1]); +} + +/*! + * @brief min value of vector + * + * @param[in] v vector + */ +CGLM_INLINE +float +glm_vec2_min(vec2 v) { + return glm_min(v[0], v[1]); +} + +/*! + * @brief check if all items are NaN (not a number) + * you should only use this in DEBUG mode or very critical asserts + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec2_isnan(vec2 v) { + return isnan(v[0]) || isnan(v[1]); +} + +/*! + * @brief check if all items are INFINITY + * you should only use this in DEBUG mode or very critical asserts + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec2_isinf(vec2 v) { + return isinf(v[0]) || isinf(v[1]); +} + +/*! + * @brief check if all items are valid number + * you should only use this in DEBUG mode or very critical asserts + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec2_isvalid(vec2 v) { + return !glm_vec2_isnan(v) && !glm_vec2_isinf(v); +} + +/*! + * @brief get sign of 32 bit float as +1, -1, 0 + * + * Important: It returns 0 for zero/NaN input + * + * @param v vector + */ +CGLM_INLINE +void +glm_vec2_sign(vec2 v, vec2 dest) { + dest[0] = glm_signf(v[0]); + dest[1] = glm_signf(v[1]); +} + +/*! + * @brief square root of each vector item + * + * @param[in] v vector + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec2_sqrt(vec2 v, vec2 dest) { + dest[0] = sqrtf(v[0]); + dest[1] = sqrtf(v[1]); +} + +#endif /* cglm_vec2_ext_h */ + + +#define GLM_VEC2_ONE_INIT {1.0f, 1.0f} +#define GLM_VEC2_ZERO_INIT {0.0f, 0.0f} + +#define GLM_VEC2_ONE ((vec2)GLM_VEC2_ONE_INIT) +#define GLM_VEC2_ZERO ((vec2)GLM_VEC2_ZERO_INIT) + +/*! + * @brief init vec2 using another vector + * + * @param[in] v a vector + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec2(float * __restrict v, vec2 dest) { + dest[0] = v[0]; + dest[1] = v[1]; +} + +/*! + * @brief copy all members of [a] to [dest] + * + * @param[in] a source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec2_copy(vec2 a, vec2 dest) { + dest[0] = a[0]; + dest[1] = a[1]; +} + +/*! + * @brief make vector zero + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec2_zero(vec2 v) { + v[0] = v[1] = 0.0f; +} + +/*! + * @brief make vector one + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec2_one(vec2 v) { + v[0] = v[1] = 1.0f; +} + +/*! + * @brief vec2 dot product + * + * @param[in] a vector1 + * @param[in] b vector2 + * + * @return dot product + */ +CGLM_INLINE +float +glm_vec2_dot(vec2 a, vec2 b) { + return a[0] * b[0] + a[1] * b[1]; +} + +/*! + * @brief vec2 cross product + * + * REF: http://allenchou.net/2013/07/cross-product-of-2d-vectors/ + * + * @param[in] a vector1 + * @param[in] b vector2 + * + * @return Z component of cross product + */ +CGLM_INLINE +float +glm_vec2_cross(vec2 a, vec2 b) { + /* just calculate the z-component */ + return a[0] * b[1] - a[1] * b[0]; +} + +/*! + * @brief norm * norm (magnitude) of vec + * + * we can use this func instead of calling norm * norm, because it would call + * sqrtf fuction twice but with this func we can avoid func call, maybe this is + * not good name for this func + * + * @param[in] v vector + * + * @return norm * norm + */ +CGLM_INLINE +float +glm_vec2_norm2(vec2 v) { + return glm_vec2_dot(v, v); +} + +/*! + * @brief norm (magnitude) of vec2 + * + * @param[in] vec vector + * + * @return norm + */ +CGLM_INLINE +float +glm_vec2_norm(vec2 vec) { + return sqrtf(glm_vec2_norm2(vec)); +} + +/*! + * @brief add a vector to b vector store result in dest + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec2_add(vec2 a, vec2 b, vec2 dest) { + dest[0] = a[0] + b[0]; + dest[1] = a[1] + b[1]; +} + +/*! + * @brief add scalar to v vector store result in dest (d = v + s) + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec2_adds(vec2 v, float s, vec2 dest) { + dest[0] = v[0] + s; + dest[1] = v[1] + s; +} + +/*! + * @brief subtract b vector from a vector store result in dest + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec2_sub(vec2 a, vec2 b, vec2 dest) { + dest[0] = a[0] - b[0]; + dest[1] = a[1] - b[1]; +} + +/*! + * @brief subtract scalar from v vector store result in dest (d = v - s) + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec2_subs(vec2 v, float s, vec2 dest) { + dest[0] = v[0] - s; + dest[1] = v[1] - s; +} + +/*! + * @brief multiply two vector (component-wise multiplication) + * + * @param a v1 + * @param b v2 + * @param dest v3 = (a[0] * b[0], a[1] * b[1]) + */ +CGLM_INLINE +void +glm_vec2_mul(vec2 a, vec2 b, vec2 dest) { + dest[0] = a[0] * b[0]; + dest[1] = a[1] * b[1]; +} + +/*! + * @brief multiply/scale vector with scalar: result = v * s + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec2_scale(vec2 v, float s, vec2 dest) { + dest[0] = v[0] * s; + dest[1] = v[1] * s; +} + +/*! + * @brief scale as vector specified: result = unit(v) * s + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec2_scale_as(vec2 v, float s, vec2 dest) { + float norm; + norm = glm_vec2_norm(v); + + if (norm == 0.0f) { + glm_vec2_zero(dest); + return; + } + + glm_vec2_scale(v, s / norm, dest); +} + +/*! + * @brief div vector with another component-wise division: d = a / b + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest result = (a[0]/b[0], a[1]/b[1]) + */ +CGLM_INLINE +void +glm_vec2_div(vec2 a, vec2 b, vec2 dest) { + dest[0] = a[0] / b[0]; + dest[1] = a[1] / b[1]; +} + +/*! + * @brief div vector with scalar: d = v / s + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest result = (a[0]/s, a[1]/s) + */ +CGLM_INLINE +void +glm_vec2_divs(vec2 v, float s, vec2 dest) { + dest[0] = v[0] / s; + dest[1] = v[1] / s; +} + +/*! + * @brief add two vectors and add result to sum + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += (a + b) + */ +CGLM_INLINE +void +glm_vec2_addadd(vec2 a, vec2 b, vec2 dest) { + dest[0] += a[0] + b[0]; + dest[1] += a[1] + b[1]; +} + +/*! + * @brief sub two vectors and add result to dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += (a + b) + */ +CGLM_INLINE +void +glm_vec2_subadd(vec2 a, vec2 b, vec2 dest) { + dest[0] += a[0] - b[0]; + dest[1] += a[1] - b[1]; +} + +/*! + * @brief mul two vectors and add result to dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += (a * b) + */ +CGLM_INLINE +void +glm_vec2_muladd(vec2 a, vec2 b, vec2 dest) { + dest[0] += a[0] * b[0]; + dest[1] += a[1] * b[1]; +} + +/*! + * @brief mul vector with scalar and add result to sum + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector + * @param[in] s scalar + * @param[out] dest dest += (a * b) + */ +CGLM_INLINE +void +glm_vec2_muladds(vec2 a, float s, vec2 dest) { + dest[0] += a[0] * s; + dest[1] += a[1] * s; +} + +/*! + * @brief add max of two vector to result/dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += max(a, b) + */ +CGLM_INLINE +void +glm_vec2_maxadd(vec2 a, vec2 b, vec2 dest) { + dest[0] += glm_max(a[0], b[0]); + dest[1] += glm_max(a[1], b[1]); +} + +/*! + * @brief add min of two vector to result/dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += min(a, b) + */ +CGLM_INLINE +void +glm_vec2_minadd(vec2 a, vec2 b, vec2 dest) { + dest[0] += glm_min(a[0], b[0]); + dest[1] += glm_min(a[1], b[1]); +} + +/*! + * @brief negate vector components and store result in dest + * + * @param[in] v vector + * @param[out] dest result vector + */ +CGLM_INLINE +void +glm_vec2_negate_to(vec2 v, vec2 dest) { + dest[0] = -v[0]; + dest[1] = -v[1]; +} + +/*! + * @brief negate vector components + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec2_negate(vec2 v) { + glm_vec2_negate_to(v, v); +} + +/*! + * @brief normalize vector and store result in same vec + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec2_normalize(vec2 v) { + float norm; + + norm = glm_vec2_norm(v); + + if (norm == 0.0f) { + v[0] = v[1] = 0.0f; + return; + } + + glm_vec2_scale(v, 1.0f / norm, v); +} + +/*! + * @brief normalize vector to dest + * + * @param[in] v source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec2_normalize_to(vec2 v, vec2 dest) { + float norm; + + norm = glm_vec2_norm(v); + + if (norm == 0.0f) { + glm_vec2_zero(dest); + return; + } + + glm_vec2_scale(v, 1.0f / norm, dest); +} + +/*! + * @brief rotate vec2 around origin by angle (CCW: counterclockwise) + * + * Formula: + * 𝑥2 = cos(a)𝑥1 − sin(a)𝑦1 + * 𝑦2 = sin(a)𝑥1 + cos(a)𝑦1 + * + * @param[in] v vector to rotate + * @param[in] angle angle by radians + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec2_rotate(vec2 v, float angle, vec2 dest) { + float c, s, x1, y1; + + c = cosf(angle); + s = sinf(angle); + + x1 = v[0]; + y1 = v[1]; + + dest[0] = c * x1 - s * y1; + dest[1] = s * x1 + c * y1; +} + +/** + * @brief squared distance between two vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @return returns squared distance (distance * distance) + */ +CGLM_INLINE +float +glm_vec2_distance2(vec2 a, vec2 b) { + return glm_pow2(b[0] - a[0]) + glm_pow2(b[1] - a[1]); +} + +/** + * @brief distance between two vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @return returns distance + */ +CGLM_INLINE +float +glm_vec2_distance(vec2 a, vec2 b) { + return sqrtf(glm_vec2_distance2(a, b)); +} + +/*! + * @brief max values of vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec2_maxv(vec2 a, vec2 b, vec2 dest) { + dest[0] = glm_max(a[0], b[0]); + dest[1] = glm_max(a[1], b[1]); +} + +/*! + * @brief min values of vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec2_minv(vec2 a, vec2 b, vec2 dest) { + dest[0] = glm_min(a[0], b[0]); + dest[1] = glm_min(a[1], b[1]); +} + +/*! + * @brief clamp vector's individual members between min and max values + * + * @param[in, out] v vector + * @param[in] minval minimum value + * @param[in] maxval maximum value + */ +CGLM_INLINE +void +glm_vec2_clamp(vec2 v, float minval, float maxval) { + v[0] = glm_clamp(v[0], minval, maxval); + v[1] = glm_clamp(v[1], minval, maxval); +} + +/*! + * @brief linear interpolation between two vector + * + * formula: from + s * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) clamped between 0 and 1 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec2_lerp(vec2 from, vec2 to, float t, vec2 dest) { + vec2 s, v; + + /* from + s * (to - from) */ + glm_vec2_fill(s, glm_clamp_zo(t)); + glm_vec2_sub(to, from, v); + glm_vec2_mul(s, v, v); + glm_vec2_add(from, v, dest); +} + +#endif /* cglm_vec2_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Macros: + GLM_VEC3_ONE_INIT + GLM_VEC3_ZERO_INIT + GLM_VEC3_ONE + GLM_VEC3_ZERO + GLM_YUP + GLM_ZUP + GLM_XUP + + Functions: + CGLM_INLINE void glm_vec3(vec4 v4, vec3 dest); + CGLM_INLINE void glm_vec3_copy(vec3 a, vec3 dest); + CGLM_INLINE void glm_vec3_zero(vec3 v); + CGLM_INLINE void glm_vec3_one(vec3 v); + CGLM_INLINE float glm_vec3_dot(vec3 a, vec3 b); + CGLM_INLINE float glm_vec3_norm2(vec3 v); + CGLM_INLINE float glm_vec3_norm(vec3 v); + CGLM_INLINE float glm_vec3_norm_one(vec3 v); + CGLM_INLINE float glm_vec3_norm_inf(vec3 v); + CGLM_INLINE void glm_vec3_add(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_adds(vec3 a, float s, vec3 dest); + CGLM_INLINE void glm_vec3_sub(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_subs(vec3 a, float s, vec3 dest); + CGLM_INLINE void glm_vec3_mul(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_scale(vec3 v, float s, vec3 dest); + CGLM_INLINE void glm_vec3_scale_as(vec3 v, float s, vec3 dest); + CGLM_INLINE void glm_vec3_div(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_divs(vec3 a, float s, vec3 dest); + CGLM_INLINE void glm_vec3_addadd(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_subadd(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_muladd(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_muladds(vec3 a, float s, vec3 dest); + CGLM_INLINE void glm_vec3_maxadd(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_minadd(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_flipsign(vec3 v); + CGLM_INLINE void glm_vec3_flipsign_to(vec3 v, vec3 dest); + CGLM_INLINE void glm_vec3_negate_to(vec3 v, vec3 dest); + CGLM_INLINE void glm_vec3_negate(vec3 v); + CGLM_INLINE void glm_vec3_inv(vec3 v); + CGLM_INLINE void glm_vec3_inv_to(vec3 v, vec3 dest); + CGLM_INLINE void glm_vec3_normalize(vec3 v); + CGLM_INLINE void glm_vec3_normalize_to(vec3 v, vec3 dest); + CGLM_INLINE void glm_vec3_cross(vec3 a, vec3 b, vec3 d); + CGLM_INLINE void glm_vec3_crossn(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE float glm_vec3_angle(vec3 a, vec3 b); + CGLM_INLINE void glm_vec3_rotate(vec3 v, float angle, vec3 axis); + CGLM_INLINE void glm_vec3_rotate_m4(mat4 m, vec3 v, vec3 dest); + CGLM_INLINE void glm_vec3_rotate_m3(mat3 m, vec3 v, vec3 dest); + CGLM_INLINE void glm_vec3_proj(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_center(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE float glm_vec3_distance(vec3 a, vec3 b); + CGLM_INLINE float glm_vec3_distance2(vec3 a, vec3 b); + CGLM_INLINE void glm_vec3_maxv(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_minv(vec3 a, vec3 b, vec3 dest); + CGLM_INLINE void glm_vec3_ortho(vec3 v, vec3 dest); + CGLM_INLINE void glm_vec3_clamp(vec3 v, float minVal, float maxVal); + CGLM_INLINE void glm_vec3_lerp(vec3 from, vec3 to, float t, vec3 dest); + CGLM_INLINE void glm_vec3_lerpc(vec3 from, vec3 to, float t, vec3 dest); + CGLM_INLINE void glm_vec3_mix(vec3 from, vec3 to, float t, vec3 dest); + CGLM_INLINE void glm_vec3_mixc(vec3 from, vec3 to, float t, vec3 dest); + CGLM_INLINE void glm_vec3_step_uni(float edge, vec3 x, vec3 dest); + CGLM_INLINE void glm_vec3_step(vec3 edge, vec3 x, vec3 dest); + CGLM_INLINE void glm_vec3_smoothstep_uni(float edge0, float edge1, vec3 x, vec3 dest); + CGLM_INLINE void glm_vec3_smoothstep(vec3 edge0, vec3 edge1, vec3 x, vec3 dest); + CGLM_INLINE void glm_vec3_smoothinterp(vec3 from, vec3 to, float t, vec3 dest); + CGLM_INLINE void glm_vec3_smoothinterpc(vec3 from, vec3 to, float t, vec3 dest); + CGLM_INLINE void glm_vec3_swizzle(vec3 v, int mask, vec3 dest); + + Convenient: + CGLM_INLINE void glm_cross(vec3 a, vec3 b, vec3 d); + CGLM_INLINE float glm_dot(vec3 a, vec3 b); + CGLM_INLINE void glm_normalize(vec3 v); + CGLM_INLINE void glm_normalize_to(vec3 v, vec3 dest); + + DEPRECATED: + glm_vec3_dup + glm_vec3_flipsign + glm_vec3_flipsign_to + glm_vec3_inv + glm_vec3_inv_to + glm_vec3_mulv + */ + +#ifndef cglm_vec3_h +#define cglm_vec3_h + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Macros: + GLM_VEC4_ONE_INIT + GLM_VEC4_BLACK_INIT + GLM_VEC4_ZERO_INIT + GLM_VEC4_ONE + GLM_VEC4_BLACK + GLM_VEC4_ZERO + + Functions: + CGLM_INLINE void glm_vec4(vec3 v3, float last, vec4 dest); + CGLM_INLINE void glm_vec4_copy3(vec4 a, vec3 dest); + CGLM_INLINE void glm_vec4_copy(vec4 v, vec4 dest); + CGLM_INLINE void glm_vec4_ucopy(vec4 v, vec4 dest); + CGLM_INLINE float glm_vec4_dot(vec4 a, vec4 b); + CGLM_INLINE float glm_vec4_norm2(vec4 v); + CGLM_INLINE float glm_vec4_norm(vec4 v); + CGLM_INLINE float glm_vec4_norm_one(vec4 v); + CGLM_INLINE float glm_vec4_norm_inf(vec4 v); + CGLM_INLINE void glm_vec4_add(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_adds(vec4 v, float s, vec4 dest); + CGLM_INLINE void glm_vec4_sub(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_subs(vec4 v, float s, vec4 dest); + CGLM_INLINE void glm_vec4_mul(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_scale(vec4 v, float s, vec4 dest); + CGLM_INLINE void glm_vec4_scale_as(vec4 v, float s, vec4 dest); + CGLM_INLINE void glm_vec4_div(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_divs(vec4 v, float s, vec4 dest); + CGLM_INLINE void glm_vec4_addadd(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_subadd(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_muladd(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_muladds(vec4 a, float s, vec4 dest); + CGLM_INLINE void glm_vec4_maxadd(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_minadd(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_negate(vec4 v); + CGLM_INLINE void glm_vec4_inv(vec4 v); + CGLM_INLINE void glm_vec4_inv_to(vec4 v, vec4 dest); + CGLM_INLINE void glm_vec4_normalize(vec4 v); + CGLM_INLINE void glm_vec4_normalize_to(vec4 vec, vec4 dest); + CGLM_INLINE float glm_vec4_distance(vec4 a, vec4 b); + CGLM_INLINE float glm_vec4_distance2(vec4 a, vec4 b); + CGLM_INLINE void glm_vec4_maxv(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_minv(vec4 a, vec4 b, vec4 dest); + CGLM_INLINE void glm_vec4_clamp(vec4 v, float minVal, float maxVal); + CGLM_INLINE void glm_vec4_lerp(vec4 from, vec4 to, float t, vec4 dest); + CGLM_INLINE void glm_vec4_lerpc(vec4 from, vec4 to, float t, vec4 dest); + CGLM_INLINE void glm_vec4_step_uni(float edge, vec4 x, vec4 dest); + CGLM_INLINE void glm_vec4_step(vec4 edge, vec4 x, vec4 dest); + CGLM_INLINE void glm_vec4_smoothstep_uni(float edge0, float edge1, vec4 x, vec4 dest); + CGLM_INLINE void glm_vec4_smoothstep(vec4 edge0, vec4 edge1, vec4 x, vec4 dest); + CGLM_INLINE void glm_vec4_smoothinterp(vec4 from, vec4 to, float t, vec4 dest); + CGLM_INLINE void glm_vec4_smoothinterpc(vec4 from, vec4 to, float t, vec4 dest); + CGLM_INLINE void glm_vec4_swizzle(vec4 v, int mask, vec4 dest); + + DEPRECATED: + glm_vec4_dup + glm_vec4_flipsign + glm_vec4_flipsign_to + glm_vec4_inv + glm_vec4_inv_to + glm_vec4_mulv + */ + +#ifndef cglm_vec4_h +#define cglm_vec4_h + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/*! + * @brief SIMD like functions + */ + +/* + Functions: + CGLM_INLINE void glm_vec4_broadcast(float val, vec4 d); + CGLM_INLINE void glm_vec4_fill(vec4 v, float val); + CGLM_INLINE bool glm_vec4_eq(vec4 v, float val); + CGLM_INLINE bool glm_vec4_eq_eps(vec4 v, float val); + CGLM_INLINE bool glm_vec4_eq_all(vec4 v); + CGLM_INLINE bool glm_vec4_eqv(vec4 a, vec4 b); + CGLM_INLINE bool glm_vec4_eqv_eps(vec4 a, vec4 b); + CGLM_INLINE float glm_vec4_max(vec4 v); + CGLM_INLINE float glm_vec4_min(vec4 v); + CGLM_INLINE bool glm_vec4_isnan(vec4 v); + CGLM_INLINE bool glm_vec4_isinf(vec4 v); + CGLM_INLINE bool glm_vec4_isvalid(vec4 v); + CGLM_INLINE void glm_vec4_sign(vec4 v, vec4 dest); + CGLM_INLINE void glm_vec4_abs(vec4 v, vec4 dest); + CGLM_INLINE void glm_vec4_fract(vec4 v, vec4 dest); + CGLM_INLINE float glm_vec4_hadd(vec4 v); + CGLM_INLINE void glm_vec4_sqrt(vec4 v, vec4 dest); + */ + +#ifndef cglm_vec4_ext_h +#define cglm_vec4_ext_h + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/*! + * @brief SIMD like functions + */ + +/* + Functions: + CGLM_INLINE void glm_vec3_broadcast(float val, vec3 d); + CGLM_INLINE void glm_vec3_fill(vec3 v, float val); + CGLM_INLINE bool glm_vec3_eq(vec3 v, float val); + CGLM_INLINE bool glm_vec3_eq_eps(vec3 v, float val); + CGLM_INLINE bool glm_vec3_eq_all(vec3 v); + CGLM_INLINE bool glm_vec3_eqv(vec3 a, vec3 b); + CGLM_INLINE bool glm_vec3_eqv_eps(vec3 a, vec3 b); + CGLM_INLINE float glm_vec3_max(vec3 v); + CGLM_INLINE float glm_vec3_min(vec3 v); + CGLM_INLINE bool glm_vec3_isnan(vec3 v); + CGLM_INLINE bool glm_vec3_isinf(vec3 v); + CGLM_INLINE bool glm_vec3_isvalid(vec3 v); + CGLM_INLINE void glm_vec3_sign(vec3 v, vec3 dest); + CGLM_INLINE void glm_vec3_abs(vec3 v, vec3 dest); + CGLM_INLINE void glm_vec3_fract(vec3 v, vec3 dest); + CGLM_INLINE float glm_vec3_hadd(vec3 v); + CGLM_INLINE void glm_vec3_sqrt(vec3 v, vec3 dest); + */ + +#ifndef cglm_vec3_ext_h +#define cglm_vec3_ext_h + + +/*! + * @brief fill a vector with specified value + * + * @param[in] val value + * @param[out] d dest + */ +CGLM_INLINE +void +glm_vec3_broadcast(float val, vec3 d) { + d[0] = d[1] = d[2] = val; +} + +/*! + * @brief fill a vector with specified value + * + * @param[out] v dest + * @param[in] val value + */ +CGLM_INLINE +void +glm_vec3_fill(vec3 v, float val) { + v[0] = v[1] = v[2] = val; +} + +/*! + * @brief check if vector is equal to value (without epsilon) + * + * @param[in] v vector + * @param[in] val value + */ +CGLM_INLINE +bool +glm_vec3_eq(vec3 v, float val) { + return v[0] == val && v[0] == v[1] && v[0] == v[2]; +} + +/*! + * @brief check if vector is equal to value (with epsilon) + * + * @param[in] v vector + * @param[in] val value + */ +CGLM_INLINE +bool +glm_vec3_eq_eps(vec3 v, float val) { + return fabsf(v[0] - val) <= GLM_FLT_EPSILON + && fabsf(v[1] - val) <= GLM_FLT_EPSILON + && fabsf(v[2] - val) <= GLM_FLT_EPSILON; +} + +/*! + * @brief check if vectors members are equal (without epsilon) + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec3_eq_all(vec3 v) { + return glm_vec3_eq_eps(v, v[0]); +} + +/*! + * @brief check if vector is equal to another (without epsilon) + * + * @param[in] a vector + * @param[in] b vector + */ +CGLM_INLINE +bool +glm_vec3_eqv(vec3 a, vec3 b) { + return a[0] == b[0] + && a[1] == b[1] + && a[2] == b[2]; +} + +/*! + * @brief check if vector is equal to another (with epsilon) + * + * @param[in] a vector + * @param[in] b vector + */ +CGLM_INLINE +bool +glm_vec3_eqv_eps(vec3 a, vec3 b) { + return fabsf(a[0] - b[0]) <= GLM_FLT_EPSILON + && fabsf(a[1] - b[1]) <= GLM_FLT_EPSILON + && fabsf(a[2] - b[2]) <= GLM_FLT_EPSILON; +} + +/*! + * @brief max value of vector + * + * @param[in] v vector + */ +CGLM_INLINE +float +glm_vec3_max(vec3 v) { + float max; + + max = v[0]; + if (v[1] > max) + max = v[1]; + if (v[2] > max) + max = v[2]; + + return max; +} + +/*! + * @brief min value of vector + * + * @param[in] v vector + */ +CGLM_INLINE +float +glm_vec3_min(vec3 v) { + float min; + + min = v[0]; + if (v[1] < min) + min = v[1]; + if (v[2] < min) + min = v[2]; + + return min; +} + +/*! + * @brief check if all items are NaN (not a number) + * you should only use this in DEBUG mode or very critical asserts + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec3_isnan(vec3 v) { + return isnan(v[0]) || isnan(v[1]) || isnan(v[2]); +} + +/*! + * @brief check if all items are INFINITY + * you should only use this in DEBUG mode or very critical asserts + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec3_isinf(vec3 v) { + return isinf(v[0]) || isinf(v[1]) || isinf(v[2]); +} + +/*! + * @brief check if all items are valid number + * you should only use this in DEBUG mode or very critical asserts + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec3_isvalid(vec3 v) { + return !glm_vec3_isnan(v) && !glm_vec3_isinf(v); +} + +/*! + * @brief get sign of 32 bit float as +1, -1, 0 + * + * Important: It returns 0 for zero/NaN input + * + * @param v vector + */ +CGLM_INLINE +void +glm_vec3_sign(vec3 v, vec3 dest) { + dest[0] = glm_signf(v[0]); + dest[1] = glm_signf(v[1]); + dest[2] = glm_signf(v[2]); +} + +/*! + * @brief absolute value of each vector item + * + * @param[in] v vector + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec3_abs(vec3 v, vec3 dest) { + dest[0] = fabsf(v[0]); + dest[1] = fabsf(v[1]); + dest[2] = fabsf(v[2]); +} + +/*! + * @brief fractional part of each vector item + * + * @param[in] v vector + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec3_fract(vec3 v, vec3 dest) { + dest[0] = fminf(v[0] - floorf(v[0]), 0.999999940395355224609375f); + dest[1] = fminf(v[1] - floorf(v[1]), 0.999999940395355224609375f); + dest[2] = fminf(v[2] - floorf(v[2]), 0.999999940395355224609375f); +} + +/*! + * @brief vector reduction by summation + * @warning could overflow + * + * @param[in] v vector + * @return sum of all vector's elements + */ +CGLM_INLINE +float +glm_vec3_hadd(vec3 v) { + return v[0] + v[1] + v[2]; +} + +/*! + * @brief square root of each vector item + * + * @param[in] v vector + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec3_sqrt(vec3 v, vec3 dest) { + dest[0] = sqrtf(v[0]); + dest[1] = sqrtf(v[1]); + dest[2] = sqrtf(v[2]); +} + +#endif /* cglm_vec3_ext_h */ + + +/*! + * @brief fill a vector with specified value + * + * @param val value + * @param d dest + */ +CGLM_INLINE +void +glm_vec4_broadcast(float val, vec4 d) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(d, _mm_set1_ps(val)); +#else + d[0] = d[1] = d[2] = d[3] = val; +#endif +} + +/*! + * @brief fill a vector with specified value + * + * @param v dest + * @param val value + */ +CGLM_INLINE +void +glm_vec4_fill(vec4 v, float val) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(v, _mm_set1_ps(val)); +#else + v[0] = v[1] = v[2] = v[3] = val; +#endif +} + +/*! + * @brief check if vector is equal to value (without epsilon) + * + * @param v vector + * @param val value + */ +CGLM_INLINE +bool +glm_vec4_eq(vec4 v, float val) { + return v[0] == val + && v[0] == v[1] + && v[0] == v[2] + && v[0] == v[3]; +} + +/*! + * @brief check if vector is equal to value (with epsilon) + * + * @param v vector + * @param val value + */ +CGLM_INLINE +bool +glm_vec4_eq_eps(vec4 v, float val) { + return fabsf(v[0] - val) <= GLM_FLT_EPSILON + && fabsf(v[1] - val) <= GLM_FLT_EPSILON + && fabsf(v[2] - val) <= GLM_FLT_EPSILON + && fabsf(v[3] - val) <= GLM_FLT_EPSILON; +} + +/*! + * @brief check if vectors members are equal (without epsilon) + * + * @param v vector + */ +CGLM_INLINE +bool +glm_vec4_eq_all(vec4 v) { + return glm_vec4_eq_eps(v, v[0]); +} + +/*! + * @brief check if vector is equal to another (without epsilon) + * + * @param a vector + * @param b vector + */ +CGLM_INLINE +bool +glm_vec4_eqv(vec4 a, vec4 b) { + return a[0] == b[0] + && a[1] == b[1] + && a[2] == b[2] + && a[3] == b[3]; +} + +/*! + * @brief check if vector is equal to another (with epsilon) + * + * @param a vector + * @param b vector + */ +CGLM_INLINE +bool +glm_vec4_eqv_eps(vec4 a, vec4 b) { + return fabsf(a[0] - b[0]) <= GLM_FLT_EPSILON + && fabsf(a[1] - b[1]) <= GLM_FLT_EPSILON + && fabsf(a[2] - b[2]) <= GLM_FLT_EPSILON + && fabsf(a[3] - b[3]) <= GLM_FLT_EPSILON; +} + +/*! + * @brief max value of vector + * + * @param v vector + */ +CGLM_INLINE +float +glm_vec4_max(vec4 v) { + float max; + + max = glm_vec3_max(v); + if (v[3] > max) + max = v[3]; + + return max; +} + +/*! + * @brief min value of vector + * + * @param v vector + */ +CGLM_INLINE +float +glm_vec4_min(vec4 v) { + float min; + + min = glm_vec3_min(v); + if (v[3] < min) + min = v[3]; + + return min; +} + +/*! + * @brief check if one of items is NaN (not a number) + * you should only use this in DEBUG mode or very critical asserts + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec4_isnan(vec4 v) { + return isnan(v[0]) || isnan(v[1]) || isnan(v[2]) || isnan(v[3]); +} + +/*! + * @brief check if one of items is INFINITY + * you should only use this in DEBUG mode or very critical asserts + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec4_isinf(vec4 v) { + return isinf(v[0]) || isinf(v[1]) || isinf(v[2]) || isinf(v[3]); +} + +/*! + * @brief check if all items are valid number + * you should only use this in DEBUG mode or very critical asserts + * + * @param[in] v vector + */ +CGLM_INLINE +bool +glm_vec4_isvalid(vec4 v) { + return !glm_vec4_isnan(v) && !glm_vec4_isinf(v); +} + +/*! + * @brief get sign of 32 bit float as +1, -1, 0 + * + * Important: It returns 0 for zero/NaN input + * + * @param v vector + */ +CGLM_INLINE +void +glm_vec4_sign(vec4 v, vec4 dest) { +#if defined( __SSE2__ ) || defined( __SSE2__ ) + __m128 x0, x1, x2, x3, x4; + + x0 = glmm_load(v); + x1 = _mm_set_ps(0.0f, 0.0f, 1.0f, -1.0f); + x2 = glmm_shuff1x(x1, 2); + + x3 = _mm_and_ps(_mm_cmpgt_ps(x0, x2), glmm_shuff1x(x1, 1)); + x4 = _mm_and_ps(_mm_cmplt_ps(x0, x2), glmm_shuff1x(x1, 0)); + + glmm_store(dest, _mm_or_ps(x3, x4)); +#else + dest[0] = glm_signf(v[0]); + dest[1] = glm_signf(v[1]); + dest[2] = glm_signf(v[2]); + dest[3] = glm_signf(v[3]); +#endif +} + +/*! + * @brief absolute value of each vector item + * + * @param[in] v vector + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec4_abs(vec4 v, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, glmm_abs(glmm_load(v))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vabsq_f32(vld1q_f32(v))); +#else + dest[0] = fabsf(v[0]); + dest[1] = fabsf(v[1]); + dest[2] = fabsf(v[2]); + dest[3] = fabsf(v[3]); +#endif +} + +/*! + * @brief fractional part of each vector item + * + * @param[in] v vector + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec4_fract(vec4 v, vec4 dest) { + dest[0] = fminf(v[0] - floorf(v[0]), 0.999999940395355224609375f); + dest[1] = fminf(v[1] - floorf(v[1]), 0.999999940395355224609375f); + dest[2] = fminf(v[2] - floorf(v[2]), 0.999999940395355224609375f); + dest[3] = fminf(v[3] - floorf(v[3]), 0.999999940395355224609375f); +} + +/*! + * @brief vector reduction by summation + * @warning could overflow + * + * @param[in] v vector + * @return sum of all vector's elements + */ +CGLM_INLINE +float +glm_vec4_hadd(vec4 v) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + return glmm_hadd(glmm_load(v)); +#else + return v[0] + v[1] + v[2] + v[3]; +#endif +} + +/*! + * @brief square root of each vector item + * + * @param[in] v vector + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec4_sqrt(vec4 v, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_sqrt_ps(glmm_load(v))); +#else + dest[0] = sqrtf(v[0]); + dest[1] = sqrtf(v[1]); + dest[2] = sqrtf(v[2]); + dest[3] = sqrtf(v[3]); +#endif +} + +#endif /* cglm_vec4_ext_h */ + + +/* DEPRECATED! functions */ +#define glm_vec4_dup3(v, dest) glm_vec4_copy3(v, dest) +#define glm_vec4_dup(v, dest) glm_vec4_copy(v, dest) +#define glm_vec4_flipsign(v) glm_vec4_negate(v) +#define glm_vec4_flipsign_to(v, dest) glm_vec4_negate_to(v, dest) +#define glm_vec4_inv(v) glm_vec4_negate(v) +#define glm_vec4_inv_to(v, dest) glm_vec4_negate_to(v, dest) +#define glm_vec4_mulv(a, b, d) glm_vec4_mul(a, b, d) + +#define GLM_VEC4_ONE_INIT {1.0f, 1.0f, 1.0f, 1.0f} +#define GLM_VEC4_BLACK_INIT {0.0f, 0.0f, 0.0f, 1.0f} +#define GLM_VEC4_ZERO_INIT {0.0f, 0.0f, 0.0f, 0.0f} + +#define GLM_VEC4_ONE ((vec4)GLM_VEC4_ONE_INIT) +#define GLM_VEC4_BLACK ((vec4)GLM_VEC4_BLACK_INIT) +#define GLM_VEC4_ZERO ((vec4)GLM_VEC4_ZERO_INIT) + +#define GLM_XXXX GLM_SHUFFLE4(0, 0, 0, 0) +#define GLM_YYYY GLM_SHUFFLE4(1, 1, 1, 1) +#define GLM_ZZZZ GLM_SHUFFLE4(2, 2, 2, 2) +#define GLM_WWWW GLM_SHUFFLE4(3, 3, 3, 3) +#define GLM_WZYX GLM_SHUFFLE4(0, 1, 2, 3) + +/*! + * @brief init vec4 using vec3 + * + * @param[in] v3 vector3 + * @param[in] last last item + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4(vec3 v3, float last, vec4 dest) { + dest[0] = v3[0]; + dest[1] = v3[1]; + dest[2] = v3[2]; + dest[3] = last; +} + +/*! + * @brief copy first 3 members of [a] to [dest] + * + * @param[in] a source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_copy3(vec4 a, vec3 dest) { + dest[0] = a[0]; + dest[1] = a[1]; + dest[2] = a[2]; +} + +/*! + * @brief copy all members of [a] to [dest] + * + * @param[in] v source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_copy(vec4 v, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, glmm_load(v)); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vld1q_f32(v)); +#else + dest[0] = v[0]; + dest[1] = v[1]; + dest[2] = v[2]; + dest[3] = v[3]; +#endif +} + +/*! + * @brief copy all members of [a] to [dest] + * + * alignment is not required + * + * @param[in] v source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_ucopy(vec4 v, vec4 dest) { + dest[0] = v[0]; + dest[1] = v[1]; + dest[2] = v[2]; + dest[3] = v[3]; +} + +/*! + * @brief make vector zero + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec4_zero(vec4 v) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(v, _mm_setzero_ps()); +#elif defined(CGLM_NEON_FP) + vst1q_f32(v, vdupq_n_f32(0.0f)); +#else + v[0] = 0.0f; + v[1] = 0.0f; + v[2] = 0.0f; + v[3] = 0.0f; +#endif +} + +/*! + * @brief make vector one + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec4_one(vec4 v) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(v, _mm_set1_ps(1.0f)); +#elif defined(CGLM_NEON_FP) + vst1q_f32(v, vdupq_n_f32(1.0f)); +#else + v[0] = 1.0f; + v[1] = 1.0f; + v[2] = 1.0f; + v[3] = 1.0f; +#endif +} + +/*! + * @brief vec4 dot product + * + * @param[in] a vector1 + * @param[in] b vector2 + * + * @return dot product + */ +CGLM_INLINE +float +glm_vec4_dot(vec4 a, vec4 b) { +#if defined(CGLM_SIMD) + return glmm_dot(glmm_load(a), glmm_load(b)); +#else + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; +#endif +} + +/*! + * @brief norm * norm (magnitude) of vec + * + * we can use this func instead of calling norm * norm, because it would call + * sqrtf fuction twice but with this func we can avoid func call, maybe this is + * not good name for this func + * + * @param[in] v vec4 + * + * @return norm * norm + */ +CGLM_INLINE +float +glm_vec4_norm2(vec4 v) { + return glm_vec4_dot(v, v); +} + +/*! + * @brief euclidean norm (magnitude), also called L2 norm + * this will give magnitude of vector in euclidean space + * + * @param[in] v vector + * + * @return norm + */ +CGLM_INLINE +float +glm_vec4_norm(vec4 v) { +#if defined(CGLM_SIMD) + return glmm_norm(glmm_load(v)); +#else + return sqrtf(glm_vec4_dot(v, v)); +#endif +} + +/*! + * @brief L1 norm of vec4 + * Also known as Manhattan Distance or Taxicab norm. + * L1 Norm is the sum of the magnitudes of the vectors in a space. + * It is calculated as the sum of the absolute values of the vector components. + * In this norm, all the components of the vector are weighted equally. + * + * This computes: + * L1 norm = |v[0]| + |v[1]| + |v[2]| + |v[3]| + * + * @param[in] v vector + * + * @return L1 norm + */ +CGLM_INLINE +float +glm_vec4_norm_one(vec4 v) { +#if defined(CGLM_SIMD) + return glmm_norm_one(glmm_load(v)); +#else + vec4 t; + glm_vec4_abs(v, t); + return glm_vec4_hadd(t); +#endif +} + +/*! + * @brief infinity norm of vec4 + * Also known as Maximum norm. + * Infinity Norm is the largest magnitude among each element of a vector. + * It is calculated as the maximum of the absolute values of the vector components. + * + * This computes: + * inf norm = max(|v[0]|, |v[1]|, |v[2]|, |v[3]|) + * + * @param[in] v vector + * + * @return infinity norm + */ +CGLM_INLINE +float +glm_vec4_norm_inf(vec4 v) { +#if defined(CGLM_SIMD) + return glmm_norm_inf(glmm_load(v)); +#else + vec4 t; + glm_vec4_abs(v, t); + return glm_vec4_max(t); +#endif +} + +/*! + * @brief add b vector to a vector store result in dest + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec4_add(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_add_ps(glmm_load(a), glmm_load(b))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vaddq_f32(vld1q_f32(a), vld1q_f32(b))); +#else + dest[0] = a[0] + b[0]; + dest[1] = a[1] + b[1]; + dest[2] = a[2] + b[2]; + dest[3] = a[3] + b[3]; +#endif +} + +/*! + * @brief add scalar to v vector store result in dest (d = v + vec(s)) + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec4_adds(vec4 v, float s, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_add_ps(glmm_load(v), _mm_set1_ps(s))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vaddq_f32(vld1q_f32(v), vdupq_n_f32(s))); +#else + dest[0] = v[0] + s; + dest[1] = v[1] + s; + dest[2] = v[2] + s; + dest[3] = v[3] + s; +#endif +} + +/*! + * @brief subtract b vector from a vector store result in dest (d = a - b) + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec4_sub(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_sub_ps(glmm_load(a), glmm_load(b))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vsubq_f32(vld1q_f32(a), vld1q_f32(b))); +#else + dest[0] = a[0] - b[0]; + dest[1] = a[1] - b[1]; + dest[2] = a[2] - b[2]; + dest[3] = a[3] - b[3]; +#endif +} + +/*! + * @brief subtract scalar from v vector store result in dest (d = v - vec(s)) + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec4_subs(vec4 v, float s, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_sub_ps(glmm_load(v), _mm_set1_ps(s))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vsubq_f32(vld1q_f32(v), vdupq_n_f32(s))); +#else + dest[0] = v[0] - s; + dest[1] = v[1] - s; + dest[2] = v[2] - s; + dest[3] = v[3] - s; +#endif +} + +/*! + * @brief multiply two vector (component-wise multiplication) + * + * @param a vector1 + * @param b vector2 + * @param dest dest = (a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]) + */ +CGLM_INLINE +void +glm_vec4_mul(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_mul_ps(glmm_load(a), glmm_load(b))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vmulq_f32(vld1q_f32(a), vld1q_f32(b))); +#else + dest[0] = a[0] * b[0]; + dest[1] = a[1] * b[1]; + dest[2] = a[2] * b[2]; + dest[3] = a[3] * b[3]; +#endif +} + +/*! + * @brief multiply/scale vec4 vector with scalar: result = v * s + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec4_scale(vec4 v, float s, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_mul_ps(glmm_load(v), _mm_set1_ps(s))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vmulq_f32(vld1q_f32(v), vdupq_n_f32(s))); +#else + dest[0] = v[0] * s; + dest[1] = v[1] * s; + dest[2] = v[2] * s; + dest[3] = v[3] * s; +#endif +} + +/*! + * @brief make vec4 vector scale as specified: result = unit(v) * s + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec4_scale_as(vec4 v, float s, vec4 dest) { + float norm; + norm = glm_vec4_norm(v); + + if (norm == 0.0f) { + glm_vec4_zero(dest); + return; + } + + glm_vec4_scale(v, s / norm, dest); +} + +/*! + * @brief div vector with another component-wise division: d = a / b + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest result = (a[0]/b[0], a[1]/b[1], a[2]/b[2], a[3]/b[3]) + */ +CGLM_INLINE +void +glm_vec4_div(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_div_ps(glmm_load(a), glmm_load(b))); +#else + dest[0] = a[0] / b[0]; + dest[1] = a[1] / b[1]; + dest[2] = a[2] / b[2]; + dest[3] = a[3] / b[3]; +#endif +} + +/*! + * @brief div vec4 vector with scalar: d = v / s + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec4_divs(vec4 v, float s, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_div_ps(glmm_load(v), _mm_set1_ps(s))); +#else + glm_vec4_scale(v, 1.0f / s, dest); +#endif +} + +/*! + * @brief add two vectors and add result to sum + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += (a + b) + */ +CGLM_INLINE +void +glm_vec4_addadd(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_add_ps(glmm_load(dest), + _mm_add_ps(glmm_load(a), + glmm_load(b)))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vaddq_f32(vld1q_f32(dest), + vaddq_f32(vld1q_f32(a), + vld1q_f32(b)))); +#else + dest[0] += a[0] + b[0]; + dest[1] += a[1] + b[1]; + dest[2] += a[2] + b[2]; + dest[3] += a[3] + b[3]; +#endif +} + +/*! + * @brief sub two vectors and add result to dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += (a - b) + */ +CGLM_INLINE +void +glm_vec4_subadd(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_add_ps(glmm_load(dest), + _mm_sub_ps(glmm_load(a), + glmm_load(b)))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vaddq_f32(vld1q_f32(dest), + vsubq_f32(vld1q_f32(a), + vld1q_f32(b)))); +#else + dest[0] += a[0] - b[0]; + dest[1] += a[1] - b[1]; + dest[2] += a[2] - b[2]; + dest[3] += a[3] - b[3]; +#endif +} + +/*! + * @brief mul two vectors and add result to dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += (a * b) + */ +CGLM_INLINE +void +glm_vec4_muladd(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_add_ps(glmm_load(dest), + _mm_mul_ps(glmm_load(a), + glmm_load(b)))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vaddq_f32(vld1q_f32(dest), + vmulq_f32(vld1q_f32(a), + vld1q_f32(b)))); +#else + dest[0] += a[0] * b[0]; + dest[1] += a[1] * b[1]; + dest[2] += a[2] * b[2]; + dest[3] += a[3] * b[3]; +#endif +} + +/*! + * @brief mul vector with scalar and add result to sum + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector + * @param[in] s scalar + * @param[out] dest dest += (a * b) + */ +CGLM_INLINE +void +glm_vec4_muladds(vec4 a, float s, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_add_ps(glmm_load(dest), + _mm_mul_ps(glmm_load(a), + _mm_set1_ps(s)))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vaddq_f32(vld1q_f32(dest), + vmulq_f32(vld1q_f32(a), + vdupq_n_f32(s)))); +#else + dest[0] += a[0] * s; + dest[1] += a[1] * s; + dest[2] += a[2] * s; + dest[3] += a[3] * s; +#endif +} + +/*! + * @brief add max of two vector to result/dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += max(a, b) + */ +CGLM_INLINE +void +glm_vec4_maxadd(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_add_ps(glmm_load(dest), + _mm_max_ps(glmm_load(a), + glmm_load(b)))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vaddq_f32(vld1q_f32(dest), + vmaxq_f32(vld1q_f32(a), + vld1q_f32(b)))); +#else + dest[0] += glm_max(a[0], b[0]); + dest[1] += glm_max(a[1], b[1]); + dest[2] += glm_max(a[2], b[2]); + dest[3] += glm_max(a[3], b[3]); +#endif +} + +/*! + * @brief add min of two vector to result/dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += min(a, b) + */ +CGLM_INLINE +void +glm_vec4_minadd(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_add_ps(glmm_load(dest), + _mm_min_ps(glmm_load(a), + glmm_load(b)))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vaddq_f32(vld1q_f32(dest), + vminq_f32(vld1q_f32(a), + vld1q_f32(b)))); +#else + dest[0] += glm_min(a[0], b[0]); + dest[1] += glm_min(a[1], b[1]); + dest[2] += glm_min(a[2], b[2]); + dest[3] += glm_min(a[3], b[3]); +#endif +} + +/*! + * @brief negate vector components and store result in dest + * + * @param[in] v vector + * @param[out] dest result vector + */ +CGLM_INLINE +void +glm_vec4_negate_to(vec4 v, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_xor_ps(glmm_load(v), _mm_set1_ps(-0.0f))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vnegq_f32(vld1q_f32(v))); +#else + dest[0] = -v[0]; + dest[1] = -v[1]; + dest[2] = -v[2]; + dest[3] = -v[3]; +#endif +} + +/*! + * @brief flip sign of all vec4 members + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec4_negate(vec4 v) { + glm_vec4_negate_to(v, v); +} + +/*! + * @brief normalize vec4 to dest + * + * @param[in] v source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_normalize_to(vec4 v, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + __m128 xdot, x0; + float dot; + + x0 = glmm_load(v); + xdot = glmm_vdot(x0, x0); + dot = _mm_cvtss_f32(xdot); + + if (dot == 0.0f) { + glmm_store(dest, _mm_setzero_ps()); + return; + } + + glmm_store(dest, _mm_div_ps(x0, _mm_sqrt_ps(xdot))); +#else + float norm; + + norm = glm_vec4_norm(v); + + if (norm == 0.0f) { + glm_vec4_zero(dest); + return; + } + + glm_vec4_scale(v, 1.0f / norm, dest); +#endif +} + +/*! + * @brief normalize vec4 and store result in same vec + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec4_normalize(vec4 v) { + glm_vec4_normalize_to(v, v); +} + +/** + * @brief distance between two vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @return returns distance + */ +CGLM_INLINE +float +glm_vec4_distance(vec4 a, vec4 b) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + return glmm_norm(_mm_sub_ps(glmm_load(a), glmm_load(b))); +#elif defined(CGLM_NEON_FP) + return glmm_norm(vsubq_f32(glmm_load(a), glmm_load(b))); +#else + return sqrtf(glm_pow2(a[0] - b[0]) + + glm_pow2(a[1] - b[1]) + + glm_pow2(a[2] - b[2]) + + glm_pow2(a[3] - b[3])); +#endif +} + +/** + * @brief squared distance between two vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @return returns squared distance + */ +CGLM_INLINE +float +glm_vec4_distance2(vec4 a, vec4 b) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + return glmm_norm2(_mm_sub_ps(glmm_load(a), glmm_load(b))); +#elif defined(CGLM_NEON_FP) + return glmm_norm2(vsubq_f32(glmm_load(a), glmm_load(b))); +#else + return glm_pow2(a[0] - b[0]) + + glm_pow2(a[1] - b[1]) + + glm_pow2(a[2] - b[2]) + + glm_pow2(a[3] - b[3]); +#endif +} + +/*! + * @brief max values of vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_maxv(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_max_ps(glmm_load(a), glmm_load(b))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vmaxq_f32(vld1q_f32(a), vld1q_f32(b))); +#else + dest[0] = glm_max(a[0], b[0]); + dest[1] = glm_max(a[1], b[1]); + dest[2] = glm_max(a[2], b[2]); + dest[3] = glm_max(a[3], b[3]); +#endif +} + +/*! + * @brief min values of vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_minv(vec4 a, vec4 b, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest, _mm_min_ps(glmm_load(a), glmm_load(b))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest, vminq_f32(vld1q_f32(a), vld1q_f32(b))); +#else + dest[0] = glm_min(a[0], b[0]); + dest[1] = glm_min(a[1], b[1]); + dest[2] = glm_min(a[2], b[2]); + dest[3] = glm_min(a[3], b[3]); +#endif +} + +/*! + * @brief clamp vector's individual members between min and max values + * + * @param[in, out] v vector + * @param[in] minVal minimum value + * @param[in] maxVal maximum value + */ +CGLM_INLINE +void +glm_vec4_clamp(vec4 v, float minVal, float maxVal) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(v, _mm_min_ps(_mm_max_ps(glmm_load(v), _mm_set1_ps(minVal)), + _mm_set1_ps(maxVal))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(v, vminq_f32(vmaxq_f32(vld1q_f32(v), vdupq_n_f32(minVal)), + vdupq_n_f32(maxVal))); +#else + v[0] = glm_clamp(v[0], minVal, maxVal); + v[1] = glm_clamp(v[1], minVal, maxVal); + v[2] = glm_clamp(v[2], minVal, maxVal); + v[3] = glm_clamp(v[3], minVal, maxVal); +#endif +} + +/*! + * @brief linear interpolation between two vectors + * + * formula: from + t * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_lerp(vec4 from, vec4 to, float t, vec4 dest) { + vec4 s, v; + + /* from + s * (to - from) */ + glm_vec4_broadcast(t, s); + glm_vec4_sub(to, from, v); + glm_vec4_mul(s, v, v); + glm_vec4_add(from, v, dest); +} + +/*! + * @brief linear interpolation between two vectors (clamped) + * + * formula: from + t * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) clamped between 0 and 1 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_lerpc(vec4 from, vec4 to, float t, vec4 dest) { + glm_vec4_lerp(from, to, glm_clamp_zo(t), dest); +} + +/*! + * @brief linear interpolation between two vectors + * + * formula: from + t * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_mix(vec4 from, vec4 to, float t, vec4 dest) { + glm_vec4_lerp(from, to, t, dest); +} + +/*! + * @brief linear interpolation between two vectors (clamped) + * + * formula: from + t * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) clamped between 0 and 1 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_mixc(vec4 from, vec4 to, float t, vec4 dest) { + glm_vec4_lerpc(from, to, t, dest); +} + +/*! + * @brief threshold function (unidimensional) + * + * @param[in] edge threshold + * @param[in] x value to test against threshold + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_step_uni(float edge, vec4 x, vec4 dest) { + dest[0] = glm_step(edge, x[0]); + dest[1] = glm_step(edge, x[1]); + dest[2] = glm_step(edge, x[2]); + dest[3] = glm_step(edge, x[3]); +} + +/*! + * @brief threshold function + * + * @param[in] edge threshold + * @param[in] x value to test against threshold + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_step(vec4 edge, vec4 x, vec4 dest) { + dest[0] = glm_step(edge[0], x[0]); + dest[1] = glm_step(edge[1], x[1]); + dest[2] = glm_step(edge[2], x[2]); + dest[3] = glm_step(edge[3], x[3]); +} + +/*! + * @brief threshold function with a smooth transition (unidimensional) + * + * @param[in] edge0 low threshold + * @param[in] edge1 high threshold + * @param[in] x value to test against threshold + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_smoothstep_uni(float edge0, float edge1, vec4 x, vec4 dest) { + dest[0] = glm_smoothstep(edge0, edge1, x[0]); + dest[1] = glm_smoothstep(edge0, edge1, x[1]); + dest[2] = glm_smoothstep(edge0, edge1, x[2]); + dest[3] = glm_smoothstep(edge0, edge1, x[3]); +} + +/*! + * @brief threshold function with a smooth transition + * + * @param[in] edge0 low threshold + * @param[in] edge1 high threshold + * @param[in] x value to test against threshold + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_smoothstep(vec4 edge0, vec4 edge1, vec4 x, vec4 dest) { + dest[0] = glm_smoothstep(edge0[0], edge1[0], x[0]); + dest[1] = glm_smoothstep(edge0[1], edge1[1], x[1]); + dest[2] = glm_smoothstep(edge0[2], edge1[2], x[2]); + dest[3] = glm_smoothstep(edge0[3], edge1[3], x[3]); +} + +/*! + * @brief smooth Hermite interpolation between two vectors + * + * formula: t^2 * (3 - 2*t) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_smoothinterp(vec4 from, vec4 to, float t, vec4 dest) { + vec4 s, v; + + /* from + smoothstep * (to - from) */ + glm_vec4_broadcast(glm_smooth(t), s); + glm_vec4_sub(to, from, v); + glm_vec4_mul(s, v, v); + glm_vec4_add(from, v, dest); +} + +/*! + * @brief smooth Hermite interpolation between two vectors (clamped) + * + * formula: t^2 * (3 - 2*t) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) clamped between 0 and 1 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_smoothinterpc(vec4 from, vec4 to, float t, vec4 dest) { + glm_vec4_smoothinterp(from, to, glm_clamp_zo(t), dest); +} + +/*! + * @brief helper to fill vec4 as [S^3, S^2, S, 1] + * + * @param[in] s parameter + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_cubic(float s, vec4 dest) { + float ss; + + ss = s * s; + + dest[0] = ss * s; + dest[1] = ss; + dest[2] = s; + dest[3] = 1.0f; +} + +/*! + * @brief swizzle vector components + * + * you can use existin masks e.g. GLM_XXXX, GLM_WZYX + * + * @param[in] v source + * @param[in] mask mask + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec4_swizzle(vec4 v, int mask, vec4 dest) { + vec4 t; + + t[0] = v[(mask & (3 << 0))]; + t[1] = v[(mask & (3 << 2)) >> 2]; + t[2] = v[(mask & (3 << 4)) >> 4]; + t[3] = v[(mask & (3 << 6)) >> 6]; + + glm_vec4_copy(t, dest); +} + +#endif /* cglm_vec4_h */ + + +/* DEPRECATED! use _copy, _ucopy versions */ +#define glm_vec3_dup(v, dest) glm_vec3_copy(v, dest) +#define glm_vec3_flipsign(v) glm_vec3_negate(v) +#define glm_vec3_flipsign_to(v, dest) glm_vec3_negate_to(v, dest) +#define glm_vec3_inv(v) glm_vec3_negate(v) +#define glm_vec3_inv_to(v, dest) glm_vec3_negate_to(v, dest) +#define glm_vec3_mulv(a, b, d) glm_vec3_mul(a, b, d) + +#define GLM_VEC3_ONE_INIT {1.0f, 1.0f, 1.0f} +#define GLM_VEC3_ZERO_INIT {0.0f, 0.0f, 0.0f} + +#define GLM_VEC3_ONE ((vec3)GLM_VEC3_ONE_INIT) +#define GLM_VEC3_ZERO ((vec3)GLM_VEC3_ZERO_INIT) + +#define GLM_YUP ((vec3){0.0f, 1.0f, 0.0f}) +#define GLM_ZUP ((vec3){0.0f, 0.0f, 1.0f}) +#define GLM_XUP ((vec3){1.0f, 0.0f, 0.0f}) +#define GLM_FORWARD ((vec3){0.0f, 0.0f, -1.0f}) + +#define GLM_XXX GLM_SHUFFLE3(0, 0, 0) +#define GLM_YYY GLM_SHUFFLE3(1, 1, 1) +#define GLM_ZZZ GLM_SHUFFLE3(2, 2, 2) +#define GLM_ZYX GLM_SHUFFLE3(0, 1, 2) + +/*! + * @brief init vec3 using vec4 + * + * @param[in] v4 vector4 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3(vec4 v4, vec3 dest) { + dest[0] = v4[0]; + dest[1] = v4[1]; + dest[2] = v4[2]; +} + +/*! + * @brief copy all members of [a] to [dest] + * + * @param[in] a source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_copy(vec3 a, vec3 dest) { + dest[0] = a[0]; + dest[1] = a[1]; + dest[2] = a[2]; +} + +/*! + * @brief make vector zero + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec3_zero(vec3 v) { + v[0] = v[1] = v[2] = 0.0f; +} + +/*! + * @brief make vector one + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec3_one(vec3 v) { + v[0] = v[1] = v[2] = 1.0f; +} + +/*! + * @brief vec3 dot product + * + * @param[in] a vector1 + * @param[in] b vector2 + * + * @return dot product + */ +CGLM_INLINE +float +glm_vec3_dot(vec3 a, vec3 b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +/*! + * @brief norm * norm (magnitude) of vec + * + * we can use this func instead of calling norm * norm, because it would call + * sqrtf fuction twice but with this func we can avoid func call, maybe this is + * not good name for this func + * + * @param[in] v vector + * + * @return norm * norm + */ +CGLM_INLINE +float +glm_vec3_norm2(vec3 v) { + return glm_vec3_dot(v, v); +} + +/*! + * @brief euclidean norm (magnitude), also called L2 norm + * this will give magnitude of vector in euclidean space + * + * @param[in] v vector + * + * @return norm + */ +CGLM_INLINE +float +glm_vec3_norm(vec3 v) { + return sqrtf(glm_vec3_norm2(v)); +} + +/*! + * @brief L1 norm of vec3 + * Also known as Manhattan Distance or Taxicab norm. + * L1 Norm is the sum of the magnitudes of the vectors in a space. + * It is calculated as the sum of the absolute values of the vector components. + * In this norm, all the components of the vector are weighted equally. + * + * This computes: + * R = |v[0]| + |v[1]| + |v[2]| + * + * @param[in] v vector + * + * @return L1 norm + */ +CGLM_INLINE +float +glm_vec3_norm_one(vec3 v) { + vec3 t; + glm_vec3_abs(v, t); + return glm_vec3_hadd(t); +} + +/*! + * @brief infinity norm of vec3 + * Also known as Maximum norm. + * Infinity Norm is the largest magnitude among each element of a vector. + * It is calculated as the maximum of the absolute values of the vector components. + * + * This computes: + * inf norm = max(|v[0]|, |v[1]|, |v[2]|) + * + * @param[in] v vector + * + * @return infinity norm + */ +CGLM_INLINE +float +glm_vec3_norm_inf(vec3 v) { + vec3 t; + glm_vec3_abs(v, t); + return glm_vec3_max(t); +} + +/*! + * @brief add a vector to b vector store result in dest + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec3_add(vec3 a, vec3 b, vec3 dest) { + dest[0] = a[0] + b[0]; + dest[1] = a[1] + b[1]; + dest[2] = a[2] + b[2]; +} + +/*! + * @brief add scalar to v vector store result in dest (d = v + s) + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec3_adds(vec3 v, float s, vec3 dest) { + dest[0] = v[0] + s; + dest[1] = v[1] + s; + dest[2] = v[2] + s; +} + +/*! + * @brief subtract b vector from a vector store result in dest + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec3_sub(vec3 a, vec3 b, vec3 dest) { + dest[0] = a[0] - b[0]; + dest[1] = a[1] - b[1]; + dest[2] = a[2] - b[2]; +} + +/*! + * @brief subtract scalar from v vector store result in dest (d = v - s) + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec3_subs(vec3 v, float s, vec3 dest) { + dest[0] = v[0] - s; + dest[1] = v[1] - s; + dest[2] = v[2] - s; +} + +/*! + * @brief multiply two vector (component-wise multiplication) + * + * @param a vector1 + * @param b vector2 + * @param dest v3 = (a[0] * b[0], a[1] * b[1], a[2] * b[2]) + */ +CGLM_INLINE +void +glm_vec3_mul(vec3 a, vec3 b, vec3 dest) { + dest[0] = a[0] * b[0]; + dest[1] = a[1] * b[1]; + dest[2] = a[2] * b[2]; +} + +/*! + * @brief multiply/scale vec3 vector with scalar: result = v * s + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec3_scale(vec3 v, float s, vec3 dest) { + dest[0] = v[0] * s; + dest[1] = v[1] * s; + dest[2] = v[2] * s; +} + +/*! + * @brief make vec3 vector scale as specified: result = unit(v) * s + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest destination vector + */ +CGLM_INLINE +void +glm_vec3_scale_as(vec3 v, float s, vec3 dest) { + float norm; + norm = glm_vec3_norm(v); + + if (norm == 0.0f) { + glm_vec3_zero(dest); + return; + } + + glm_vec3_scale(v, s / norm, dest); +} + +/*! + * @brief div vector with another component-wise division: d = a / b + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest result = (a[0]/b[0], a[1]/b[1], a[2]/b[2]) + */ +CGLM_INLINE +void +glm_vec3_div(vec3 a, vec3 b, vec3 dest) { + dest[0] = a[0] / b[0]; + dest[1] = a[1] / b[1]; + dest[2] = a[2] / b[2]; +} + +/*! + * @brief div vector with scalar: d = v / s + * + * @param[in] v vector + * @param[in] s scalar + * @param[out] dest result = (a[0]/s, a[1]/s, a[2]/s) + */ +CGLM_INLINE +void +glm_vec3_divs(vec3 v, float s, vec3 dest) { + dest[0] = v[0] / s; + dest[1] = v[1] / s; + dest[2] = v[2] / s; +} + +/*! + * @brief add two vectors and add result to sum + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += (a + b) + */ +CGLM_INLINE +void +glm_vec3_addadd(vec3 a, vec3 b, vec3 dest) { + dest[0] += a[0] + b[0]; + dest[1] += a[1] + b[1]; + dest[2] += a[2] + b[2]; +} + +/*! + * @brief sub two vectors and add result to dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += (a + b) + */ +CGLM_INLINE +void +glm_vec3_subadd(vec3 a, vec3 b, vec3 dest) { + dest[0] += a[0] - b[0]; + dest[1] += a[1] - b[1]; + dest[2] += a[2] - b[2]; +} + +/*! + * @brief mul two vectors and add result to dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += (a * b) + */ +CGLM_INLINE +void +glm_vec3_muladd(vec3 a, vec3 b, vec3 dest) { + dest[0] += a[0] * b[0]; + dest[1] += a[1] * b[1]; + dest[2] += a[2] * b[2]; +} + +/*! + * @brief mul vector with scalar and add result to sum + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector + * @param[in] s scalar + * @param[out] dest dest += (a * b) + */ +CGLM_INLINE +void +glm_vec3_muladds(vec3 a, float s, vec3 dest) { + dest[0] += a[0] * s; + dest[1] += a[1] * s; + dest[2] += a[2] * s; +} + +/*! + * @brief add max of two vector to result/dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += max(a, b) + */ +CGLM_INLINE +void +glm_vec3_maxadd(vec3 a, vec3 b, vec3 dest) { + dest[0] += glm_max(a[0], b[0]); + dest[1] += glm_max(a[1], b[1]); + dest[2] += glm_max(a[2], b[2]); +} + +/*! + * @brief add min of two vector to result/dest + * + * it applies += operator so dest must be initialized + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest dest += min(a, b) + */ +CGLM_INLINE +void +glm_vec3_minadd(vec3 a, vec3 b, vec3 dest) { + dest[0] += glm_min(a[0], b[0]); + dest[1] += glm_min(a[1], b[1]); + dest[2] += glm_min(a[2], b[2]); +} + +/*! + * @brief negate vector components and store result in dest + * + * @param[in] v vector + * @param[out] dest result vector + */ +CGLM_INLINE +void +glm_vec3_negate_to(vec3 v, vec3 dest) { + dest[0] = -v[0]; + dest[1] = -v[1]; + dest[2] = -v[2]; +} + +/*! + * @brief negate vector components + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec3_negate(vec3 v) { + glm_vec3_negate_to(v, v); +} + +/*! + * @brief normalize vec3 and store result in same vec + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_vec3_normalize(vec3 v) { + float norm; + + norm = glm_vec3_norm(v); + + if (norm == 0.0f) { + v[0] = v[1] = v[2] = 0.0f; + return; + } + + glm_vec3_scale(v, 1.0f / norm, v); +} + +/*! + * @brief normalize vec3 to dest + * + * @param[in] v source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_normalize_to(vec3 v, vec3 dest) { + float norm; + + norm = glm_vec3_norm(v); + + if (norm == 0.0f) { + glm_vec3_zero(dest); + return; + } + + glm_vec3_scale(v, 1.0f / norm, dest); +} + +/*! + * @brief cross product of two vector (RH) + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_cross(vec3 a, vec3 b, vec3 dest) { + /* (u2.v3 - u3.v2, u3.v1 - u1.v3, u1.v2 - u2.v1) */ + dest[0] = a[1] * b[2] - a[2] * b[1]; + dest[1] = a[2] * b[0] - a[0] * b[2]; + dest[2] = a[0] * b[1] - a[1] * b[0]; +} + +/*! + * @brief cross product of two vector (RH) and normalize the result + * + * @param[in] a vector 1 + * @param[in] b vector 2 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_crossn(vec3 a, vec3 b, vec3 dest) { + glm_vec3_cross(a, b, dest); + glm_vec3_normalize(dest); +} + +/*! + * @brief angle betwen two vector + * + * @param[in] a vector1 + * @param[in] b vector2 + * + * @return angle as radians + */ +CGLM_INLINE +float +glm_vec3_angle(vec3 a, vec3 b) { + float norm, dot; + + /* maybe compiler generate approximation instruction (rcp) */ + norm = 1.0f / (glm_vec3_norm(a) * glm_vec3_norm(b)); + dot = glm_vec3_dot(a, b) * norm; + + if (dot > 1.0f) + return 0.0f; + else if (dot < -1.0f) + return CGLM_PI; + + return acosf(dot); +} + +/*! + * @brief rotate vec3 around axis by angle using Rodrigues' rotation formula + * + * @param[in, out] v vector + * @param[in] axis axis vector (must be unit vector) + * @param[in] angle angle by radians + */ +CGLM_INLINE +void +glm_vec3_rotate(vec3 v, float angle, vec3 axis) { + vec3 v1, v2, k; + float c, s; + + c = cosf(angle); + s = sinf(angle); + + glm_vec3_normalize_to(axis, k); + + /* Right Hand, Rodrigues' rotation formula: + v = v*cos(t) + (kxv)sin(t) + k*(k.v)(1 - cos(t)) + */ + glm_vec3_scale(v, c, v1); + + glm_vec3_cross(k, v, v2); + glm_vec3_scale(v2, s, v2); + + glm_vec3_add(v1, v2, v1); + + glm_vec3_scale(k, glm_vec3_dot(k, v) * (1.0f - c), v2); + glm_vec3_add(v1, v2, v); +} + +/*! + * @brief apply rotation matrix to vector + * + * matrix format should be (no perspective): + * a b c x + * e f g y + * i j k z + * 0 0 0 w + * + * @param[in] m affine matrix or rot matrix + * @param[in] v vector + * @param[out] dest rotated vector + */ +CGLM_INLINE +void +glm_vec3_rotate_m4(mat4 m, vec3 v, vec3 dest) { + vec4 x, y, z, res; + + glm_vec4_normalize_to(m[0], x); + glm_vec4_normalize_to(m[1], y); + glm_vec4_normalize_to(m[2], z); + + glm_vec4_scale(x, v[0], res); + glm_vec4_muladds(y, v[1], res); + glm_vec4_muladds(z, v[2], res); + + glm_vec3(res, dest); +} + +/*! + * @brief apply rotation matrix to vector + * + * @param[in] m affine matrix or rot matrix + * @param[in] v vector + * @param[out] dest rotated vector + */ +CGLM_INLINE +void +glm_vec3_rotate_m3(mat3 m, vec3 v, vec3 dest) { + vec4 res, x, y, z; + + glm_vec4(m[0], 0.0f, x); + glm_vec4(m[1], 0.0f, y); + glm_vec4(m[2], 0.0f, z); + + glm_vec4_normalize(x); + glm_vec4_normalize(y); + glm_vec4_normalize(z); + + glm_vec4_scale(x, v[0], res); + glm_vec4_muladds(y, v[1], res); + glm_vec4_muladds(z, v[2], res); + + glm_vec3(res, dest); +} + +/*! + * @brief project a vector onto b vector + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest projected vector + */ +CGLM_INLINE +void +glm_vec3_proj(vec3 a, vec3 b, vec3 dest) { + glm_vec3_scale(b, + glm_vec3_dot(a, b) / glm_vec3_norm2(b), + dest); +} + +/** + * @brief find center point of two vector + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest center point + */ +CGLM_INLINE +void +glm_vec3_center(vec3 a, vec3 b, vec3 dest) { + glm_vec3_add(a, b, dest); + glm_vec3_scale(dest, 0.5f, dest); +} + +/** + * @brief squared distance between two vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @return returns squared distance (distance * distance) + */ +CGLM_INLINE +float +glm_vec3_distance2(vec3 a, vec3 b) { + return glm_pow2(a[0] - b[0]) + + glm_pow2(a[1] - b[1]) + + glm_pow2(a[2] - b[2]); +} + +/** + * @brief distance between two vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @return returns distance + */ +CGLM_INLINE +float +glm_vec3_distance(vec3 a, vec3 b) { + return sqrtf(glm_vec3_distance2(a, b)); +} + +/*! + * @brief max values of vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_maxv(vec3 a, vec3 b, vec3 dest) { + dest[0] = glm_max(a[0], b[0]); + dest[1] = glm_max(a[1], b[1]); + dest[2] = glm_max(a[2], b[2]); +} + +/*! + * @brief min values of vectors + * + * @param[in] a vector1 + * @param[in] b vector2 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_minv(vec3 a, vec3 b, vec3 dest) { + dest[0] = glm_min(a[0], b[0]); + dest[1] = glm_min(a[1], b[1]); + dest[2] = glm_min(a[2], b[2]); +} + +/*! + * @brief possible orthogonal/perpendicular vector + * + * @param[in] v vector + * @param[out] dest orthogonal/perpendicular vector + */ +CGLM_INLINE +void +glm_vec3_ortho(vec3 v, vec3 dest) { + dest[0] = v[1] - v[2]; + dest[1] = v[2] - v[0]; + dest[2] = v[0] - v[1]; +} + +/*! + * @brief clamp vector's individual members between min and max values + * + * @param[in, out] v vector + * @param[in] minVal minimum value + * @param[in] maxVal maximum value + */ +CGLM_INLINE +void +glm_vec3_clamp(vec3 v, float minVal, float maxVal) { + v[0] = glm_clamp(v[0], minVal, maxVal); + v[1] = glm_clamp(v[1], minVal, maxVal); + v[2] = glm_clamp(v[2], minVal, maxVal); +} + +/*! + * @brief linear interpolation between two vectors + * + * formula: from + s * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_lerp(vec3 from, vec3 to, float t, vec3 dest) { + vec3 s, v; + + /* from + s * (to - from) */ + glm_vec3_broadcast(t, s); + glm_vec3_sub(to, from, v); + glm_vec3_mul(s, v, v); + glm_vec3_add(from, v, dest); +} + +/*! + * @brief linear interpolation between two vectors (clamped) + * + * formula: from + s * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) clamped between 0 and 1 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_lerpc(vec3 from, vec3 to, float t, vec3 dest) { + glm_vec3_lerp(from, to, glm_clamp_zo(t), dest); +} + +/*! + * @brief linear interpolation between two vectors + * + * formula: from + s * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_mix(vec3 from, vec3 to, float t, vec3 dest) { + glm_vec3_lerp(from, to, t, dest); +} + +/*! + * @brief linear interpolation between two vectors (clamped) + * + * formula: from + s * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) clamped between 0 and 1 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_mixc(vec3 from, vec3 to, float t, vec3 dest) { + glm_vec3_lerpc(from, to, t, dest); +} + +/*! + * @brief threshold function (unidimensional) + * + * @param[in] edge threshold + * @param[in] x value to test against threshold + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_step_uni(float edge, vec3 x, vec3 dest) { + dest[0] = glm_step(edge, x[0]); + dest[1] = glm_step(edge, x[1]); + dest[2] = glm_step(edge, x[2]); +} + +/*! + * @brief threshold function + * + * @param[in] edge threshold + * @param[in] x value to test against threshold + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_step(vec3 edge, vec3 x, vec3 dest) { + dest[0] = glm_step(edge[0], x[0]); + dest[1] = glm_step(edge[1], x[1]); + dest[2] = glm_step(edge[2], x[2]); +} + +/*! + * @brief threshold function with a smooth transition (unidimensional) + * + * @param[in] edge0 low threshold + * @param[in] edge1 high threshold + * @param[in] x value to test against threshold + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_smoothstep_uni(float edge0, float edge1, vec3 x, vec3 dest) { + dest[0] = glm_smoothstep(edge0, edge1, x[0]); + dest[1] = glm_smoothstep(edge0, edge1, x[1]); + dest[2] = glm_smoothstep(edge0, edge1, x[2]); +} + +/*! + * @brief threshold function with a smooth transition + * + * @param[in] edge0 low threshold + * @param[in] edge1 high threshold + * @param[in] x value to test against threshold + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_smoothstep(vec3 edge0, vec3 edge1, vec3 x, vec3 dest) { + dest[0] = glm_smoothstep(edge0[0], edge1[0], x[0]); + dest[1] = glm_smoothstep(edge0[1], edge1[1], x[1]); + dest[2] = glm_smoothstep(edge0[2], edge1[2], x[2]); +} + +/*! + * @brief smooth Hermite interpolation between two vectors + * + * formula: from + s * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_smoothinterp(vec3 from, vec3 to, float t, vec3 dest) { + vec3 s, v; + + /* from + s * (to - from) */ + glm_vec3_broadcast(glm_smooth(t), s); + glm_vec3_sub(to, from, v); + glm_vec3_mul(s, v, v); + glm_vec3_add(from, v, dest); +} + +/*! + * @brief smooth Hermite interpolation between two vectors (clamped) + * + * formula: from + s * (to - from) + * + * @param[in] from from value + * @param[in] to to value + * @param[in] t interpolant (amount) clamped between 0 and 1 + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_smoothinterpc(vec3 from, vec3 to, float t, vec3 dest) { + glm_vec3_smoothinterp(from, to, glm_clamp_zo(t), dest); +} + +/*! + * @brief swizzle vector components + * + * you can use existin masks e.g. GLM_XXX, GLM_ZYX + * + * @param[in] v source + * @param[in] mask mask + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_vec3_swizzle(vec3 v, int mask, vec3 dest) { + vec3 t; + + t[0] = v[(mask & (3 << 0))]; + t[1] = v[(mask & (3 << 2)) >> 2]; + t[2] = v[(mask & (3 << 4)) >> 4]; + + glm_vec3_copy(t, dest); +} + +/*! + * @brief vec3 cross product + * + * this is just convenient wrapper + * + * @param[in] a source 1 + * @param[in] b source 2 + * @param[out] d destination + */ +CGLM_INLINE +void +glm_cross(vec3 a, vec3 b, vec3 d) { + glm_vec3_cross(a, b, d); +} + +/*! + * @brief vec3 dot product + * + * this is just convenient wrapper + * + * @param[in] a vector1 + * @param[in] b vector2 + * + * @return dot product + */ +CGLM_INLINE +float +glm_dot(vec3 a, vec3 b) { + return glm_vec3_dot(a, b); +} + +/*! + * @brief normalize vec3 and store result in same vec + * + * this is just convenient wrapper + * + * @param[in, out] v vector + */ +CGLM_INLINE +void +glm_normalize(vec3 v) { + glm_vec3_normalize(v); +} + +/*! + * @brief normalize vec3 to dest + * + * this is just convenient wrapper + * + * @param[in] v source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_normalize_to(vec3 v, vec3 dest) { + glm_vec3_normalize_to(v, dest); +} + +#endif /* cglm_vec3_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/*! + * Most of functions in this header are optimized manually with SIMD + * if available. You dont need to call/incude SIMD headers manually + */ + +/* + Macros: + GLM_MAT4_IDENTITY_INIT + GLM_MAT4_ZERO_INIT + GLM_MAT4_IDENTITY + GLM_MAT4_ZERO + + Functions: + CGLM_INLINE void glm_mat4_ucopy(mat4 mat, mat4 dest); + CGLM_INLINE void glm_mat4_copy(mat4 mat, mat4 dest); + CGLM_INLINE void glm_mat4_identity(mat4 mat); + CGLM_INLINE void glm_mat4_identity_array(mat4 * restrict mat, size_t count); + CGLM_INLINE void glm_mat4_zero(mat4 mat); + CGLM_INLINE void glm_mat4_pick3(mat4 mat, mat3 dest); + CGLM_INLINE void glm_mat4_pick3t(mat4 mat, mat3 dest); + CGLM_INLINE void glm_mat4_ins3(mat3 mat, mat4 dest); + CGLM_INLINE void glm_mat4_mul(mat4 m1, mat4 m2, mat4 dest); + CGLM_INLINE void glm_mat4_mulN(mat4 *matrices[], int len, mat4 dest); + CGLM_INLINE void glm_mat4_mulv(mat4 m, vec4 v, vec4 dest); + CGLM_INLINE void glm_mat4_mulv3(mat4 m, vec3 v, vec3 dest); + CGLM_INLINE float glm_mat4_trace(mat4 m); + CGLM_INLINE float glm_mat4_trace3(mat4 m); + CGLM_INLINE void glm_mat4_quat(mat4 m, versor dest) ; + CGLM_INLINE void glm_mat4_transpose_to(mat4 m, mat4 dest); + CGLM_INLINE void glm_mat4_transpose(mat4 m); + CGLM_INLINE void glm_mat4_scale_p(mat4 m, float s); + CGLM_INLINE void glm_mat4_scale(mat4 m, float s); + CGLM_INLINE float glm_mat4_det(mat4 mat); + CGLM_INLINE void glm_mat4_inv(mat4 mat, mat4 dest); + CGLM_INLINE void glm_mat4_inv_fast(mat4 mat, mat4 dest); + CGLM_INLINE void glm_mat4_swap_col(mat4 mat, int col1, int col2); + CGLM_INLINE void glm_mat4_swap_row(mat4 mat, int row1, int row2); + CGLM_INLINE float glm_mat4_rmc(vec4 r, mat4 m, vec4 c); + */ + +#ifndef cglm_mat_h +#define cglm_mat_h + + +#ifdef CGLM_SSE_FP +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_mat_sse_h +#define cglm_mat_sse_h +#if defined( __SSE__ ) || defined( __SSE2__ ) + + +#define glm_mat4_inv_precise_sse2(mat, dest) glm_mat4_inv_sse2(mat, dest) + +CGLM_INLINE +void +glm_mat4_scale_sse2(mat4 m, float s) { + __m128 x0; + x0 = _mm_set1_ps(s); + + glmm_store(m[0], _mm_mul_ps(glmm_load(m[0]), x0)); + glmm_store(m[1], _mm_mul_ps(glmm_load(m[1]), x0)); + glmm_store(m[2], _mm_mul_ps(glmm_load(m[2]), x0)); + glmm_store(m[3], _mm_mul_ps(glmm_load(m[3]), x0)); +} + +CGLM_INLINE +void +glm_mat4_transp_sse2(mat4 m, mat4 dest) { + __m128 r0, r1, r2, r3; + + r0 = glmm_load(m[0]); + r1 = glmm_load(m[1]); + r2 = glmm_load(m[2]); + r3 = glmm_load(m[3]); + + _MM_TRANSPOSE4_PS(r0, r1, r2, r3); + + glmm_store(dest[0], r0); + glmm_store(dest[1], r1); + glmm_store(dest[2], r2); + glmm_store(dest[3], r3); +} + +CGLM_INLINE +void +glm_mat4_mul_sse2(mat4 m1, mat4 m2, mat4 dest) { + /* D = R * L (Column-Major) */ + + __m128 l0, l1, l2, l3, r; + + l0 = glmm_load(m1[0]); + l1 = glmm_load(m1[1]); + l2 = glmm_load(m1[2]); + l3 = glmm_load(m1[3]); + + r = glmm_load(m2[0]); + glmm_store(dest[0], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 2), l2), + _mm_mul_ps(glmm_shuff1x(r, 3), l3)))); + r = glmm_load(m2[1]); + glmm_store(dest[1], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 2), l2), + _mm_mul_ps(glmm_shuff1x(r, 3), l3)))); + r = glmm_load(m2[2]); + glmm_store(dest[2], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 2), l2), + _mm_mul_ps(glmm_shuff1x(r, 3), l3)))); + + r = glmm_load(m2[3]); + glmm_store(dest[3], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 2), l2), + _mm_mul_ps(glmm_shuff1x(r, 3), l3)))); +} + +CGLM_INLINE +void +glm_mat4_mulv_sse2(mat4 m, vec4 v, vec4 dest) { + __m128 x0, x1, x2; + + x0 = glmm_load(v); + x1 = _mm_add_ps(_mm_mul_ps(glmm_load(m[0]), glmm_shuff1x(x0, 0)), + _mm_mul_ps(glmm_load(m[1]), glmm_shuff1x(x0, 1))); + + x2 = _mm_add_ps(_mm_mul_ps(glmm_load(m[2]), glmm_shuff1x(x0, 2)), + _mm_mul_ps(glmm_load(m[3]), glmm_shuff1x(x0, 3))); + + glmm_store(dest, _mm_add_ps(x1, x2)); +} + +CGLM_INLINE +float +glm_mat4_det_sse2(mat4 mat) { + __m128 r0, r1, r2, r3, x0, x1, x2; + + /* 127 <- 0, [square] det(A) = det(At) */ + r0 = glmm_load(mat[0]); /* d c b a */ + r1 = glmm_load(mat[1]); /* h g f e */ + r2 = glmm_load(mat[2]); /* l k j i */ + r3 = glmm_load(mat[3]); /* p o n m */ + + /* + t[1] = j * p - n * l; + t[2] = j * o - n * k; + t[3] = i * p - m * l; + t[4] = i * o - m * k; + */ + x0 = _mm_sub_ps(_mm_mul_ps(glmm_shuff1(r2, 0, 0, 1, 1), + glmm_shuff1(r3, 2, 3, 2, 3)), + _mm_mul_ps(glmm_shuff1(r3, 0, 0, 1, 1), + glmm_shuff1(r2, 2, 3, 2, 3))); + /* + t[0] = k * p - o * l; + t[0] = k * p - o * l; + t[5] = i * n - m * j; + t[5] = i * n - m * j; + */ + x1 = _mm_sub_ps(_mm_mul_ps(glmm_shuff1(r2, 0, 0, 2, 2), + glmm_shuff1(r3, 1, 1, 3, 3)), + _mm_mul_ps(glmm_shuff1(r3, 0, 0, 2, 2), + glmm_shuff1(r2, 1, 1, 3, 3))); + + /* + a * (f * t[0] - g * t[1] + h * t[2]) + - b * (e * t[0] - g * t[3] + h * t[4]) + + c * (e * t[1] - f * t[3] + h * t[5]) + - d * (e * t[2] - f * t[4] + g * t[5]) + */ + x2 = _mm_sub_ps(_mm_mul_ps(glmm_shuff1(r1, 0, 0, 0, 1), + _mm_shuffle_ps(x1, x0, _MM_SHUFFLE(1, 0, 0, 0))), + _mm_mul_ps(glmm_shuff1(r1, 1, 1, 2, 2), + glmm_shuff1(x0, 3, 2, 2, 0))); + + x2 = _mm_add_ps(x2, + _mm_mul_ps(glmm_shuff1(r1, 2, 3, 3, 3), + _mm_shuffle_ps(x0, x1, _MM_SHUFFLE(2, 2, 3, 1)))); + x2 = _mm_xor_ps(x2, _mm_set_ps(-0.f, 0.f, -0.f, 0.f)); + + x0 = _mm_mul_ps(r0, x2); + x0 = _mm_add_ps(x0, glmm_shuff1(x0, 0, 1, 2, 3)); + x0 = _mm_add_ps(x0, glmm_shuff1(x0, 1, 3, 3, 1)); + + return _mm_cvtss_f32(x0); +} + +CGLM_INLINE +void +glm_mat4_inv_fast_sse2(mat4 mat, mat4 dest) { + __m128 r0, r1, r2, r3, + v0, v1, v2, v3, + t0, t1, t2, t3, t4, t5, + x0, x1, x2, x3, x4, x5, x6, x7; + + /* 127 <- 0 */ + r0 = glmm_load(mat[0]); /* d c b a */ + r1 = glmm_load(mat[1]); /* h g f e */ + r2 = glmm_load(mat[2]); /* l k j i */ + r3 = glmm_load(mat[3]); /* p o n m */ + + x0 = _mm_shuffle_ps(r2, r3, _MM_SHUFFLE(3, 2, 3, 2)); /* p o l k */ + x1 = glmm_shuff1(x0, 1, 3, 3, 3); /* l p p p */ + x2 = glmm_shuff1(x0, 0, 2, 2, 2); /* k o o o */ + x0 = _mm_shuffle_ps(r2, r1, _MM_SHUFFLE(3, 3, 3, 3)); /* h h l l */ + x3 = _mm_shuffle_ps(r2, r1, _MM_SHUFFLE(2, 2, 2, 2)); /* g g k k */ + + /* t1[0] = k * p - o * l; + t1[0] = k * p - o * l; + t2[0] = g * p - o * h; + t3[0] = g * l - k * h; */ + t0 = _mm_sub_ps(_mm_mul_ps(x3, x1), _mm_mul_ps(x2, x0)); + + x4 = _mm_shuffle_ps(r2, r3, _MM_SHUFFLE(2, 1, 2, 1)); /* o n k j */ + x4 = glmm_shuff1(x4, 0, 2, 2, 2); /* j n n n */ + x5 = _mm_shuffle_ps(r2, r1, _MM_SHUFFLE(1, 1, 1, 1)); /* f f j j */ + + /* t1[1] = j * p - n * l; + t1[1] = j * p - n * l; + t2[1] = f * p - n * h; + t3[1] = f * l - j * h; */ + t1 = _mm_sub_ps(_mm_mul_ps(x5, x1), _mm_mul_ps(x4, x0)); + + /* t1[2] = j * o - n * k + t1[2] = j * o - n * k; + t2[2] = f * o - n * g; + t3[2] = f * k - j * g; */ + t2 = _mm_sub_ps(_mm_mul_ps(x5, x2), _mm_mul_ps(x4, x3)); + + x6 = _mm_shuffle_ps(r2, r1, _MM_SHUFFLE(0, 0, 0, 0)); /* e e i i */ + x7 = glmm_shuff2(r3, r2, 0, 0, 0, 0, 2, 0, 0, 0); /* i m m m */ + + /* t1[3] = i * p - m * l; + t1[3] = i * p - m * l; + t2[3] = e * p - m * h; + t3[3] = e * l - i * h; */ + t3 = _mm_sub_ps(_mm_mul_ps(x6, x1), _mm_mul_ps(x7, x0)); + + /* t1[4] = i * o - m * k; + t1[4] = i * o - m * k; + t2[4] = e * o - m * g; + t3[4] = e * k - i * g; */ + t4 = _mm_sub_ps(_mm_mul_ps(x6, x2), _mm_mul_ps(x7, x3)); + + /* t1[5] = i * n - m * j; + t1[5] = i * n - m * j; + t2[5] = e * n - m * f; + t3[5] = e * j - i * f; */ + t5 = _mm_sub_ps(_mm_mul_ps(x6, x4), _mm_mul_ps(x7, x5)); + + x0 = glmm_shuff2(r1, r0, 0, 0, 0, 0, 2, 2, 2, 0); /* a a a e */ + x1 = glmm_shuff2(r1, r0, 1, 1, 1, 1, 2, 2, 2, 0); /* b b b f */ + x2 = glmm_shuff2(r1, r0, 2, 2, 2, 2, 2, 2, 2, 0); /* c c c g */ + x3 = glmm_shuff2(r1, r0, 3, 3, 3, 3, 2, 2, 2, 0); /* d d d h */ + + /* + dest[0][0] = f * t1[0] - g * t1[1] + h * t1[2]; + dest[0][1] =-(b * t1[0] - c * t1[1] + d * t1[2]); + dest[0][2] = b * t2[0] - c * t2[1] + d * t2[2]; + dest[0][3] =-(b * t3[0] - c * t3[1] + d * t3[2]); */ + v0 = _mm_add_ps(_mm_mul_ps(x3, t2), + _mm_sub_ps(_mm_mul_ps(x1, t0), + _mm_mul_ps(x2, t1))); + v0 = _mm_xor_ps(v0, _mm_set_ps(-0.f, 0.f, -0.f, 0.f)); + + /* + dest[1][0] =-(e * t1[0] - g * t1[3] + h * t1[4]); + dest[1][1] = a * t1[0] - c * t1[3] + d * t1[4]; + dest[1][2] =-(a * t2[0] - c * t2[3] + d * t2[4]); + dest[1][3] = a * t3[0] - c * t3[3] + d * t3[4]; */ + v1 = _mm_add_ps(_mm_mul_ps(x3, t4), + _mm_sub_ps(_mm_mul_ps(x0, t0), + _mm_mul_ps(x2, t3))); + v1 = _mm_xor_ps(v1, _mm_set_ps(0.f, -0.f, 0.f, -0.f)); + + /* + dest[2][0] = e * t1[1] - f * t1[3] + h * t1[5]; + dest[2][1] =-(a * t1[1] - b * t1[3] + d * t1[5]); + dest[2][2] = a * t2[1] - b * t2[3] + d * t2[5]; + dest[2][3] =-(a * t3[1] - b * t3[3] + d * t3[5]);*/ + v2 = _mm_add_ps(_mm_mul_ps(x3, t5), + _mm_sub_ps(_mm_mul_ps(x0, t1), + _mm_mul_ps(x1, t3))); + v2 = _mm_xor_ps(v2, _mm_set_ps(-0.f, 0.f, -0.f, 0.f)); + + /* + dest[3][0] =-(e * t1[2] - f * t1[4] + g * t1[5]); + dest[3][1] = a * t1[2] - b * t1[4] + c * t1[5]; + dest[3][2] =-(a * t2[2] - b * t2[4] + c * t2[5]); + dest[3][3] = a * t3[2] - b * t3[4] + c * t3[5]; */ + v3 = _mm_add_ps(_mm_mul_ps(x2, t5), + _mm_sub_ps(_mm_mul_ps(x0, t2), + _mm_mul_ps(x1, t4))); + v3 = _mm_xor_ps(v3, _mm_set_ps(0.f, -0.f, 0.f, -0.f)); + + /* determinant */ + x0 = _mm_shuffle_ps(v0, v1, _MM_SHUFFLE(0, 0, 0, 0)); + x1 = _mm_shuffle_ps(v2, v3, _MM_SHUFFLE(0, 0, 0, 0)); + x0 = _mm_shuffle_ps(x0, x1, _MM_SHUFFLE(2, 0, 2, 0)); + + x0 = _mm_mul_ps(x0, r0); + x0 = _mm_add_ps(x0, glmm_shuff1(x0, 0, 1, 2, 3)); + x0 = _mm_add_ps(x0, glmm_shuff1(x0, 1, 0, 0, 1)); + x0 = _mm_rcp_ps(x0); + + glmm_store(dest[0], _mm_mul_ps(v0, x0)); + glmm_store(dest[1], _mm_mul_ps(v1, x0)); + glmm_store(dest[2], _mm_mul_ps(v2, x0)); + glmm_store(dest[3], _mm_mul_ps(v3, x0)); +} + +CGLM_INLINE +void +glm_mat4_inv_sse2(mat4 mat, mat4 dest) { + __m128 r0, r1, r2, r3, + v0, v1, v2, v3, + t0, t1, t2, t3, t4, t5, + x0, x1, x2, x3, x4, x5, x6, x7; + + /* 127 <- 0 */ + r0 = glmm_load(mat[0]); /* d c b a */ + r1 = glmm_load(mat[1]); /* h g f e */ + r2 = glmm_load(mat[2]); /* l k j i */ + r3 = glmm_load(mat[3]); /* p o n m */ + + x0 = _mm_shuffle_ps(r2, r3, _MM_SHUFFLE(3, 2, 3, 2)); /* p o l k */ + x1 = glmm_shuff1(x0, 1, 3, 3, 3); /* l p p p */ + x2 = glmm_shuff1(x0, 0, 2, 2, 2); /* k o o o */ + x0 = _mm_shuffle_ps(r2, r1, _MM_SHUFFLE(3, 3, 3, 3)); /* h h l l */ + x3 = _mm_shuffle_ps(r2, r1, _MM_SHUFFLE(2, 2, 2, 2)); /* g g k k */ + + /* t1[0] = k * p - o * l; + t1[0] = k * p - o * l; + t2[0] = g * p - o * h; + t3[0] = g * l - k * h; */ + t0 = _mm_sub_ps(_mm_mul_ps(x3, x1), _mm_mul_ps(x2, x0)); + + x4 = _mm_shuffle_ps(r2, r3, _MM_SHUFFLE(2, 1, 2, 1)); /* o n k j */ + x4 = glmm_shuff1(x4, 0, 2, 2, 2); /* j n n n */ + x5 = _mm_shuffle_ps(r2, r1, _MM_SHUFFLE(1, 1, 1, 1)); /* f f j j */ + + /* t1[1] = j * p - n * l; + t1[1] = j * p - n * l; + t2[1] = f * p - n * h; + t3[1] = f * l - j * h; */ + t1 = _mm_sub_ps(_mm_mul_ps(x5, x1), _mm_mul_ps(x4, x0)); + + /* t1[2] = j * o - n * k + t1[2] = j * o - n * k; + t2[2] = f * o - n * g; + t3[2] = f * k - j * g; */ + t2 = _mm_sub_ps(_mm_mul_ps(x5, x2), _mm_mul_ps(x4, x3)); + + x6 = _mm_shuffle_ps(r2, r1, _MM_SHUFFLE(0, 0, 0, 0)); /* e e i i */ + x7 = glmm_shuff2(r3, r2, 0, 0, 0, 0, 2, 0, 0, 0); /* i m m m */ + + /* t1[3] = i * p - m * l; + t1[3] = i * p - m * l; + t2[3] = e * p - m * h; + t3[3] = e * l - i * h; */ + t3 = _mm_sub_ps(_mm_mul_ps(x6, x1), _mm_mul_ps(x7, x0)); + + /* t1[4] = i * o - m * k; + t1[4] = i * o - m * k; + t2[4] = e * o - m * g; + t3[4] = e * k - i * g; */ + t4 = _mm_sub_ps(_mm_mul_ps(x6, x2), _mm_mul_ps(x7, x3)); + + /* t1[5] = i * n - m * j; + t1[5] = i * n - m * j; + t2[5] = e * n - m * f; + t3[5] = e * j - i * f; */ + t5 = _mm_sub_ps(_mm_mul_ps(x6, x4), _mm_mul_ps(x7, x5)); + + x0 = glmm_shuff2(r1, r0, 0, 0, 0, 0, 2, 2, 2, 0); /* a a a e */ + x1 = glmm_shuff2(r1, r0, 1, 1, 1, 1, 2, 2, 2, 0); /* b b b f */ + x2 = glmm_shuff2(r1, r0, 2, 2, 2, 2, 2, 2, 2, 0); /* c c c g */ + x3 = glmm_shuff2(r1, r0, 3, 3, 3, 3, 2, 2, 2, 0); /* d d d h */ + + /* + dest[0][0] = f * t1[0] - g * t1[1] + h * t1[2]; + dest[0][1] =-(b * t1[0] - c * t1[1] + d * t1[2]); + dest[0][2] = b * t2[0] - c * t2[1] + d * t2[2]; + dest[0][3] =-(b * t3[0] - c * t3[1] + d * t3[2]); */ + v0 = _mm_add_ps(_mm_mul_ps(x3, t2), + _mm_sub_ps(_mm_mul_ps(x1, t0), + _mm_mul_ps(x2, t1))); + v0 = _mm_xor_ps(v0, _mm_set_ps(-0.f, 0.f, -0.f, 0.f)); + + /* + dest[1][0] =-(e * t1[0] - g * t1[3] + h * t1[4]); + dest[1][1] = a * t1[0] - c * t1[3] + d * t1[4]; + dest[1][2] =-(a * t2[0] - c * t2[3] + d * t2[4]); + dest[1][3] = a * t3[0] - c * t3[3] + d * t3[4]; */ + v1 = _mm_add_ps(_mm_mul_ps(x3, t4), + _mm_sub_ps(_mm_mul_ps(x0, t0), + _mm_mul_ps(x2, t3))); + v1 = _mm_xor_ps(v1, _mm_set_ps(0.f, -0.f, 0.f, -0.f)); + + /* + dest[2][0] = e * t1[1] - f * t1[3] + h * t1[5]; + dest[2][1] =-(a * t1[1] - b * t1[3] + d * t1[5]); + dest[2][2] = a * t2[1] - b * t2[3] + d * t2[5]; + dest[2][3] =-(a * t3[1] - b * t3[3] + d * t3[5]);*/ + v2 = _mm_add_ps(_mm_mul_ps(x3, t5), + _mm_sub_ps(_mm_mul_ps(x0, t1), + _mm_mul_ps(x1, t3))); + v2 = _mm_xor_ps(v2, _mm_set_ps(-0.f, 0.f, -0.f, 0.f)); + + /* + dest[3][0] =-(e * t1[2] - f * t1[4] + g * t1[5]); + dest[3][1] = a * t1[2] - b * t1[4] + c * t1[5]; + dest[3][2] =-(a * t2[2] - b * t2[4] + c * t2[5]); + dest[3][3] = a * t3[2] - b * t3[4] + c * t3[5]; */ + v3 = _mm_add_ps(_mm_mul_ps(x2, t5), + _mm_sub_ps(_mm_mul_ps(x0, t2), + _mm_mul_ps(x1, t4))); + v3 = _mm_xor_ps(v3, _mm_set_ps(0.f, -0.f, 0.f, -0.f)); + + /* determinant */ + x0 = _mm_shuffle_ps(v0, v1, _MM_SHUFFLE(0, 0, 0, 0)); + x1 = _mm_shuffle_ps(v2, v3, _MM_SHUFFLE(0, 0, 0, 0)); + x0 = _mm_shuffle_ps(x0, x1, _MM_SHUFFLE(2, 0, 2, 0)); + + x0 = _mm_mul_ps(x0, r0); + x0 = _mm_add_ps(x0, glmm_shuff1(x0, 0, 1, 2, 3)); + x0 = _mm_add_ps(x0, glmm_shuff1(x0, 1, 0, 0, 1)); + x0 = _mm_div_ps(_mm_set1_ps(1.0f), x0); + + glmm_store(dest[0], _mm_mul_ps(v0, x0)); + glmm_store(dest[1], _mm_mul_ps(v1, x0)); + glmm_store(dest[2], _mm_mul_ps(v2, x0)); + glmm_store(dest[3], _mm_mul_ps(v3, x0)); +} + +#endif +#endif /* cglm_mat_sse_h */ + +#endif + +#ifdef CGLM_AVX_FP +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_mat_simd_avx_h +#define cglm_mat_simd_avx_h +#ifdef __AVX__ + + +#include + +CGLM_INLINE +void +glm_mat4_mul_avx(mat4 m1, mat4 m2, mat4 dest) { + /* D = R * L (Column-Major) */ + + __m256 y0, y1, y2, y3, y4, y5, y6, y7, y8, y9; + + y0 = glmm_load256(m2[0]); /* h g f e d c b a */ + y1 = glmm_load256(m2[2]); /* p o n m l k j i */ + + y2 = glmm_load256(m1[0]); /* h g f e d c b a */ + y3 = glmm_load256(m1[2]); /* p o n m l k j i */ + + /* 0x03: 0b00000011 */ + y4 = _mm256_permute2f128_ps(y2, y2, 0x03); /* d c b a h g f e */ + y5 = _mm256_permute2f128_ps(y3, y3, 0x03); /* l k j i p o n m */ + + /* f f f f a a a a */ + /* h h h h c c c c */ + /* e e e e b b b b */ + /* g g g g d d d d */ + y6 = _mm256_permutevar_ps(y0, _mm256_set_epi32(1, 1, 1, 1, 0, 0, 0, 0)); + y7 = _mm256_permutevar_ps(y0, _mm256_set_epi32(3, 3, 3, 3, 2, 2, 2, 2)); + y8 = _mm256_permutevar_ps(y0, _mm256_set_epi32(0, 0, 0, 0, 1, 1, 1, 1)); + y9 = _mm256_permutevar_ps(y0, _mm256_set_epi32(2, 2, 2, 2, 3, 3, 3, 3)); + + glmm_store256(dest[0], + _mm256_add_ps(_mm256_add_ps(_mm256_mul_ps(y2, y6), + _mm256_mul_ps(y3, y7)), + _mm256_add_ps(_mm256_mul_ps(y4, y8), + _mm256_mul_ps(y5, y9)))); + + /* n n n n i i i i */ + /* p p p p k k k k */ + /* m m m m j j j j */ + /* o o o o l l l l */ + y6 = _mm256_permutevar_ps(y1, _mm256_set_epi32(1, 1, 1, 1, 0, 0, 0, 0)); + y7 = _mm256_permutevar_ps(y1, _mm256_set_epi32(3, 3, 3, 3, 2, 2, 2, 2)); + y8 = _mm256_permutevar_ps(y1, _mm256_set_epi32(0, 0, 0, 0, 1, 1, 1, 1)); + y9 = _mm256_permutevar_ps(y1, _mm256_set_epi32(2, 2, 2, 2, 3, 3, 3, 3)); + + glmm_store256(dest[2], + _mm256_add_ps(_mm256_add_ps(_mm256_mul_ps(y2, y6), + _mm256_mul_ps(y3, y7)), + _mm256_add_ps(_mm256_mul_ps(y4, y8), + _mm256_mul_ps(y5, y9)))); +} + +#endif +#endif /* cglm_mat_simd_avx_h */ + +#endif + +#ifdef CGLM_NEON_FP +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_mat4_neon_h +#define cglm_mat4_neon_h +#if defined(__ARM_NEON_FP) + + +CGLM_INLINE +void +glm_mat4_scale_neon(mat4 m, float s) { + float32x4_t v0; + + v0 = vdupq_n_f32(s); + + vst1q_f32(m[0], vmulq_f32(vld1q_f32(m[0]), v0)); + vst1q_f32(m[1], vmulq_f32(vld1q_f32(m[1]), v0)); + vst1q_f32(m[2], vmulq_f32(vld1q_f32(m[2]), v0)); + vst1q_f32(m[3], vmulq_f32(vld1q_f32(m[3]), v0)); +} + +CGLM_INLINE +void +glm_mat4_transp_neon(mat4 m, mat4 dest) { + float32x4x4_t vmat; + + vmat = vld4q_f32(m[0]); + + vst1q_f32(dest[0], vmat.val[0]); + vst1q_f32(dest[1], vmat.val[1]); + vst1q_f32(dest[2], vmat.val[2]); + vst1q_f32(dest[3], vmat.val[3]); +} + +CGLM_INLINE +void +glm_mat4_mul_neon(mat4 m1, mat4 m2, mat4 dest) { + /* D = R * L (Column-Major) */ + float32x4_t l0, l1, l2, l3, r, d0, d1, d2, d3; + + l0 = vld1q_f32(m2[0]); + l1 = vld1q_f32(m2[1]); + l2 = vld1q_f32(m2[2]); + l3 = vld1q_f32(m2[3]); + + r = vld1q_f32(m1[0]); + d0 = vmulq_lane_f32(r, vget_low_f32(l0), 0); + d1 = vmulq_lane_f32(r, vget_low_f32(l1), 0); + d2 = vmulq_lane_f32(r, vget_low_f32(l2), 0); + d3 = vmulq_lane_f32(r, vget_low_f32(l3), 0); + + r = vld1q_f32(m1[1]); + d0 = vmlaq_lane_f32(d0, r, vget_low_f32(l0), 1); + d1 = vmlaq_lane_f32(d1, r, vget_low_f32(l1), 1); + d2 = vmlaq_lane_f32(d2, r, vget_low_f32(l2), 1); + d3 = vmlaq_lane_f32(d3, r, vget_low_f32(l3), 1); + + r = vld1q_f32(m1[2]); + d0 = vmlaq_lane_f32(d0, r, vget_high_f32(l0), 0); + d1 = vmlaq_lane_f32(d1, r, vget_high_f32(l1), 0); + d2 = vmlaq_lane_f32(d2, r, vget_high_f32(l2), 0); + d3 = vmlaq_lane_f32(d3, r, vget_high_f32(l3), 0); + + r = vld1q_f32(m1[3]); + d0 = vmlaq_lane_f32(d0, r, vget_high_f32(l0), 1); + d1 = vmlaq_lane_f32(d1, r, vget_high_f32(l1), 1); + d2 = vmlaq_lane_f32(d2, r, vget_high_f32(l2), 1); + d3 = vmlaq_lane_f32(d3, r, vget_high_f32(l3), 1); + + vst1q_f32(dest[0], d0); + vst1q_f32(dest[1], d1); + vst1q_f32(dest[2], d2); + vst1q_f32(dest[3], d3); +} + +CGLM_INLINE +void +glm_mat4_mulv_neon(mat4 m, vec4 v, vec4 dest) { + float32x4_t l0, l1, l2, l3; + float32x2_t vlo, vhi; + + l0 = vld1q_f32(m[0]); + l1 = vld1q_f32(m[1]); + l2 = vld1q_f32(m[2]); + l3 = vld1q_f32(m[3]); + + vlo = vld1_f32(&v[0]); + vhi = vld1_f32(&v[2]); + + l0 = vmulq_lane_f32(l0, vlo, 0); + l0 = vmlaq_lane_f32(l0, l1, vlo, 1); + l0 = vmlaq_lane_f32(l0, l2, vhi, 0); + l0 = vmlaq_lane_f32(l0, l3, vhi, 1); + + vst1q_f32(dest, l0); +} + +#endif +#endif /* cglm_mat4_neon_h */ + +#endif + +#ifdef DEBUG +# include +#endif + +#define GLM_MAT4_IDENTITY_INIT {{1.0f, 0.0f, 0.0f, 0.0f}, \ + {0.0f, 1.0f, 0.0f, 0.0f}, \ + {0.0f, 0.0f, 1.0f, 0.0f}, \ + {0.0f, 0.0f, 0.0f, 1.0f}} + +#define GLM_MAT4_ZERO_INIT {{0.0f, 0.0f, 0.0f, 0.0f}, \ + {0.0f, 0.0f, 0.0f, 0.0f}, \ + {0.0f, 0.0f, 0.0f, 0.0f}, \ + {0.0f, 0.0f, 0.0f, 0.0f}} + +/* for C only */ +#define GLM_MAT4_IDENTITY ((mat4)GLM_MAT4_IDENTITY_INIT) +#define GLM_MAT4_ZERO ((mat4)GLM_MAT4_ZERO_INIT) + +/* DEPRECATED! use _copy, _ucopy versions */ +#define glm_mat4_udup(mat, dest) glm_mat4_ucopy(mat, dest) +#define glm_mat4_dup(mat, dest) glm_mat4_copy(mat, dest) + +/* DEPRECATED! default is precise now. */ +#define glm_mat4_inv_precise(mat, dest) glm_mat4_inv(mat, dest) + +/*! + * @brief copy all members of [mat] to [dest] + * + * matrix may not be aligned, u stands for unaligned, this may be useful when + * copying a matrix from external source e.g. asset importer... + * + * @param[in] mat source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_mat4_ucopy(mat4 mat, mat4 dest) { + dest[0][0] = mat[0][0]; dest[1][0] = mat[1][0]; + dest[0][1] = mat[0][1]; dest[1][1] = mat[1][1]; + dest[0][2] = mat[0][2]; dest[1][2] = mat[1][2]; + dest[0][3] = mat[0][3]; dest[1][3] = mat[1][3]; + + dest[2][0] = mat[2][0]; dest[3][0] = mat[3][0]; + dest[2][1] = mat[2][1]; dest[3][1] = mat[3][1]; + dest[2][2] = mat[2][2]; dest[3][2] = mat[3][2]; + dest[2][3] = mat[2][3]; dest[3][3] = mat[3][3]; +} + +/*! + * @brief copy all members of [mat] to [dest] + * + * @param[in] mat source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_mat4_copy(mat4 mat, mat4 dest) { +#ifdef __AVX__ + glmm_store256(dest[0], glmm_load256(mat[0])); + glmm_store256(dest[2], glmm_load256(mat[2])); +#elif defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(dest[0], glmm_load(mat[0])); + glmm_store(dest[1], glmm_load(mat[1])); + glmm_store(dest[2], glmm_load(mat[2])); + glmm_store(dest[3], glmm_load(mat[3])); +#elif defined(CGLM_NEON_FP) + vst1q_f32(dest[0], vld1q_f32(mat[0])); + vst1q_f32(dest[1], vld1q_f32(mat[1])); + vst1q_f32(dest[2], vld1q_f32(mat[2])); + vst1q_f32(dest[3], vld1q_f32(mat[3])); +#else + glm_mat4_ucopy(mat, dest); +#endif +} + +/*! + * @brief make given matrix identity. It is identical with below, + * but it is more easy to do that with this func especially for members + * e.g. glm_mat4_identity(aStruct->aMatrix); + * + * @code + * glm_mat4_copy(GLM_MAT4_IDENTITY, mat); // C only + * + * // or + * mat4 mat = GLM_MAT4_IDENTITY_INIT; + * @endcode + * + * @param[in, out] mat destination + */ +CGLM_INLINE +void +glm_mat4_identity(mat4 mat) { + CGLM_ALIGN_MAT mat4 t = GLM_MAT4_IDENTITY_INIT; + glm_mat4_copy(t, mat); +} + +/*! + * @brief make given matrix array's each element identity matrix + * + * @param[in, out] mat matrix array (must be aligned (16/32) + * if alignment is not disabled) + * + * @param[in] count count of matrices + */ +CGLM_INLINE +void +glm_mat4_identity_array(mat4 * __restrict mat, size_t count) { + CGLM_ALIGN_MAT mat4 t = GLM_MAT4_IDENTITY_INIT; + size_t i; + + for (i = 0; i < count; i++) { + glm_mat4_copy(t, mat[i]); + } +} + +/*! + * @brief make given matrix zero. + * + * @param[in, out] mat matrix + */ +CGLM_INLINE +void +glm_mat4_zero(mat4 mat) { + CGLM_ALIGN_MAT mat4 t = GLM_MAT4_ZERO_INIT; + glm_mat4_copy(t, mat); +} + +/*! + * @brief copy upper-left of mat4 to mat3 + * + * @param[in] mat source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_mat4_pick3(mat4 mat, mat3 dest) { + dest[0][0] = mat[0][0]; + dest[0][1] = mat[0][1]; + dest[0][2] = mat[0][2]; + + dest[1][0] = mat[1][0]; + dest[1][1] = mat[1][1]; + dest[1][2] = mat[1][2]; + + dest[2][0] = mat[2][0]; + dest[2][1] = mat[2][1]; + dest[2][2] = mat[2][2]; +} + +/*! + * @brief copy upper-left of mat4 to mat3 (transposed) + * + * the postfix t stands for transpose + * + * @param[in] mat source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_mat4_pick3t(mat4 mat, mat3 dest) { + dest[0][0] = mat[0][0]; + dest[0][1] = mat[1][0]; + dest[0][2] = mat[2][0]; + + dest[1][0] = mat[0][1]; + dest[1][1] = mat[1][1]; + dest[1][2] = mat[2][1]; + + dest[2][0] = mat[0][2]; + dest[2][1] = mat[1][2]; + dest[2][2] = mat[2][2]; +} + +/*! + * @brief copy mat3 to mat4's upper-left + * + * @param[in] mat source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_mat4_ins3(mat3 mat, mat4 dest) { + dest[0][0] = mat[0][0]; + dest[0][1] = mat[0][1]; + dest[0][2] = mat[0][2]; + + dest[1][0] = mat[1][0]; + dest[1][1] = mat[1][1]; + dest[1][2] = mat[1][2]; + + dest[2][0] = mat[2][0]; + dest[2][1] = mat[2][1]; + dest[2][2] = mat[2][2]; +} + +/*! + * @brief multiply m1 and m2 to dest + * + * m1, m2 and dest matrices can be same matrix, it is possible to write this: + * + * @code + * mat4 m = GLM_MAT4_IDENTITY_INIT; + * glm_mat4_mul(m, m, m); + * @endcode + * + * @param[in] m1 left matrix + * @param[in] m2 right matrix + * @param[out] dest destination matrix + */ +CGLM_INLINE +void +glm_mat4_mul(mat4 m1, mat4 m2, mat4 dest) { +#ifdef __AVX__ + glm_mat4_mul_avx(m1, m2, dest); +#elif defined( __SSE__ ) || defined( __SSE2__ ) + glm_mat4_mul_sse2(m1, m2, dest); +#elif defined(CGLM_NEON_FP) + glm_mat4_mul_neon(m1, m2, dest); +#else + float a00 = m1[0][0], a01 = m1[0][1], a02 = m1[0][2], a03 = m1[0][3], + a10 = m1[1][0], a11 = m1[1][1], a12 = m1[1][2], a13 = m1[1][3], + a20 = m1[2][0], a21 = m1[2][1], a22 = m1[2][2], a23 = m1[2][3], + a30 = m1[3][0], a31 = m1[3][1], a32 = m1[3][2], a33 = m1[3][3], + + b00 = m2[0][0], b01 = m2[0][1], b02 = m2[0][2], b03 = m2[0][3], + b10 = m2[1][0], b11 = m2[1][1], b12 = m2[1][2], b13 = m2[1][3], + b20 = m2[2][0], b21 = m2[2][1], b22 = m2[2][2], b23 = m2[2][3], + b30 = m2[3][0], b31 = m2[3][1], b32 = m2[3][2], b33 = m2[3][3]; + + dest[0][0] = a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03; + dest[0][1] = a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03; + dest[0][2] = a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03; + dest[0][3] = a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03; + dest[1][0] = a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13; + dest[1][1] = a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13; + dest[1][2] = a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13; + dest[1][3] = a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13; + dest[2][0] = a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23; + dest[2][1] = a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23; + dest[2][2] = a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23; + dest[2][3] = a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23; + dest[3][0] = a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33; + dest[3][1] = a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33; + dest[3][2] = a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33; + dest[3][3] = a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33; +#endif +} + +/*! + * @brief mupliply N mat4 matrices and store result in dest + * + * this function lets you multiply multiple (more than two or more...) matrices + *

multiplication will be done in loop, this may reduce instructions + * size but if len is too small then compiler may unroll whole loop, + * usage: + * @code + * mat m1, m2, m3, m4, res; + * + * glm_mat4_mulN((mat4 *[]){&m1, &m2, &m3, &m4}, 4, res); + * @endcode + * + * @warning matrices parameter is pointer array not mat4 array! + * + * @param[in] matrices mat4 * array + * @param[in] len matrices count + * @param[out] dest result + */ +CGLM_INLINE +void +glm_mat4_mulN(mat4 * __restrict matrices[], uint32_t len, mat4 dest) { + uint32_t i; + +#ifdef DEBUG + assert(len > 1 && "there must be least 2 matrices to go!"); +#endif + + glm_mat4_mul(*matrices[0], *matrices[1], dest); + + for (i = 2; i < len; i++) + glm_mat4_mul(dest, *matrices[i], dest); +} + +/*! + * @brief multiply mat4 with vec4 (column vector) and store in dest vector + * + * @param[in] m mat4 (left) + * @param[in] v vec4 (right, column vector) + * @param[out] dest vec4 (result, column vector) + */ +CGLM_INLINE +void +glm_mat4_mulv(mat4 m, vec4 v, vec4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_mat4_mulv_sse2(m, v, dest); +#elif defined(CGLM_NEON_FP) + glm_mat4_mulv_neon(m, v, dest); +#else + vec4 res; + res[0] = m[0][0] * v[0] + m[1][0] * v[1] + m[2][0] * v[2] + m[3][0] * v[3]; + res[1] = m[0][1] * v[0] + m[1][1] * v[1] + m[2][1] * v[2] + m[3][1] * v[3]; + res[2] = m[0][2] * v[0] + m[1][2] * v[1] + m[2][2] * v[2] + m[3][2] * v[3]; + res[3] = m[0][3] * v[0] + m[1][3] * v[1] + m[2][3] * v[2] + m[3][3] * v[3]; + glm_vec4_copy(res, dest); +#endif +} + +/*! + * @brief trace of matrix + * + * sum of the elements on the main diagonal from upper left to the lower right + * + * @param[in] m matrix + */ +CGLM_INLINE +float +glm_mat4_trace(mat4 m) { + return m[0][0] + m[1][1] + m[2][2] + m[3][3]; +} + +/*! + * @brief trace of matrix (rotation part) + * + * sum of the elements on the main diagonal from upper left to the lower right + * + * @param[in] m matrix + */ +CGLM_INLINE +float +glm_mat4_trace3(mat4 m) { + return m[0][0] + m[1][1] + m[2][2]; +} + +/*! + * @brief convert mat4's rotation part to quaternion + * + * @param[in] m affine matrix + * @param[out] dest destination quaternion + */ +CGLM_INLINE +void +glm_mat4_quat(mat4 m, versor dest) { + float trace, r, rinv; + + /* it seems using like m12 instead of m[1][2] causes extra instructions */ + + trace = m[0][0] + m[1][1] + m[2][2]; + if (trace >= 0.0f) { + r = sqrtf(1.0f + trace); + rinv = 0.5f / r; + + dest[0] = rinv * (m[1][2] - m[2][1]); + dest[1] = rinv * (m[2][0] - m[0][2]); + dest[2] = rinv * (m[0][1] - m[1][0]); + dest[3] = r * 0.5f; + } else if (m[0][0] >= m[1][1] && m[0][0] >= m[2][2]) { + r = sqrtf(1.0f - m[1][1] - m[2][2] + m[0][0]); + rinv = 0.5f / r; + + dest[0] = r * 0.5f; + dest[1] = rinv * (m[0][1] + m[1][0]); + dest[2] = rinv * (m[0][2] + m[2][0]); + dest[3] = rinv * (m[1][2] - m[2][1]); + } else if (m[1][1] >= m[2][2]) { + r = sqrtf(1.0f - m[0][0] - m[2][2] + m[1][1]); + rinv = 0.5f / r; + + dest[0] = rinv * (m[0][1] + m[1][0]); + dest[1] = r * 0.5f; + dest[2] = rinv * (m[1][2] + m[2][1]); + dest[3] = rinv * (m[2][0] - m[0][2]); + } else { + r = sqrtf(1.0f - m[0][0] - m[1][1] + m[2][2]); + rinv = 0.5f / r; + + dest[0] = rinv * (m[0][2] + m[2][0]); + dest[1] = rinv * (m[1][2] + m[2][1]); + dest[2] = r * 0.5f; + dest[3] = rinv * (m[0][1] - m[1][0]); + } +} + +/*! + * @brief multiply vector with mat4 + * + * actually the result is vec4, after multiplication the last component + * is trimmed. if you need it don't use this func. + * + * @param[in] m mat4(affine transform) + * @param[in] v vec3 + * @param[in] last 4th item to make it vec4 + * @param[out] dest result vector (vec3) + */ +CGLM_INLINE +void +glm_mat4_mulv3(mat4 m, vec3 v, float last, vec3 dest) { + vec4 res; + glm_vec4(v, last, res); + glm_mat4_mulv(m, res, res); + glm_vec3(res, dest); +} + +/*! + * @brief transpose mat4 and store in dest + * + * source matrix will not be transposed unless dest is m + * + * @param[in] m matrix + * @param[out] dest result + */ +CGLM_INLINE +void +glm_mat4_transpose_to(mat4 m, mat4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_mat4_transp_sse2(m, dest); +#elif defined(CGLM_NEON_FP) + glm_mat4_transp_neon(m, dest); +#else + dest[0][0] = m[0][0]; dest[1][0] = m[0][1]; + dest[0][1] = m[1][0]; dest[1][1] = m[1][1]; + dest[0][2] = m[2][0]; dest[1][2] = m[2][1]; + dest[0][3] = m[3][0]; dest[1][3] = m[3][1]; + dest[2][0] = m[0][2]; dest[3][0] = m[0][3]; + dest[2][1] = m[1][2]; dest[3][1] = m[1][3]; + dest[2][2] = m[2][2]; dest[3][2] = m[2][3]; + dest[2][3] = m[3][2]; dest[3][3] = m[3][3]; +#endif +} + +/*! + * @brief tranpose mat4 and store result in same matrix + * + * @param[in, out] m source and dest + */ +CGLM_INLINE +void +glm_mat4_transpose(mat4 m) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_mat4_transp_sse2(m, m); +#elif defined(CGLM_NEON_FP) + glm_mat4_transp_neon(m, m); +#else + mat4 d; + glm_mat4_transpose_to(m, d); + glm_mat4_ucopy(d, m); +#endif +} + +/*! + * @brief scale (multiply with scalar) matrix without simd optimization + * + * multiply matrix with scalar + * + * @param[in, out] m matrix + * @param[in] s scalar + */ +CGLM_INLINE +void +glm_mat4_scale_p(mat4 m, float s) { + m[0][0] *= s; m[0][1] *= s; m[0][2] *= s; m[0][3] *= s; + m[1][0] *= s; m[1][1] *= s; m[1][2] *= s; m[1][3] *= s; + m[2][0] *= s; m[2][1] *= s; m[2][2] *= s; m[2][3] *= s; + m[3][0] *= s; m[3][1] *= s; m[3][2] *= s; m[3][3] *= s; +} + +/*! + * @brief scale (multiply with scalar) matrix + * + * multiply matrix with scalar + * + * @param[in, out] m matrix + * @param[in] s scalar + */ +CGLM_INLINE +void +glm_mat4_scale(mat4 m, float s) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_mat4_scale_sse2(m, s); +#elif defined(CGLM_NEON_FP) + glm_mat4_scale_neon(m, s); +#else + glm_mat4_scale_p(m, s); +#endif +} + +/*! + * @brief mat4 determinant + * + * @param[in] mat matrix + * + * @return determinant + */ +CGLM_INLINE +float +glm_mat4_det(mat4 mat) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + return glm_mat4_det_sse2(mat); +#else + /* [square] det(A) = det(At) */ + float t[6]; + float a = mat[0][0], b = mat[0][1], c = mat[0][2], d = mat[0][3], + e = mat[1][0], f = mat[1][1], g = mat[1][2], h = mat[1][3], + i = mat[2][0], j = mat[2][1], k = mat[2][2], l = mat[2][3], + m = mat[3][0], n = mat[3][1], o = mat[3][2], p = mat[3][3]; + + t[0] = k * p - o * l; + t[1] = j * p - n * l; + t[2] = j * o - n * k; + t[3] = i * p - m * l; + t[4] = i * o - m * k; + t[5] = i * n - m * j; + + return a * (f * t[0] - g * t[1] + h * t[2]) + - b * (e * t[0] - g * t[3] + h * t[4]) + + c * (e * t[1] - f * t[3] + h * t[5]) + - d * (e * t[2] - f * t[4] + g * t[5]); +#endif +} + +/*! + * @brief inverse mat4 and store in dest + * + * @param[in] mat matrix + * @param[out] dest inverse matrix + */ +CGLM_INLINE +void +glm_mat4_inv(mat4 mat, mat4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_mat4_inv_sse2(mat, dest); +#else + float t[6]; + float det; + float a = mat[0][0], b = mat[0][1], c = mat[0][2], d = mat[0][3], + e = mat[1][0], f = mat[1][1], g = mat[1][2], h = mat[1][3], + i = mat[2][0], j = mat[2][1], k = mat[2][2], l = mat[2][3], + m = mat[3][0], n = mat[3][1], o = mat[3][2], p = mat[3][3]; + + t[0] = k * p - o * l; t[1] = j * p - n * l; t[2] = j * o - n * k; + t[3] = i * p - m * l; t[4] = i * o - m * k; t[5] = i * n - m * j; + + dest[0][0] = f * t[0] - g * t[1] + h * t[2]; + dest[1][0] =-(e * t[0] - g * t[3] + h * t[4]); + dest[2][0] = e * t[1] - f * t[3] + h * t[5]; + dest[3][0] =-(e * t[2] - f * t[4] + g * t[5]); + + dest[0][1] =-(b * t[0] - c * t[1] + d * t[2]); + dest[1][1] = a * t[0] - c * t[3] + d * t[4]; + dest[2][1] =-(a * t[1] - b * t[3] + d * t[5]); + dest[3][1] = a * t[2] - b * t[4] + c * t[5]; + + t[0] = g * p - o * h; t[1] = f * p - n * h; t[2] = f * o - n * g; + t[3] = e * p - m * h; t[4] = e * o - m * g; t[5] = e * n - m * f; + + dest[0][2] = b * t[0] - c * t[1] + d * t[2]; + dest[1][2] =-(a * t[0] - c * t[3] + d * t[4]); + dest[2][2] = a * t[1] - b * t[3] + d * t[5]; + dest[3][2] =-(a * t[2] - b * t[4] + c * t[5]); + + t[0] = g * l - k * h; t[1] = f * l - j * h; t[2] = f * k - j * g; + t[3] = e * l - i * h; t[4] = e * k - i * g; t[5] = e * j - i * f; + + dest[0][3] =-(b * t[0] - c * t[1] + d * t[2]); + dest[1][3] = a * t[0] - c * t[3] + d * t[4]; + dest[2][3] =-(a * t[1] - b * t[3] + d * t[5]); + dest[3][3] = a * t[2] - b * t[4] + c * t[5]; + + det = 1.0f / (a * dest[0][0] + b * dest[1][0] + + c * dest[2][0] + d * dest[3][0]); + + glm_mat4_scale_p(dest, det); +#endif +} + +/*! + * @brief inverse mat4 and store in dest + * + * this func uses reciprocal approximation without extra corrections + * e.g Newton-Raphson. this should work faster than normal, + * to get more precise use glm_mat4_inv version. + * + * NOTE: You will lose precision, glm_mat4_inv is more accurate + * + * @param[in] mat matrix + * @param[out] dest inverse matrix + */ +CGLM_INLINE +void +glm_mat4_inv_fast(mat4 mat, mat4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_mat4_inv_fast_sse2(mat, dest); +#else + glm_mat4_inv(mat, dest); +#endif +} + +/*! + * @brief swap two matrix columns + * + * @param[in,out] mat matrix + * @param[in] col1 col1 + * @param[in] col2 col2 + */ +CGLM_INLINE +void +glm_mat4_swap_col(mat4 mat, int col1, int col2) { + CGLM_ALIGN(16) vec4 tmp; + glm_vec4_copy(mat[col1], tmp); + glm_vec4_copy(mat[col2], mat[col1]); + glm_vec4_copy(tmp, mat[col2]); +} + +/*! + * @brief swap two matrix rows + * + * @param[in,out] mat matrix + * @param[in] row1 row1 + * @param[in] row2 row2 + */ +CGLM_INLINE +void +glm_mat4_swap_row(mat4 mat, int row1, int row2) { + CGLM_ALIGN(16) vec4 tmp; + tmp[0] = mat[0][row1]; + tmp[1] = mat[1][row1]; + tmp[2] = mat[2][row1]; + tmp[3] = mat[3][row1]; + + mat[0][row1] = mat[0][row2]; + mat[1][row1] = mat[1][row2]; + mat[2][row1] = mat[2][row2]; + mat[3][row1] = mat[3][row2]; + + mat[0][row2] = tmp[0]; + mat[1][row2] = tmp[1]; + mat[2][row2] = tmp[2]; + mat[3][row2] = tmp[3]; +} + +/*! + * @brief helper for R (row vector) * M (matrix) * C (column vector) + * + * rmc stands for Row * Matrix * Column + * + * the result is scalar because R * M = Matrix1x4 (row vector), + * then Matrix1x4 * Vec4 (column vector) = Matrix1x1 (Scalar) + * + * @param[in] r row vector or matrix1x4 + * @param[in] m matrix4x4 + * @param[in] c column vector or matrix4x1 + * + * @return scalar value e.g. B(s) + */ +CGLM_INLINE +float +glm_mat4_rmc(vec4 r, mat4 m, vec4 c) { + vec4 tmp; + glm_mat4_mulv(m, c, tmp); + return glm_vec4_dot(r, tmp); +} + +#endif /* cglm_mat_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Macros: + GLM_MAT3_IDENTITY_INIT + GLM_MAT3_ZERO_INIT + GLM_MAT3_IDENTITY + GLM_MAT3_ZERO + glm_mat3_dup(mat, dest) + + Functions: + CGLM_INLINE void glm_mat3_copy(mat3 mat, mat3 dest); + CGLM_INLINE void glm_mat3_identity(mat3 mat); + CGLM_INLINE void glm_mat3_identity_array(mat3 * restrict mat, size_t count); + CGLM_INLINE void glm_mat3_zero(mat3 mat); + CGLM_INLINE void glm_mat3_mul(mat3 m1, mat3 m2, mat3 dest); + CGLM_INLINE void glm_mat3_transpose_to(mat3 m, mat3 dest); + CGLM_INLINE void glm_mat3_transpose(mat3 m); + CGLM_INLINE void glm_mat3_mulv(mat3 m, vec3 v, vec3 dest); + CGLM_INLINE float glm_mat3_trace(mat3 m); + CGLM_INLINE void glm_mat3_quat(mat3 m, versor dest); + CGLM_INLINE void glm_mat3_scale(mat3 m, float s); + CGLM_INLINE float glm_mat3_det(mat3 mat); + CGLM_INLINE void glm_mat3_inv(mat3 mat, mat3 dest); + CGLM_INLINE void glm_mat3_swap_col(mat3 mat, int col1, int col2); + CGLM_INLINE void glm_mat3_swap_row(mat3 mat, int row1, int row2); + CGLM_INLINE float glm_mat3_rmc(vec3 r, mat3 m, vec3 c); + */ + +#ifndef cglm_mat3_h +#define cglm_mat3_h + + +#ifdef CGLM_SSE_FP +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_mat3_sse_h +#define cglm_mat3_sse_h +#if defined( __SSE__ ) || defined( __SSE2__ ) + + +CGLM_INLINE +void +glm_mat3_mul_sse2(mat3 m1, mat3 m2, mat3 dest) { + __m128 l0, l1, l2; + __m128 r0, r1, r2; + __m128 x0, x1, x2; + + l0 = _mm_loadu_ps(m1[0]); + l1 = _mm_loadu_ps(&m1[1][1]); + l2 = _mm_set1_ps(m1[2][2]); + + r0 = _mm_loadu_ps(m2[0]); + r1 = _mm_loadu_ps(&m2[1][1]); + r2 = _mm_set1_ps(m2[2][2]); + + x1 = glmm_shuff2(l0, l1, 1, 0, 3, 3, 0, 3, 2, 0); + x2 = glmm_shuff2(l1, l2, 0, 0, 3, 2, 0, 2, 1, 0); + + x0 = _mm_add_ps(_mm_mul_ps(glmm_shuff1(l0, 0, 2, 1, 0), + glmm_shuff1(r0, 3, 0, 0, 0)), + _mm_mul_ps(x1, glmm_shuff2(r0, r1, 0, 0, 1, 1, 2, 0, 0, 0))); + + x0 = _mm_add_ps(x0, + _mm_mul_ps(x2, glmm_shuff2(r0, r1, 1, 1, 2, 2, 2, 0, 0, 0))); + + _mm_storeu_ps(dest[0], x0); + + x0 = _mm_add_ps(_mm_mul_ps(glmm_shuff1(l0, 1, 0, 2, 1), + _mm_shuffle_ps(r0, r1, _MM_SHUFFLE(2, 2, 3, 3))), + _mm_mul_ps(glmm_shuff1(x1, 1, 0, 2, 1), + glmm_shuff1(r1, 3, 3, 0, 0))); + + x0 = _mm_add_ps(x0, + _mm_mul_ps(glmm_shuff1(x2, 1, 0, 2, 1), + _mm_shuffle_ps(r1, r2, _MM_SHUFFLE(0, 0, 1, 1)))); + + _mm_storeu_ps(&dest[1][1], x0); + + dest[2][2] = m1[0][2] * m2[2][0] + + m1[1][2] * m2[2][1] + + m1[2][2] * m2[2][2]; +} + +#endif +#endif /* cglm_mat3_sse_h */ + +#endif + +#define GLM_MAT3_IDENTITY_INIT {{1.0f, 0.0f, 0.0f}, \ + {0.0f, 1.0f, 0.0f}, \ + {0.0f, 0.0f, 1.0f}} +#define GLM_MAT3_ZERO_INIT {{0.0f, 0.0f, 0.0f}, \ + {0.0f, 0.0f, 0.0f}, \ + {0.0f, 0.0f, 0.0f}} + + +/* for C only */ +#define GLM_MAT3_IDENTITY ((mat3)GLM_MAT3_IDENTITY_INIT) +#define GLM_MAT3_ZERO ((mat3)GLM_MAT3_ZERO_INIT) + +/* DEPRECATED! use _copy, _ucopy versions */ +#define glm_mat3_dup(mat, dest) glm_mat3_copy(mat, dest) + +/*! + * @brief copy all members of [mat] to [dest] + * + * @param[in] mat source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_mat3_copy(mat3 mat, mat3 dest) { + dest[0][0] = mat[0][0]; + dest[0][1] = mat[0][1]; + dest[0][2] = mat[0][2]; + + dest[1][0] = mat[1][0]; + dest[1][1] = mat[1][1]; + dest[1][2] = mat[1][2]; + + dest[2][0] = mat[2][0]; + dest[2][1] = mat[2][1]; + dest[2][2] = mat[2][2]; +} + +/*! + * @brief make given matrix identity. It is identical with below, + * but it is more easy to do that with this func especially for members + * e.g. glm_mat3_identity(aStruct->aMatrix); + * + * @code + * glm_mat3_copy(GLM_MAT3_IDENTITY, mat); // C only + * + * // or + * mat3 mat = GLM_MAT3_IDENTITY_INIT; + * @endcode + * + * @param[in, out] mat destination + */ +CGLM_INLINE +void +glm_mat3_identity(mat3 mat) { + CGLM_ALIGN_MAT mat3 t = GLM_MAT3_IDENTITY_INIT; + glm_mat3_copy(t, mat); +} + +/*! + * @brief make given matrix array's each element identity matrix + * + * @param[in, out] mat matrix array (must be aligned (16/32) + * if alignment is not disabled) + * + * @param[in] count count of matrices + */ +CGLM_INLINE +void +glm_mat3_identity_array(mat3 * __restrict mat, size_t count) { + CGLM_ALIGN_MAT mat3 t = GLM_MAT3_IDENTITY_INIT; + size_t i; + + for (i = 0; i < count; i++) { + glm_mat3_copy(t, mat[i]); + } +} + +/*! + * @brief make given matrix zero. + * + * @param[in, out] mat matrix + */ +CGLM_INLINE +void +glm_mat3_zero(mat3 mat) { + CGLM_ALIGN_MAT mat3 t = GLM_MAT3_ZERO_INIT; + glm_mat3_copy(t, mat); +} + +/*! + * @brief multiply m1 and m2 to dest + * + * m1, m2 and dest matrices can be same matrix, it is possible to write this: + * + * @code + * mat3 m = GLM_MAT3_IDENTITY_INIT; + * glm_mat3_mul(m, m, m); + * @endcode + * + * @param[in] m1 left matrix + * @param[in] m2 right matrix + * @param[out] dest destination matrix + */ +CGLM_INLINE +void +glm_mat3_mul(mat3 m1, mat3 m2, mat3 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_mat3_mul_sse2(m1, m2, dest); +#else + float a00 = m1[0][0], a01 = m1[0][1], a02 = m1[0][2], + a10 = m1[1][0], a11 = m1[1][1], a12 = m1[1][2], + a20 = m1[2][0], a21 = m1[2][1], a22 = m1[2][2], + + b00 = m2[0][0], b01 = m2[0][1], b02 = m2[0][2], + b10 = m2[1][0], b11 = m2[1][1], b12 = m2[1][2], + b20 = m2[2][0], b21 = m2[2][1], b22 = m2[2][2]; + + dest[0][0] = a00 * b00 + a10 * b01 + a20 * b02; + dest[0][1] = a01 * b00 + a11 * b01 + a21 * b02; + dest[0][2] = a02 * b00 + a12 * b01 + a22 * b02; + dest[1][0] = a00 * b10 + a10 * b11 + a20 * b12; + dest[1][1] = a01 * b10 + a11 * b11 + a21 * b12; + dest[1][2] = a02 * b10 + a12 * b11 + a22 * b12; + dest[2][0] = a00 * b20 + a10 * b21 + a20 * b22; + dest[2][1] = a01 * b20 + a11 * b21 + a21 * b22; + dest[2][2] = a02 * b20 + a12 * b21 + a22 * b22; +#endif +} + +/*! + * @brief transpose mat3 and store in dest + * + * source matrix will not be transposed unless dest is m + * + * @param[in] m matrix + * @param[out] dest result + */ +CGLM_INLINE +void +glm_mat3_transpose_to(mat3 m, mat3 dest) { + dest[0][0] = m[0][0]; + dest[0][1] = m[1][0]; + dest[0][2] = m[2][0]; + dest[1][0] = m[0][1]; + dest[1][1] = m[1][1]; + dest[1][2] = m[2][1]; + dest[2][0] = m[0][2]; + dest[2][1] = m[1][2]; + dest[2][2] = m[2][2]; +} + +/*! + * @brief tranpose mat3 and store result in same matrix + * + * @param[in, out] m source and dest + */ +CGLM_INLINE +void +glm_mat3_transpose(mat3 m) { + CGLM_ALIGN_MAT mat3 tmp; + + tmp[0][1] = m[1][0]; + tmp[0][2] = m[2][0]; + tmp[1][0] = m[0][1]; + tmp[1][2] = m[2][1]; + tmp[2][0] = m[0][2]; + tmp[2][1] = m[1][2]; + + m[0][1] = tmp[0][1]; + m[0][2] = tmp[0][2]; + m[1][0] = tmp[1][0]; + m[1][2] = tmp[1][2]; + m[2][0] = tmp[2][0]; + m[2][1] = tmp[2][1]; +} + +/*! + * @brief multiply mat3 with vec3 (column vector) and store in dest vector + * + * @param[in] m mat3 (left) + * @param[in] v vec3 (right, column vector) + * @param[out] dest vec3 (result, column vector) + */ +CGLM_INLINE +void +glm_mat3_mulv(mat3 m, vec3 v, vec3 dest) { + vec3 res; + res[0] = m[0][0] * v[0] + m[1][0] * v[1] + m[2][0] * v[2]; + res[1] = m[0][1] * v[0] + m[1][1] * v[1] + m[2][1] * v[2]; + res[2] = m[0][2] * v[0] + m[1][2] * v[1] + m[2][2] * v[2]; + glm_vec3_copy(res, dest); +} + +/*! + * @brief trace of matrix + * + * sum of the elements on the main diagonal from upper left to the lower right + * + * @param[in] m matrix + */ +CGLM_INLINE +float +glm_mat3_trace(mat3 m) { + return m[0][0] + m[1][1] + m[2][2]; +} + +/*! + * @brief convert mat3 to quaternion + * + * @param[in] m rotation matrix + * @param[out] dest destination quaternion + */ +CGLM_INLINE +void +glm_mat3_quat(mat3 m, versor dest) { + float trace, r, rinv; + + /* it seems using like m12 instead of m[1][2] causes extra instructions */ + + trace = m[0][0] + m[1][1] + m[2][2]; + if (trace >= 0.0f) { + r = sqrtf(1.0f + trace); + rinv = 0.5f / r; + + dest[0] = rinv * (m[1][2] - m[2][1]); + dest[1] = rinv * (m[2][0] - m[0][2]); + dest[2] = rinv * (m[0][1] - m[1][0]); + dest[3] = r * 0.5f; + } else if (m[0][0] >= m[1][1] && m[0][0] >= m[2][2]) { + r = sqrtf(1.0f - m[1][1] - m[2][2] + m[0][0]); + rinv = 0.5f / r; + + dest[0] = r * 0.5f; + dest[1] = rinv * (m[0][1] + m[1][0]); + dest[2] = rinv * (m[0][2] + m[2][0]); + dest[3] = rinv * (m[1][2] - m[2][1]); + } else if (m[1][1] >= m[2][2]) { + r = sqrtf(1.0f - m[0][0] - m[2][2] + m[1][1]); + rinv = 0.5f / r; + + dest[0] = rinv * (m[0][1] + m[1][0]); + dest[1] = r * 0.5f; + dest[2] = rinv * (m[1][2] + m[2][1]); + dest[3] = rinv * (m[2][0] - m[0][2]); + } else { + r = sqrtf(1.0f - m[0][0] - m[1][1] + m[2][2]); + rinv = 0.5f / r; + + dest[0] = rinv * (m[0][2] + m[2][0]); + dest[1] = rinv * (m[1][2] + m[2][1]); + dest[2] = r * 0.5f; + dest[3] = rinv * (m[0][1] - m[1][0]); + } +} + +/*! + * @brief scale (multiply with scalar) matrix + * + * multiply matrix with scalar + * + * @param[in, out] m matrix + * @param[in] s scalar + */ +CGLM_INLINE +void +glm_mat3_scale(mat3 m, float s) { + m[0][0] *= s; m[0][1] *= s; m[0][2] *= s; + m[1][0] *= s; m[1][1] *= s; m[1][2] *= s; + m[2][0] *= s; m[2][1] *= s; m[2][2] *= s; +} + +/*! + * @brief mat3 determinant + * + * @param[in] mat matrix + * + * @return determinant + */ +CGLM_INLINE +float +glm_mat3_det(mat3 mat) { + float a = mat[0][0], b = mat[0][1], c = mat[0][2], + d = mat[1][0], e = mat[1][1], f = mat[1][2], + g = mat[2][0], h = mat[2][1], i = mat[2][2]; + + return a * (e * i - h * f) - d * (b * i - c * h) + g * (b * f - c * e); +} + +/*! + * @brief inverse mat3 and store in dest + * + * @param[in] mat matrix + * @param[out] dest inverse matrix + */ +CGLM_INLINE +void +glm_mat3_inv(mat3 mat, mat3 dest) { + float det; + float a = mat[0][0], b = mat[0][1], c = mat[0][2], + d = mat[1][0], e = mat[1][1], f = mat[1][2], + g = mat[2][0], h = mat[2][1], i = mat[2][2]; + + dest[0][0] = e * i - f * h; + dest[0][1] = -(b * i - h * c); + dest[0][2] = b * f - e * c; + dest[1][0] = -(d * i - g * f); + dest[1][1] = a * i - c * g; + dest[1][2] = -(a * f - d * c); + dest[2][0] = d * h - g * e; + dest[2][1] = -(a * h - g * b); + dest[2][2] = a * e - b * d; + + det = 1.0f / (a * dest[0][0] + b * dest[1][0] + c * dest[2][0]); + + glm_mat3_scale(dest, det); +} + +/*! + * @brief swap two matrix columns + * + * @param[in,out] mat matrix + * @param[in] col1 col1 + * @param[in] col2 col2 + */ +CGLM_INLINE +void +glm_mat3_swap_col(mat3 mat, int col1, int col2) { + vec3 tmp; + glm_vec3_copy(mat[col1], tmp); + glm_vec3_copy(mat[col2], mat[col1]); + glm_vec3_copy(tmp, mat[col2]); +} + +/*! + * @brief swap two matrix rows + * + * @param[in,out] mat matrix + * @param[in] row1 row1 + * @param[in] row2 row2 + */ +CGLM_INLINE +void +glm_mat3_swap_row(mat3 mat, int row1, int row2) { + vec3 tmp; + tmp[0] = mat[0][row1]; + tmp[1] = mat[1][row1]; + tmp[2] = mat[2][row1]; + + mat[0][row1] = mat[0][row2]; + mat[1][row1] = mat[1][row2]; + mat[2][row1] = mat[2][row2]; + + mat[0][row2] = tmp[0]; + mat[1][row2] = tmp[1]; + mat[2][row2] = tmp[2]; +} + +/*! + * @brief helper for R (row vector) * M (matrix) * C (column vector) + * + * rmc stands for Row * Matrix * Column + * + * the result is scalar because R * M = Matrix1x3 (row vector), + * then Matrix1x3 * Vec3 (column vector) = Matrix1x1 (Scalar) + * + * @param[in] r row vector or matrix1x3 + * @param[in] m matrix3x3 + * @param[in] c column vector or matrix3x1 + * + * @return scalar value e.g. Matrix1x1 + */ +CGLM_INLINE +float +glm_mat3_rmc(vec3 r, mat3 m, vec3 c) { + vec3 tmp; + glm_mat3_mulv(m, c, tmp); + return glm_vec3_dot(r, tmp); +} + +#endif /* cglm_mat3_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Macros: + GLM_MAT2_IDENTITY_INIT + GLM_MAT2_ZERO_INIT + GLM_MAT2_IDENTITY + GLM_MAT2_ZERO + + Functions: + CGLM_INLINE void glm_mat2_copy(mat2 mat, mat2 dest) + CGLM_INLINE void glm_mat2_identity(mat2 mat) + CGLM_INLINE void glm_mat2_identity_array(mat2 * restrict mat, size_t count) + CGLM_INLINE void glm_mat2_zero(mat2 mat) + CGLM_INLINE void glm_mat2_mul(mat2 m1, mat2 m2, mat2 dest) + CGLM_INLINE void glm_mat2_transpose_to(mat2 m, mat2 dest) + CGLM_INLINE void glm_mat2_transpose(mat2 m) + CGLM_INLINE void glm_mat2_mulv(mat2 m, vec2 v, vec2 dest) + CGLM_INLINE float glm_mat2_trace(mat2 m) + CGLM_INLINE void glm_mat2_scale(mat2 m, float s) + CGLM_INLINE float glm_mat2_det(mat2 mat) + CGLM_INLINE void glm_mat2_inv(mat2 mat, mat2 dest) + CGLM_INLINE void glm_mat2_swap_col(mat2 mat, int col1, int col2) + CGLM_INLINE void glm_mat2_swap_row(mat2 mat, int row1, int row2) + CGLM_INLINE float glm_mat2_rmc(vec2 r, mat2 m, vec2 c) + */ + +#ifndef cglm_mat2_h +#define cglm_mat2_h + + +#ifdef CGLM_SSE_FP +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_mat2_sse_h +#define cglm_mat2_sse_h +#if defined( __SSE__ ) || defined( __SSE2__ ) + + +CGLM_INLINE +void +glm_mat2_mul_sse2(mat2 m1, mat2 m2, mat2 dest) { + __m128 x0, x1, x2; + + x1 = glmm_load(m1[0]); /* d c b a */ + x2 = glmm_load(m2[0]); /* h g f e */ + + /* + dest[0][0] = a * e + c * f; + dest[0][1] = b * e + d * f; + dest[1][0] = a * g + c * h; + dest[1][1] = b * g + d * h; + */ + x0 = _mm_mul_ps(_mm_movelh_ps(x1, x1), glmm_shuff1(x2, 2, 2, 0, 0)); + x1 = _mm_mul_ps(_mm_movehl_ps(x1, x1), glmm_shuff1(x2, 3, 3, 1, 1)); + x1 = _mm_add_ps(x0, x1); + + glmm_store(dest[0], x1); +} + +CGLM_INLINE +void +glm_mat2_transp_sse2(mat2 m, mat2 dest) { + /* d c b a */ + /* d b c a */ + glmm_store(dest[0], glmm_shuff1(glmm_load(m[0]), 3, 1, 2, 0)); +} + +#endif +#endif /* cglm_mat2_sse_h */ + +#endif + +#define GLM_MAT2_IDENTITY_INIT {{1.0f, 0.0f}, {0.0f, 1.0f}} +#define GLM_MAT2_ZERO_INIT {{0.0f, 0.0f}, {0.0f, 0.0f}} + +/* for C only */ +#define GLM_MAT2_IDENTITY ((mat2)GLM_MAT2_IDENTITY_INIT) +#define GLM_MAT2_ZERO ((mat2)GLM_MAT2_ZERO_INIT) + +/*! + * @brief copy all members of [mat] to [dest] + * + * @param[in] mat source + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_mat2_copy(mat2 mat, mat2 dest) { + glm_vec4_ucopy(mat[0], dest[0]); +} + +/*! + * @brief make given matrix identity. It is identical with below, + * but it is more easy to do that with this func especially for members + * e.g. glm_mat2_identity(aStruct->aMatrix); + * + * @code + * glm_mat2_copy(GLM_MAT2_IDENTITY, mat); // C only + * + * // or + * mat2 mat = GLM_MAT2_IDENTITY_INIT; + * @endcode + * + * @param[in, out] mat destination + */ +CGLM_INLINE +void +glm_mat2_identity(mat2 mat) { + CGLM_ALIGN_MAT mat2 t = GLM_MAT2_IDENTITY_INIT; + glm_mat2_copy(t, mat); +} + +/*! + * @brief make given matrix array's each element identity matrix + * + * @param[in, out] mat matrix array (must be aligned (16) + * if alignment is not disabled) + * + * @param[in] count count of matrices + */ +CGLM_INLINE +void +glm_mat2_identity_array(mat2 * __restrict mat, size_t count) { + CGLM_ALIGN_MAT mat2 t = GLM_MAT2_IDENTITY_INIT; + size_t i; + + for (i = 0; i < count; i++) { + glm_mat2_copy(t, mat[i]); + } +} + +/*! + * @brief make given matrix zero. + * + * @param[in, out] mat matrix + */ +CGLM_INLINE +void +glm_mat2_zero(mat2 mat) { + CGLM_ALIGN_MAT mat2 t = GLM_MAT2_ZERO_INIT; + glm_mat2_copy(t, mat); +} + +/*! + * @brief multiply m1 and m2 to dest + * + * m1, m2 and dest matrices can be same matrix, it is possible to write this: + * + * @code + * mat2 m = GLM_MAT2_IDENTITY_INIT; + * glm_mat2_mul(m, m, m); + * @endcode + * + * @param[in] m1 left matrix + * @param[in] m2 right matrix + * @param[out] dest destination matrix + */ +CGLM_INLINE +void +glm_mat2_mul(mat2 m1, mat2 m2, mat2 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_mat2_mul_sse2(m1, m2, dest); +#else + float a00 = m1[0][0], a01 = m1[0][1], + a10 = m1[1][0], a11 = m1[1][1], + b00 = m2[0][0], b01 = m2[0][1], + b10 = m2[1][0], b11 = m2[1][1]; + + dest[0][0] = a00 * b00 + a10 * b01; + dest[0][1] = a01 * b00 + a11 * b01; + dest[1][0] = a00 * b10 + a10 * b11; + dest[1][1] = a01 * b10 + a11 * b11; +#endif +} + +/*! + * @brief transpose mat2 and store in dest + * + * source matrix will not be transposed unless dest is m + * + * @param[in] m matrix + * @param[out] dest result + */ +CGLM_INLINE +void +glm_mat2_transpose_to(mat2 m, mat2 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_mat2_transp_sse2(m, dest); +#else + dest[0][0] = m[0][0]; + dest[0][1] = m[1][0]; + dest[1][0] = m[0][1]; + dest[1][1] = m[1][1]; +#endif +} + +/*! + * @brief tranpose mat2 and store result in same matrix + * + * @param[in, out] m source and dest + */ +CGLM_INLINE +void +glm_mat2_transpose(mat2 m) { + float tmp; + tmp = m[0][1]; + m[0][1] = m[1][0]; + m[1][0] = tmp; +} + +/*! + * @brief multiply mat2 with vec2 (column vector) and store in dest vector + * + * @param[in] m mat2 (left) + * @param[in] v vec2 (right, column vector) + * @param[out] dest vec2 (result, column vector) + */ +CGLM_INLINE +void +glm_mat2_mulv(mat2 m, vec2 v, vec2 dest) { + dest[0] = m[0][0] * v[0] + m[1][0] * v[1]; + dest[1] = m[0][1] * v[0] + m[1][1] * v[1]; +} + +/*! + * @brief trace of matrix + * + * sum of the elements on the main diagonal from upper left to the lower right + * + * @param[in] m matrix + */ +CGLM_INLINE +float +glm_mat2_trace(mat2 m) { + return m[0][0] + m[1][1]; +} + +/*! + * @brief scale (multiply with scalar) matrix + * + * multiply matrix with scalar + * + * @param[in, out] m matrix + * @param[in] s scalar + */ +CGLM_INLINE +void +glm_mat2_scale(mat2 m, float s) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(m[0], _mm_mul_ps(_mm_loadu_ps(m[0]), _mm_set1_ps(s))); +#elif defined(CGLM_NEON_FP) + vst1q_f32(m[0], vmulq_f32(vld1q_f32(m[0]), vdupq_n_f32(s))); +#else + m[0][0] = m[0][0] * s; + m[0][1] = m[0][1] * s; + m[1][0] = m[1][0] * s; + m[1][1] = m[1][1] * s; +#endif +} + +/*! + * @brief mat2 determinant + * + * @param[in] mat matrix + * + * @return determinant + */ +CGLM_INLINE +float +glm_mat2_det(mat2 mat) { + return mat[0][0] * mat[1][1] - mat[1][0] * mat[0][1]; +} + +/*! + * @brief inverse mat2 and store in dest + * + * @param[in] mat matrix + * @param[out] dest inverse matrix + */ +CGLM_INLINE +void +glm_mat2_inv(mat2 mat, mat2 dest) { + float det; + float a = mat[0][0], b = mat[0][1], + c = mat[1][0], d = mat[1][1]; + + det = 1.0f / (a * d - b * c); + + dest[0][0] = d * det; + dest[0][1] = -b * det; + dest[1][0] = -c * det; + dest[1][1] = a * det; +} + +/*! + * @brief swap two matrix columns + * + * @param[in,out] mat matrix + * @param[in] col1 col1 + * @param[in] col2 col2 + */ +CGLM_INLINE +void +glm_mat2_swap_col(mat2 mat, int col1, int col2) { + float a, b; + + a = mat[col1][0]; + b = mat[col1][1]; + + mat[col1][0] = mat[col2][0]; + mat[col1][1] = mat[col2][1]; + + mat[col2][0] = a; + mat[col2][1] = b; +} + +/*! + * @brief swap two matrix rows + * + * @param[in,out] mat matrix + * @param[in] row1 row1 + * @param[in] row2 row2 + */ +CGLM_INLINE +void +glm_mat2_swap_row(mat2 mat, int row1, int row2) { + float a, b; + + a = mat[0][row1]; + b = mat[1][row1]; + + mat[0][row1] = mat[0][row2]; + mat[1][row1] = mat[1][row2]; + + mat[0][row2] = a; + mat[1][row2] = b; +} + +/*! + * @brief helper for R (row vector) * M (matrix) * C (column vector) + * + * rmc stands for Row * Matrix * Column + * + * the result is scalar because R * M = Matrix1x2 (row vector), + * then Matrix1x2 * Vec2 (column vector) = Matrix1x1 (Scalar) + * + * @param[in] r row vector or matrix1x2 + * @param[in] m matrix2x2 + * @param[in] c column vector or matrix2x1 + * + * @return scalar value e.g. Matrix1x1 + */ +CGLM_INLINE +float +glm_mat2_rmc(vec2 r, mat2 m, vec2 c) { + vec2 tmp; + glm_mat2_mulv(m, c, tmp); + return glm_vec2_dot(r, tmp); +} + +#endif /* cglm_mat2_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Functions: + CGLM_INLINE void glm_translate_to(mat4 m, vec3 v, mat4 dest); + CGLM_INLINE void glm_translate(mat4 m, vec3 v); + CGLM_INLINE void glm_translate_x(mat4 m, float to); + CGLM_INLINE void glm_translate_y(mat4 m, float to); + CGLM_INLINE void glm_translate_z(mat4 m, float to); + CGLM_INLINE void glm_translate_make(mat4 m, vec3 v); + CGLM_INLINE void glm_scale_to(mat4 m, vec3 v, mat4 dest); + CGLM_INLINE void glm_scale_make(mat4 m, vec3 v); + CGLM_INLINE void glm_scale(mat4 m, vec3 v); + CGLM_INLINE void glm_scale_uni(mat4 m, float s); + CGLM_INLINE void glm_rotate_x(mat4 m, float angle, mat4 dest); + CGLM_INLINE void glm_rotate_y(mat4 m, float angle, mat4 dest); + CGLM_INLINE void glm_rotate_z(mat4 m, float angle, mat4 dest); + CGLM_INLINE void glm_rotate_make(mat4 m, float angle, vec3 axis); + CGLM_INLINE void glm_rotate(mat4 m, float angle, vec3 axis); + CGLM_INLINE void glm_rotate_at(mat4 m, vec3 pivot, float angle, vec3 axis); + CGLM_INLINE void glm_rotate_atm(mat4 m, vec3 pivot, float angle, vec3 axis); + CGLM_INLINE void glm_decompose_scalev(mat4 m, vec3 s); + CGLM_INLINE bool glm_uniscaled(mat4 m); + CGLM_INLINE void glm_decompose_rs(mat4 m, mat4 r, vec3 s); + CGLM_INLINE void glm_decompose(mat4 m, vec4 t, mat4 r, vec3 s); + */ + +#ifndef cglm_affine_h +#define cglm_affine_h + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Functions: + CGLM_INLINE void glm_mul(mat4 m1, mat4 m2, mat4 dest); + CGLM_INLINE void glm_inv_tr(mat4 mat); + */ + +#ifndef cglm_affine_mat_h +#define cglm_affine_mat_h + + +#ifdef CGLM_SSE_FP +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_affine_mat_sse2_h +#define cglm_affine_mat_sse2_h +#if defined( __SSE__ ) || defined( __SSE2__ ) + + +CGLM_INLINE +void +glm_mul_sse2(mat4 m1, mat4 m2, mat4 dest) { + /* D = R * L (Column-Major) */ + __m128 l0, l1, l2, l3, r; + + l0 = glmm_load(m1[0]); + l1 = glmm_load(m1[1]); + l2 = glmm_load(m1[2]); + l3 = glmm_load(m1[3]); + + r = glmm_load(m2[0]); + glmm_store(dest[0], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_mul_ps(glmm_shuff1x(r, 2), l2))); + + r = glmm_load(m2[1]); + glmm_store(dest[1], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_mul_ps(glmm_shuff1x(r, 2), l2))); + + r = glmm_load(m2[2]); + glmm_store(dest[2], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_mul_ps(glmm_shuff1x(r, 2), l2))); + + r = glmm_load(m2[3]); + glmm_store(dest[3], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 2), l2), + _mm_mul_ps(glmm_shuff1x(r, 3), l3)))); +} + +CGLM_INLINE +void +glm_mul_rot_sse2(mat4 m1, mat4 m2, mat4 dest) { + /* D = R * L (Column-Major) */ + __m128 l0, l1, l2, l3, r; + + l0 = glmm_load(m1[0]); + l1 = glmm_load(m1[1]); + l2 = glmm_load(m1[2]); + l3 = glmm_load(m1[3]); + + r = glmm_load(m2[0]); + glmm_store(dest[0], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_mul_ps(glmm_shuff1x(r, 2), l2))); + + r = glmm_load(m2[1]); + glmm_store(dest[1], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_mul_ps(glmm_shuff1x(r, 2), l2))); + + r = glmm_load(m2[2]); + glmm_store(dest[2], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_shuff1x(r, 0), l0), + _mm_mul_ps(glmm_shuff1x(r, 1), l1)), + _mm_mul_ps(glmm_shuff1x(r, 2), l2))); + + glmm_store(dest[3], l3); +} + +CGLM_INLINE +void +glm_inv_tr_sse2(mat4 mat) { + __m128 r0, r1, r2, r3, x0, x1; + + r0 = glmm_load(mat[0]); + r1 = glmm_load(mat[1]); + r2 = glmm_load(mat[2]); + r3 = glmm_load(mat[3]); + x1 = _mm_set_ps(1.0f, 0.0f, 0.0f, 0.0f); + + _MM_TRANSPOSE4_PS(r0, r1, r2, x1); + + x0 = _mm_add_ps(_mm_mul_ps(r0, glmm_shuff1(r3, 0, 0, 0, 0)), + _mm_mul_ps(r1, glmm_shuff1(r3, 1, 1, 1, 1))); + x0 = _mm_add_ps(x0, _mm_mul_ps(r2, glmm_shuff1(r3, 2, 2, 2, 2))); + x0 = _mm_xor_ps(x0, _mm_set1_ps(-0.f)); + + x0 = _mm_add_ps(x0, x1); + + glmm_store(mat[0], r0); + glmm_store(mat[1], r1); + glmm_store(mat[2], r2); + glmm_store(mat[3], x0); +} + +#endif +#endif /* cglm_affine_mat_sse2_h */ + +#endif + +#ifdef CGLM_AVX_FP +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_affine_mat_avx_h +#define cglm_affine_mat_avx_h +#ifdef __AVX__ + + +#include + +CGLM_INLINE +void +glm_mul_avx(mat4 m1, mat4 m2, mat4 dest) { + /* D = R * L (Column-Major) */ + + __m256 y0, y1, y2, y3, y4, y5, y6, y7, y8, y9; + + y0 = glmm_load256(m2[0]); /* h g f e d c b a */ + y1 = glmm_load256(m2[2]); /* p o n m l k j i */ + + y2 = glmm_load256(m1[0]); /* h g f e d c b a */ + y3 = glmm_load256(m1[2]); /* p o n m l k j i */ + + /* 0x03: 0b00000011 */ + y4 = _mm256_permute2f128_ps(y2, y2, 0x03); /* d c b a h g f e */ + y5 = _mm256_permute2f128_ps(y3, y3, 0x03); /* l k j i p o n m */ + + /* f f f f a a a a */ + /* h h h h c c c c */ + /* e e e e b b b b */ + /* g g g g d d d d */ + y6 = _mm256_permutevar_ps(y0, _mm256_set_epi32(1, 1, 1, 1, 0, 0, 0, 0)); + y7 = _mm256_permutevar_ps(y0, _mm256_set_epi32(3, 3, 3, 3, 2, 2, 2, 2)); + y8 = _mm256_permutevar_ps(y0, _mm256_set_epi32(0, 0, 0, 0, 1, 1, 1, 1)); + y9 = _mm256_permutevar_ps(y0, _mm256_set_epi32(2, 2, 2, 2, 3, 3, 3, 3)); + + glmm_store256(dest[0], + _mm256_add_ps(_mm256_add_ps(_mm256_mul_ps(y2, y6), + _mm256_mul_ps(y3, y7)), + _mm256_add_ps(_mm256_mul_ps(y4, y8), + _mm256_mul_ps(y5, y9)))); + + /* n n n n i i i i */ + /* p p p p k k k k */ + /* m m m m j j j j */ + /* o o o o l l l l */ + y6 = _mm256_permutevar_ps(y1, _mm256_set_epi32(1, 1, 1, 1, 0, 0, 0, 0)); + y7 = _mm256_permutevar_ps(y1, _mm256_set_epi32(3, 3, 3, 3, 2, 2, 2, 2)); + y8 = _mm256_permutevar_ps(y1, _mm256_set_epi32(0, 0, 0, 0, 1, 1, 1, 1)); + y9 = _mm256_permutevar_ps(y1, _mm256_set_epi32(2, 2, 2, 2, 3, 3, 3, 3)); + + glmm_store256(dest[2], + _mm256_add_ps(_mm256_add_ps(_mm256_mul_ps(y2, y6), + _mm256_mul_ps(y3, y7)), + _mm256_add_ps(_mm256_mul_ps(y4, y8), + _mm256_mul_ps(y5, y9)))); +} + +#endif +#endif /* cglm_affine_mat_avx_h */ + +#endif + +/*! + * @brief this is similar to glm_mat4_mul but specialized to affine transform + * + * Matrix format should be: + * R R R X + * R R R Y + * R R R Z + * 0 0 0 W + * + * this reduces some multiplications. It should be faster than mat4_mul. + * if you are not sure about matrix format then DON'T use this! use mat4_mul + * + * @param[in] m1 affine matrix 1 + * @param[in] m2 affine matrix 2 + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_mul(mat4 m1, mat4 m2, mat4 dest) { +#ifdef __AVX__ + glm_mul_avx(m1, m2, dest); +#elif defined( __SSE__ ) || defined( __SSE2__ ) + glm_mul_sse2(m1, m2, dest); +#else + float a00 = m1[0][0], a01 = m1[0][1], a02 = m1[0][2], a03 = m1[0][3], + a10 = m1[1][0], a11 = m1[1][1], a12 = m1[1][2], a13 = m1[1][3], + a20 = m1[2][0], a21 = m1[2][1], a22 = m1[2][2], a23 = m1[2][3], + a30 = m1[3][0], a31 = m1[3][1], a32 = m1[3][2], a33 = m1[3][3], + + b00 = m2[0][0], b01 = m2[0][1], b02 = m2[0][2], + b10 = m2[1][0], b11 = m2[1][1], b12 = m2[1][2], + b20 = m2[2][0], b21 = m2[2][1], b22 = m2[2][2], + b30 = m2[3][0], b31 = m2[3][1], b32 = m2[3][2], b33 = m2[3][3]; + + dest[0][0] = a00 * b00 + a10 * b01 + a20 * b02; + dest[0][1] = a01 * b00 + a11 * b01 + a21 * b02; + dest[0][2] = a02 * b00 + a12 * b01 + a22 * b02; + dest[0][3] = a03 * b00 + a13 * b01 + a23 * b02; + + dest[1][0] = a00 * b10 + a10 * b11 + a20 * b12; + dest[1][1] = a01 * b10 + a11 * b11 + a21 * b12; + dest[1][2] = a02 * b10 + a12 * b11 + a22 * b12; + dest[1][3] = a03 * b10 + a13 * b11 + a23 * b12; + + dest[2][0] = a00 * b20 + a10 * b21 + a20 * b22; + dest[2][1] = a01 * b20 + a11 * b21 + a21 * b22; + dest[2][2] = a02 * b20 + a12 * b21 + a22 * b22; + dest[2][3] = a03 * b20 + a13 * b21 + a23 * b22; + + dest[3][0] = a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33; + dest[3][1] = a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33; + dest[3][2] = a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33; + dest[3][3] = a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33; +#endif +} + +/*! + * @brief this is similar to glm_mat4_mul but specialized to affine transform + * + * Right Matrix format should be: + * R R R 0 + * R R R 0 + * R R R 0 + * 0 0 0 1 + * + * this reduces some multiplications. It should be faster than mat4_mul. + * if you are not sure about matrix format then DON'T use this! use mat4_mul + * + * @param[in] m1 affine matrix 1 + * @param[in] m2 affine matrix 2 + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_mul_rot(mat4 m1, mat4 m2, mat4 dest) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_mul_rot_sse2(m1, m2, dest); +#else + float a00 = m1[0][0], a01 = m1[0][1], a02 = m1[0][2], a03 = m1[0][3], + a10 = m1[1][0], a11 = m1[1][1], a12 = m1[1][2], a13 = m1[1][3], + a20 = m1[2][0], a21 = m1[2][1], a22 = m1[2][2], a23 = m1[2][3], + a30 = m1[3][0], a31 = m1[3][1], a32 = m1[3][2], a33 = m1[3][3], + + b00 = m2[0][0], b01 = m2[0][1], b02 = m2[0][2], + b10 = m2[1][0], b11 = m2[1][1], b12 = m2[1][2], + b20 = m2[2][0], b21 = m2[2][1], b22 = m2[2][2]; + + dest[0][0] = a00 * b00 + a10 * b01 + a20 * b02; + dest[0][1] = a01 * b00 + a11 * b01 + a21 * b02; + dest[0][2] = a02 * b00 + a12 * b01 + a22 * b02; + dest[0][3] = a03 * b00 + a13 * b01 + a23 * b02; + + dest[1][0] = a00 * b10 + a10 * b11 + a20 * b12; + dest[1][1] = a01 * b10 + a11 * b11 + a21 * b12; + dest[1][2] = a02 * b10 + a12 * b11 + a22 * b12; + dest[1][3] = a03 * b10 + a13 * b11 + a23 * b12; + + dest[2][0] = a00 * b20 + a10 * b21 + a20 * b22; + dest[2][1] = a01 * b20 + a11 * b21 + a21 * b22; + dest[2][2] = a02 * b20 + a12 * b21 + a22 * b22; + dest[2][3] = a03 * b20 + a13 * b21 + a23 * b22; + + dest[3][0] = a30; + dest[3][1] = a31; + dest[3][2] = a32; + dest[3][3] = a33; +#endif +} + +/*! + * @brief inverse orthonormal rotation + translation matrix (ridig-body) + * + * @code + * X = | R T | X' = | R' -R'T | + * | 0 1 | | 0 1 | + * @endcode + * + * @param[in,out] mat matrix + */ +CGLM_INLINE +void +glm_inv_tr(mat4 mat) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_inv_tr_sse2(mat); +#else + CGLM_ALIGN_MAT mat3 r; + CGLM_ALIGN(8) vec3 t; + + /* rotate */ + glm_mat4_pick3t(mat, r); + glm_mat4_ins3(r, mat); + + /* translate */ + glm_mat3_mulv(r, mat[3], t); + glm_vec3_negate(t); + glm_vec3_copy(t, mat[3]); +#endif +} + +#endif /* cglm_affine_mat_h */ + + +/*! + * @brief translate existing transform matrix by v vector + * and stores result in same matrix + * + * @param[in, out] m affine transfrom + * @param[in] v translate vector [x, y, z] + */ +CGLM_INLINE +void +glm_translate(mat4 m, vec3 v) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(m[3], + _mm_add_ps(_mm_add_ps(_mm_mul_ps(glmm_load(m[0]), + _mm_set1_ps(v[0])), + _mm_mul_ps(glmm_load(m[1]), + _mm_set1_ps(v[1]))), + _mm_add_ps(_mm_mul_ps(glmm_load(m[2]), + _mm_set1_ps(v[2])), + glmm_load(m[3])))) + ; +#else + vec4 v1, v2, v3; + + glm_vec4_scale(m[0], v[0], v1); + glm_vec4_scale(m[1], v[1], v2); + glm_vec4_scale(m[2], v[2], v3); + + glm_vec4_add(v1, m[3], m[3]); + glm_vec4_add(v2, m[3], m[3]); + glm_vec4_add(v3, m[3], m[3]); +#endif +} + +/*! + * @brief translate existing transform matrix by v vector + * and store result in dest + * + * source matrix will remain same + * + * @param[in] m affine transfrom + * @param[in] v translate vector [x, y, z] + * @param[out] dest translated matrix + */ +CGLM_INLINE +void +glm_translate_to(mat4 m, vec3 v, mat4 dest) { + glm_mat4_copy(m, dest); + glm_translate(dest, v); +} + +/*! + * @brief translate existing transform matrix by x factor + * + * @param[in, out] m affine transfrom + * @param[in] x x factor + */ +CGLM_INLINE +void +glm_translate_x(mat4 m, float x) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(m[3], + _mm_add_ps(_mm_mul_ps(glmm_load(m[0]), + _mm_set1_ps(x)), + glmm_load(m[3]))) + ; +#else + vec4 v1; + glm_vec4_scale(m[0], x, v1); + glm_vec4_add(v1, m[3], m[3]); +#endif +} + +/*! + * @brief translate existing transform matrix by y factor + * + * @param[in, out] m affine transfrom + * @param[in] y y factor + */ +CGLM_INLINE +void +glm_translate_y(mat4 m, float y) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(m[3], + _mm_add_ps(_mm_mul_ps(glmm_load(m[1]), + _mm_set1_ps(y)), + glmm_load(m[3]))) + ; +#else + vec4 v1; + glm_vec4_scale(m[1], y, v1); + glm_vec4_add(v1, m[3], m[3]); +#endif +} + +/*! + * @brief translate existing transform matrix by z factor + * + * @param[in, out] m affine transfrom + * @param[in] z z factor + */ +CGLM_INLINE +void +glm_translate_z(mat4 m, float z) { +#if defined( __SSE__ ) || defined( __SSE2__ ) + glmm_store(m[3], + _mm_add_ps(_mm_mul_ps(glmm_load(m[2]), + _mm_set1_ps(z)), + glmm_load(m[3]))) + ; +#else + vec4 v1; + glm_vec4_scale(m[2], z, v1); + glm_vec4_add(v1, m[3], m[3]); +#endif +} + +/*! + * @brief creates NEW translate transform matrix by v vector + * + * @param[out] m affine transfrom + * @param[in] v translate vector [x, y, z] + */ +CGLM_INLINE +void +glm_translate_make(mat4 m, vec3 v) { + glm_mat4_identity(m); + glm_vec3_copy(v, m[3]); +} + +/*! + * @brief scale existing transform matrix by v vector + * and store result in dest + * + * @param[in] m affine transfrom + * @param[in] v scale vector [x, y, z] + * @param[out] dest scaled matrix + */ +CGLM_INLINE +void +glm_scale_to(mat4 m, vec3 v, mat4 dest) { + glm_vec4_scale(m[0], v[0], dest[0]); + glm_vec4_scale(m[1], v[1], dest[1]); + glm_vec4_scale(m[2], v[2], dest[2]); + + glm_vec4_copy(m[3], dest[3]); +} + +/*! + * @brief creates NEW scale matrix by v vector + * + * @param[out] m affine transfrom + * @param[in] v scale vector [x, y, z] + */ +CGLM_INLINE +void +glm_scale_make(mat4 m, vec3 v) { + glm_mat4_identity(m); + m[0][0] = v[0]; + m[1][1] = v[1]; + m[2][2] = v[2]; +} + +/*! + * @brief scales existing transform matrix by v vector + * and stores result in same matrix + * + * @param[in, out] m affine transfrom + * @param[in] v scale vector [x, y, z] + */ +CGLM_INLINE +void +glm_scale(mat4 m, vec3 v) { + glm_scale_to(m, v, m); +} + +/*! + * @brief applies uniform scale to existing transform matrix v = [s, s, s] + * and stores result in same matrix + * + * @param[in, out] m affine transfrom + * @param[in] s scale factor + */ +CGLM_INLINE +void +glm_scale_uni(mat4 m, float s) { + CGLM_ALIGN(8) vec3 v = { s, s, s }; + glm_scale_to(m, v, m); +} + +/*! + * @brief rotate existing transform matrix around X axis by angle + * and store result in dest + * + * @param[in] m affine transfrom + * @param[in] angle angle (radians) + * @param[out] dest rotated matrix + */ +CGLM_INLINE +void +glm_rotate_x(mat4 m, float angle, mat4 dest) { + CGLM_ALIGN_MAT mat4 t = GLM_MAT4_IDENTITY_INIT; + float c, s; + + c = cosf(angle); + s = sinf(angle); + + t[1][1] = c; + t[1][2] = s; + t[2][1] = -s; + t[2][2] = c; + + glm_mul_rot(m, t, dest); +} + +/*! + * @brief rotate existing transform matrix around Y axis by angle + * and store result in dest + * + * @param[in] m affine transfrom + * @param[in] angle angle (radians) + * @param[out] dest rotated matrix + */ +CGLM_INLINE +void +glm_rotate_y(mat4 m, float angle, mat4 dest) { + CGLM_ALIGN_MAT mat4 t = GLM_MAT4_IDENTITY_INIT; + float c, s; + + c = cosf(angle); + s = sinf(angle); + + t[0][0] = c; + t[0][2] = -s; + t[2][0] = s; + t[2][2] = c; + + glm_mul_rot(m, t, dest); +} + +/*! + * @brief rotate existing transform matrix around Z axis by angle + * and store result in dest + * + * @param[in] m affine transfrom + * @param[in] angle angle (radians) + * @param[out] dest rotated matrix + */ +CGLM_INLINE +void +glm_rotate_z(mat4 m, float angle, mat4 dest) { + CGLM_ALIGN_MAT mat4 t = GLM_MAT4_IDENTITY_INIT; + float c, s; + + c = cosf(angle); + s = sinf(angle); + + t[0][0] = c; + t[0][1] = s; + t[1][0] = -s; + t[1][1] = c; + + glm_mul_rot(m, t, dest); +} + +/*! + * @brief creates NEW rotation matrix by angle and axis + * + * axis will be normalized so you don't need to normalize it + * + * @param[out] m affine transfrom + * @param[in] angle angle (radians) + * @param[in] axis axis + */ +CGLM_INLINE +void +glm_rotate_make(mat4 m, float angle, vec3 axis) { + CGLM_ALIGN(8) vec3 axisn, v, vs; + float c; + + c = cosf(angle); + + glm_vec3_normalize_to(axis, axisn); + glm_vec3_scale(axisn, 1.0f - c, v); + glm_vec3_scale(axisn, sinf(angle), vs); + + glm_vec3_scale(axisn, v[0], m[0]); + glm_vec3_scale(axisn, v[1], m[1]); + glm_vec3_scale(axisn, v[2], m[2]); + + m[0][0] += c; m[1][0] -= vs[2]; m[2][0] += vs[1]; + m[0][1] += vs[2]; m[1][1] += c; m[2][1] -= vs[0]; + m[0][2] -= vs[1]; m[1][2] += vs[0]; m[2][2] += c; + + m[0][3] = m[1][3] = m[2][3] = m[3][0] = m[3][1] = m[3][2] = 0.0f; + m[3][3] = 1.0f; +} + +/*! + * @brief rotate existing transform matrix around given axis by angle + * + * @param[in, out] m affine transfrom + * @param[in] angle angle (radians) + * @param[in] axis axis + */ +CGLM_INLINE +void +glm_rotate(mat4 m, float angle, vec3 axis) { + CGLM_ALIGN_MAT mat4 rot; + glm_rotate_make(rot, angle, axis); + glm_mul_rot(m, rot, m); +} + +/*! + * @brief rotate existing transform + * around given axis by angle at given pivot point (rotation center) + * + * @param[in, out] m affine transfrom + * @param[in] pivot rotation center + * @param[in] angle angle (radians) + * @param[in] axis axis + */ +CGLM_INLINE +void +glm_rotate_at(mat4 m, vec3 pivot, float angle, vec3 axis) { + CGLM_ALIGN(8) vec3 pivotInv; + + glm_vec3_negate_to(pivot, pivotInv); + + glm_translate(m, pivot); + glm_rotate(m, angle, axis); + glm_translate(m, pivotInv); +} + +/*! + * @brief creates NEW rotation matrix by angle and axis at given point + * + * this creates rotation matrix, it assumes you don't have a matrix + * + * this should work faster than glm_rotate_at because it reduces + * one glm_translate. + * + * @param[out] m affine transfrom + * @param[in] pivot rotation center + * @param[in] angle angle (radians) + * @param[in] axis axis + */ +CGLM_INLINE +void +glm_rotate_atm(mat4 m, vec3 pivot, float angle, vec3 axis) { + CGLM_ALIGN(8) vec3 pivotInv; + + glm_vec3_negate_to(pivot, pivotInv); + + glm_translate_make(m, pivot); + glm_rotate(m, angle, axis); + glm_translate(m, pivotInv); +} + +/*! + * @brief decompose scale vector + * + * @param[in] m affine transform + * @param[out] s scale vector (Sx, Sy, Sz) + */ +CGLM_INLINE +void +glm_decompose_scalev(mat4 m, vec3 s) { + s[0] = glm_vec3_norm(m[0]); + s[1] = glm_vec3_norm(m[1]); + s[2] = glm_vec3_norm(m[2]); +} + +/*! + * @brief returns true if matrix is uniform scaled. This is helpful for + * creating normal matrix. + * + * @param[in] m m + * + * @return boolean + */ +CGLM_INLINE +bool +glm_uniscaled(mat4 m) { + CGLM_ALIGN(8) vec3 s; + glm_decompose_scalev(m, s); + return glm_vec3_eq_all(s); +} + +/*! + * @brief decompose rotation matrix (mat4) and scale vector [Sx, Sy, Sz] + * DON'T pass projected matrix here + * + * @param[in] m affine transform + * @param[out] r rotation matrix + * @param[out] s scale matrix + */ +CGLM_INLINE +void +glm_decompose_rs(mat4 m, mat4 r, vec3 s) { + CGLM_ALIGN(16) vec4 t = {0.0f, 0.0f, 0.0f, 1.0f}; + CGLM_ALIGN(8) vec3 v; + + glm_vec4_copy(m[0], r[0]); + glm_vec4_copy(m[1], r[1]); + glm_vec4_copy(m[2], r[2]); + glm_vec4_copy(t, r[3]); + + s[0] = glm_vec3_norm(m[0]); + s[1] = glm_vec3_norm(m[1]); + s[2] = glm_vec3_norm(m[2]); + + glm_vec4_scale(r[0], 1.0f/s[0], r[0]); + glm_vec4_scale(r[1], 1.0f/s[1], r[1]); + glm_vec4_scale(r[2], 1.0f/s[2], r[2]); + + /* Note from Apple Open Source (asume that the matrix is orthonormal): + check for a coordinate system flip. If the determinant + is -1, then negate the matrix and the scaling factors. */ + glm_vec3_cross(m[0], m[1], v); + if (glm_vec3_dot(v, m[2]) < 0.0f) { + glm_vec4_negate(r[0]); + glm_vec4_negate(r[1]); + glm_vec4_negate(r[2]); + glm_vec3_negate(s); + } +} + +/*! + * @brief decompose affine transform, TODO: extract shear factors. + * DON'T pass projected matrix here + * + * @param[in] m affine transfrom + * @param[out] t translation vector + * @param[out] r rotation matrix (mat4) + * @param[out] s scaling vector [X, Y, Z] + */ +CGLM_INLINE +void +glm_decompose(mat4 m, vec4 t, mat4 r, vec3 s) { + glm_vec4_copy(m[3], t); + glm_decompose_rs(m, r, s); +} + +#endif /* cglm_affine_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Functions: + CGLM_INLINE void glm_frustum(float left, float right, + float bottom, float top, + float nearVal, float farVal, + mat4 dest) + CGLM_INLINE void glm_ortho(float left, float right, + float bottom, float top, + float nearVal, float farVal, + mat4 dest) + CGLM_INLINE void glm_ortho_aabb(vec3 box[2], mat4 dest) + CGLM_INLINE void glm_ortho_aabb_p(vec3 box[2], float padding, mat4 dest) + CGLM_INLINE void glm_ortho_aabb_pz(vec3 box[2], float padding, mat4 dest) + CGLM_INLINE void glm_ortho_default(float aspect, mat4 dest) + CGLM_INLINE void glm_ortho_default_s(float aspect, float size, mat4 dest) + CGLM_INLINE void glm_perspective(float fovy, + float aspect, + float nearVal, + float farVal, + mat4 dest) + CGLM_INLINE void glm_perspective_default(float aspect, mat4 dest) + CGLM_INLINE void glm_perspective_resize(float aspect, mat4 proj) + CGLM_INLINE void glm_lookat(vec3 eye, vec3 center, vec3 up, mat4 dest) + CGLM_INLINE void glm_look(vec3 eye, vec3 dir, vec3 up, mat4 dest) + CGLM_INLINE void glm_look_anyup(vec3 eye, vec3 dir, mat4 dest) + CGLM_INLINE void glm_persp_decomp(mat4 proj, + float *nearVal, float *farVal, + float *top, float *bottom, + float *left, float *right) + CGLM_INLINE void glm_persp_decompv(mat4 proj, float dest[6]) + CGLM_INLINE void glm_persp_decomp_x(mat4 proj, float *left, float *right) + CGLM_INLINE void glm_persp_decomp_y(mat4 proj, float *top, float *bottom) + CGLM_INLINE void glm_persp_decomp_z(mat4 proj, float *nearv, float *farv) + CGLM_INLINE void glm_persp_decomp_far(mat4 proj, float *farVal) + CGLM_INLINE void glm_persp_decomp_near(mat4 proj, float *nearVal) + CGLM_INLINE float glm_persp_fovy(mat4 proj) + CGLM_INLINE float glm_persp_aspect(mat4 proj) + CGLM_INLINE void glm_persp_sizes(mat4 proj, float fovy, vec4 dest) + */ + +#ifndef cglm_vcam_h +#define cglm_vcam_h + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_plane_h +#define cglm_plane_h + + +/* + Plane equation: Ax + By + Cz + D = 0; + + It stored in vec4 as [A, B, C, D]. (A, B, C) is normal and D is distance +*/ + +/* + Functions: + CGLM_INLINE void glm_plane_normalize(vec4 plane); + */ + +/*! + * @brief normalizes a plane + * + * @param[in, out] plane plane to normalize + */ +CGLM_INLINE +void +glm_plane_normalize(vec4 plane) { + float norm; + + if ((norm = glm_vec3_norm(plane)) == 0.0f) { + glm_vec4_zero(plane); + return; + } + + glm_vec4_scale(plane, 1.0f / norm, plane); +} + +#endif /* cglm_plane_h */ + + +/*! + * @brief set up perspective peprojection matrix + * + * @param[in] left viewport.left + * @param[in] right viewport.right + * @param[in] bottom viewport.bottom + * @param[in] top viewport.top + * @param[in] nearVal near clipping plane + * @param[in] farVal far clipping plane + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_frustum(float left, float right, + float bottom, float top, + float nearVal, float farVal, + mat4 dest) { + float rl, tb, fn, nv; + + glm_mat4_zero(dest); + + rl = 1.0f / (right - left); + tb = 1.0f / (top - bottom); + fn =-1.0f / (farVal - nearVal); + nv = 2.0f * nearVal; + + dest[0][0] = nv * rl; + dest[1][1] = nv * tb; + dest[2][0] = (right + left) * rl; + dest[2][1] = (top + bottom) * tb; + dest[2][2] = (farVal + nearVal) * fn; + dest[2][3] =-1.0f; + dest[3][2] = farVal * nv * fn; +} + +/*! + * @brief set up orthographic projection matrix + * + * @param[in] left viewport.left + * @param[in] right viewport.right + * @param[in] bottom viewport.bottom + * @param[in] top viewport.top + * @param[in] nearVal near clipping plane + * @param[in] farVal far clipping plane + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_ortho(float left, float right, + float bottom, float top, + float nearVal, float farVal, + mat4 dest) { + float rl, tb, fn; + + glm_mat4_zero(dest); + + rl = 1.0f / (right - left); + tb = 1.0f / (top - bottom); + fn =-1.0f / (farVal - nearVal); + + dest[0][0] = 2.0f * rl; + dest[1][1] = 2.0f * tb; + dest[2][2] = 2.0f * fn; + dest[3][0] =-(right + left) * rl; + dest[3][1] =-(top + bottom) * tb; + dest[3][2] = (farVal + nearVal) * fn; + dest[3][3] = 1.0f; +} + +/*! + * @brief set up orthographic projection matrix using bounding box + * + * bounding box (AABB) must be in view space + * + * @param[in] box AABB + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_ortho_aabb(vec3 box[2], mat4 dest) { + glm_ortho(box[0][0], box[1][0], + box[0][1], box[1][1], + -box[1][2], -box[0][2], + dest); +} + +/*! + * @brief set up orthographic projection matrix using bounding box + * + * bounding box (AABB) must be in view space + * + * @param[in] box AABB + * @param[in] padding padding + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_ortho_aabb_p(vec3 box[2], float padding, mat4 dest) { + glm_ortho(box[0][0] - padding, box[1][0] + padding, + box[0][1] - padding, box[1][1] + padding, + -(box[1][2] + padding), -(box[0][2] - padding), + dest); +} + +/*! + * @brief set up orthographic projection matrix using bounding box + * + * bounding box (AABB) must be in view space + * + * @param[in] box AABB + * @param[in] padding padding for near and far + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_ortho_aabb_pz(vec3 box[2], float padding, mat4 dest) { + glm_ortho(box[0][0], box[1][0], + box[0][1], box[1][1], + -(box[1][2] + padding), -(box[0][2] - padding), + dest); +} + +/*! + * @brief set up unit orthographic projection matrix + * + * @param[in] aspect aspect ration ( width / height ) + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_ortho_default(float aspect, mat4 dest) { + if (aspect >= 1.0f) { + glm_ortho(-aspect, aspect, -1.0f, 1.0f, -100.0f, 100.0f, dest); + return; + } + + aspect = 1.0f / aspect; + + glm_ortho(-1.0f, 1.0f, -aspect, aspect, -100.0f, 100.0f, dest); +} + +/*! + * @brief set up orthographic projection matrix with given CUBE size + * + * @param[in] aspect aspect ratio ( width / height ) + * @param[in] size cube size + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_ortho_default_s(float aspect, float size, mat4 dest) { + if (aspect >= 1.0f) { + glm_ortho(-size * aspect, + size * aspect, + -size, + size, + -size - 100.0f, + size + 100.0f, + dest); + return; + } + + glm_ortho(-size, + size, + -size / aspect, + size / aspect, + -size - 100.0f, + size + 100.0f, + dest); +} + +/*! + * @brief set up perspective projection matrix + * + * @param[in] fovy field of view angle + * @param[in] aspect aspect ratio ( width / height ) + * @param[in] nearVal near clipping plane + * @param[in] farVal far clipping planes + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_perspective(float fovy, + float aspect, + float nearVal, + float farVal, + mat4 dest) { + float f, fn; + + glm_mat4_zero(dest); + + f = 1.0f / tanf(fovy * 0.5f); + fn = 1.0f / (nearVal - farVal); + + dest[0][0] = f / aspect; + dest[1][1] = f; + dest[2][2] = (nearVal + farVal) * fn; + dest[2][3] =-1.0f; + dest[3][2] = 2.0f * nearVal * farVal * fn; +} + +/*! + * @brief extend perspective projection matrix's far distance + * + * this function does not guarantee far >= near, be aware of that! + * + * @param[in, out] proj projection matrix to extend + * @param[in] deltaFar distance from existing far (negative to shink) + */ +CGLM_INLINE +void +glm_persp_move_far(mat4 proj, float deltaFar) { + float fn, farVal, nearVal, p22, p32; + + p22 = proj[2][2]; + p32 = proj[3][2]; + + nearVal = p32 / (p22 - 1.0f); + farVal = p32 / (p22 + 1.0f) + deltaFar; + fn = 1.0f / (nearVal - farVal); + + proj[2][2] = (nearVal + farVal) * fn; + proj[3][2] = 2.0f * nearVal * farVal * fn; +} + +/*! + * @brief set up perspective projection matrix with default near/far + * and angle values + * + * @param[in] aspect aspect ratio ( width / height ) + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_perspective_default(float aspect, mat4 dest) { + glm_perspective(GLM_PI_4f, aspect, 0.01f, 100.0f, dest); +} + +/*! + * @brief resize perspective matrix by aspect ratio ( width / height ) + * this makes very easy to resize proj matrix when window /viewport + * reized + * + * @param[in] aspect aspect ratio ( width / height ) + * @param[in, out] proj perspective projection matrix + */ +CGLM_INLINE +void +glm_perspective_resize(float aspect, mat4 proj) { + if (proj[0][0] == 0.0f) + return; + + proj[0][0] = proj[1][1] / aspect; +} + +/*! + * @brief set up view matrix + * + * NOTE: The UP vector must not be parallel to the line of sight from + * the eye point to the reference point + * + * @param[in] eye eye vector + * @param[in] center center vector + * @param[in] up up vector + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_lookat(vec3 eye, vec3 center, vec3 up, mat4 dest) { + CGLM_ALIGN(8) vec3 f, u, s; + + glm_vec3_sub(center, eye, f); + glm_vec3_normalize(f); + + glm_vec3_crossn(f, up, s); + glm_vec3_cross(s, f, u); + + dest[0][0] = s[0]; + dest[0][1] = u[0]; + dest[0][2] =-f[0]; + dest[1][0] = s[1]; + dest[1][1] = u[1]; + dest[1][2] =-f[1]; + dest[2][0] = s[2]; + dest[2][1] = u[2]; + dest[2][2] =-f[2]; + dest[3][0] =-glm_vec3_dot(s, eye); + dest[3][1] =-glm_vec3_dot(u, eye); + dest[3][2] = glm_vec3_dot(f, eye); + dest[0][3] = dest[1][3] = dest[2][3] = 0.0f; + dest[3][3] = 1.0f; +} + +/*! + * @brief set up view matrix + * + * convenient wrapper for lookat: if you only have direction not target self + * then this might be useful. Because you need to get target from direction. + * + * NOTE: The UP vector must not be parallel to the line of sight from + * the eye point to the reference point + * + * @param[in] eye eye vector + * @param[in] dir direction vector + * @param[in] up up vector + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_look(vec3 eye, vec3 dir, vec3 up, mat4 dest) { + CGLM_ALIGN(8) vec3 target; + glm_vec3_add(eye, dir, target); + glm_lookat(eye, target, up, dest); +} + +/*! + * @brief set up view matrix + * + * convenient wrapper for look: if you only have direction and if you don't + * care what UP vector is then this might be useful to create view matrix + * + * @param[in] eye eye vector + * @param[in] dir direction vector + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_look_anyup(vec3 eye, vec3 dir, mat4 dest) { + CGLM_ALIGN(8) vec3 up; + glm_vec3_ortho(dir, up); + glm_look(eye, dir, up, dest); +} + +/*! + * @brief decomposes frustum values of perspective projection. + * + * @param[in] proj perspective projection matrix + * @param[out] nearVal near + * @param[out] farVal far + * @param[out] top top + * @param[out] bottom bottom + * @param[out] left left + * @param[out] right right + */ +CGLM_INLINE +void +glm_persp_decomp(mat4 proj, + float * __restrict nearVal, float * __restrict farVal, + float * __restrict top, float * __restrict bottom, + float * __restrict left, float * __restrict right) { + float m00, m11, m20, m21, m22, m32, n, f; + float n_m11, n_m00; + + m00 = proj[0][0]; + m11 = proj[1][1]; + m20 = proj[2][0]; + m21 = proj[2][1]; + m22 = proj[2][2]; + m32 = proj[3][2]; + + n = m32 / (m22 - 1.0f); + f = m32 / (m22 + 1.0f); + + n_m11 = n / m11; + n_m00 = n / m00; + + *nearVal = n; + *farVal = f; + *bottom = n_m11 * (m21 - 1.0f); + *top = n_m11 * (m21 + 1.0f); + *left = n_m00 * (m20 - 1.0f); + *right = n_m00 * (m20 + 1.0f); +} + +/*! + * @brief decomposes frustum values of perspective projection. + * this makes easy to get all values at once + * + * @param[in] proj perspective projection matrix + * @param[out] dest array + */ +CGLM_INLINE +void +glm_persp_decompv(mat4 proj, float dest[6]) { + glm_persp_decomp(proj, &dest[0], &dest[1], &dest[2], + &dest[3], &dest[4], &dest[5]); +} + +/*! + * @brief decomposes left and right values of perspective projection. + * x stands for x axis (left / right axis) + * + * @param[in] proj perspective projection matrix + * @param[out] left left + * @param[out] right right + */ +CGLM_INLINE +void +glm_persp_decomp_x(mat4 proj, + float * __restrict left, + float * __restrict right) { + float nearVal, m20, m00; + + m00 = proj[0][0]; + m20 = proj[2][0]; + + nearVal = proj[3][2] / (proj[3][3] - 1.0f); + *left = nearVal * (m20 - 1.0f) / m00; + *right = nearVal * (m20 + 1.0f) / m00; +} + +/*! + * @brief decomposes top and bottom values of perspective projection. + * y stands for y axis (top / botom axis) + * + * @param[in] proj perspective projection matrix + * @param[out] top top + * @param[out] bottom bottom + */ +CGLM_INLINE +void +glm_persp_decomp_y(mat4 proj, + float * __restrict top, + float * __restrict bottom) { + float nearVal, m21, m11; + + m21 = proj[2][1]; + m11 = proj[1][1]; + + nearVal = proj[3][2] / (proj[3][3] - 1.0f); + *bottom = nearVal * (m21 - 1) / m11; + *top = nearVal * (m21 + 1) / m11; +} + +/*! + * @brief decomposes near and far values of perspective projection. + * z stands for z axis (near / far axis) + * + * @param[in] proj perspective projection matrix + * @param[out] nearVal near + * @param[out] farVal far + */ +CGLM_INLINE +void +glm_persp_decomp_z(mat4 proj, + float * __restrict nearVal, + float * __restrict farVal) { + float m32, m22; + + m32 = proj[3][2]; + m22 = proj[2][2]; + + *nearVal = m32 / (m22 - 1.0f); + *farVal = m32 / (m22 + 1.0f); +} + +/*! + * @brief decomposes far value of perspective projection. + * + * @param[in] proj perspective projection matrix + * @param[out] farVal far + */ +CGLM_INLINE +void +glm_persp_decomp_far(mat4 proj, float * __restrict farVal) { + *farVal = proj[3][2] / (proj[2][2] + 1.0f); +} + +/*! + * @brief decomposes near value of perspective projection. + * + * @param[in] proj perspective projection matrix + * @param[out] nearVal near + */ +CGLM_INLINE +void +glm_persp_decomp_near(mat4 proj, float * __restrict nearVal) { + *nearVal = proj[3][2] / (proj[2][2] - 1.0f); +} + +/*! + * @brief returns field of view angle along the Y-axis (in radians) + * + * if you need to degrees, use glm_deg to convert it or use this: + * fovy_deg = glm_deg(glm_persp_fovy(projMatrix)) + * + * @param[in] proj perspective projection matrix + */ +CGLM_INLINE +float +glm_persp_fovy(mat4 proj) { + return 2.0f * atanf(1.0f / proj[1][1]); +} + +/*! + * @brief returns aspect ratio of perspective projection + * + * @param[in] proj perspective projection matrix + */ +CGLM_INLINE +float +glm_persp_aspect(mat4 proj) { + return proj[1][1] / proj[0][0]; +} + +/*! + * @brief returns sizes of near and far planes of perspective projection + * + * @param[in] proj perspective projection matrix + * @param[in] fovy fovy (see brief) + * @param[out] dest sizes order: [Wnear, Hnear, Wfar, Hfar] + */ +CGLM_INLINE +void +glm_persp_sizes(mat4 proj, float fovy, vec4 dest) { + float t, a, nearVal, farVal; + + t = 2.0f * tanf(fovy * 0.5f); + a = glm_persp_aspect(proj); + + glm_persp_decomp_z(proj, &nearVal, &farVal); + + dest[1] = t * nearVal; + dest[3] = t * farVal; + dest[0] = a * dest[1]; + dest[2] = a * dest[3]; +} + +#endif /* cglm_vcam_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_frustum_h +#define cglm_frustum_h + + +#define GLM_LBN 0 /* left bottom near */ +#define GLM_LTN 1 /* left top near */ +#define GLM_RTN 2 /* right top near */ +#define GLM_RBN 3 /* right bottom near */ + +#define GLM_LBF 4 /* left bottom far */ +#define GLM_LTF 5 /* left top far */ +#define GLM_RTF 6 /* right top far */ +#define GLM_RBF 7 /* right bottom far */ + +#define GLM_LEFT 0 +#define GLM_RIGHT 1 +#define GLM_BOTTOM 2 +#define GLM_TOP 3 +#define GLM_NEAR 4 +#define GLM_FAR 5 + +/* you can override clip space coords + but you have to provide all with same name + e.g.: define GLM_CSCOORD_LBN {0.0f, 0.0f, 1.0f, 1.0f} */ +#ifndef GLM_CUSTOM_CLIPSPACE + +/* near */ +#define GLM_CSCOORD_LBN {-1.0f, -1.0f, -1.0f, 1.0f} +#define GLM_CSCOORD_LTN {-1.0f, 1.0f, -1.0f, 1.0f} +#define GLM_CSCOORD_RTN { 1.0f, 1.0f, -1.0f, 1.0f} +#define GLM_CSCOORD_RBN { 1.0f, -1.0f, -1.0f, 1.0f} + +/* far */ +#define GLM_CSCOORD_LBF {-1.0f, -1.0f, 1.0f, 1.0f} +#define GLM_CSCOORD_LTF {-1.0f, 1.0f, 1.0f, 1.0f} +#define GLM_CSCOORD_RTF { 1.0f, 1.0f, 1.0f, 1.0f} +#define GLM_CSCOORD_RBF { 1.0f, -1.0f, 1.0f, 1.0f} + +#endif + +/*! + * @brief extracts view frustum planes + * + * planes' space: + * 1- if m = proj: View Space + * 2- if m = viewProj: World Space + * 3- if m = MVP: Object Space + * + * You probably want to extract planes in world space so use viewProj as m + * Computing viewProj: + * glm_mat4_mul(proj, view, viewProj); + * + * Exracted planes order: [left, right, bottom, top, near, far] + * + * @param[in] m matrix (see brief) + * @param[out] dest extracted view frustum planes (see brief) + */ +CGLM_INLINE +void +glm_frustum_planes(mat4 m, vec4 dest[6]) { + mat4 t; + + glm_mat4_transpose_to(m, t); + + glm_vec4_add(t[3], t[0], dest[0]); /* left */ + glm_vec4_sub(t[3], t[0], dest[1]); /* right */ + glm_vec4_add(t[3], t[1], dest[2]); /* bottom */ + glm_vec4_sub(t[3], t[1], dest[3]); /* top */ + glm_vec4_add(t[3], t[2], dest[4]); /* near */ + glm_vec4_sub(t[3], t[2], dest[5]); /* far */ + + glm_plane_normalize(dest[0]); + glm_plane_normalize(dest[1]); + glm_plane_normalize(dest[2]); + glm_plane_normalize(dest[3]); + glm_plane_normalize(dest[4]); + glm_plane_normalize(dest[5]); +} + +/*! + * @brief extracts view frustum corners using clip-space coordinates + * + * corners' space: + * 1- if m = invViewProj: World Space + * 2- if m = invMVP: Object Space + * + * You probably want to extract corners in world space so use invViewProj + * Computing invViewProj: + * glm_mat4_mul(proj, view, viewProj); + * ... + * glm_mat4_inv(viewProj, invViewProj); + * + * if you have a near coord at i index, you can get it's far coord by i + 4 + * + * Find center coordinates: + * for (j = 0; j < 4; j++) { + * glm_vec3_center(corners[i], corners[i + 4], centerCorners[i]); + * } + * + * @param[in] invMat matrix (see brief) + * @param[out] dest exracted view frustum corners (see brief) + */ +CGLM_INLINE +void +glm_frustum_corners(mat4 invMat, vec4 dest[8]) { + vec4 c[8]; + + /* indexOf(nearCoord) = indexOf(farCoord) + 4 */ + vec4 csCoords[8] = { + GLM_CSCOORD_LBN, + GLM_CSCOORD_LTN, + GLM_CSCOORD_RTN, + GLM_CSCOORD_RBN, + + GLM_CSCOORD_LBF, + GLM_CSCOORD_LTF, + GLM_CSCOORD_RTF, + GLM_CSCOORD_RBF + }; + + glm_mat4_mulv(invMat, csCoords[0], c[0]); + glm_mat4_mulv(invMat, csCoords[1], c[1]); + glm_mat4_mulv(invMat, csCoords[2], c[2]); + glm_mat4_mulv(invMat, csCoords[3], c[3]); + glm_mat4_mulv(invMat, csCoords[4], c[4]); + glm_mat4_mulv(invMat, csCoords[5], c[5]); + glm_mat4_mulv(invMat, csCoords[6], c[6]); + glm_mat4_mulv(invMat, csCoords[7], c[7]); + + glm_vec4_scale(c[0], 1.0f / c[0][3], dest[0]); + glm_vec4_scale(c[1], 1.0f / c[1][3], dest[1]); + glm_vec4_scale(c[2], 1.0f / c[2][3], dest[2]); + glm_vec4_scale(c[3], 1.0f / c[3][3], dest[3]); + glm_vec4_scale(c[4], 1.0f / c[4][3], dest[4]); + glm_vec4_scale(c[5], 1.0f / c[5][3], dest[5]); + glm_vec4_scale(c[6], 1.0f / c[6][3], dest[6]); + glm_vec4_scale(c[7], 1.0f / c[7][3], dest[7]); +} + +/*! + * @brief finds center of view frustum + * + * @param[in] corners view frustum corners + * @param[out] dest view frustum center + */ +CGLM_INLINE +void +glm_frustum_center(vec4 corners[8], vec4 dest) { + vec4 center; + + glm_vec4_copy(corners[0], center); + + glm_vec4_add(corners[1], center, center); + glm_vec4_add(corners[2], center, center); + glm_vec4_add(corners[3], center, center); + glm_vec4_add(corners[4], center, center); + glm_vec4_add(corners[5], center, center); + glm_vec4_add(corners[6], center, center); + glm_vec4_add(corners[7], center, center); + + glm_vec4_scale(center, 0.125f, dest); +} + +/*! + * @brief finds bounding box of frustum relative to given matrix e.g. view mat + * + * @param[in] corners view frustum corners + * @param[in] m matrix to convert existing conners + * @param[out] box bounding box as array [min, max] + */ +CGLM_INLINE +void +glm_frustum_box(vec4 corners[8], mat4 m, vec3 box[2]) { + vec4 v; + vec3 min, max; + int i; + + glm_vec3_broadcast(FLT_MAX, min); + glm_vec3_broadcast(-FLT_MAX, max); + + for (i = 0; i < 8; i++) { + glm_mat4_mulv(m, corners[i], v); + + min[0] = glm_min(min[0], v[0]); + min[1] = glm_min(min[1], v[1]); + min[2] = glm_min(min[2], v[2]); + + max[0] = glm_max(max[0], v[0]); + max[1] = glm_max(max[1], v[1]); + max[2] = glm_max(max[2], v[2]); + } + + glm_vec3_copy(min, box[0]); + glm_vec3_copy(max, box[1]); +} + +/*! + * @brief finds planes corners which is between near and far planes (parallel) + * + * this will be helpful if you want to split a frustum e.g. CSM/PSSM. This will + * find planes' corners but you will need to one more plane. + * Actually you have it, it is near, far or created previously with this func ;) + * + * @param[in] corners view frustum corners + * @param[in] splitDist split distance + * @param[in] farDist far distance (zFar) + * @param[out] planeCorners plane corners [LB, LT, RT, RB] + */ +CGLM_INLINE +void +glm_frustum_corners_at(vec4 corners[8], + float splitDist, + float farDist, + vec4 planeCorners[4]) { + vec4 corner; + float dist, sc; + + /* because distance and scale is same for all */ + dist = glm_vec3_distance(corners[GLM_RTF], corners[GLM_RTN]); + sc = dist * (splitDist / farDist); + + /* left bottom */ + glm_vec4_sub(corners[GLM_LBF], corners[GLM_LBN], corner); + glm_vec4_scale_as(corner, sc, corner); + glm_vec4_add(corners[GLM_LBN], corner, planeCorners[0]); + + /* left top */ + glm_vec4_sub(corners[GLM_LTF], corners[GLM_LTN], corner); + glm_vec4_scale_as(corner, sc, corner); + glm_vec4_add(corners[GLM_LTN], corner, planeCorners[1]); + + /* right top */ + glm_vec4_sub(corners[GLM_RTF], corners[GLM_RTN], corner); + glm_vec4_scale_as(corner, sc, corner); + glm_vec4_add(corners[GLM_RTN], corner, planeCorners[2]); + + /* right bottom */ + glm_vec4_sub(corners[GLM_RBF], corners[GLM_RBN], corner); + glm_vec4_scale_as(corner, sc, corner); + glm_vec4_add(corners[GLM_RBN], corner, planeCorners[3]); +} + +#endif /* cglm_frustum_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Macros: + GLM_QUAT_IDENTITY_INIT + GLM_QUAT_IDENTITY + + Functions: + CGLM_INLINE void glm_quat_identity(versor q); + CGLM_INLINE void glm_quat_init(versor q, float x, float y, float z, float w); + CGLM_INLINE void glm_quat(versor q, float angle, float x, float y, float z); + CGLM_INLINE void glm_quatv(versor q, float angle, vec3 axis); + CGLM_INLINE void glm_quat_copy(versor q, versor dest); + CGLM_INLINE float glm_quat_norm(versor q); + CGLM_INLINE void glm_quat_normalize(versor q); + CGLM_INLINE void glm_quat_normalize_to(versor q, versor dest); + CGLM_INLINE float glm_quat_dot(versor p, versor q); + CGLM_INLINE void glm_quat_conjugate(versor q, versor dest); + CGLM_INLINE void glm_quat_inv(versor q, versor dest); + CGLM_INLINE void glm_quat_add(versor p, versor q, versor dest); + CGLM_INLINE void glm_quat_sub(versor p, versor q, versor dest); + CGLM_INLINE float glm_quat_real(versor q); + CGLM_INLINE void glm_quat_imag(versor q, vec3 dest); + CGLM_INLINE void glm_quat_imagn(versor q, vec3 dest); + CGLM_INLINE float glm_quat_imaglen(versor q); + CGLM_INLINE float glm_quat_angle(versor q); + CGLM_INLINE void glm_quat_axis(versor q, vec3 dest); + CGLM_INLINE void glm_quat_mul(versor p, versor q, versor dest); + CGLM_INLINE void glm_quat_mat4(versor q, mat4 dest); + CGLM_INLINE void glm_quat_mat4t(versor q, mat4 dest); + CGLM_INLINE void glm_quat_mat3(versor q, mat3 dest); + CGLM_INLINE void glm_quat_mat3t(versor q, mat3 dest); + CGLM_INLINE void glm_quat_lerp(versor from, versor to, float t, versor dest); + CGLM_INLINE void glm_quat_lerpc(versor from, versor to, float t, versor dest); + CGLM_INLINE void glm_quat_slerp(versor q, versor r, float t, versor dest); + CGLM_INLINE void glm_quat_look(vec3 eye, versor ori, mat4 dest); + CGLM_INLINE void glm_quat_for(vec3 dir, vec3 fwd, vec3 up, versor dest); + CGLM_INLINE void glm_quat_forp(vec3 from, + vec3 to, + vec3 fwd, + vec3 up, + versor dest); + CGLM_INLINE void glm_quat_rotatev(versor q, vec3 v, vec3 dest); + CGLM_INLINE void glm_quat_rotate(mat4 m, versor q, mat4 dest); + */ + +#ifndef cglm_quat_h +#define cglm_quat_h + + +#ifdef CGLM_SSE_FP +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_quat_simd_h +#define cglm_quat_simd_h +#if defined( __SSE__ ) || defined( __SSE2__ ) + + +CGLM_INLINE +void +glm_quat_mul_sse2(versor p, versor q, versor dest) { + /* + + (a1 b2 + b1 a2 + c1 d2 − d1 c2)i + + (a1 c2 − b1 d2 + c1 a2 + d1 b2)j + + (a1 d2 + b1 c2 − c1 b2 + d1 a2)k + a1 a2 − b1 b2 − c1 c2 − d1 d2 + */ + + __m128 xp, xq, x0, r; + + xp = glmm_load(p); /* 3 2 1 0 */ + xq = glmm_load(q); + + r = _mm_mul_ps(glmm_shuff1x(xp, 3), xq); + + x0 = _mm_xor_ps(glmm_shuff1x(xp, 0), _mm_set_ps(-0.f, 0.f, -0.f, 0.f)); + r = _mm_add_ps(r, _mm_mul_ps(x0, glmm_shuff1(xq, 0, 1, 2, 3))); + + x0 = _mm_xor_ps(glmm_shuff1x(xp, 1), _mm_set_ps(-0.f, -0.f, 0.f, 0.f)); + r = _mm_add_ps(r, _mm_mul_ps(x0, glmm_shuff1(xq, 1, 0, 3, 2))); + + x0 = _mm_xor_ps(glmm_shuff1x(xp, 2), _mm_set_ps(-0.f, 0.f, 0.f, -0.f)); + r = _mm_add_ps(r, _mm_mul_ps(x0, glmm_shuff1(xq, 2, 3, 0, 1))); + + glmm_store(dest, r); +} + + +#endif +#endif /* cglm_quat_simd_h */ + +#endif + +CGLM_INLINE +void +glm_mat4_mulv(mat4 m, vec4 v, vec4 dest); + +CGLM_INLINE +void +glm_mul_rot(mat4 m1, mat4 m2, mat4 dest); + +CGLM_INLINE +void +glm_translate(mat4 m, vec3 v); + +/* + * IMPORTANT: + * ---------------------------------------------------------------------------- + * cglm stores quat as [x, y, z, w] since v0.3.6 + * + * it was [w, x, y, z] before v0.3.6 it has been changed to [x, y, z, w] + * with v0.3.6 version. + * ---------------------------------------------------------------------------- + */ + +#define GLM_QUAT_IDENTITY_INIT {0.0f, 0.0f, 0.0f, 1.0f} +#define GLM_QUAT_IDENTITY ((versor)GLM_QUAT_IDENTITY_INIT) + +/*! + * @brief makes given quat to identity + * + * @param[in, out] q quaternion + */ +CGLM_INLINE +void +glm_quat_identity(versor q) { + CGLM_ALIGN(16) versor v = GLM_QUAT_IDENTITY_INIT; + glm_vec4_copy(v, q); +} + +/*! + * @brief make given quaternion array's each element identity quaternion + * + * @param[in, out] q quat array (must be aligned (16) + * if alignment is not disabled) + * + * @param[in] count count of quaternions + */ +CGLM_INLINE +void +glm_quat_identity_array(versor * __restrict q, size_t count) { + CGLM_ALIGN(16) versor v = GLM_QUAT_IDENTITY_INIT; + size_t i; + + for (i = 0; i < count; i++) { + glm_vec4_copy(v, q[i]); + } +} + +/*! + * @brief inits quaterion with raw values + * + * @param[out] q quaternion + * @param[in] x x + * @param[in] y y + * @param[in] z z + * @param[in] w w (real part) + */ +CGLM_INLINE +void +glm_quat_init(versor q, float x, float y, float z, float w) { + q[0] = x; + q[1] = y; + q[2] = z; + q[3] = w; +} + +/*! + * @brief creates NEW quaternion with axis vector + * + * @param[out] q quaternion + * @param[in] angle angle (radians) + * @param[in] axis axis + */ +CGLM_INLINE +void +glm_quatv(versor q, float angle, vec3 axis) { + CGLM_ALIGN(8) vec3 k; + float a, c, s; + + a = angle * 0.5f; + c = cosf(a); + s = sinf(a); + + glm_normalize_to(axis, k); + + q[0] = s * k[0]; + q[1] = s * k[1]; + q[2] = s * k[2]; + q[3] = c; +} + +/*! + * @brief creates NEW quaternion with individual axis components + * + * @param[out] q quaternion + * @param[in] angle angle (radians) + * @param[in] x axis.x + * @param[in] y axis.y + * @param[in] z axis.z + */ +CGLM_INLINE +void +glm_quat(versor q, float angle, float x, float y, float z) { + CGLM_ALIGN(8) vec3 axis = {x, y, z}; + glm_quatv(q, angle, axis); +} + +/*! + * @brief copy quaternion to another one + * + * @param[in] q quaternion + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_quat_copy(versor q, versor dest) { + glm_vec4_copy(q, dest); +} + +/*! + * @brief returns norm (magnitude) of quaternion + * + * @param[out] q quaternion + */ +CGLM_INLINE +float +glm_quat_norm(versor q) { + return glm_vec4_norm(q); +} + +/*! + * @brief normalize quaternion and store result in dest + * + * @param[in] q quaternion to normalze + * @param[out] dest destination quaternion + */ +CGLM_INLINE +void +glm_quat_normalize_to(versor q, versor dest) { +#if defined( __SSE2__ ) || defined( __SSE2__ ) + __m128 xdot, x0; + float dot; + + x0 = glmm_load(q); + xdot = glmm_vdot(x0, x0); + dot = _mm_cvtss_f32(xdot); + + if (dot <= 0.0f) { + glm_quat_identity(dest); + return; + } + + glmm_store(dest, _mm_div_ps(x0, _mm_sqrt_ps(xdot))); +#else + float dot; + + dot = glm_vec4_norm2(q); + + if (dot <= 0.0f) { + glm_quat_identity(dest); + return; + } + + glm_vec4_scale(q, 1.0f / sqrtf(dot), dest); +#endif +} + +/*! + * @brief normalize quaternion + * + * @param[in, out] q quaternion + */ +CGLM_INLINE +void +glm_quat_normalize(versor q) { + glm_quat_normalize_to(q, q); +} + +/*! + * @brief dot product of two quaternion + * + * @param[in] p quaternion 1 + * @param[in] q quaternion 2 + */ +CGLM_INLINE +float +glm_quat_dot(versor p, versor q) { + return glm_vec4_dot(p, q); +} + +/*! + * @brief conjugate of quaternion + * + * @param[in] q quaternion + * @param[out] dest conjugate + */ +CGLM_INLINE +void +glm_quat_conjugate(versor q, versor dest) { + glm_vec4_negate_to(q, dest); + dest[3] = -dest[3]; +} + +/*! + * @brief inverse of non-zero quaternion + * + * @param[in] q quaternion + * @param[out] dest inverse quaternion + */ +CGLM_INLINE +void +glm_quat_inv(versor q, versor dest) { + CGLM_ALIGN(16) versor conj; + glm_quat_conjugate(q, conj); + glm_vec4_scale(conj, 1.0f / glm_vec4_norm2(q), dest); +} + +/*! + * @brief add (componentwise) two quaternions and store result in dest + * + * @param[in] p quaternion 1 + * @param[in] q quaternion 2 + * @param[out] dest result quaternion + */ +CGLM_INLINE +void +glm_quat_add(versor p, versor q, versor dest) { + glm_vec4_add(p, q, dest); +} + +/*! + * @brief subtract (componentwise) two quaternions and store result in dest + * + * @param[in] p quaternion 1 + * @param[in] q quaternion 2 + * @param[out] dest result quaternion + */ +CGLM_INLINE +void +glm_quat_sub(versor p, versor q, versor dest) { + glm_vec4_sub(p, q, dest); +} + +/*! + * @brief returns real part of quaternion + * + * @param[in] q quaternion + */ +CGLM_INLINE +float +glm_quat_real(versor q) { + return q[3]; +} + +/*! + * @brief returns imaginary part of quaternion + * + * @param[in] q quaternion + * @param[out] dest imag + */ +CGLM_INLINE +void +glm_quat_imag(versor q, vec3 dest) { + dest[0] = q[0]; + dest[1] = q[1]; + dest[2] = q[2]; +} + +/*! + * @brief returns normalized imaginary part of quaternion + * + * @param[in] q quaternion + */ +CGLM_INLINE +void +glm_quat_imagn(versor q, vec3 dest) { + glm_normalize_to(q, dest); +} + +/*! + * @brief returns length of imaginary part of quaternion + * + * @param[in] q quaternion + */ +CGLM_INLINE +float +glm_quat_imaglen(versor q) { + return glm_vec3_norm(q); +} + +/*! + * @brief returns angle of quaternion + * + * @param[in] q quaternion + */ +CGLM_INLINE +float +glm_quat_angle(versor q) { + /* + sin(theta / 2) = length(x*x + y*y + z*z) + cos(theta / 2) = w + theta = 2 * atan(sin(theta / 2) / cos(theta / 2)) + */ + return 2.0f * atan2f(glm_quat_imaglen(q), glm_quat_real(q)); +} + +/*! + * @brief axis of quaternion + * + * @param[in] q quaternion + * @param[out] dest axis of quaternion + */ +CGLM_INLINE +void +glm_quat_axis(versor q, vec3 dest) { + glm_quat_imagn(q, dest); +} + +/*! + * @brief multiplies two quaternion and stores result in dest + * this is also called Hamilton Product + * + * According to WikiPedia: + * The product of two rotation quaternions [clarification needed] will be + * equivalent to the rotation q followed by the rotation p + * + * @param[in] p quaternion 1 + * @param[in] q quaternion 2 + * @param[out] dest result quaternion + */ +CGLM_INLINE +void +glm_quat_mul(versor p, versor q, versor dest) { + /* + + (a1 b2 + b1 a2 + c1 d2 − d1 c2)i + + (a1 c2 − b1 d2 + c1 a2 + d1 b2)j + + (a1 d2 + b1 c2 − c1 b2 + d1 a2)k + a1 a2 − b1 b2 − c1 c2 − d1 d2 + */ +#if defined( __SSE__ ) || defined( __SSE2__ ) + glm_quat_mul_sse2(p, q, dest); +#else + dest[0] = p[3] * q[0] + p[0] * q[3] + p[1] * q[2] - p[2] * q[1]; + dest[1] = p[3] * q[1] - p[0] * q[2] + p[1] * q[3] + p[2] * q[0]; + dest[2] = p[3] * q[2] + p[0] * q[1] - p[1] * q[0] + p[2] * q[3]; + dest[3] = p[3] * q[3] - p[0] * q[0] - p[1] * q[1] - p[2] * q[2]; +#endif +} + +/*! + * @brief convert quaternion to mat4 + * + * @param[in] q quaternion + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_quat_mat4(versor q, mat4 dest) { + float w, x, y, z, + xx, yy, zz, + xy, yz, xz, + wx, wy, wz, norm, s; + + norm = glm_quat_norm(q); + s = norm > 0.0f ? 2.0f / norm : 0.0f; + + x = q[0]; + y = q[1]; + z = q[2]; + w = q[3]; + + xx = s * x * x; xy = s * x * y; wx = s * w * x; + yy = s * y * y; yz = s * y * z; wy = s * w * y; + zz = s * z * z; xz = s * x * z; wz = s * w * z; + + dest[0][0] = 1.0f - yy - zz; + dest[1][1] = 1.0f - xx - zz; + dest[2][2] = 1.0f - xx - yy; + + dest[0][1] = xy + wz; + dest[1][2] = yz + wx; + dest[2][0] = xz + wy; + + dest[1][0] = xy - wz; + dest[2][1] = yz - wx; + dest[0][2] = xz - wy; + + dest[0][3] = 0.0f; + dest[1][3] = 0.0f; + dest[2][3] = 0.0f; + dest[3][0] = 0.0f; + dest[3][1] = 0.0f; + dest[3][2] = 0.0f; + dest[3][3] = 1.0f; +} + +/*! + * @brief convert quaternion to mat4 (transposed) + * + * @param[in] q quaternion + * @param[out] dest result matrix as transposed + */ +CGLM_INLINE +void +glm_quat_mat4t(versor q, mat4 dest) { + float w, x, y, z, + xx, yy, zz, + xy, yz, xz, + wx, wy, wz, norm, s; + + norm = glm_quat_norm(q); + s = norm > 0.0f ? 2.0f / norm : 0.0f; + + x = q[0]; + y = q[1]; + z = q[2]; + w = q[3]; + + xx = s * x * x; xy = s * x * y; wx = s * w * x; + yy = s * y * y; yz = s * y * z; wy = s * w * y; + zz = s * z * z; xz = s * x * z; wz = s * w * z; + + dest[0][0] = 1.0f - yy - zz; + dest[1][1] = 1.0f - xx - zz; + dest[2][2] = 1.0f - xx - yy; + + dest[1][0] = xy + wz; + dest[2][1] = yz + wx; + dest[0][2] = xz + wy; + + dest[0][1] = xy - wz; + dest[1][2] = yz - wx; + dest[2][0] = xz - wy; + + dest[0][3] = 0.0f; + dest[1][3] = 0.0f; + dest[2][3] = 0.0f; + dest[3][0] = 0.0f; + dest[3][1] = 0.0f; + dest[3][2] = 0.0f; + dest[3][3] = 1.0f; +} + +/*! + * @brief convert quaternion to mat3 + * + * @param[in] q quaternion + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_quat_mat3(versor q, mat3 dest) { + float w, x, y, z, + xx, yy, zz, + xy, yz, xz, + wx, wy, wz, norm, s; + + norm = glm_quat_norm(q); + s = norm > 0.0f ? 2.0f / norm : 0.0f; + + x = q[0]; + y = q[1]; + z = q[2]; + w = q[3]; + + xx = s * x * x; xy = s * x * y; wx = s * w * x; + yy = s * y * y; yz = s * y * z; wy = s * w * y; + zz = s * z * z; xz = s * x * z; wz = s * w * z; + + dest[0][0] = 1.0f - yy - zz; + dest[1][1] = 1.0f - xx - zz; + dest[2][2] = 1.0f - xx - yy; + + dest[0][1] = xy + wz; + dest[1][2] = yz + wx; + dest[2][0] = xz + wy; + + dest[1][0] = xy - wz; + dest[2][1] = yz - wx; + dest[0][2] = xz - wy; +} + +/*! + * @brief convert quaternion to mat3 (transposed) + * + * @param[in] q quaternion + * @param[out] dest result matrix + */ +CGLM_INLINE +void +glm_quat_mat3t(versor q, mat3 dest) { + float w, x, y, z, + xx, yy, zz, + xy, yz, xz, + wx, wy, wz, norm, s; + + norm = glm_quat_norm(q); + s = norm > 0.0f ? 2.0f / norm : 0.0f; + + x = q[0]; + y = q[1]; + z = q[2]; + w = q[3]; + + xx = s * x * x; xy = s * x * y; wx = s * w * x; + yy = s * y * y; yz = s * y * z; wy = s * w * y; + zz = s * z * z; xz = s * x * z; wz = s * w * z; + + dest[0][0] = 1.0f - yy - zz; + dest[1][1] = 1.0f - xx - zz; + dest[2][2] = 1.0f - xx - yy; + + dest[1][0] = xy + wz; + dest[2][1] = yz + wx; + dest[0][2] = xz + wy; + + dest[0][1] = xy - wz; + dest[1][2] = yz - wx; + dest[2][0] = xz - wy; +} + +/*! + * @brief interpolates between two quaternions + * using linear interpolation (LERP) + * + * @param[in] from from + * @param[in] to to + * @param[in] t interpolant (amount) + * @param[out] dest result quaternion + */ +CGLM_INLINE +void +glm_quat_lerp(versor from, versor to, float t, versor dest) { + glm_vec4_lerp(from, to, t, dest); +} + +/*! + * @brief interpolates between two quaternions + * using linear interpolation (LERP) + * + * @param[in] from from + * @param[in] to to + * @param[in] t interpolant (amount) clamped between 0 and 1 + * @param[out] dest result quaternion + */ +CGLM_INLINE +void +glm_quat_lerpc(versor from, versor to, float t, versor dest) { + glm_vec4_lerpc(from, to, t, dest); +} + +/*! + * @brief interpolates between two quaternions + * using spherical linear interpolation (SLERP) + * + * @param[in] from from + * @param[in] to to + * @param[in] t amout + * @param[out] dest result quaternion + */ +CGLM_INLINE +void +glm_quat_slerp(versor from, versor to, float t, versor dest) { + CGLM_ALIGN(16) vec4 q1, q2; + float cosTheta, sinTheta, angle; + + cosTheta = glm_quat_dot(from, to); + glm_quat_copy(from, q1); + + if (fabsf(cosTheta) >= 1.0f) { + glm_quat_copy(q1, dest); + return; + } + + if (cosTheta < 0.0f) { + glm_vec4_negate(q1); + cosTheta = -cosTheta; + } + + sinTheta = sqrtf(1.0f - cosTheta * cosTheta); + + /* LERP to avoid zero division */ + if (fabsf(sinTheta) < 0.001f) { + glm_quat_lerp(from, to, t, dest); + return; + } + + /* SLERP */ + angle = acosf(cosTheta); + glm_vec4_scale(q1, sinf((1.0f - t) * angle), q1); + glm_vec4_scale(to, sinf(t * angle), q2); + + glm_vec4_add(q1, q2, q1); + glm_vec4_scale(q1, 1.0f / sinTheta, dest); +} + +/*! + * @brief creates view matrix using quaternion as camera orientation + * + * @param[in] eye eye + * @param[in] ori orientation in world space as quaternion + * @param[out] dest view matrix + */ +CGLM_INLINE +void +glm_quat_look(vec3 eye, versor ori, mat4 dest) { + /* orientation */ + glm_quat_mat4t(ori, dest); + + /* translate */ + glm_mat4_mulv3(dest, eye, 1.0f, dest[3]); + glm_vec3_negate(dest[3]); +} + +/*! + * @brief creates look rotation quaternion + * + * @param[in] dir direction to look + * @param[in] up up vector + * @param[out] dest destination quaternion + */ +CGLM_INLINE +void +glm_quat_for(vec3 dir, vec3 up, versor dest) { + CGLM_ALIGN_MAT mat3 m; + + glm_vec3_normalize_to(dir, m[2]); + + /* No need to negate in LH, but we use RH here */ + glm_vec3_negate(m[2]); + + glm_vec3_crossn(up, m[2], m[0]); + glm_vec3_cross(m[2], m[0], m[1]); + + glm_mat3_quat(m, dest); +} + +/*! + * @brief creates look rotation quaternion using source and + * destination positions p suffix stands for position + * + * @param[in] from source point + * @param[in] to destination point + * @param[in] up up vector + * @param[out] dest destination quaternion + */ +CGLM_INLINE +void +glm_quat_forp(vec3 from, vec3 to, vec3 up, versor dest) { + CGLM_ALIGN(8) vec3 dir; + glm_vec3_sub(to, from, dir); + glm_quat_for(dir, up, dest); +} + +/*! + * @brief rotate vector using using quaternion + * + * @param[in] q quaternion + * @param[in] v vector to rotate + * @param[out] dest rotated vector + */ +CGLM_INLINE +void +glm_quat_rotatev(versor q, vec3 v, vec3 dest) { + CGLM_ALIGN(16) versor p; + CGLM_ALIGN(8) vec3 u, v1, v2; + float s; + + glm_quat_normalize_to(q, p); + glm_quat_imag(p, u); + s = glm_quat_real(p); + + glm_vec3_scale(u, 2.0f * glm_vec3_dot(u, v), v1); + glm_vec3_scale(v, s * s - glm_vec3_dot(u, u), v2); + glm_vec3_add(v1, v2, v1); + + glm_vec3_cross(u, v, v2); + glm_vec3_scale(v2, 2.0f * s, v2); + + glm_vec3_add(v1, v2, dest); +} + +/*! + * @brief rotate existing transform matrix using quaternion + * + * @param[in] m existing transform matrix + * @param[in] q quaternion + * @param[out] dest rotated matrix/transform + */ +CGLM_INLINE +void +glm_quat_rotate(mat4 m, versor q, mat4 dest) { + CGLM_ALIGN_MAT mat4 rot; + glm_quat_mat4(q, rot); + glm_mul_rot(m, rot, dest); +} + +/*! + * @brief rotate existing transform matrix using quaternion at pivot point + * + * @param[in, out] m existing transform matrix + * @param[in] q quaternion + * @param[out] pivot pivot + */ +CGLM_INLINE +void +glm_quat_rotate_at(mat4 m, versor q, vec3 pivot) { + CGLM_ALIGN(8) vec3 pivotInv; + + glm_vec3_negate_to(pivot, pivotInv); + + glm_translate(m, pivot); + glm_quat_rotate(m, q, m); + glm_translate(m, pivotInv); +} + +/*! + * @brief rotate NEW transform matrix using quaternion at pivot point + * + * this creates rotation matrix, it assumes you don't have a matrix + * + * this should work faster than glm_quat_rotate_at because it reduces + * one glm_translate. + * + * @param[out] m existing transform matrix + * @param[in] q quaternion + * @param[in] pivot pivot + */ +CGLM_INLINE +void +glm_quat_rotate_atm(mat4 m, versor q, vec3 pivot) { + CGLM_ALIGN(8) vec3 pivotInv; + + glm_vec3_negate_to(pivot, pivotInv); + + glm_translate_make(m, pivot); + glm_quat_rotate(m, q, m); + glm_translate(m, pivotInv); +} + +#endif /* cglm_quat_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + NOTE: + angles must be passed as [X-Angle, Y-Angle, Z-angle] order + For instance you don't pass angles as [Z-Angle, X-Angle, Y-angle] to + glm_euler_zxy funciton, All RELATED functions accept angles same order + which is [X, Y, Z]. + */ + +/* + Types: + enum glm_euler_seq + + Functions: + CGLM_INLINE glm_euler_seq glm_euler_order(int newOrder[3]); + CGLM_INLINE void glm_euler_angles(mat4 m, vec3 dest); + CGLM_INLINE void glm_euler(vec3 angles, mat4 dest); + CGLM_INLINE void glm_euler_xyz(vec3 angles, mat4 dest); + CGLM_INLINE void glm_euler_zyx(vec3 angles, mat4 dest); + CGLM_INLINE void glm_euler_zxy(vec3 angles, mat4 dest); + CGLM_INLINE void glm_euler_xzy(vec3 angles, mat4 dest); + CGLM_INLINE void glm_euler_yzx(vec3 angles, mat4 dest); + CGLM_INLINE void glm_euler_yxz(vec3 angles, mat4 dest); + CGLM_INLINE void glm_euler_by_order(vec3 angles, + glm_euler_seq ord, + mat4 dest); + */ + +#ifndef cglm_euler_h +#define cglm_euler_h + + +/*! + * if you have axis order like vec3 orderVec = [0, 1, 2] or [0, 2, 1]... + * vector then you can convert it to this enum by doing this: + * @code + * glm_euler_seq order; + * order = orderVec[0] | orderVec[1] << 2 | orderVec[2] << 4; + * @endcode + * you may need to explicit cast if required + */ +typedef enum glm_euler_seq { + GLM_EULER_XYZ = 0 << 0 | 1 << 2 | 2 << 4, + GLM_EULER_XZY = 0 << 0 | 2 << 2 | 1 << 4, + GLM_EULER_YZX = 1 << 0 | 2 << 2 | 0 << 4, + GLM_EULER_YXZ = 1 << 0 | 0 << 2 | 2 << 4, + GLM_EULER_ZXY = 2 << 0 | 0 << 2 | 1 << 4, + GLM_EULER_ZYX = 2 << 0 | 1 << 2 | 0 << 4 +} glm_euler_seq; + +CGLM_INLINE +glm_euler_seq +glm_euler_order(int ord[3]) { + return (glm_euler_seq)(ord[0] << 0 | ord[1] << 2 | ord[2] << 4); +} + +/*! + * @brief extract euler angles (in radians) using xyz order + * + * @param[in] m affine transform + * @param[out] dest angles vector [x, y, z] + */ +CGLM_INLINE +void +glm_euler_angles(mat4 m, vec3 dest) { + float m00, m01, m10, m11, m20, m21, m22; + float thetaX, thetaY, thetaZ; + + m00 = m[0][0]; m10 = m[1][0]; m20 = m[2][0]; + m01 = m[0][1]; m11 = m[1][1]; m21 = m[2][1]; + m22 = m[2][2]; + + if (m20 < 1.0f) { + if (m20 > -1.0f) { + thetaY = asinf(m20); + thetaX = atan2f(-m21, m22); + thetaZ = atan2f(-m10, m00); + } else { /* m20 == -1 */ + /* Not a unique solution */ + thetaY = -GLM_PI_2f; + thetaX = -atan2f(m01, m11); + thetaZ = 0.0f; + } + } else { /* m20 == +1 */ + thetaY = GLM_PI_2f; + thetaX = atan2f(m01, m11); + thetaZ = 0.0f; + } + + dest[0] = thetaX; + dest[1] = thetaY; + dest[2] = thetaZ; +} + +/*! + * @brief build rotation matrix from euler angles + * + * @param[in] angles angles as vector [Xangle, Yangle, Zangle] + * @param[out] dest rotation matrix + */ +CGLM_INLINE +void +glm_euler_xyz(vec3 angles, mat4 dest) { + float cx, cy, cz, + sx, sy, sz, czsx, cxcz, sysz; + + sx = sinf(angles[0]); cx = cosf(angles[0]); + sy = sinf(angles[1]); cy = cosf(angles[1]); + sz = sinf(angles[2]); cz = cosf(angles[2]); + + czsx = cz * sx; + cxcz = cx * cz; + sysz = sy * sz; + + dest[0][0] = cy * cz; + dest[0][1] = czsx * sy + cx * sz; + dest[0][2] = -cxcz * sy + sx * sz; + dest[1][0] = -cy * sz; + dest[1][1] = cxcz - sx * sysz; + dest[1][2] = czsx + cx * sysz; + dest[2][0] = sy; + dest[2][1] = -cy * sx; + dest[2][2] = cx * cy; + dest[0][3] = 0.0f; + dest[1][3] = 0.0f; + dest[2][3] = 0.0f; + dest[3][0] = 0.0f; + dest[3][1] = 0.0f; + dest[3][2] = 0.0f; + dest[3][3] = 1.0f; +} + +/*! + * @brief build rotation matrix from euler angles + * + * @param[in] angles angles as vector [Xangle, Yangle, Zangle] + * @param[out] dest rotation matrix + */ +CGLM_INLINE +void +glm_euler(vec3 angles, mat4 dest) { + glm_euler_xyz(angles, dest); +} + +/*! + * @brief build rotation matrix from euler angles + * + * @param[in] angles angles as vector [Xangle, Yangle, Zangle] + * @param[out] dest rotation matrix + */ +CGLM_INLINE +void +glm_euler_xzy(vec3 angles, mat4 dest) { + float cx, cy, cz, + sx, sy, sz, sxsy, cysx, cxsy, cxcy; + + sx = sinf(angles[0]); cx = cosf(angles[0]); + sy = sinf(angles[1]); cy = cosf(angles[1]); + sz = sinf(angles[2]); cz = cosf(angles[2]); + + sxsy = sx * sy; + cysx = cy * sx; + cxsy = cx * sy; + cxcy = cx * cy; + + dest[0][0] = cy * cz; + dest[0][1] = sxsy + cxcy * sz; + dest[0][2] = -cxsy + cysx * sz; + dest[1][0] = -sz; + dest[1][1] = cx * cz; + dest[1][2] = cz * sx; + dest[2][0] = cz * sy; + dest[2][1] = -cysx + cxsy * sz; + dest[2][2] = cxcy + sxsy * sz; + dest[0][3] = 0.0f; + dest[1][3] = 0.0f; + dest[2][3] = 0.0f; + dest[3][0] = 0.0f; + dest[3][1] = 0.0f; + dest[3][2] = 0.0f; + dest[3][3] = 1.0f; +} + + +/*! + * @brief build rotation matrix from euler angles + * + * @param[in] angles angles as vector [Xangle, Yangle, Zangle] + * @param[out] dest rotation matrix + */ +CGLM_INLINE +void +glm_euler_yxz(vec3 angles, mat4 dest) { + float cx, cy, cz, + sx, sy, sz, cycz, sysz, czsy, cysz; + + sx = sinf(angles[0]); cx = cosf(angles[0]); + sy = sinf(angles[1]); cy = cosf(angles[1]); + sz = sinf(angles[2]); cz = cosf(angles[2]); + + cycz = cy * cz; + sysz = sy * sz; + czsy = cz * sy; + cysz = cy * sz; + + dest[0][0] = cycz + sx * sysz; + dest[0][1] = cx * sz; + dest[0][2] = -czsy + cysz * sx; + dest[1][0] = -cysz + czsy * sx; + dest[1][1] = cx * cz; + dest[1][2] = cycz * sx + sysz; + dest[2][0] = cx * sy; + dest[2][1] = -sx; + dest[2][2] = cx * cy; + dest[0][3] = 0.0f; + dest[1][3] = 0.0f; + dest[2][3] = 0.0f; + dest[3][0] = 0.0f; + dest[3][1] = 0.0f; + dest[3][2] = 0.0f; + dest[3][3] = 1.0f; +} + +/*! + * @brief build rotation matrix from euler angles + * + * @param[in] angles angles as vector [Xangle, Yangle, Zangle] + * @param[out] dest rotation matrix + */ +CGLM_INLINE +void +glm_euler_yzx(vec3 angles, mat4 dest) { + float cx, cy, cz, + sx, sy, sz, sxsy, cxcy, cysx, cxsy; + + sx = sinf(angles[0]); cx = cosf(angles[0]); + sy = sinf(angles[1]); cy = cosf(angles[1]); + sz = sinf(angles[2]); cz = cosf(angles[2]); + + sxsy = sx * sy; + cxcy = cx * cy; + cysx = cy * sx; + cxsy = cx * sy; + + dest[0][0] = cy * cz; + dest[0][1] = sz; + dest[0][2] = -cz * sy; + dest[1][0] = sxsy - cxcy * sz; + dest[1][1] = cx * cz; + dest[1][2] = cysx + cxsy * sz; + dest[2][0] = cxsy + cysx * sz; + dest[2][1] = -cz * sx; + dest[2][2] = cxcy - sxsy * sz; + dest[0][3] = 0.0f; + dest[1][3] = 0.0f; + dest[2][3] = 0.0f; + dest[3][0] = 0.0f; + dest[3][1] = 0.0f; + dest[3][2] = 0.0f; + dest[3][3] = 1.0f; +} + +/*! + * @brief build rotation matrix from euler angles + * + * @param[in] angles angles as vector [Xangle, Yangle, Zangle] + * @param[out] dest rotation matrix + */ +CGLM_INLINE +void +glm_euler_zxy(vec3 angles, mat4 dest) { + float cx, cy, cz, + sx, sy, sz, cycz, sxsy, cysz; + + sx = sinf(angles[0]); cx = cosf(angles[0]); + sy = sinf(angles[1]); cy = cosf(angles[1]); + sz = sinf(angles[2]); cz = cosf(angles[2]); + + cycz = cy * cz; + sxsy = sx * sy; + cysz = cy * sz; + + dest[0][0] = cycz - sxsy * sz; + dest[0][1] = cz * sxsy + cysz; + dest[0][2] = -cx * sy; + dest[1][0] = -cx * sz; + dest[1][1] = cx * cz; + dest[1][2] = sx; + dest[2][0] = cz * sy + cysz * sx; + dest[2][1] = -cycz * sx + sy * sz; + dest[2][2] = cx * cy; + dest[0][3] = 0.0f; + dest[1][3] = 0.0f; + dest[2][3] = 0.0f; + dest[3][0] = 0.0f; + dest[3][1] = 0.0f; + dest[3][2] = 0.0f; + dest[3][3] = 1.0f; +} + +/*! + * @brief build rotation matrix from euler angles + * + * @param[in] angles angles as vector [Xangle, Yangle, Zangle] + * @param[out] dest rotation matrix + */ +CGLM_INLINE +void +glm_euler_zyx(vec3 angles, mat4 dest) { + float cx, cy, cz, + sx, sy, sz, czsx, cxcz, sysz; + + sx = sinf(angles[0]); cx = cosf(angles[0]); + sy = sinf(angles[1]); cy = cosf(angles[1]); + sz = sinf(angles[2]); cz = cosf(angles[2]); + + czsx = cz * sx; + cxcz = cx * cz; + sysz = sy * sz; + + dest[0][0] = cy * cz; + dest[0][1] = cy * sz; + dest[0][2] = -sy; + dest[1][0] = czsx * sy - cx * sz; + dest[1][1] = cxcz + sx * sysz; + dest[1][2] = cy * sx; + dest[2][0] = cxcz * sy + sx * sz; + dest[2][1] = -czsx + cx * sysz; + dest[2][2] = cx * cy; + dest[0][3] = 0.0f; + dest[1][3] = 0.0f; + dest[2][3] = 0.0f; + dest[3][0] = 0.0f; + dest[3][1] = 0.0f; + dest[3][2] = 0.0f; + dest[3][3] = 1.0f; +} + +/*! + * @brief build rotation matrix from euler angles + * + * @param[in] angles angles as vector [Xangle, Yangle, Zangle] + * @param[in] ord euler order + * @param[out] dest rotation matrix + */ +CGLM_INLINE +void +glm_euler_by_order(vec3 angles, glm_euler_seq ord, mat4 dest) { + float cx, cy, cz, + sx, sy, sz; + + float cycz, cysz, cysx, cxcy, + czsy, cxcz, czsx, cxsz, + sysz; + + sx = sinf(angles[0]); cx = cosf(angles[0]); + sy = sinf(angles[1]); cy = cosf(angles[1]); + sz = sinf(angles[2]); cz = cosf(angles[2]); + + cycz = cy * cz; cysz = cy * sz; + cysx = cy * sx; cxcy = cx * cy; + czsy = cz * sy; cxcz = cx * cz; + czsx = cz * sx; cxsz = cx * sz; + sysz = sy * sz; + + switch (ord) { + case GLM_EULER_XZY: + dest[0][0] = cycz; + dest[0][1] = sx * sy + cx * cysz; + dest[0][2] = -cx * sy + cysx * sz; + dest[1][0] = -sz; + dest[1][1] = cxcz; + dest[1][2] = czsx; + dest[2][0] = czsy; + dest[2][1] = -cysx + cx * sysz; + dest[2][2] = cxcy + sx * sysz; + break; + case GLM_EULER_XYZ: + dest[0][0] = cycz; + dest[0][1] = czsx * sy + cxsz; + dest[0][2] = -cx * czsy + sx * sz; + dest[1][0] = -cysz; + dest[1][1] = cxcz - sx * sysz; + dest[1][2] = czsx + cx * sysz; + dest[2][0] = sy; + dest[2][1] = -cysx; + dest[2][2] = cxcy; + break; + case GLM_EULER_YXZ: + dest[0][0] = cycz + sx * sysz; + dest[0][1] = cxsz; + dest[0][2] = -czsy + cysx * sz; + dest[1][0] = czsx * sy - cysz; + dest[1][1] = cxcz; + dest[1][2] = cycz * sx + sysz; + dest[2][0] = cx * sy; + dest[2][1] = -sx; + dest[2][2] = cxcy; + break; + case GLM_EULER_YZX: + dest[0][0] = cycz; + dest[0][1] = sz; + dest[0][2] = -czsy; + dest[1][0] = sx * sy - cx * cysz; + dest[1][1] = cxcz; + dest[1][2] = cysx + cx * sysz; + dest[2][0] = cx * sy + cysx * sz; + dest[2][1] = -czsx; + dest[2][2] = cxcy - sx * sysz; + break; + case GLM_EULER_ZXY: + dest[0][0] = cycz - sx * sysz; + dest[0][1] = czsx * sy + cysz; + dest[0][2] = -cx * sy; + dest[1][0] = -cxsz; + dest[1][1] = cxcz; + dest[1][2] = sx; + dest[2][0] = czsy + cysx * sz; + dest[2][1] = -cycz * sx + sysz; + dest[2][2] = cxcy; + break; + case GLM_EULER_ZYX: + dest[0][0] = cycz; + dest[0][1] = cysz; + dest[0][2] = -sy; + dest[1][0] = czsx * sy - cxsz; + dest[1][1] = cxcz + sx * sysz; + dest[1][2] = cysx; + dest[2][0] = cx * czsy + sx * sz; + dest[2][1] = -czsx + cx * sysz; + dest[2][2] = cxcy; + break; + } + + dest[0][3] = 0.0f; + dest[1][3] = 0.0f; + dest[2][3] = 0.0f; + dest[3][0] = 0.0f; + dest[3][1] = 0.0f; + dest[3][2] = 0.0f; + dest[3][3] = 1.0f; +} + +#endif /* cglm_euler_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_box_h +#define cglm_box_h + + +/*! + * @brief apply transform to Axis-Aligned Bounding Box + * + * @param[in] box bounding box + * @param[in] m transform matrix + * @param[out] dest transformed bounding box + */ +CGLM_INLINE +void +glm_aabb_transform(vec3 box[2], mat4 m, vec3 dest[2]) { + vec3 v[2], xa, xb, ya, yb, za, zb; + + glm_vec3_scale(m[0], box[0][0], xa); + glm_vec3_scale(m[0], box[1][0], xb); + + glm_vec3_scale(m[1], box[0][1], ya); + glm_vec3_scale(m[1], box[1][1], yb); + + glm_vec3_scale(m[2], box[0][2], za); + glm_vec3_scale(m[2], box[1][2], zb); + + /* translation + min(xa, xb) + min(ya, yb) + min(za, zb) */ + glm_vec3(m[3], v[0]); + glm_vec3_minadd(xa, xb, v[0]); + glm_vec3_minadd(ya, yb, v[0]); + glm_vec3_minadd(za, zb, v[0]); + + /* translation + max(xa, xb) + max(ya, yb) + max(za, zb) */ + glm_vec3(m[3], v[1]); + glm_vec3_maxadd(xa, xb, v[1]); + glm_vec3_maxadd(ya, yb, v[1]); + glm_vec3_maxadd(za, zb, v[1]); + + glm_vec3_copy(v[0], dest[0]); + glm_vec3_copy(v[1], dest[1]); +} + +/*! + * @brief merges two AABB bounding box and creates new one + * + * two box must be in same space, if one of box is in different space then + * you should consider to convert it's space by glm_box_space + * + * @param[in] box1 bounding box 1 + * @param[in] box2 bounding box 2 + * @param[out] dest merged bounding box + */ +CGLM_INLINE +void +glm_aabb_merge(vec3 box1[2], vec3 box2[2], vec3 dest[2]) { + dest[0][0] = glm_min(box1[0][0], box2[0][0]); + dest[0][1] = glm_min(box1[0][1], box2[0][1]); + dest[0][2] = glm_min(box1[0][2], box2[0][2]); + + dest[1][0] = glm_max(box1[1][0], box2[1][0]); + dest[1][1] = glm_max(box1[1][1], box2[1][1]); + dest[1][2] = glm_max(box1[1][2], box2[1][2]); +} + +/*! + * @brief crops a bounding box with another one. + * + * this could be useful for gettng a bbox which fits with view frustum and + * object bounding boxes. In this case you crop view frustum box with objects + * box + * + * @param[in] box bounding box 1 + * @param[in] cropBox crop box + * @param[out] dest cropped bounding box + */ +CGLM_INLINE +void +glm_aabb_crop(vec3 box[2], vec3 cropBox[2], vec3 dest[2]) { + dest[0][0] = glm_max(box[0][0], cropBox[0][0]); + dest[0][1] = glm_max(box[0][1], cropBox[0][1]); + dest[0][2] = glm_max(box[0][2], cropBox[0][2]); + + dest[1][0] = glm_min(box[1][0], cropBox[1][0]); + dest[1][1] = glm_min(box[1][1], cropBox[1][1]); + dest[1][2] = glm_min(box[1][2], cropBox[1][2]); +} + +/*! + * @brief crops a bounding box with another one. + * + * this could be useful for gettng a bbox which fits with view frustum and + * object bounding boxes. In this case you crop view frustum box with objects + * box + * + * @param[in] box bounding box + * @param[in] cropBox crop box + * @param[in] clampBox miniumum box + * @param[out] dest cropped bounding box + */ +CGLM_INLINE +void +glm_aabb_crop_until(vec3 box[2], + vec3 cropBox[2], + vec3 clampBox[2], + vec3 dest[2]) { + glm_aabb_crop(box, cropBox, dest); + glm_aabb_merge(clampBox, dest, dest); +} + +/*! + * @brief check if AABB intersects with frustum planes + * + * this could be useful for frustum culling using AABB. + * + * OPTIMIZATION HINT: + * if planes order is similar to LEFT, RIGHT, BOTTOM, TOP, NEAR, FAR + * then this method should run even faster because it would only use two + * planes if object is not inside the two planes + * fortunately cglm extracts planes as this order! just pass what you got! + * + * @param[in] box bounding box + * @param[in] planes frustum planes + */ +CGLM_INLINE +bool +glm_aabb_frustum(vec3 box[2], vec4 planes[6]) { + float *p, dp; + int i; + + for (i = 0; i < 6; i++) { + p = planes[i]; + dp = p[0] * box[p[0] > 0.0f][0] + + p[1] * box[p[1] > 0.0f][1] + + p[2] * box[p[2] > 0.0f][2]; + + if (dp < -p[3]) + return false; + } + + return true; +} + +/*! + * @brief invalidate AABB min and max values + * + * @param[in, out] box bounding box + */ +CGLM_INLINE +void +glm_aabb_invalidate(vec3 box[2]) { + glm_vec3_broadcast(FLT_MAX, box[0]); + glm_vec3_broadcast(-FLT_MAX, box[1]); +} + +/*! + * @brief check if AABB is valid or not + * + * @param[in] box bounding box + */ +CGLM_INLINE +bool +glm_aabb_isvalid(vec3 box[2]) { + return glm_vec3_max(box[0]) != FLT_MAX + && glm_vec3_min(box[1]) != -FLT_MAX; +} + +/*! + * @brief distance between of min and max + * + * @param[in] box bounding box + */ +CGLM_INLINE +float +glm_aabb_size(vec3 box[2]) { + return glm_vec3_distance(box[0], box[1]); +} + +/*! + * @brief radius of sphere which surrounds AABB + * + * @param[in] box bounding box + */ +CGLM_INLINE +float +glm_aabb_radius(vec3 box[2]) { + return glm_aabb_size(box) * 0.5f; +} + +/*! + * @brief computes center point of AABB + * + * @param[in] box bounding box + * @param[out] dest center of bounding box + */ +CGLM_INLINE +void +glm_aabb_center(vec3 box[2], vec3 dest) { + glm_vec3_center(box[0], box[1], dest); +} + +/*! + * @brief check if two AABB intersects + * + * @param[in] box bounding box + * @param[in] other other bounding box + */ +CGLM_INLINE +bool +glm_aabb_aabb(vec3 box[2], vec3 other[2]) { + return (box[0][0] <= other[1][0] && box[1][0] >= other[0][0]) + && (box[0][1] <= other[1][1] && box[1][1] >= other[0][1]) + && (box[0][2] <= other[1][2] && box[1][2] >= other[0][2]); +} + +/*! + * @brief check if AABB intersects with sphere + * + * https://github.com/erich666/GraphicsGems/blob/master/gems/BoxSphere.c + * Solid Box - Solid Sphere test. + * + * @param[in] box solid bounding box + * @param[in] s solid sphere + */ +CGLM_INLINE +bool +glm_aabb_sphere(vec3 box[2], vec4 s) { + float dmin; + int a, b, c; + + a = s[0] >= box[0][0]; + b = s[1] >= box[0][1]; + c = s[2] >= box[0][2]; + + dmin = glm_pow2(s[0] - box[a][0]) + + glm_pow2(s[1] - box[b][1]) + + glm_pow2(s[2] - box[c][2]); + + return dmin <= glm_pow2(s[3]); +} + +/*! + * @brief check if point is inside of AABB + * + * @param[in] box bounding box + * @param[in] point point + */ +CGLM_INLINE +bool +glm_aabb_point(vec3 box[2], vec3 point) { + return (point[0] >= box[0][0] && point[0] <= box[1][0]) + && (point[1] >= box[0][1] && point[1] <= box[1][1]) + && (point[2] >= box[0][2] && point[2] <= box[1][2]); +} + +/*! + * @brief check if AABB contains other AABB + * + * @param[in] box bounding box + * @param[in] other other bounding box + */ +CGLM_INLINE +bool +glm_aabb_contains(vec3 box[2], vec3 other[2]) { + return (box[0][0] <= other[0][0] && box[1][0] >= other[1][0]) + && (box[0][1] <= other[0][1] && box[1][1] >= other[1][1]) + && (box[0][2] <= other[0][2] && box[1][2] >= other[1][2]); +} + +#endif /* cglm_box_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_color_h +#define cglm_color_h + + +/*! + * @brief averages the color channels into one value + * + * @param[in] rgb RGB color + */ +CGLM_INLINE +float +glm_luminance(vec3 rgb) { + vec3 l = {0.212671f, 0.715160f, 0.072169f}; + return glm_dot(rgb, l); +} + +#endif /* cglm_color_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Functions: + CGLM_INLINE void glm_mat4_print(mat4 matrix, FILE *ostream); + CGLM_INLINE void glm_mat3_print(mat3 matrix, FILE *ostream); + CGLM_INLINE void glm_vec4_print(vec4 vec, FILE *ostream); + CGLM_INLINE void glm_vec3_print(vec3 vec, FILE *ostream); + CGLM_INLINE void glm_ivec3_print(ivec3 vec, FILE *ostream); + CGLM_INLINE void glm_versor_print(versor vec, FILE *ostream); + */ + +/* + cglm tried to enable print functions in debug mode and disable them in + release/production mode to eliminate printing costs. + + if you need to force enable then define CGLM_DEFINE_PRINTS macro not DEBUG one + + Print functions are enabled if: + + - DEBUG or _DEBUG macro is defined (mostly defined automatically in debugging) + - CGLM_DEFINE_PRINTS macro is defined including release/production + which makes enabled printing always + - glmc_ calls for io are always prints + + */ + +/* DEPRECATED: CGLM_NO_PRINTS_NOOP (use CGLM_DEFINE_PRINTS) */ + +#ifndef cglm_io_h +#define cglm_io_h +#if defined(DEBUG) || defined(_DEBUG) \ + || defined(CGLM_DEFINE_PRINTS) || defined(CGLM_LIB_SRC) \ + || defined(CGLM_NO_PRINTS_NOOP) + + +#include +#include + +#ifndef CGLM_PRINT_PRECISION +# define CGLM_PRINT_PRECISION 5 +#endif + +#ifndef CGLM_PRINT_MAX_TO_SHORT +# define CGLM_PRINT_MAX_TO_SHORT 1e5 +#endif + +#ifndef CGLM_PRINT_COLOR +# define CGLM_PRINT_COLOR "\033[36m" +#endif + +#ifndef CGLM_PRINT_COLOR_RESET +# define CGLM_PRINT_COLOR_RESET "\033[0m" +#endif + +CGLM_INLINE +void +glm_mat4_print(mat4 matrix, + FILE * __restrict ostream) { + char buff[16]; + int i, j, cw[4], cwi; + +#define m 4 +#define n 4 + + fprintf(ostream, "Matrix (float%dx%d): " CGLM_PRINT_COLOR "\n" , m, n); + + cw[0] = cw[1] = cw[2] = cw[3] = 0; + + for (i = 0; i < m; i++) { + for (j = 0; j < n; j++) { + if (matrix[i][j] < CGLM_PRINT_MAX_TO_SHORT) + cwi = sprintf(buff, "% .*f", CGLM_PRINT_PRECISION, matrix[i][j]); + else + cwi = sprintf(buff, "% g", matrix[i][j]); + cw[i] = GLM_MAX(cw[i], cwi); + } + } + + for (i = 0; i < m; i++) { + fprintf(ostream, " |"); + + for (j = 0; j < n; j++) + if (matrix[i][j] < CGLM_PRINT_MAX_TO_SHORT) + fprintf(ostream, " % *.*f", cw[j], CGLM_PRINT_PRECISION, matrix[j][i]); + else + fprintf(ostream, " % *g", cw[j], matrix[j][i]); + + fprintf(ostream, " |\n"); + } + + fprintf(ostream, CGLM_PRINT_COLOR_RESET "\n"); + +#undef m +#undef n +} + + +CGLM_INLINE +void +glm_mat3_print(mat3 matrix, + FILE * __restrict ostream) { + char buff[16]; + int i, j, cw[4], cwi; + +#define m 3 +#define n 3 + + fprintf(ostream, "Matrix (float%dx%d): " CGLM_PRINT_COLOR "\n", m, n); + + cw[0] = cw[1] = cw[2] = 0; + + for (i = 0; i < m; i++) { + for (j = 0; j < n; j++) { + if (matrix[i][j] < CGLM_PRINT_MAX_TO_SHORT) + cwi = sprintf(buff, "% .*f", CGLM_PRINT_PRECISION, matrix[i][j]); + else + cwi = sprintf(buff, "% g", matrix[i][j]); + cw[i] = GLM_MAX(cw[i], cwi); + } + } + + for (i = 0; i < m; i++) { + fprintf(ostream, " |"); + + for (j = 0; j < n; j++) + if (matrix[i][j] < CGLM_PRINT_MAX_TO_SHORT) + fprintf(ostream, " % *.*f", cw[j], CGLM_PRINT_PRECISION, matrix[j][i]); + else + fprintf(ostream, " % *g", cw[j], matrix[j][i]); + + fprintf(ostream, " |\n"); + } + + fprintf(ostream, CGLM_PRINT_COLOR_RESET "\n"); + +#undef m +#undef n +} + +CGLM_INLINE +void +glm_mat2_print(mat2 matrix, + FILE * __restrict ostream) { + char buff[16]; + int i, j, cw[4], cwi; + +#define m 2 +#define n 2 + + fprintf(ostream, "Matrix (float%dx%d): " CGLM_PRINT_COLOR "\n", m, n); + + cw[0] = cw[1] = 0; + + for (i = 0; i < m; i++) { + for (j = 0; j < n; j++) { + if (matrix[i][j] < CGLM_PRINT_MAX_TO_SHORT) + cwi = sprintf(buff, "% .*f", CGLM_PRINT_PRECISION, matrix[i][j]); + else + cwi = sprintf(buff, "% g", matrix[i][j]); + cw[i] = GLM_MAX(cw[i], cwi); + } + } + + for (i = 0; i < m; i++) { + fprintf(ostream, " |"); + + for (j = 0; j < n; j++) + if (matrix[i][j] < CGLM_PRINT_MAX_TO_SHORT) + fprintf(ostream, " % *.*f", cw[j], CGLM_PRINT_PRECISION, matrix[j][i]); + else + fprintf(ostream, " % *g", cw[j], matrix[j][i]); + + fprintf(ostream, " |\n"); + } + + fprintf(ostream, CGLM_PRINT_COLOR_RESET "\n"); + +#undef m +#undef n +} + +CGLM_INLINE +void +glm_vec4_print(vec4 vec, + FILE * __restrict ostream) { + int i; + +#define m 4 + + fprintf(ostream, "Vector (float%d): " CGLM_PRINT_COLOR "\n (", m); + + for (i = 0; i < m; i++) { + if (vec[i] < CGLM_PRINT_MAX_TO_SHORT) + fprintf(ostream, " % .*f", CGLM_PRINT_PRECISION, vec[i]); + else + fprintf(ostream, " % g", vec[i]); + } + + fprintf(ostream, " )" CGLM_PRINT_COLOR_RESET "\n\n"); + +#undef m +} + +CGLM_INLINE +void +glm_vec3_print(vec3 vec, + FILE * __restrict ostream) { + int i; + +#define m 3 + + fprintf(ostream, "Vector (float%d): " CGLM_PRINT_COLOR "\n (", m); + + for (i = 0; i < m; i++) { + if (vec[i] < CGLM_PRINT_MAX_TO_SHORT) + fprintf(ostream, " % .*f", CGLM_PRINT_PRECISION, vec[i]); + else + fprintf(ostream, " % g", vec[i]); + } + + fprintf(ostream, " )" CGLM_PRINT_COLOR_RESET "\n\n"); + +#undef m +} + +CGLM_INLINE +void +glm_ivec3_print(ivec3 vec, + FILE * __restrict ostream) { + int i; + +#define m 3 + + fprintf(ostream, "Vector (int%d): " CGLM_PRINT_COLOR "\n (", m); + + for (i = 0; i < m; i++) + fprintf(ostream, " % d", vec[i]); + + fprintf(ostream, " )" CGLM_PRINT_COLOR_RESET "\n\n"); + +#undef m +} + +CGLM_INLINE +void +glm_vec2_print(vec2 vec, + FILE * __restrict ostream) { + int i; + +#define m 2 + + fprintf(ostream, "Vector (float%d): " CGLM_PRINT_COLOR "\n (", m); + + for (i = 0; i < m; i++) { + if (vec[i] < CGLM_PRINT_MAX_TO_SHORT) + fprintf(ostream, " % .*f", CGLM_PRINT_PRECISION, vec[i]); + else + fprintf(ostream, " % g", vec[i]); + } + + fprintf(ostream, " )" CGLM_PRINT_COLOR_RESET "\n\n"); + +#undef m +} + +CGLM_INLINE +void +glm_versor_print(versor vec, + FILE * __restrict ostream) { + int i; + +#define m 4 + + fprintf(ostream, "Quaternion (float%d): " CGLM_PRINT_COLOR "\n (", m); + + for (i = 0; i < m; i++) { + if (vec[i] < CGLM_PRINT_MAX_TO_SHORT) + fprintf(ostream, " % .*f", CGLM_PRINT_PRECISION, vec[i]); + else + fprintf(ostream, " % g", vec[i]); + } + + + fprintf(ostream, " )" CGLM_PRINT_COLOR_RESET "\n\n"); + +#undef m +} + +CGLM_INLINE +void +glm_aabb_print(vec3 bbox[2], + const char * __restrict tag, + FILE * __restrict ostream) { + int i, j; + +#define m 3 + + fprintf(ostream, "AABB (%s): " CGLM_PRINT_COLOR "\n", tag ? tag: "float"); + + for (i = 0; i < 2; i++) { + fprintf(ostream, " ("); + + for (j = 0; j < m; j++) { + if (bbox[i][j] < CGLM_PRINT_MAX_TO_SHORT) + fprintf(ostream, " % .*f", CGLM_PRINT_PRECISION, bbox[i][j]); + else + fprintf(ostream, " % g", bbox[i][j]); + } + + fprintf(ostream, " )\n"); + } + + fprintf(ostream, CGLM_PRINT_COLOR_RESET "\n"); + +#undef m +} + +#else + + +#include +#include + +/* NOOP: Remove print from DEBUG */ +#define glm_mat4_print(v, s) (void)v; (void)s; +#define glm_mat3_print(v, s) (void)v; (void)s; +#define glm_mat2_print(v, s) (void)v; (void)s; +#define glm_vec4_print(v, s) (void)v; (void)s; +#define glm_vec3_print(v, s) (void)v; (void)s; +#define glm_ivec3_print(v, s) (void)v; (void)s; +#define glm_vec2_print(v, s) (void)v; (void)s; +#define glm_versor_print(v, s) (void)v; (void)s; +#define glm_aabb_print(v, t, s) (void)v; (void)t; (void)s; + +#endif +#endif /* cglm_io_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_project_h +#define cglm_project_h + + +/*! + * @brief maps the specified viewport coordinates into specified space [1] + * the matrix should contain projection matrix. + * + * if you don't have ( and don't want to have ) an inverse matrix then use + * glm_unproject version. You may use existing inverse of matrix in somewhere + * else, this is why glm_unprojecti exists to save save inversion cost + * + * [1] space: + * 1- if m = invProj: View Space + * 2- if m = invViewProj: World Space + * 3- if m = invMVP: Object Space + * + * You probably want to map the coordinates into object space + * so use invMVP as m + * + * Computing viewProj: + * glm_mat4_mul(proj, view, viewProj); + * glm_mat4_mul(viewProj, model, MVP); + * glm_mat4_inv(viewProj, invMVP); + * + * @param[in] pos point/position in viewport coordinates + * @param[in] invMat matrix (see brief) + * @param[in] vp viewport as [x, y, width, height] + * @param[out] dest unprojected coordinates + */ +CGLM_INLINE +void +glm_unprojecti(vec3 pos, mat4 invMat, vec4 vp, vec3 dest) { + vec4 v; + + v[0] = 2.0f * (pos[0] - vp[0]) / vp[2] - 1.0f; + v[1] = 2.0f * (pos[1] - vp[1]) / vp[3] - 1.0f; + v[2] = 2.0f * pos[2] - 1.0f; + v[3] = 1.0f; + + glm_mat4_mulv(invMat, v, v); + glm_vec4_scale(v, 1.0f / v[3], v); + glm_vec3(v, dest); +} + +/*! + * @brief maps the specified viewport coordinates into specified space [1] + * the matrix should contain projection matrix. + * + * this is same as glm_unprojecti except this function get inverse matrix for + * you. + * + * [1] space: + * 1- if m = proj: View Space + * 2- if m = viewProj: World Space + * 3- if m = MVP: Object Space + * + * You probably want to map the coordinates into object space + * so use MVP as m + * + * Computing viewProj and MVP: + * glm_mat4_mul(proj, view, viewProj); + * glm_mat4_mul(viewProj, model, MVP); + * + * @param[in] pos point/position in viewport coordinates + * @param[in] m matrix (see brief) + * @param[in] vp viewport as [x, y, width, height] + * @param[out] dest unprojected coordinates + */ +CGLM_INLINE +void +glm_unproject(vec3 pos, mat4 m, vec4 vp, vec3 dest) { + mat4 inv; + glm_mat4_inv(m, inv); + glm_unprojecti(pos, inv, vp, dest); +} + +/*! + * @brief map object coordinates to window coordinates + * + * Computing MVP: + * glm_mat4_mul(proj, view, viewProj); + * glm_mat4_mul(viewProj, model, MVP); + * + * @param[in] pos object coordinates + * @param[in] m MVP matrix + * @param[in] vp viewport as [x, y, width, height] + * @param[out] dest projected coordinates + */ +CGLM_INLINE +void +glm_project(vec3 pos, mat4 m, vec4 vp, vec3 dest) { + CGLM_ALIGN(16) vec4 pos4, vone = GLM_VEC4_ONE_INIT; + + glm_vec4(pos, 1.0f, pos4); + + glm_mat4_mulv(m, pos4, pos4); + glm_vec4_scale(pos4, 1.0f / pos4[3], pos4); /* pos = pos / pos.w */ + glm_vec4_add(pos4, vone, pos4); + glm_vec4_scale(pos4, 0.5f, pos4); + + dest[0] = pos4[0] * vp[2] + vp[0]; + dest[1] = pos4[1] * vp[3] + vp[1]; + dest[2] = pos4[2]; +} + +#endif /* cglm_project_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_sphere_h +#define cglm_sphere_h + + +/* + Sphere Representation in cglm: [center.x, center.y, center.z, radii] + + You could use this representation or you can convert it to vec4 before call + any function + */ + +/*! + * @brief helper for getting sphere radius + * + * @param[in] s sphere + * + * @return returns radii + */ +CGLM_INLINE +float +glm_sphere_radii(vec4 s) { + return s[3]; +} + +/*! + * @brief apply transform to sphere, it is just wrapper for glm_mat4_mulv3 + * + * @param[in] s sphere + * @param[in] m transform matrix + * @param[out] dest transformed sphere + */ +CGLM_INLINE +void +glm_sphere_transform(vec4 s, mat4 m, vec4 dest) { + glm_mat4_mulv3(m, s, 1.0f, dest); + dest[3] = s[3]; +} + +/*! + * @brief merges two spheres and creates a new one + * + * two sphere must be in same space, for instance if one in world space then + * the other must be in world space too, not in local space. + * + * @param[in] s1 sphere 1 + * @param[in] s2 sphere 2 + * @param[out] dest merged/extended sphere + */ +CGLM_INLINE +void +glm_sphere_merge(vec4 s1, vec4 s2, vec4 dest) { + float dist, radii; + + dist = glm_vec3_distance(s1, s2); + radii = dist + s1[3] + s2[3]; + + radii = glm_max(radii, s1[3]); + radii = glm_max(radii, s2[3]); + + glm_vec3_center(s1, s2, dest); + dest[3] = radii; +} + +/*! + * @brief check if two sphere intersects + * + * @param[in] s1 sphere + * @param[in] s2 other sphere + */ +CGLM_INLINE +bool +glm_sphere_sphere(vec4 s1, vec4 s2) { + return glm_vec3_distance2(s1, s2) <= glm_pow2(s1[3] + s2[3]); +} + +/*! + * @brief check if sphere intersects with point + * + * @param[in] s sphere + * @param[in] point point + */ +CGLM_INLINE +bool +glm_sphere_point(vec4 s, vec3 point) { + float rr; + rr = s[3] * s[3]; + return glm_vec3_distance2(point, s) <= rr; +} + +#endif /* cglm_sphere_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_ease_h +#define cglm_ease_h + + +CGLM_INLINE +float +glm_ease_linear(float t) { + return t; +} + +CGLM_INLINE +float +glm_ease_sine_in(float t) { + return sinf((t - 1.0f) * GLM_PI_2f) + 1.0f; +} + +CGLM_INLINE +float +glm_ease_sine_out(float t) { + return sinf(t * GLM_PI_2f); +} + +CGLM_INLINE +float +glm_ease_sine_inout(float t) { + return 0.5f * (1.0f - cosf(t * GLM_PIf)); +} + +CGLM_INLINE +float +glm_ease_quad_in(float t) { + return t * t; +} + +CGLM_INLINE +float +glm_ease_quad_out(float t) { + return -(t * (t - 2.0f)); +} + +CGLM_INLINE +float +glm_ease_quad_inout(float t) { + float tt; + + tt = t * t; + if (t < 0.5f) + return 2.0f * tt; + + return (-2.0f * tt) + (4.0f * t) - 1.0f; +} + +CGLM_INLINE +float +glm_ease_cubic_in(float t) { + return t * t * t; +} + +CGLM_INLINE +float +glm_ease_cubic_out(float t) { + float f; + f = t - 1.0f; + return f * f * f + 1.0f; +} + +CGLM_INLINE +float +glm_ease_cubic_inout(float t) { + float f; + + if (t < 0.5f) + return 4.0f * t * t * t; + + f = 2.0f * t - 2.0f; + + return 0.5f * f * f * f + 1.0f; +} + +CGLM_INLINE +float +glm_ease_quart_in(float t) { + float f; + f = t * t; + return f * f; +} + +CGLM_INLINE +float +glm_ease_quart_out(float t) { + float f; + + f = t - 1.0f; + + return f * f * f * (1.0f - t) + 1.0f; +} + +CGLM_INLINE +float +glm_ease_quart_inout(float t) { + float f, g; + + if (t < 0.5f) { + f = t * t; + return 8.0f * f * f; + } + + f = t - 1.0f; + g = f * f; + + return -8.0f * g * g + 1.0f; +} + +CGLM_INLINE +float +glm_ease_quint_in(float t) { + float f; + f = t * t; + return f * f * t; +} + +CGLM_INLINE +float +glm_ease_quint_out(float t) { + float f, g; + + f = t - 1.0f; + g = f * f; + + return g * g * f + 1.0f; +} + +CGLM_INLINE +float +glm_ease_quint_inout(float t) { + float f, g; + + if (t < 0.5f) { + f = t * t; + return 16.0f * f * f * t; + } + + f = 2.0f * t - 2.0f; + g = f * f; + + return 0.5f * g * g * f + 1.0f; +} + +CGLM_INLINE +float +glm_ease_exp_in(float t) { + if (t == 0.0f) + return t; + + return powf(2.0f, 10.0f * (t - 1.0f)); +} + +CGLM_INLINE +float +glm_ease_exp_out(float t) { + if (t == 1.0f) + return t; + + return 1.0f - powf(2.0f, -10.0f * t); +} + +CGLM_INLINE +float +glm_ease_exp_inout(float t) { + if (t == 0.0f || t == 1.0f) + return t; + + if (t < 0.5f) + return 0.5f * powf(2.0f, (20.0f * t) - 10.0f); + + return -0.5f * powf(2.0f, (-20.0f * t) + 10.0f) + 1.0f; +} + +CGLM_INLINE +float +glm_ease_circ_in(float t) { + return 1.0f - sqrtf(1.0f - (t * t)); +} + +CGLM_INLINE +float +glm_ease_circ_out(float t) { + return sqrtf((2.0f - t) * t); +} + +CGLM_INLINE +float +glm_ease_circ_inout(float t) { + if (t < 0.5f) + return 0.5f * (1.0f - sqrtf(1.0f - 4.0f * (t * t))); + + return 0.5f * (sqrtf(-((2.0f * t) - 3.0f) * ((2.0f * t) - 1.0f)) + 1.0f); +} + +CGLM_INLINE +float +glm_ease_back_in(float t) { + float o, z; + + o = 1.70158f; + z = ((o + 1.0f) * t) - o; + + return t * t * z; +} + +CGLM_INLINE +float +glm_ease_back_out(float t) { + float o, z, n; + + o = 1.70158f; + n = t - 1.0f; + z = (o + 1.0f) * n + o; + + return n * n * z + 1.0f; +} + +CGLM_INLINE +float +glm_ease_back_inout(float t) { + float o, z, n, m, s, x; + + o = 1.70158f; + s = o * 1.525f; + x = 0.5; + n = t / 0.5f; + + if (n < 1.0f) { + z = (s + 1) * n - s; + m = n * n * z; + return x * m; + } + + n -= 2.0f; + z = (s + 1.0f) * n + s; + m = (n * n * z) + 2; + + return x * m; +} + +CGLM_INLINE +float +glm_ease_elast_in(float t) { + return sinf(13.0f * GLM_PI_2f * t) * powf(2.0f, 10.0f * (t - 1.0f)); +} + +CGLM_INLINE +float +glm_ease_elast_out(float t) { + return sinf(-13.0f * GLM_PI_2f * (t + 1.0f)) * powf(2.0f, -10.0f * t) + 1.0f; +} + +CGLM_INLINE +float +glm_ease_elast_inout(float t) { + float a; + + a = 2.0f * t; + + if (t < 0.5f) + return 0.5f * sinf(13.0f * GLM_PI_2f * a) + * powf(2.0f, 10.0f * (a - 1.0f)); + + return 0.5f * (sinf(-13.0f * GLM_PI_2f * a) + * powf(2.0f, -10.0f * (a - 1.0f)) + 2.0f); +} + +CGLM_INLINE +float +glm_ease_bounce_out(float t) { + float tt; + + tt = t * t; + + if (t < (4.0f / 11.0f)) + return (121.0f * tt) / 16.0f; + + if (t < 8.0f / 11.0f) + return ((363.0f / 40.0f) * tt) - ((99.0f / 10.0f) * t) + (17.0f / 5.0f); + + if (t < (9.0f / 10.0f)) + return (4356.0f / 361.0f) * tt + - (35442.0f / 1805.0f) * t + + (16061.0f / 1805.0f); + + return ((54.0f / 5.0f) * tt) - ((513.0f / 25.0f) * t) + (268.0f / 25.0f); +} + +CGLM_INLINE +float +glm_ease_bounce_in(float t) { + return 1.0f - glm_ease_bounce_out(1.0f - t); +} + +CGLM_INLINE +float +glm_ease_bounce_inout(float t) { + if (t < 0.5f) + return 0.5f * (1.0f - glm_ease_bounce_out(t * 2.0f)); + + return 0.5f * glm_ease_bounce_out(t * 2.0f - 1.0f) + 0.5f; +} + +#endif /* cglm_ease_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_curve_h +#define cglm_curve_h + + +/*! + * @brief helper function to calculate S*M*C multiplication for curves + * + * This function does not encourage you to use SMC, + * instead it is a helper if you use SMC. + * + * if you want to specify S as vector then use more generic glm_mat4_rmc() func. + * + * Example usage: + * B(s) = glm_smc(s, GLM_BEZIER_MAT, (vec4){p0, c0, c1, p1}) + * + * @param[in] s parameter between 0 and 1 (this will be [s3, s2, s, 1]) + * @param[in] m basis matrix + * @param[in] c position/control vector + * + * @return B(s) + */ +CGLM_INLINE +float +glm_smc(float s, mat4 m, vec4 c) { + vec4 vs; + glm_vec4_cubic(s, vs); + return glm_mat4_rmc(vs, m, c); +} + +#endif /* cglm_curve_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +#ifndef cglm_bezier_h +#define cglm_bezier_h + + +#define GLM_BEZIER_MAT_INIT {{-1.0f, 3.0f, -3.0f, 1.0f}, \ + { 3.0f, -6.0f, 3.0f, 0.0f}, \ + {-3.0f, 3.0f, 0.0f, 0.0f}, \ + { 1.0f, 0.0f, 0.0f, 0.0f}} +#define GLM_HERMITE_MAT_INIT {{ 2.0f, -3.0f, 0.0f, 1.0f}, \ + {-2.0f, 3.0f, 0.0f, 0.0f}, \ + { 1.0f, -2.0f, 1.0f, 0.0f}, \ + { 1.0f, -1.0f, 0.0f, 0.0f}} +/* for C only */ +#define GLM_BEZIER_MAT ((mat4)GLM_BEZIER_MAT_INIT) +#define GLM_HERMITE_MAT ((mat4)GLM_HERMITE_MAT_INIT) + +#define CGLM_DECASTEL_EPS 1e-9f +#define CGLM_DECASTEL_MAX 1000.0f +#define CGLM_DECASTEL_SMALL 1e-20f + +/*! + * @brief cubic bezier interpolation + * + * Formula: + * B(s) = P0*(1-s)^3 + 3*C0*s*(1-s)^2 + 3*C1*s^2*(1-s) + P1*s^3 + * + * similar result using matrix: + * B(s) = glm_smc(t, GLM_BEZIER_MAT, (vec4){p0, c0, c1, p1}) + * + * glm_eq(glm_smc(...), glm_bezier(...)) should return TRUE + * + * @param[in] s parameter between 0 and 1 + * @param[in] p0 begin point + * @param[in] c0 control point 1 + * @param[in] c1 control point 2 + * @param[in] p1 end point + * + * @return B(s) + */ +CGLM_INLINE +float +glm_bezier(float s, float p0, float c0, float c1, float p1) { + float x, xx, ss, xs3, a; + + x = 1.0f - s; + xx = x * x; + ss = s * s; + xs3 = (s - ss) * 3.0f; + a = p0 * xx + c0 * xs3; + + return a + s * (c1 * xs3 + p1 * ss - a); +} + +/*! + * @brief cubic hermite interpolation + * + * Formula: + * H(s) = P0*(2*s^3 - 3*s^2 + 1) + T0*(s^3 - 2*s^2 + s) + * + P1*(-2*s^3 + 3*s^2) + T1*(s^3 - s^2) + * + * similar result using matrix: + * H(s) = glm_smc(t, GLM_HERMITE_MAT, (vec4){p0, p1, c0, c1}) + * + * glm_eq(glm_smc(...), glm_hermite(...)) should return TRUE + * + * @param[in] s parameter between 0 and 1 + * @param[in] p0 begin point + * @param[in] t0 tangent 1 + * @param[in] t1 tangent 2 + * @param[in] p1 end point + * + * @return H(s) + */ +CGLM_INLINE +float +glm_hermite(float s, float p0, float t0, float t1, float p1) { + float ss, d, a, b, c, e, f; + + ss = s * s; + a = ss + ss; + c = a + ss; + b = a * s; + d = s * ss; + f = d - ss; + e = b - c; + + return p0 * (e + 1.0f) + t0 * (f - ss + s) + t1 * f - p1 * e; +} + +/*! + * @brief iterative way to solve cubic equation + * + * @param[in] prm parameter between 0 and 1 + * @param[in] p0 begin point + * @param[in] c0 control point 1 + * @param[in] c1 control point 2 + * @param[in] p1 end point + * + * @return parameter to use in cubic equation + */ +CGLM_INLINE +float +glm_decasteljau(float prm, float p0, float c0, float c1, float p1) { + float u, v, a, b, c, d, e, f; + int i; + + if (prm - p0 < CGLM_DECASTEL_SMALL) + return 0.0f; + + if (p1 - prm < CGLM_DECASTEL_SMALL) + return 1.0f; + + u = 0.0f; + v = 1.0f; + + for (i = 0; i < CGLM_DECASTEL_MAX; i++) { + /* de Casteljau Subdivision */ + a = (p0 + c0) * 0.5f; + b = (c0 + c1) * 0.5f; + c = (c1 + p1) * 0.5f; + d = (a + b) * 0.5f; + e = (b + c) * 0.5f; + f = (d + e) * 0.5f; /* this one is on the curve! */ + + /* The curve point is close enough to our wanted t */ + if (fabsf(f - prm) < CGLM_DECASTEL_EPS) + return glm_clamp_zo((u + v) * 0.5f); + + /* dichotomy */ + if (f < prm) { + p0 = f; + c0 = e; + c1 = c; + u = (u + v) * 0.5f; + } else { + c0 = a; + c1 = d; + p1 = f; + v = (u + v) * 0.5f; + } + } + + return glm_clamp_zo((u + v) * 0.5f); +} + +#endif /* cglm_bezier_h */ + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Functions: + CGLM_INLINE bool glm_line_triangle_intersect(vec3 origin, + vec3 direction, + vec3 v0, + vec3 v1, + vec3 v2, + float *d); +*/ + +#ifndef cglm_ray_h +#define cglm_ray_h + + +/*! + * @brief Möller–Trumbore ray-triangle intersection algorithm + * + * @param[in] origin origin of ray + * @param[in] direction direction of ray + * @param[in] v0 first vertex of triangle + * @param[in] v1 second vertex of triangle + * @param[in] v2 third vertex of triangle + * @param[in, out] d distance to intersection + * @return whether there is intersection + */ + +CGLM_INLINE +bool +glm_ray_triangle(vec3 origin, + vec3 direction, + vec3 v0, + vec3 v1, + vec3 v2, + float *d) { + vec3 edge1, edge2, p, t, q; + float det, inv_det, u, v, dist; + const float epsilon = 0.000001f; + + glm_vec3_sub(v1, v0, edge1); + glm_vec3_sub(v2, v0, edge2); + glm_vec3_cross(direction, edge2, p); + + det = glm_vec3_dot(edge1, p); + if (det > -epsilon && det < epsilon) + return false; + + inv_det = 1.0f / det; + + glm_vec3_sub(origin, v0, t); + + u = inv_det * glm_vec3_dot(t, p); + if (u < 0.0f || u > 1.0f) + return false; + + glm_vec3_cross(t, edge1, q); + + v = inv_det * glm_vec3_dot(direction, q); + if (v < 0.0f || u + v > 1.0f) + return false; + + dist = inv_det * glm_vec3_dot(edge2, q); + + if (d) + *d = dist; + + return dist > epsilon; +} + +#endif + +/* + * Copyright (c), Recep Aslantas. + * + * MIT License (MIT), http://opensource.org/licenses/MIT + * Full license can be found in the LICENSE file + */ + +/* + Functions: + CGLM_INLINE void glm_translate2d(mat3 m, vec2 v) + CGLM_INLINE void glm_translate2d_to(mat3 m, vec2 v, mat3 dest) + CGLM_INLINE void glm_translate2d_x(mat3 m, float x) + CGLM_INLINE void glm_translate2d_y(mat3 m, float y) + CGLM_INLINE void glm_translate2d_make(mat3 m, vec2 v) + CGLM_INLINE void glm_scale2d_to(mat3 m, vec2 v, mat3 dest) + CGLM_INLINE void glm_scale2d_make(mat3 m, vec2 v) + CGLM_INLINE void glm_scale2d(mat3 m, vec2 v) + CGLM_INLINE void glm_scale2d_uni(mat3 m, float s) + CGLM_INLINE void glm_rotate2d_make(mat3 m, float angle) + CGLM_INLINE void glm_rotate2d(mat3 m, float angle) + CGLM_INLINE void glm_rotate2d_to(mat3 m, float angle, mat3 dest) + */ + +#ifndef cglm_affine2d_h +#define cglm_affine2d_h + + +/*! + * @brief translate existing 2d transform matrix by v vector + * and stores result in same matrix + * + * @param[in, out] m affine transfrom + * @param[in] v translate vector [x, y] + */ +CGLM_INLINE +void +glm_translate2d(mat3 m, vec2 v) { + m[2][0] = m[0][0] * v[0] + m[1][0] * v[1] + m[2][0]; + m[2][1] = m[0][1] * v[0] + m[1][1] * v[1] + m[2][1]; + m[2][2] = m[0][2] * v[0] + m[1][2] * v[1] + m[2][2]; +} + +/*! + * @brief translate existing 2d transform matrix by v vector + * and store result in dest + * + * source matrix will remain same + * + * @param[in] m affine transfrom + * @param[in] v translate vector [x, y] + * @param[out] dest translated matrix + */ +CGLM_INLINE +void +glm_translate2d_to(mat3 m, vec2 v, mat3 dest) { + glm_mat3_copy(m, dest); + glm_translate2d(dest, v); +} + +/*! + * @brief translate existing 2d transform matrix by x factor + * + * @param[in, out] m affine transfrom + * @param[in] x x factor + */ +CGLM_INLINE +void +glm_translate2d_x(mat3 m, float x) { + m[2][0] = m[0][0] * x + m[2][0]; + m[2][1] = m[0][1] * x + m[2][1]; + m[2][2] = m[0][2] * x + m[2][2]; +} + +/*! + * @brief translate existing 2d transform matrix by y factor + * + * @param[in, out] m affine transfrom + * @param[in] y y factor + */ +CGLM_INLINE +void +glm_translate2d_y(mat3 m, float y) { + m[2][0] = m[1][0] * y + m[2][0]; + m[2][1] = m[1][1] * y + m[2][1]; + m[2][2] = m[1][2] * y + m[2][2]; +} + +/*! + * @brief creates NEW translate 2d transform matrix by v vector + * + * @param[out] m affine transfrom + * @param[in] v translate vector [x, y] + */ +CGLM_INLINE +void +glm_translate2d_make(mat3 m, vec2 v) { + glm_mat3_identity(m); + m[2][0] = v[0]; + m[2][1] = v[1]; +} + +/*! + * @brief scale existing 2d transform matrix by v vector + * and store result in dest + * + * @param[in] m affine transfrom + * @param[in] v scale vector [x, y] + * @param[out] dest scaled matrix + */ +CGLM_INLINE +void +glm_scale2d_to(mat3 m, vec2 v, mat3 dest) { + dest[0][0] = m[0][0] * v[0]; + dest[0][1] = m[0][1] * v[0]; + dest[0][2] = m[0][2] * v[0]; + + dest[1][0] = m[1][0] * v[1]; + dest[1][1] = m[1][1] * v[1]; + dest[1][2] = m[1][2] * v[1]; + + dest[2][0] = m[2][0]; + dest[2][1] = m[2][1]; + dest[2][2] = m[2][2]; +} + +/*! + * @brief creates NEW 2d scale matrix by v vector + * + * @param[out] m affine transfrom + * @param[in] v scale vector [x, y] + */ +CGLM_INLINE +void +glm_scale2d_make(mat3 m, vec2 v) { + glm_mat3_identity(m); + m[0][0] = v[0]; + m[1][1] = v[1]; +} + +/*! + * @brief scales existing 2d transform matrix by v vector + * and stores result in same matrix + * + * @param[in, out] m affine transfrom + * @param[in] v scale vector [x, y] + */ +CGLM_INLINE +void +glm_scale2d(mat3 m, vec2 v) { + m[0][0] = m[0][0] * v[0]; + m[0][1] = m[0][1] * v[0]; + m[0][2] = m[0][2] * v[0]; + + m[1][0] = m[1][0] * v[1]; + m[1][1] = m[1][1] * v[1]; + m[1][2] = m[1][2] * v[1]; +} + +/*! + * @brief applies uniform scale to existing 2d transform matrix v = [s, s] + * and stores result in same matrix + * + * @param[in, out] m affine transfrom + * @param[in] s scale factor + */ +CGLM_INLINE +void +glm_scale2d_uni(mat3 m, float s) { + m[0][0] = m[0][0] * s; + m[0][1] = m[0][1] * s; + m[0][2] = m[0][2] * s; + + m[1][0] = m[1][0] * s; + m[1][1] = m[1][1] * s; + m[1][2] = m[1][2] * s; +} + +/*! + * @brief creates NEW rotation matrix by angle around Z axis + * + * @param[out] m affine transfrom + * @param[in] angle angle (radians) + */ +CGLM_INLINE +void +glm_rotate2d_make(mat3 m, float angle) { + float c, s; + + s = sinf(angle); + c = cosf(angle); + + m[0][0] = c; + m[0][1] = s; + m[0][2] = 0; + + m[1][0] = -s; + m[1][1] = c; + m[1][2] = 0; + + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = 1.0f; +} + +/*! + * @brief rotate existing 2d transform matrix around Z axis by angle + * and store result in same matrix + * + * @param[in, out] m affine transfrom + * @param[in] angle angle (radians) + */ +CGLM_INLINE +void +glm_rotate2d(mat3 m, float angle) { + float m00 = m[0][0], m10 = m[1][0], + m01 = m[0][1], m11 = m[1][1], + m02 = m[0][2], m12 = m[1][2]; + float c, s; + + s = sinf(angle); + c = cosf(angle); + + m[0][0] = m00 * c + m10 * s; + m[0][1] = m01 * c + m11 * s; + m[0][2] = m02 * c + m12 * s; + + m[1][0] = m00 * -s + m10 * c; + m[1][1] = m01 * -s + m11 * c; + m[1][2] = m02 * -s + m12 * c; +} + +/*! + * @brief rotate existing 2d transform matrix around Z axis by angle + * and store result in dest + * + * @param[in] m affine transfrom + * @param[in] angle angle (radians) + * @param[out] dest destination + */ +CGLM_INLINE +void +glm_rotate2d_to(mat3 m, float angle, mat3 dest) { + float m00 = m[0][0], m10 = m[1][0], + m01 = m[0][1], m11 = m[1][1], + m02 = m[0][2], m12 = m[1][2]; + float c, s; + + s = sinf(angle); + c = cosf(angle); + + dest[0][0] = m00 * c + m10 * s; + dest[0][1] = m01 * c + m11 * s; + dest[0][2] = m02 * c + m12 * s; + + dest[1][0] = m00 * -s + m10 * c; + dest[1][1] = m01 * -s + m11 * c; + dest[1][2] = m02 * -s + m12 * c; + + dest[2][0] = m[2][0]; + dest[2][1] = m[2][1]; + dest[2][2] = m[2][2]; +} + +#endif /* cglm_affine2d_h */ + + +#endif /* cglm_h */ + + +#ifdef __cplusplus +} +#endif + diff --git a/deps/dependee.json b/deps/dependee.json index 4efec183..9db0558f 100644 --- a/deps/dependee.json +++ b/deps/dependee.json @@ -1,15 +1,75 @@ { + "dependee": { + "lang.c": { + "${cfg sanitize}": { + "defines": [ + "FLECS_SANITIZE" + ] + } + }, + "lang.cpp": { + "${cfg sanitize}": { + "defines": [ + "FLECS_SANITIZE" + ] + } + } + }, "lang.c": { "${os linux}": { "lib": [ "rt", - "pthread" + "pthread", + "m", + "GL", + "X11", + "Xi", + "Xcursor", + "dl" + ], + "${cfg debug}": { + "export-symbols": true + }, + "${cfg sanitize}": { + "export-symbols": true + } + }, + "${os windows}": { + "lib": [ + "ws2_32", + "dbgHelp" + ], + "defines": [ + "_WINDOWS", + "_USRDLL", + "CGLM_EXPORTS", + "CGLM_DLL" ] }, "${cfg sanitize}": { "defines": [ "FLECS_SANITIZE" ] + }, + "${os darwin}": { + "ldflags": [ + "-framework Cocoa", + "-framework QuartzCore", + "-framework OpenGL" + ] + }, + "${target em}": { + "ldflags": [ + "-s USE_WEBGL2=1" + ], + "${cfg debug}": { + "ldflags": [ + "-s GL_DEBUG=1" + ] + }, + "embed": [ + "etc\/sokol\/shaders" + ] } } } \ No newline at end of file diff --git a/deps/flecs.c b/deps/flecs.c index 8f3777d6..39eb4e2c 100644 --- a/deps/flecs.c +++ b/deps/flecs.c @@ -1,69 +1,44 @@ +/** + * @file bootstrap.c + * @brief Bootstrap entities in the flecs.core namespace. + * + * Before the ECS storage can be used, core entities such first need to be + * initialized. For example, components in Flecs are stored as entities in the + * ECS storage itself with an EcsComponent component, but before this component + * can be stored, the component itself needs to be initialized. + * + * The bootstrap code uses lower-level APIs to initialize the data structures. + * After bootstrap is completed, regular ECS operations can be used to create + * entities and components. + * + * The bootstrap file also includes several lifecycle hooks and observers for + * builtin features, such as relationship properties and hooks for keeping the + * entity name administration in sync with the (Identifier, Name) component. + */ + #include "flecs.h" +/** + * @file private_api.h + * @brief Private functions. + */ + #ifndef FLECS_PRIVATE_H #define FLECS_PRIVATE_H -#ifndef FLECS_TYPES_PRIVATE_H -#define FLECS_TYPES_PRIVATE_H - #ifndef __MACH__ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #endif -#include +#include #include -#ifndef FLECS_NDEBUG -#include /* easier debugging, throws warning in release for printfs */ -#endif - -/** - * @file entity_index.h - * @brief Entity index data structure. - * - * The entity index stores the table, row for an entity id. It is implemented as - * a sparse set. This file contains convenience macro's for working with the - * entity index. - */ - -#ifndef FLECS_ENTITY_INDEX_H -#define FLECS_ENTITY_INDEX_H - -#ifdef __cplusplus -extern "C" { -#endif - -#define ecs_eis(world) (&((world)->store.entity_index)) -#define ecs_eis_get(world, entity) flecs_sparse_get(ecs_eis(world), ecs_record_t, entity) -#define ecs_eis_get_any(world, entity) flecs_sparse_get_any(ecs_eis(world), ecs_record_t, entity) -#define ecs_eis_set(world, entity, ...) (flecs_sparse_set(ecs_eis(world), ecs_record_t, entity, (__VA_ARGS__))) -#define ecs_eis_ensure(world, entity) flecs_sparse_ensure(ecs_eis(world), ecs_record_t, entity) -#define ecs_eis_delete(world, entity) flecs_sparse_remove(ecs_eis(world), entity) -#define ecs_eis_set_generation(world, entity) flecs_sparse_set_generation(ecs_eis(world), entity) -#define ecs_eis_is_alive(world, entity) flecs_sparse_is_alive(ecs_eis(world), entity) -#define ecs_eis_get_current(world, entity) flecs_sparse_get_alive(ecs_eis(world), entity) -#define ecs_eis_exists(world, entity) flecs_sparse_exists(ecs_eis(world), entity) -#define ecs_eis_recycle(world) flecs_sparse_new_id(ecs_eis(world)) -#define ecs_eis_clear_entity(world, entity, is_watched) ecs_eis_set(ecs_eis(world), entity, &(ecs_record_t){NULL, is_watched}) -#define ecs_eis_set_size(world, size) flecs_sparse_set_size(ecs_eis(world), size) -#define ecs_eis_count(world) flecs_sparse_count(ecs_eis(world)) -#define ecs_eis_clear(world) flecs_sparse_clear(ecs_eis(world)) -#define ecs_eis_copy(world) flecs_sparse_copy(ecs_eis(world)) -#define ecs_eis_free(world) flecs_sparse_free(ecs_eis(world)) -#define ecs_eis_memory(world, allocd, used) flecs_sparse_memory(ecs_eis(world), allocd, used) - -#ifdef __cplusplus -} -#endif - -#endif +#include +#include /** * @file bitset.h - * @brief Bitset datastructure. - * - * Simple bitset implementation. The bitset allows for storage of arbitrary - * numbers of bits. + * @brief Bitset data structure. */ #ifndef FLECS_BITSET_H @@ -85,7 +60,7 @@ FLECS_DBG_API void flecs_bitset_init( ecs_bitset_t *bs); -/** Deinialize bitset. */ +/** Deinitialize bitset. */ FLECS_DBG_API void flecs_bitset_fini( ecs_bitset_t *bs); @@ -140,13284 +115,14356 @@ void flecs_bitset_swap( #endif /** - * @file switch_list.h - * @brief Interleaved linked list for storing mutually exclusive values. - * - * Datastructure that stores N interleaved linked lists in an array. - * This allows for efficient storage of elements with mutually exclusive values. - * Each linked list has a header element which points to the index in the array - * that stores the first node of the list. Each list node points to the next - * array element. - * - * The datastructure needs to be created with min and max values, so that it can - * allocate an array of headers that can be directly indexed by the value. The - * values are stored in a contiguous array, which allows for the values to be - * iterated without having to follow the linked list nodes. - * - * The datastructure allows for efficient storage and retrieval for values with - * mutually exclusive values, such as enumeration values. The linked list allows - * an application to obtain all elements for a given (enumeration) value without - * having to search. - * - * While the list accepts 64 bit values, it only uses the lower 32bits of the - * value for selecting the correct linked list. + * @file datastructures/name_index.h + * @brief Data structure for resolving 64bit keys by string (name). */ -#ifndef FLECS_SWITCH_LIST_H -#define FLECS_SWITCH_LIST_H - - -typedef struct flecs_switch_header_t { - int32_t element; /* First element for value */ - int32_t count; /* Number of elements for value */ -} flecs_switch_header_t; - -typedef struct flecs_switch_node_t { - int32_t next; /* Next node in list */ - int32_t prev; /* Prev node in list */ -} flecs_switch_node_t; - -struct ecs_switch_t { - uint64_t min; /* Minimum value the switch can store */ - uint64_t max; /* Maximum value the switch can store */ - flecs_switch_header_t *headers; /* Array with headers, indexed by value */ - ecs_vector_t *nodes; /* Vector with nodes, of type flecs_switch_node_t */ - ecs_vector_t *values; /* Vector with values, of type uint64_t */ -}; - -/** Create new switch. */ -FLECS_DBG_API -ecs_switch_t* flecs_switch_new( - uint64_t min, - uint64_t max, - int32_t elements); - -/** Free switch. */ -FLECS_DBG_API -void flecs_switch_free( - ecs_switch_t *sw); +#ifndef FLECS_NAME_INDEX_H +#define FLECS_NAME_INDEX_H -/** Remove all values. */ -FLECS_DBG_API -void flecs_switch_clear( - ecs_switch_t *sw); +/** Type used for internal string hashmap */ +typedef struct ecs_hashed_string_t { + char *value; + ecs_size_t length; + uint64_t hash; +} ecs_hashed_string_t; -/** Add element to switch, initialize value to 0 */ -FLECS_DBG_API -void flecs_switch_add( - ecs_switch_t *sw); +void flecs_name_index_init( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator); -/** Set number of elements in switch list */ -FLECS_DBG_API -void flecs_switch_set_count( - ecs_switch_t *sw, - int32_t count); +void flecs_name_index_init_if( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator); -/** Get number of elements */ -FLECS_DBG_API -int32_t flecs_switch_count( - ecs_switch_t *sw); +bool flecs_name_index_is_init( + const ecs_hashmap_t *hm); -/** Ensure that element exists. */ -FLECS_DBG_API -void flecs_switch_ensure( - ecs_switch_t *sw, - int32_t count); +ecs_hashmap_t* flecs_name_index_new( + ecs_allocator_t *allocator); -/** Add n elements. */ -FLECS_DBG_API -void flecs_switch_addn( - ecs_switch_t *sw, - int32_t count); +void flecs_name_index_fini( + ecs_hashmap_t *map); -/** Set value of element. */ -FLECS_DBG_API -void flecs_switch_set( - ecs_switch_t *sw, - int32_t element, - uint64_t value); +void flecs_name_index_free( + ecs_hashmap_t *map); -/** Remove element. */ -FLECS_DBG_API -void flecs_switch_remove( - ecs_switch_t *sw, - int32_t element); +ecs_hashmap_t* flecs_name_index_copy( + ecs_hashmap_t *dst); -/** Get value for element. */ -FLECS_DBG_API -uint64_t flecs_switch_get( - const ecs_switch_t *sw, - int32_t element); +ecs_hashed_string_t flecs_get_hashed_string( + const char *name, + ecs_size_t length, + uint64_t hash); -/** Swap element. */ -FLECS_DBG_API -void flecs_switch_swap( - ecs_switch_t *sw, - int32_t elem_1, - int32_t elem_2); +const uint64_t* flecs_name_index_find_ptr( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash); -/** Get vector with all values. Use together with count(). */ -FLECS_DBG_API -ecs_vector_t* flecs_switch_values( - const ecs_switch_t *sw); +uint64_t flecs_name_index_find( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash); -/** Return number of different values. */ -FLECS_DBG_API -int32_t flecs_switch_case_count( - const ecs_switch_t *sw, - uint64_t value); +void flecs_name_index_ensure( + ecs_hashmap_t *map, + uint64_t id, + const char *name, + ecs_size_t length, + uint64_t hash); -/** Return first element for value. */ -FLECS_DBG_API -int32_t flecs_switch_first( - const ecs_switch_t *sw, - uint64_t value); +void flecs_name_index_remove( + ecs_hashmap_t *map, + uint64_t id, + uint64_t hash); -/** Return next element for value. Use with first(). */ -FLECS_DBG_API -int32_t flecs_switch_next( - const ecs_switch_t *sw, - int32_t elem); +void flecs_name_index_update_name( + ecs_hashmap_t *map, + uint64_t e, + uint64_t hash, + const char *name); -#ifdef __cplusplus -extern "C" { #endif -#ifdef __cplusplus -} -#endif +/** + * @file datastructures/entity_index.h + * @brief Entity index data structure. + * + * The entity index stores the table, row for an entity id. + */ + +#ifndef FLECS_ENTITY_INDEX_H +#define FLECS_ENTITY_INDEX_H -#endif +#define FLECS_ENTITY_PAGE_SIZE (1 << FLECS_ENTITY_PAGE_BITS) +#define FLECS_ENTITY_PAGE_MASK (FLECS_ENTITY_PAGE_SIZE - 1) + +typedef struct ecs_entity_index_page_t { + ecs_record_t records[FLECS_ENTITY_PAGE_SIZE]; +} ecs_entity_index_page_t; + +typedef struct ecs_entity_index_t { + ecs_vec_t dense; + ecs_vec_t pages; + int32_t alive_count; + uint64_t max_id; + ecs_allocator_t *allocator; +} ecs_entity_index_t; + +/** Initialize entity index. */ +void flecs_entity_index_init( + ecs_allocator_t *allocator, + ecs_entity_index_t *index); + +/** Deinitialize entity index. */ +void flecs_entity_index_fini( + ecs_entity_index_t *index); + +/* Get entity (must exist/must be alive) */ +ecs_record_t* flecs_entity_index_get( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Get entity (must exist/may not be alive) */ +ecs_record_t* flecs_entity_index_get_any( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Get entity (may not exist/must be alive) */ +ecs_record_t* flecs_entity_index_try_get( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Get entity (may not exist/may not be alive) */ +ecs_record_t* flecs_entity_index_try_get_any( + const ecs_entity_index_t *index, + uint64_t entity); + +/** Ensure entity exists. */ +ecs_record_t* flecs_entity_index_ensure( + ecs_entity_index_t *index, + uint64_t entity); + +/* Remove entity */ +void flecs_entity_index_remove( + ecs_entity_index_t *index, + uint64_t entity); + +/* Set generation of entity */ +void flecs_entity_index_make_alive( + ecs_entity_index_t *index, + uint64_t entity); + +/* Get current generation of entity */ +uint64_t flecs_entity_index_get_alive( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Return whether entity is alive */ +bool flecs_entity_index_is_alive( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Return whether entity is valid */ +bool flecs_entity_index_is_valid( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Return whether entity exists */ +bool flecs_entity_index_exists( + const ecs_entity_index_t *index, + uint64_t entity); + +/* Create or recycle entity id */ +uint64_t flecs_entity_index_new_id( + ecs_entity_index_t *index); + +/* Bulk create or recycle new entity ids */ +uint64_t* flecs_entity_index_new_ids( + ecs_entity_index_t *index, + int32_t count); +/* Set size of index */ +void flecs_entity_index_set_size( + ecs_entity_index_t *index, + int32_t size); -#define ECS_MAX_JOBS_PER_WORKER (16) +/* Return number of entities in index */ +int32_t flecs_entity_index_count( + const ecs_entity_index_t *index); -/* Magic number for a flecs object */ -#define ECS_OBJECT_MAGIC (0x6563736f) +/* Return number of allocated entities in index */ +int32_t flecs_entity_index_size( + const ecs_entity_index_t *index); -/* Magic number to identify the type of the object */ -#define ecs_world_t_magic (0x65637377) -#define ecs_stage_t_magic (0x65637373) -#define ecs_query_t_magic (0x65637371) -#define ecs_rule_t_magic (0x65637375) -#define ecs_table_t_magic (0x65637374) -#define ecs_filter_t_magic (0x65637366) -#define ecs_trigger_t_magic (0x65637372) -#define ecs_observer_t_magic (0x65637362) +/* Return number of not alive entities in index */ +int32_t flecs_entity_index_not_alive_count( + const ecs_entity_index_t *index); -/* Mixin kinds */ -typedef enum ecs_mixin_kind_t { - EcsMixinWorld, - EcsMixinObservable, - EcsMixinIterable, - EcsMixinBase, /* If mixin can't be found in object, look in base */ - EcsMixinMax -} ecs_mixin_kind_t; +/* Clear entity index */ +void flecs_entity_index_clear( + ecs_entity_index_t *index); -/* The mixin array contains pointers to mixin members for different kinds of - * flecs objects. This allows the API to retrieve data from an object regardless - * of its type. Each mixin array is only stored once per type */ -struct ecs_mixins_t { - const char *type_name; /* Include name of mixin type so debug code doesn't - * need to know about every object */ - ecs_size_t elems[EcsMixinMax]; -}; +/* Shrink entity index */ +void flecs_entity_index_shrink( + ecs_entity_index_t *index); -/* Mixin tables */ -extern ecs_mixins_t ecs_world_t_mixins; -extern ecs_mixins_t ecs_stage_t_mixins; -extern ecs_mixins_t ecs_filter_t_mixins; -extern ecs_mixins_t ecs_query_t_mixins; +/* Return number of alive entities in index */ +const uint64_t* flecs_entity_index_ids( + const ecs_entity_index_t *index); -/* Types that have no mixins */ -#define ecs_table_t_mixins (&(ecs_mixins_t){ NULL }) +#define ecs_eis(world) (&((world)->store.entity_index)) +#define flecs_entities_init(world) flecs_entity_index_init(&world->allocator, ecs_eis(world)) +#define flecs_entities_fini(world) flecs_entity_index_fini(ecs_eis(world)) +#define flecs_entities_get(world, entity) flecs_entity_index_get(ecs_eis(world), entity) +#define flecs_entities_try(world, entity) flecs_entity_index_try_get(ecs_eis(world), entity) +#define flecs_entities_get_any(world, entity) flecs_entity_index_get_any(ecs_eis(world), entity) +#define flecs_entities_ensure(world, entity) flecs_entity_index_ensure(ecs_eis(world), entity) +#define flecs_entities_remove(world, entity) flecs_entity_index_remove(ecs_eis(world), entity) +#define flecs_entities_make_alive(world, entity) flecs_entity_index_make_alive(ecs_eis(world), entity) +#define flecs_entities_get_alive(world, entity) flecs_entity_index_get_alive(ecs_eis(world), entity) +#define flecs_entities_is_alive(world, entity) flecs_entity_index_is_alive(ecs_eis(world), entity) +#define flecs_entities_is_valid(world, entity) flecs_entity_index_is_valid(ecs_eis(world), entity) +#define flecs_entities_exists(world, entity) flecs_entity_index_exists(ecs_eis(world), entity) +#define flecs_entities_new_id(world) flecs_entity_index_new_id(ecs_eis(world)) +#define flecs_entities_new_ids(world, count) flecs_entity_index_new_ids(ecs_eis(world), count) +#define flecs_entities_max_id(world) (ecs_eis(world)->max_id) +#define flecs_entities_set_size(world, size) flecs_entity_index_set_size(ecs_eis(world), size) +#define flecs_entities_count(world) flecs_entity_index_count(ecs_eis(world)) +#define flecs_entities_size(world) flecs_entity_index_size(ecs_eis(world)) +#define flecs_entities_not_alive_count(world) flecs_entity_index_not_alive_count(ecs_eis(world)) +#define flecs_entities_clear(world) flecs_entity_index_clear(ecs_eis(world)) +#define flecs_entities_ids(world) flecs_entity_index_ids(ecs_eis(world)) -/** Type used for internal string hashmap */ -typedef struct ecs_hashed_string_t { - char *value; - ecs_size_t length; - uint64_t hash; -} ecs_hashed_string_t; +#endif -/* Table event type for notifying tables of world events */ -typedef enum ecs_table_eventkind_t { - EcsTableTriggersForId, - EcsTableNoTriggersForId, -} ecs_table_eventkind_t; +/** + * @file storage/table_cache.h + * @brief Data structure for fast table iteration/lookups. + */ -typedef struct ecs_table_event_t { - ecs_table_eventkind_t kind; +#ifndef FLECS_TABLE_CACHE_H_ +#define FLECS_TABLE_CACHE_H_ - /* Query event */ - ecs_query_t *query; +/** Linked list of tables in table cache */ +typedef struct ecs_table_cache_list_t { + ecs_table_cache_hdr_t *first; + ecs_table_cache_hdr_t *last; + int32_t count; +} ecs_table_cache_list_t; - /* Component info event */ - ecs_entity_t component; +/** Table cache */ +typedef struct ecs_table_cache_t { + ecs_map_t index; /* */ + ecs_table_cache_list_t tables; +} ecs_table_cache_t; - /* Trigger match */ - ecs_entity_t event; +void ecs_table_cache_init( + ecs_world_t *world, + ecs_table_cache_t *cache); - /* If the nubmer of fields gets out of hand, this can be turned into a union - * but since events are very temporary objects, this works for now and makes - * initializing an event a bit simpler. */ -} ecs_table_event_t; +void ecs_table_cache_fini( + ecs_table_cache_t *cache); -/** A component column. */ -struct ecs_column_t { - ecs_vector_t *data; /* Column data */ -}; +void ecs_table_cache_insert( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *result); -/** A switch column. */ -typedef struct ecs_sw_column_t { - ecs_switch_t *data; /* Column data */ - ecs_table_t *type; /* Table with switch type */ -} ecs_sw_column_t; +void ecs_table_cache_replace( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *elem); -/** A bitset column. */ -typedef struct ecs_bs_column_t { - ecs_bitset_t data; /* Column data */ -} ecs_bs_column_t; +void* ecs_table_cache_remove( + ecs_table_cache_t *cache, + uint64_t table_id, + ecs_table_cache_hdr_t *elem); -/** Stage-specific component data */ -struct ecs_data_t { - ecs_vector_t *entities; /* Entity identifiers */ - ecs_vector_t *record_ptrs; /* Ptrs to records in main entity index */ - ecs_column_t *columns; /* Component columns */ - ecs_sw_column_t *sw_columns; /* Switch columns */ - ecs_bs_column_t *bs_columns; /* Bitset columns */ -}; +void* ecs_table_cache_get( + const ecs_table_cache_t *cache, + const ecs_table_t *table); -/** Flags for quickly checking for special properties of a table. */ -#define EcsTableHasBuiltins 1u /* Does table have builtin components */ -#define EcsTableIsPrefab 2u /* Does the table store prefabs */ -#define EcsTableHasIsA 4u /* Does the table have IsA relation */ -#define EcsTableHasChildOf 8u /* Does the table type ChildOf relation */ -#define EcsTableHasPairs 16u /* Does the table type have pairs */ -#define EcsTableHasModule 32u /* Does the table have module data */ -#define EcsTableIsDisabled 128u /* Does the table type has EcsDisabled */ -#define EcsTableHasCtors 256u -#define EcsTableHasDtors 512u -#define EcsTableHasCopy 1024u -#define EcsTableHasMove 2048u -#define EcsTableHasOnAdd 4096u -#define EcsTableHasOnRemove 8192u -#define EcsTableHasOnSet 16384u -#define EcsTableHasUnSet 32768u -#define EcsTableHasSwitch 65536u -#define EcsTableHasDisabled 131072u - -/* Composite constants */ -#define EcsTableHasLifecycle (EcsTableHasCtors | EcsTableHasDtors) -#define EcsTableIsComplex (EcsTableHasLifecycle | EcsTableHasSwitch | EcsTableHasDisabled) -#define EcsTableHasAddActions (EcsTableHasIsA | EcsTableHasSwitch | EcsTableHasCtors | EcsTableHasOnAdd | EcsTableHasOnSet) -#define EcsTableHasRemoveActions (EcsTableHasIsA | EcsTableHasDtors | EcsTableHasOnRemove | EcsTableHasUnSet) +#define flecs_table_cache_count(cache) (cache)->tables.count -/** Cache of added/removed components for non-trivial edges between tables */ -typedef struct ecs_table_diff_t { - ecs_ids_t added; /* Components added between tables */ - ecs_ids_t removed; /* Components removed between tables */ - ecs_ids_t on_set; /* OnSet from exposing/adding base components */ - ecs_ids_t un_set; /* UnSet from hiding/removing base components */ -} ecs_table_diff_t; +bool flecs_table_cache_iter( + const ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out); -/** Edge linked list (used to keep track of incoming edges) */ -typedef struct ecs_graph_edge_hdr_t { - struct ecs_graph_edge_hdr_t *prev; - struct ecs_graph_edge_hdr_t *next; -} ecs_graph_edge_hdr_t; +bool flecs_table_cache_empty_iter( + const ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out); -/** Single edge. */ -typedef struct ecs_graph_edge_t { - ecs_graph_edge_hdr_t hdr; - ecs_table_t *from; /* Edge source table */ - ecs_table_t *to; /* Edge destination table */ - ecs_table_diff_t *diff; /* Index into diff vector, if non trivial edge */ - ecs_id_t id; /* Id associated with edge */ -} ecs_graph_edge_t; +bool flecs_table_cache_all_iter( + const ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out); -/* Edges to other tables. */ -typedef struct ecs_graph_edges_t { - ecs_graph_edge_t *lo; /* Small array optimized for low edges */ - ecs_map_t hi; /* Map for hi edges (map) */ -} ecs_graph_edges_t; +const ecs_table_cache_hdr_t* flecs_table_cache_next_( + ecs_table_cache_iter_t *it); -/* Table graph node */ -typedef struct ecs_graph_node_t { - /* Outgoing edges */ - ecs_graph_edges_t add; - ecs_graph_edges_t remove; +#define flecs_table_cache_next(it, T)\ + (ECS_CONST_CAST(T*, flecs_table_cache_next_(it))) - /* Incoming edges (next = add edges, prev = remove edges) */ - ecs_graph_edge_hdr_t refs; -} ecs_graph_node_t; +#endif -/** A table is the Flecs equivalent of an archetype. Tables store all entities - * with a specific set of components. Tables are automatically created when an - * entity has a set of components not previously observed before. When a new - * table is created, it is automatically matched with existing queries */ -struct ecs_table_t { - uint64_t id; /* Table id in sparse set */ - ecs_type_t type; /* Identifies table type in type_index */ - ecs_flags32_t flags; /* Flags for testing table properties */ - uint16_t storage_count; /* Number of (non-zero sized) components */ - uint16_t generation; /* Used for table cleanup */ - - struct ecs_table_record_t *records; /* Array with table records */ - ecs_table_t *storage_table; /* Table w/type without tags */ - ecs_id_t *storage_ids; /* Storage ids (prevent indirection) */ - int32_t *storage_map; /* Map type <-> storage type - * - 0..count(T): type -> storage_type - * - count(T)..count(S): storage_type -> type - */ +/** + * @file storage/component_index.h + * @brief Index for (amongst others) looking up tables by component id. + */ - ecs_graph_node_t node; /* Graph node */ - ecs_data_t storage; /* Component storage */ - ecs_type_info_t *type_info; /* Cached type info */ +#ifndef FLECS_COMPONENT_INDEX_H +#define FLECS_COMPONENT_INDEX_H - int32_t *dirty_state; /* Keep track of changes in columns */ - - int16_t sw_column_count; - int16_t sw_column_offset; - int16_t bs_column_count; - int16_t bs_column_offset; - - int32_t alloc_count; /* Increases when columns are reallocd */ - int32_t refcount; /* Increased when used as storage table */ - int16_t lock; /* Prevents modifications */ - uint16_t record_count; /* Table record count including wildcards */ -}; +/* Linked list of id records */ +typedef struct ecs_id_record_elem_t { + struct ecs_component_record_t *prev, *next; +} ecs_id_record_elem_t; -/** Must appear as first member in payload of table cache */ -typedef struct ecs_table_cache_hdr_t { - struct ecs_table_cache_t *cache; +typedef struct ecs_reachable_elem_t { + const ecs_table_record_t *tr; + ecs_record_t *record; + ecs_entity_t src; + ecs_id_t id; +#ifndef FLECS_NDEBUG ecs_table_t *table; - struct ecs_table_cache_hdr_t *prev, *next; - bool empty; -} ecs_table_cache_hdr_t; - -/** Linked list of tables in table cache */ -typedef struct ecs_table_cache_list_t { - ecs_table_cache_hdr_t *first; - ecs_table_cache_hdr_t *last; - int32_t count; -} ecs_table_cache_list_t; - -/** Table cache */ -typedef struct ecs_table_cache_t { - ecs_map_t index; /* */ - ecs_table_cache_list_t tables; - ecs_table_cache_list_t empty_tables; -} ecs_table_cache_t; - -/* Sparse query column */ -typedef struct flecs_switch_term_t { - ecs_sw_column_t *sw_column; - ecs_entity_t sw_case; - int32_t signature_column_index; -} flecs_switch_term_t; - -/* Bitset query column */ -typedef struct flecs_bitset_term_t { - ecs_bs_column_t *bs_column; - int32_t column_index; -} flecs_bitset_term_t; - -typedef struct ecs_query_table_match_t ecs_query_table_match_t; - -/** List node used to iterate tables in a query. - * The list of nodes dictates the order in which tables should be iterated by a - * query. A single node may refer to the table multiple times with different - * offset/count parameters, which enables features such as sorting. */ -struct ecs_query_table_node_t { - ecs_query_table_match_t *match; /* Reference to the match */ - int32_t offset; /* Starting point in table */ - int32_t count; /* Number of entities to iterate in table */ - ecs_query_table_node_t *next, *prev; -}; +#endif +} ecs_reachable_elem_t; -/** Type containing data for a table matched with a query. - * A single table can have multiple matches, if the query contains wildcards. */ -struct ecs_query_table_match_t { - ecs_query_table_node_t node; /* Embedded list node */ +typedef struct ecs_reachable_cache_t { + int32_t generation; + int32_t current; + ecs_vec_t ids; /* vec */ +} ecs_reachable_cache_t; - ecs_table_t *table; /* The current table. */ - int32_t *columns; /* Mapping from query terms to table columns */ - ecs_id_t *ids; /* Resolved (component) ids for current table */ - ecs_entity_t *subjects; /* Subjects (sources) of ids */ - ecs_size_t *sizes; /* Sizes for ids for current table */ - ecs_ref_t *references; /* Cached components for non-this terms */ +/* Component index data that just applies to pairs */ +typedef struct ecs_pair_record_t { + /* Name lookup index (currently only used for ChildOf pairs) */ + ecs_hashmap_t *name_index; - ecs_vector_t *sparse_columns; /* Column ids of sparse columns */ - ecs_vector_t *bitset_columns; /* Column ids with disabled flags */ + /* Vector with ordered children */ + ecs_vec_t ordered_children; - uint64_t group_id; /* Value used to organize tables in groups */ + /* Tables with non-fragmenting children */ + ecs_map_t children_tables; /* map */ - /* Linked list with all matched non-empty tables, in iteration order */ - ecs_query_table_list_t list; + /* Component id of record */ + ecs_id_t id; - /* Contains head/tail to nodes of query groups (if group_by is used) */ - ecs_map_t groups; + /* Flags for id */ + ecs_flags32_t flags; - /* Handle to system (optional) */ - ecs_entity_t system; +#ifdef FLECS_DEBUG_INFO + /* String representation of id (used for debug visualization) */ + char *str; +#endif - /* Used for sorting */ - ecs_entity_t order_by_component; - ecs_order_by_action_t order_by; - ecs_vector_t *table_slices; + /* Cached pointer to type info for id, if id contains data. */ + const ecs_type_info_t *type_info; - /* Used for grouping */ - ecs_entity_t group_by_id; - ecs_group_by_action_t group_by; - void *group_by_ctx; - ecs_ctx_free_t group_by_ctx_free; + /* Storage for sparse components */ + void *sparse; - /* Subqueries */ - ecs_query_t *parent; - ecs_vector_t *subqueries; + /* Backref to tables with edges to non-fragmenting component ids */ + ecs_vec_t dont_fragment_tables; - /* Flags for query properties */ - ecs_flags32_t flags; + /* Pair data */ + ecs_pair_record_t *pair; - uint64_t id; /* Id of query in query storage */ - int32_t cascade_by; /* Identify cascade column */ - int32_t match_count; /* How often have tables been (un)matched */ - int32_t prev_match_count; /* Track if sorting is needed */ - int32_t rematch_count; /* Track which tables were added during rematch */ - bool constraints_satisfied; /* Are all term constraints satisfied */ + /* All non-fragmenting ids */ + ecs_id_record_elem_t non_fragmenting; - /* Mixins */ - ecs_world_t *world; - ecs_iterable_t iterable; + /* Refcount */ + int32_t refcount; }; -/** All triggers for a specific (component) id */ -typedef struct ecs_event_id_record_t { - /* Triggers for Self */ - ecs_map_t triggers; /* map */ +/* Bootstrap cached id records */ +void flecs_components_init( + ecs_world_t *world); - /* Triggers for SuperSet, SubSet */ - ecs_map_t set_triggers; /* map */ +/* Cleanup all id records in world */ +void flecs_components_fini( + ecs_world_t *world); - /* Triggers for Self with non-This subject */ - ecs_map_t entity_triggers; /* map */ +/* Like flecs_components_ensure, but creates only if world is not in threaded mode */ +ecs_component_record_t* flecs_components_try_ensure( + ecs_world_t *world, + ecs_id_t id); - /* Number of active triggers for (component) id */ - int32_t trigger_count; -} ecs_event_id_record_t; +/* Increase refcount of component record */ +void flecs_component_claim( + ecs_world_t *world, + ecs_component_record_t *cr); -/** All triggers for a specific event */ -typedef struct ecs_event_record_t { - ecs_map_t event_ids; /* map */ -} ecs_event_record_t; +/* Decrease refcount of component record, delete if 0 */ +int32_t flecs_component_release( + ecs_world_t *world, + ecs_component_record_t *cr); -/** Types for deferred operations */ -typedef enum ecs_defer_op_kind_t { - EcsOpNew, - EcsOpClone, - EcsOpBulkNew, - EcsOpAdd, - EcsOpRemove, - EcsOpSet, - EcsOpMut, - EcsOpModified, - EcsOpDelete, - EcsOpClear, - EcsOpOnDeleteAction, - EcsOpEnable, - EcsOpDisable -} ecs_defer_op_kind_t; - -typedef struct ecs_defer_op_1_t { - ecs_entity_t entity; /* Entity id */ - void *value; /* Value (used for set / get_mut) */ - ecs_size_t size; /* Size of value */ - bool clone_value; /* Clone entity with value (used for clone) */ -} ecs_defer_op_1_t; - -typedef struct ecs_defer_op_n_t { - ecs_entity_t *entities; - int32_t count; -} ecs_defer_op_n_t; +/* Release all empty tables in component record */ +bool flecs_component_release_tables( + ecs_world_t *world, + ecs_component_record_t *cr); -typedef struct ecs_defer_op_t { - ecs_defer_op_kind_t kind; /* Operation kind */ - ecs_id_t id; /* (Component) id */ - union { - ecs_defer_op_1_t _1; - ecs_defer_op_n_t _n; - } is; -} ecs_defer_op_t; +/* Set (component) type info for component record */ +bool flecs_component_set_type_info( + ecs_world_t *world, + ecs_component_record_t *cr, + const ecs_type_info_t *ti); -/** A stage is a data structure in which delta's are stored until it is safe to - * merge those delta's with the main world stage. A stage allows flecs systems - * to arbitrarily add/remove/set components and create/delete entities while - * iterating. Additionally, worker threads have their own stage that lets them - * mutate the state of entities without requiring locks. */ -struct ecs_stage_t { - ecs_header_t hdr; +/* Return next (R, *) record */ +ecs_component_record_t* flecs_component_first_next( + ecs_component_record_t *cr); - int32_t id; /* Unique id that identifies the stage */ +/* Return next (*, T) record */ +ecs_component_record_t* flecs_component_second_next( + ecs_component_record_t *cr); - /* Are operations deferred? */ - int32_t defer; - ecs_vector_t *defer_queue; +/* Return next traversable (*, T) record */ +ecs_component_record_t* flecs_component_trav_next( + ecs_component_record_t *cr); - ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ - ecs_world_t *world; /* Reference to world */ - ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ +/* Ensure name index for component record */ +ecs_hashmap_t* flecs_component_name_index_ensure( + ecs_world_t *world, + ecs_component_record_t *cr); - /* One-shot actions to be executed after the merge */ - ecs_vector_t *post_frame_actions; +/* Get name index for component record */ +ecs_hashmap_t* flecs_component_name_index_get( + const ecs_world_t *world, + ecs_component_record_t *cr); - /* Namespacing */ - ecs_entity_t scope; /* Entity of current scope */ - ecs_entity_t with; /* Id to add by default to new entities */ - ecs_entity_t base; /* Currently instantiated top-level base */ - ecs_entity_t *lookup_path; /* Search path used by lookup operations */ - - /* Properties */ - bool auto_merge; /* Should this stage automatically merge? */ - bool asynchronous; /* Is stage asynchronous? (write only) */ -}; +/* Init sparse storage */ +void flecs_component_init_sparse( + ecs_world_t *world, + ecs_component_record_t *cr); -/* Component monitor */ -typedef struct ecs_monitor_t { - ecs_vector_t *queries; /* vector */ - bool is_dirty; /* Should queries be rematched? */ -} ecs_monitor_t; +/* Return flags for matching component records */ +ecs_flags32_t flecs_id_flags_get( + ecs_world_t *world, + ecs_id_t id); -/* Component monitors */ -typedef struct ecs_monitor_set_t { - ecs_map_t monitors; /* map */ - bool is_dirty; /* Should monitors be evaluated? */ -} ecs_monitor_set_t; +/* Delete entities in sparse storage */ +void flecs_component_delete_sparse( + ecs_world_t *world, + ecs_component_record_t *cr); -/* Payload for table index which returns all tables for a given component, with - * the column of the component in the table. */ -typedef struct ecs_table_record_t { - ecs_table_cache_hdr_t hdr; - ecs_id_t id; - int32_t column; - int32_t count; -} ecs_table_record_t; +void flecs_component_record_init_dont_fragment( + ecs_world_t *world, + ecs_component_record_t *cr); -/* Payload for id index which contains all datastructures for an id. */ -struct ecs_id_record_t { - /* Cache with all tables that contain the id. Must be first member. */ - ecs_table_cache_t cache; /* table_cache */ +void flecs_component_record_init_exclusive( + ecs_world_t *world, + ecs_component_record_t *cr); - /* Flags for id */ - ecs_flags32_t flags; +void flecs_component_shrink( + ecs_component_record_t *cr); - /* Name lookup index (currently only used for ChildOf pairs) */ - ecs_hashmap_t *name_index; +void flecs_component_update_childof_depth( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_entity_t tgt, + const ecs_record_t *tgt_record); - /* Cached pointer to type info for id */ - const ecs_type_info_t *type_info; -}; +void flecs_component_update_childof_w_depth( + ecs_world_t *world, + ecs_component_record_t *cr, + int32_t depth); -typedef struct ecs_store_t { - /* Entity lookup */ - ecs_sparse_t entity_index; /* sparse */ +void flecs_component_ordered_children_init( + ecs_world_t *world, + ecs_component_record_t *cr); - /* Table lookup by id */ - ecs_sparse_t tables; /* sparse */ +#endif - /* Table lookup by hash */ - ecs_hashmap_t table_map; /* hashmap */ +/** + * @file storage/table.h + * @brief Table storage implementation. + */ - /* Root table */ - ecs_table_t root; +#ifndef FLECS_TABLE_H +#define FLECS_TABLE_H - /* Table edge cache */ - ecs_graph_edge_hdr_t *first_free; -} ecs_store_t; +/** + * @file storage/table_graph.h + * @brief Table graph types and functions. + */ -/** Supporting type to store looked up or derived entity data */ -typedef struct ecs_entity_info_t { - ecs_record_t *record; /* Main stage record in entity index */ - ecs_table_t *table; /* Table. Not set if entity is empty */ - ecs_data_t *data; /* Stage-specific table columns */ - int32_t row; /* Row in table */ - uint32_t row_flags; /* Row flags (used to track observables) */ -} ecs_entity_info_t; - -/** Supporting type to store looked up component data in specific table */ -typedef struct ecs_column_info_t { - ecs_entity_t id; - const ecs_type_info_t *ci; - int32_t column; -} ecs_column_info_t; +#ifndef FLECS_TABLE_GRAPH_H +#define FLECS_TABLE_GRAPH_H -/* fini actions */ -typedef struct ecs_action_elem_t { - ecs_fini_action_t action; - void *ctx; -} ecs_action_elem_t; +/** Cache of added/removed components for non-trivial edges between tables */ +#define ECS_TABLE_DIFF_INIT { .added = {0}} + +/** Builder for table diff. The table diff type itself doesn't use ecs_vec_t to + * conserve memory on table edges (a type doesn't have the size field), whereas + * a vec for the builder is more convenient to use & has allocator support. */ +typedef struct ecs_table_diff_builder_t { + ecs_vec_t added; + ecs_vec_t removed; + ecs_flags32_t added_flags; + ecs_flags32_t removed_flags; +} ecs_table_diff_builder_t; -/** The world stores and manages all ECS data. An application can have more than - * one world, but data is not shared between worlds. */ -struct ecs_world_t { - ecs_header_t hdr; +/** Edge linked list (used to keep track of incoming edges) */ +typedef struct ecs_graph_edge_hdr_t { + struct ecs_graph_edge_hdr_t *prev; + struct ecs_graph_edge_hdr_t *next; +} ecs_graph_edge_hdr_t; - /* -- Type metadata -- */ - ecs_map_t id_index; /* map */ - ecs_sparse_t *type_info; /* sparse */ +/** Single edge. */ +typedef struct ecs_graph_edge_t { + ecs_graph_edge_hdr_t hdr; + ecs_table_t *from; /* Edge source table */ + ecs_table_t *to; /* Edge destination table */ + ecs_table_diff_t *diff; /* Added/removed components for edge */ + ecs_id_t id; /* Id associated with edge */ +} ecs_graph_edge_t; - /* Cached handle to (IsA, *) */ - ecs_id_record_t *idr_isa_wildcard; +/* Edges to other tables. */ +typedef struct ecs_graph_edges_t { + ecs_graph_edge_t *lo; /* Small array optimized for low edges */ + ecs_map_t *hi; /* Map for hi edges (map) */ +} ecs_graph_edges_t; - /* -- Mixins -- */ - ecs_world_t *self; - ecs_observable_t observable; - ecs_iterable_t iterable; +/* Table graph node */ +typedef struct ecs_graph_node_t { + /* Outgoing edges */ + ecs_graph_edges_t add; + ecs_graph_edges_t remove; - /* Unique id per generated event used to prevent duplicate notifications */ - int32_t event_id; + /* Incoming edges (next = add edges, prev = remove edges) */ + ecs_graph_edge_hdr_t refs; +} ecs_graph_node_t; - /* Is entity range checking enabled? */ - bool range_check_enabled; +/** Add to existing type */ +void flecs_type_add( + ecs_world_t *world, + ecs_type_t *type, + ecs_id_t add); +/* Remove from existing type */ +void flecs_type_remove( + ecs_world_t *world, + ecs_type_t *type, + ecs_id_t remove); - /* -- Data storage -- */ +/** Copy type. */ +ecs_type_t flecs_type_copy( + ecs_world_t *world, + const ecs_type_t *src); - ecs_store_t store; +/** Free type. */ +void flecs_type_free( + ecs_world_t *world, + ecs_type_t *type); +/* Find table by removing id from current table */ +ecs_table_t *flecs_table_traverse_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff); - /* -- Storages for API objects -- */ +/* Cleanup incoming and outgoing edges for table */ +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table); - ecs_sparse_t *queries; /* sparse */ - ecs_sparse_t *triggers; /* sparse */ - ecs_sparse_t *observers; /* sparse */ +/* Table diff builder, used to build id lists that indicate the difference in + * ids between two tables. */ +void flecs_table_diff_builder_init( + ecs_world_t *world, + ecs_table_diff_builder_t *builder); +void flecs_table_diff_builder_fini( + ecs_world_t *world, + ecs_table_diff_builder_t *builder); - /* -- Pending table event buffers -- */ +void flecs_table_diff_builder_clear( + ecs_table_diff_builder_t *builder); - ecs_sparse_t *pending_buffer; /* sparse */ - ecs_sparse_t *pending_tables; /* sparse */ - +void flecs_table_diff_build_append_table( + ecs_world_t *world, + ecs_table_diff_builder_t *dst, + ecs_table_diff_t *src); - /* Used to track when cache needs to be updated */ - ecs_monitor_set_t monitors; /* map */ +void flecs_table_diff_build( + ecs_world_t *world, + ecs_table_diff_builder_t *builder, + ecs_table_diff_t *diff, + int32_t added_offset, + int32_t removed_offset); +void flecs_table_diff_build_noalloc( + ecs_table_diff_builder_t *builder, + ecs_table_diff_t *diff); - /* -- Systems -- */ +void flecs_table_edges_add_flags( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_flags32_t flags); - ecs_entity_t pipeline; /* Current pipeline */ - ecs_vector_t *fini_tasks; /* Tasks to execute on ecs_fini */ +ecs_table_t* flecs_find_table_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_table_diff_builder_t *diff); +void flecs_table_hashmap_init( + ecs_world_t *world, + ecs_hashmap_t *hm); - /* -- Lookup Indices -- */ +void flecs_table_clear_edges_for_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t component); - ecs_map_t type_handles; /* Handles to named types */ +#endif - /* -- Identifiers -- */ +#ifdef FLECS_SANITIZE +#define ecs_vec_from_column(arg_column, table, arg_elem_size) {\ + .array = (arg_column)->data,\ + .count = table->data.count,\ + .size = table->data.size,\ + .elem_size = arg_elem_size\ +} - ecs_hashmap_t aliases; - ecs_hashmap_t symbols; - const char *name_prefix; /* Remove prefix from C names in modules */ +#define ecs_vec_from_column_ext(arg_column, arg_count, arg_size, arg_elem_size) {\ + .array = (arg_column)->data,\ + .count = arg_count,\ + .size = arg_size,\ + .elem_size = arg_elem_size\ +} +#define ecs_vec_from_entities(table) {\ + .array = table->data.entities,\ + .count = table->data.count,\ + .size = table->data.size,\ + .elem_size = ECS_SIZEOF(ecs_entity_t)\ +} +#else +#define ecs_vec_from_column(arg_column, table, arg_elem_size) {\ + .array = (arg_column)->data,\ + .count = table->data.count,\ + .size = table->data.size,\ +} - /* -- Staging -- */ +#define ecs_vec_from_column_ext(arg_column, arg_count, arg_size, arg_elem_size) {\ + .array = (arg_column)->data,\ + .count = arg_count,\ + .size = arg_size,\ +} - ecs_stage_t stage; /* Main stage */ - ecs_vector_t *worker_stages; /* Stages for threads */ +#define ecs_vec_from_entities(table) {\ + .array = table->data.entities,\ + .count = table->data.count,\ + .size = table->data.size,\ +} +#endif +#define ecs_vec_from_column_t(arg_column, table, T)\ + ecs_vec_from_column(arg_column, table, ECS_SIZEOF(T)) - /* -- Multithreading -- */ +/* Table event type for notifying tables of world events */ +typedef enum ecs_table_eventkind_t { + EcsTableTriggersForId, + EcsTableNoTriggersForId, +} ecs_table_eventkind_t; - ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ - ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ - ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ - int32_t workers_running; /* Number of threads running */ - int32_t workers_waiting; /* Number of workers waiting on sync */ +typedef struct ecs_table_event_t { + ecs_table_eventkind_t kind; + /* Component info event */ + ecs_entity_t component; - /* -- Time management -- */ + /* Event match */ + ecs_entity_t event; - ecs_time_t world_start_time; /* Timestamp of simulation start */ - ecs_time_t frame_start_time; /* Timestamp of frame start */ - FLECS_FLOAT fps_sleep; /* Sleep time to prevent fps overshoot */ + /* If the number of fields gets out of hand, this can be turned into a union + * but since events are very temporary objects, this works for now and makes + * initializing an event a bit simpler. */ +} ecs_table_event_t; +/** Overrides (set if table overrides components) */ - /* -- Metrics -- */ +/* Override type used for tables with a single IsA pair */ +typedef struct ecs_table_1_override_t { + const ecs_pair_record_t *pair; /* Pair data for (IsA, base) */ + int32_t generation; /* Reachable cache generation for IsA pair */ +} ecs_table_1_override_t; - ecs_world_info_t info; +/* Override type used for tables with n IsA pairs (less common) */ +typedef struct ecs_table_n_overrides_t { + const ecs_table_record_t *tr; /* Table record for (IsA, *) */ + int32_t *generations; /* Reachable cache generations (one per IsA pair) */ +} ecs_table_n_overrides_t; +typedef struct ecs_table_overrides_t { + union { + ecs_table_1_override_t _1; + ecs_table_n_overrides_t _n; + } is; + ecs_ref_t *refs; /* Refs to base components (one for each column) */ +} ecs_table_overrides_t; - /* -- World lock -- */ +/** Infrequently accessed data not stored inline in ecs_table_t */ +typedef struct ecs_table__t { + uint64_t hash; /* Type hash */ + int32_t lock; /* Prevents modifications */ + int32_t traversable_count; /* Traversable relationship targets in table */ - ecs_os_mutex_t mutex; /* Locks the world if locking enabled */ - ecs_os_mutex_t thr_sync; /* Used to signal threads at end of frame */ - ecs_os_cond_t thr_cond; /* Used to signal threads at end of frame */ + uint16_t generation; /* Used for table cleanup */ + int16_t record_count; /* Table record count including wildcards */ + int16_t bs_count; + int16_t bs_offset; + ecs_bitset_t *bs_columns; /* Bitset columns */ - /* -- Defered operation count -- */ - - int32_t new_count; - int32_t bulk_new_count; - int32_t delete_count; - int32_t clear_count; - int32_t add_count; - int32_t remove_count; - int32_t set_count; - int32_t discard_count; - - - /* -- World state -- */ - - bool quit_workers; /* Signals worker threads to quit */ - bool is_readonly; /* Is world being progressed */ - bool is_fini; /* Is the world being cleaned up? */ - bool measure_frame_time; /* Time spent on each frame */ - bool measure_system_time; /* Time spent by each system */ - bool should_quit; /* Did a system signal that app should quit */ - bool locking_enabled; /* Lock world when in progress */ - - void *context; /* Application context */ - ecs_vector_t *fini_actions; /* Callbacks to execute when world exits */ -}; + struct ecs_table_record_t *records; /* Array with table records */ +#ifdef FLECS_DEBUG_INFO + /* Fields used for debug visualization */ + struct { + ecs_world_t *world; + ecs_entity_t id; + } parent; /* Parent. Include world so it can be cast + * to a flecs::entity. */ + int16_t name_column; /* Column with entity name */ + int16_t doc_name_column; /* Column with entity doc name */ #endif +} ecs_table__t; -/** - * @file table_cache.h - * @brief Data structure for fast table iteration/lookups. - */ - -#ifndef FLECS_TABLE_CACHE_H_ -#define FLECS_TABLE_CACHE_H_ +/** Table column */ +typedef struct ecs_column_t { + void *data; /* Array with component data */ + ecs_type_info_t *ti; /* Component type info */ +} ecs_column_t; -#ifdef __cplusplus -extern "C" { -#endif +/** Table data */ +struct ecs_data_t { + ecs_entity_t *entities; /* Entity ids */ + ecs_column_t *columns; /* Component data */ + ecs_table_overrides_t *overrides; /* Component overrides (for tables with IsA pairs) */ + int32_t count; + int32_t size; +}; -void ecs_table_cache_init( - ecs_table_cache_t *cache); -void ecs_table_cache_fini( - ecs_table_cache_t *cache); +/** A table is the Flecs equivalent of an archetype. Tables store all entities + * with a specific set of components. Tables are automatically created when an + * entity has a set of components not previously observed before. When a new + * table is created, it is automatically matched with existing queries */ +struct ecs_table_t { + uint64_t id; /* Table id in sparse set */ + ecs_flags32_t flags; /* Flags for testing table properties */ + int16_t column_count; /* Number of components (excluding tags) */ + uint16_t version; /* Version of table */ -void ecs_table_cache_insert( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *result); + uint64_t bloom_filter; /* For quick matching with queries */ -void* ecs_table_cache_remove( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *elem); + ecs_flags32_t trait_flags; /* Cached trait flags for entities in table */ + int16_t keep; /* Refcount for keeping table alive. */ + int16_t childof_index; /* Quick access to index of ChildOf pair in table. */ -void* ecs_table_cache_get( - const ecs_table_cache_t *cache, - const ecs_table_t *table); + ecs_type_t type; /* Vector with component ids */ -bool ecs_table_cache_set_empty( - ecs_table_cache_t *cache, - const ecs_table_t *table, - bool empty); + ecs_data_t data; /* Component storage */ + ecs_graph_node_t node; /* Graph node */ -bool ecs_table_cache_is_empty( - const ecs_table_cache_t *cache); + int16_t *component_map; /* Get column for component id */ + int32_t *dirty_state; /* Keep track of changes in columns */ + int16_t *column_map; /* Map type index <-> column + * - 0..count(T): type index -> column + * - count(T)..count(C): column -> type index + */ -#define ecs_table_cache_count(cache) (cache)->tables.count -#define ecs_table_cache_empty_count(cache) (cache)->empty_tables.count + ecs_table__t *_; /* Infrequently accessed table metadata */ +}; -void ecs_table_cache_fini_delete_all( +/* Init table */ +void flecs_table_init( ecs_world_t *world, - ecs_table_cache_t *cache); + ecs_table_t *table, + ecs_table_t *from); -#ifdef __cplusplus -} -#endif +/** Find or create table for a set of components */ +ecs_table_t* flecs_table_find_or_create( + ecs_world_t *world, + ecs_type_t *type); -#endif +/* Initialize columns for data */ +void flecs_table_init_data( + ecs_world_t *world, + ecs_table_t *table); -/* From: https://github.com/svpv/qsort/blob/master/qsort.h - * Use custom qsort implementation rather than relying on the version in libc to - * ensure that results are consistent across platforms. - */ +/* Reset a table to its initial state */ +void flecs_table_reset( + ecs_world_t *world, + ecs_table_t *table); -/* - * Copyright (c) 2013, 2017 Alexey Tourbin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ +/* Add a new entry to the table for the specified entity */ +int32_t flecs_table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + bool construct, + bool on_add); -/* - * This is a traditional Quicksort implementation which mostly follows - * [Sedgewick 1978]. Sorting is performed entirely on array indices, - * while actual access to the array elements is abstracted out with the - * user-defined `LESS` and `SWAP` primitives. - * - * Synopsis: - * QSORT(N, LESS, SWAP); - * where - * N - the number of elements in A[]; - * LESS(i, j) - compares A[i] to A[j]; - * SWAP(i, j) - exchanges A[i] with A[j]. - */ +/* Delete an entity from the table. */ +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + int32_t index, + bool destruct); -#ifndef QSORT_H -#define QSORT_H - -/* Sort 3 elements. */ -#define Q_SORT3(q_a1, q_a2, q_a3, Q_LESS, Q_SWAP) \ -do { \ - if (Q_LESS(q_a2, q_a1)) { \ - if (Q_LESS(q_a3, q_a2)) \ - Q_SWAP(q_a1, q_a3); \ - else { \ - Q_SWAP(q_a1, q_a2); \ - if (Q_LESS(q_a3, q_a2)) \ - Q_SWAP(q_a2, q_a3); \ - } \ - } \ - else if (Q_LESS(q_a3, q_a2)) { \ - Q_SWAP(q_a2, q_a3); \ - if (Q_LESS(q_a2, q_a1)) \ - Q_SWAP(q_a1, q_a2); \ - } \ -} while (0) - -/* Partition [q_l,q_r] around a pivot. After partitioning, - * [q_l,q_j] are the elements that are less than or equal to the pivot, - * while [q_i,q_r] are the elements greater than or equal to the pivot. */ -#define Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP) \ -do { \ - /* The middle element, not to be confused with the median. */ \ - Q_UINT q_m = q_l + ((q_r - q_l) >> 1); \ - /* Reorder the second, the middle, and the last items. \ - * As [Edelkamp Weiss 2016] explain, using the second element \ - * instead of the first one helps avoid bad behaviour for \ - * decreasingly sorted arrays. This method is used in recent \ - * versions of gcc's std::sort, see gcc bug 58437#c13, although \ - * the details are somewhat different (cf. #c14). */ \ - Q_SORT3(q_l + 1, q_m, q_r, Q_LESS, Q_SWAP); \ - /* Place the median at the beginning. */ \ - Q_SWAP(q_l, q_m); \ - /* Partition [q_l+2, q_r-1] around the median which is in q_l. \ - * q_i and q_j are initially off by one, they get decremented \ - * in the do-while loops. */ \ - q_i = q_l + 1; q_j = q_r; \ - while (1) { \ - do q_i++; while (Q_LESS(q_i, q_l)); \ - do q_j--; while (Q_LESS(q_l, q_j)); \ - if (q_i >= q_j) break; /* Sedgewick says "until j < i" */ \ - Q_SWAP(q_i, q_j); \ - } \ - /* Compensate for the i==j case. */ \ - q_i = q_j + 1; \ - /* Put the median to its final place. */ \ - Q_SWAP(q_l, q_j); \ - /* The median is not part of the left subfile. */ \ - q_j--; \ -} while (0) - -/* Insertion sort is applied to small subfiles - this is contrary to - * Sedgewick's suggestion to run a separate insertion sort pass after - * the partitioning is done. The reason I don't like a separate pass - * is that it triggers extra comparisons, because it can't see that the - * medians are already in their final positions and need not be rechecked. - * Since I do not assume that comparisons are cheap, I also do not try - * to eliminate the (q_j > q_l) boundary check. */ -#define Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP) \ -do { \ - Q_UINT q_i, q_j; \ - /* For each item starting with the second... */ \ - for (q_i = q_l + 1; q_i <= q_r; q_i++) \ - /* move it down the array so that the first part is sorted. */ \ - for (q_j = q_i; q_j > q_l && (Q_LESS(q_j, q_j - 1)); q_j--) \ - Q_SWAP(q_j, q_j - 1); \ -} while (0) - -/* When the size of [q_l,q_r], i.e. q_r-q_l+1, is greater than or equal to - * Q_THRESH, the algorithm performs recursive partitioning. When the size - * drops below Q_THRESH, the algorithm switches to insertion sort. - * The minimum valid value is probably 5 (with 5 items, the second and - * the middle items, the middle itself being rounded down, are distinct). */ -#define Q_THRESH 16 - -/* The main loop. */ -#define Q_LOOP(Q_UINT, Q_N, Q_LESS, Q_SWAP) \ -do { \ - Q_UINT q_l = 0; \ - Q_UINT q_r = (Q_N) - 1; \ - Q_UINT q_sp = 0; /* the number of frames pushed to the stack */ \ - struct { Q_UINT q_l, q_r; } \ - /* On 32-bit platforms, to sort a "char[3GB+]" array, \ - * it may take full 32 stack frames. On 64-bit CPUs, \ - * though, the address space is limited to 48 bits. \ - * The usage is further reduced if Q_N has a 32-bit type. */ \ - q_st[sizeof(Q_UINT) > 4 && sizeof(Q_N) > 4 ? 48 : 32]; \ - while (1) { \ - if (q_r - q_l + 1 >= Q_THRESH) { \ - Q_UINT q_i, q_j; \ - Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP); \ - /* Now have two subfiles: [q_l,q_j] and [q_i,q_r]. \ - * Dealing with them depends on which one is bigger. */ \ - if (q_j - q_l >= q_r - q_i) \ - Q_SUBFILES(q_l, q_j, q_i, q_r); \ - else \ - Q_SUBFILES(q_i, q_r, q_l, q_j); \ - } \ - else { \ - Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP); \ - /* Pop subfiles from the stack, until it gets empty. */ \ - if (q_sp == 0) break; \ - q_sp--; \ - q_l = q_st[q_sp].q_l; \ - q_r = q_st[q_sp].q_r; \ - } \ - } \ -} while (0) - -/* The missing part: dealing with subfiles. - * Assumes that the first subfile is not smaller than the second. */ -#define Q_SUBFILES(q_l1, q_r1, q_l2, q_r2) \ -do { \ - /* If the second subfile is only a single element, it needs \ - * no further processing. The first subfile will be processed \ - * on the next iteration (both subfiles cannot be only a single \ - * element, due to Q_THRESH). */ \ - if (q_l2 == q_r2) { \ - q_l = q_l1; \ - q_r = q_r1; \ - } \ - else { \ - /* Otherwise, both subfiles need processing. \ - * Push the larger subfile onto the stack. */ \ - q_st[q_sp].q_l = q_l1; \ - q_st[q_sp].q_r = q_r1; \ - q_sp++; \ - /* Process the smaller subfile on the next iteration. */ \ - q_l = q_l2; \ - q_r = q_r2; \ - } \ -} while (0) - -/* And now, ladies and gentlemen, may I proudly present to you... */ -#define QSORT(Q_N, Q_LESS, Q_SWAP) \ -do { \ - if ((Q_N) > 1) \ - /* We could check sizeof(Q_N) and use "unsigned", but at least \ - * on x86_64, this has the performance penalty of up to 5%. */ \ - Q_LOOP(ecs_size_t, Q_N, Q_LESS, Q_SWAP); \ -} while (0) - -void ecs_qsort( - void *base, - ecs_size_t nitems, - ecs_size_t size, - int (*compar)(const void *, const void*)); +/* Move a row from one table to another */ +void flecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *new_table, + int32_t new_index, + ecs_table_t *old_table, + int32_t old_index, + bool construct); -#define ecs_qsort_t(base, nitems, T, compar) \ - ecs_qsort(base, nitems, ECS_SIZEOF(T), compar) +/* Grow table with specified number of records. Populate table with entities, + * starting from specified entity id. */ +int32_t flecs_table_appendn( + ecs_world_t *world, + ecs_table_t *table, + int32_t count, + const ecs_entity_t *ids); -#endif +/* Shrink table to contents */ +bool flecs_table_shrink( + ecs_world_t *world, + ecs_table_t *table); -/** - * @file name_index.h - * @brief Data structure used for id <-> name lookups. - */ +/* Get dirty state for table columns */ +int32_t* flecs_table_get_dirty_state( + ecs_world_t *world, + ecs_table_t *table); -#ifndef FLECS_NAME_INDEX_H -#define FLECS_NAME_INDEX_H +/* Initialize root table */ +void flecs_init_root_table( + ecs_world_t *world); -void flecs_name_index_init( - ecs_hashmap_t *hm); +/* Unset components in table */ +void flecs_table_remove_actions( + ecs_world_t *world, + ecs_table_t *table); -ecs_hashmap_t* flecs_name_index_new(void); +/* Free table */ +void flecs_table_fini( + ecs_world_t *world, + ecs_table_t *table); -void flecs_name_index_fini( - ecs_hashmap_t *map); +/* Free table */ +void flecs_table_free_type( + ecs_world_t *world, + ecs_table_t *table); -void flecs_name_index_free( - ecs_hashmap_t *map); +/* Merge data of one table into another table */ +void flecs_table_merge( + ecs_world_t *world, + ecs_table_t *new_table, + ecs_table_t *old_table); -ecs_hashed_string_t flecs_get_hashed_string( - const char *name, - ecs_size_t length, - uint64_t hash); +void flecs_table_swap( + ecs_world_t *world, + ecs_table_t *table, + int32_t row_1, + int32_t row_2); -const uint64_t* flecs_name_index_find_ptr( - const ecs_hashmap_t *map, - const char *name, - ecs_size_t length, - uint64_t hash); +void flecs_table_mark_dirty( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t component); -uint64_t flecs_name_index_find( - const ecs_hashmap_t *map, - const char *name, - ecs_size_t length, - uint64_t hash); +void flecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_table_event_t *event); -void flecs_name_index_ensure( - ecs_hashmap_t *map, - uint64_t id, - const char *name, - ecs_size_t length, - uint64_t hash); +void flecs_table_delete_entities( + ecs_world_t *world, + ecs_table_t *table); -void flecs_name_index_remove( - ecs_hashmap_t *map, - uint64_t id, - uint64_t hash); +/* Increase observer count of table */ +void flecs_table_traversable_add( + ecs_table_t *table, + int32_t value); -void flecs_name_index_update_name( - ecs_hashmap_t *map, - uint64_t e, - uint64_t hash, - const char *name); +void flecs_table_emit( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t event); -#endif +int32_t flecs_table_get_toggle_column( + ecs_table_t *table, + ecs_id_t id); +ecs_bitset_t* flecs_table_get_toggle( + ecs_table_t *table, + ecs_id_t id); -//////////////////////////////////////////////////////////////////////////////// -//// Core bootstrap functions -//////////////////////////////////////////////////////////////////////////////// +ecs_id_t flecs_column_id( + ecs_table_t *table, + int32_t column_index); -/* Bootstrap world */ -void flecs_bootstrap( - ecs_world_t *world); +uint64_t flecs_table_bloom_filter_add( + uint64_t filter, + uint64_t value); -#define flecs_bootstrap_component(world, id)\ - ecs_component_init(world, &(ecs_component_desc_t){\ - .entity = {\ - .entity = ecs_id(id),\ - .name = #id,\ - .symbol = #id\ - },\ - .size = sizeof(id),\ - .alignment = ECS_ALIGNOF(id)\ - }); +bool flecs_table_bloom_filter_test( + const ecs_table_t *table, + uint64_t filter); -#define flecs_bootstrap_tag(world, name)\ - ecs_add_id(world, name, EcsFinal);\ - ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ - ecs_set(world, name, EcsComponent, {.size = 0});\ - ecs_set_name(world, name, (char*)&#name[ecs_os_strlen(world->name_prefix)]);\ - ecs_set_symbol(world, name, #name) +const ecs_ref_t* flecs_table_get_override( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + const ecs_component_record_t *cr, + ecs_ref_t *storage); +void flecs_table_keep( + ecs_table_t *table); -/* Bootstrap functions for other parts in the code */ -void flecs_bootstrap_hierarchy(ecs_world_t *world); +void flecs_table_release( + ecs_table_t *table); -//////////////////////////////////////////////////////////////////////////////// -//// Entity API -//////////////////////////////////////////////////////////////////////////////// +ecs_component_record_t* flecs_table_get_childof_cr( + const ecs_world_t *world, + const ecs_table_t *table); -/* Mark an entity as being watched. This is used to trigger automatic rematching - * when entities used in system expressions change their components. */ -void flecs_add_flag( - ecs_world_t *world, - ecs_entity_t entity, - uint32_t flag); +ecs_pair_record_t* flecs_table_get_childof_pr( + const ecs_world_t *world, + const ecs_table_t *table); -/* Obtain entity info */ -bool flecs_get_info( +ecs_hashmap_t* flecs_table_get_name_index( const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_info_t *info); + const ecs_table_t *table); -void flecs_register_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name); +#endif -void flecs_unregister_name( - ecs_world_t *world, - ecs_entity_t entity); +/** + * @file storage/sparse_storage.h + * @brief Sparse component storage. + */ +#ifndef FLECS_SPARSE_STORAGE_H +#define FLECS_SPARSE_STORAGE_H -//////////////////////////////////////////////////////////////////////////////// -//// World API -//////////////////////////////////////////////////////////////////////////////// +bool flecs_component_sparse_has( + ecs_component_record_t *cr, + ecs_entity_t entity); -/* Get current stage */ -ecs_stage_t* flecs_stage_from_world( - ecs_world_t **world_ptr); +void* flecs_component_sparse_get( + const ecs_world_t *world, + ecs_component_record_t *cr, + ecs_table_t *table, + ecs_entity_t entity); -/* Get current thread-specific stage from readonly world */ -const ecs_stage_t* flecs_stage_from_readonly_world( - const ecs_world_t *world); +void* flecs_component_sparse_insert( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_table_t *table, + int32_t row); -/* Get component callbacks */ -const ecs_type_info_t *flecs_get_type_info( - const ecs_world_t *world, - ecs_entity_t component); +void* flecs_component_sparse_emplace( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_table_t *table, + int32_t row); -/* Get or create component callbacks */ -ecs_type_info_t* flecs_ensure_type_info( +void flecs_component_sparse_remove( ecs_world_t *world, - ecs_entity_t component); + ecs_component_record_t *cr, + ecs_table_t *table, + int32_t row); -void flecs_init_type_info( +void flecs_component_sparse_remove_all( ecs_world_t *world, - ecs_entity_t component, - ecs_size_t size, - ecs_size_t alignment); + ecs_component_record_t *cr); -#define flecs_init_type_info_t(world, T)\ - flecs_init_type_info(world, ecs_id(T), ECS_SIZEOF(T), ECS_ALIGNOF(T)) +#endif -void flecs_eval_component_monitors( - ecs_world_t *world); +/** + * @file storage/ordered_children.h + * @brief Storage for ordered list of entity (child) ids. + */ -void flecs_monitor_mark_dirty( - ecs_world_t *world, - ecs_entity_t id); +#ifndef FLECS_ORDERED_CHILDREN_H +#define FLECS_ORDERED_CHILDREN_H -void flecs_monitor_register( +/* Initialize ordered children storage. */ +void flecs_ordered_children_init( ecs_world_t *world, - ecs_entity_t id, - ecs_query_t *query); + ecs_component_record_t *cr); -void flecs_monitor_unregister( +/* Free ordered children storage. */ +void flecs_ordered_children_fini( ecs_world_t *world, - ecs_entity_t id, - ecs_query_t *query); + ecs_component_record_t *cr); -void flecs_notify_tables( +/* Populate ordered children storage with existing children. */ +void flecs_ordered_children_populate( ecs_world_t *world, - ecs_id_t id, - ecs_table_event_t *event); + ecs_component_record_t *cr); -void flecs_notify_queries( - ecs_world_t *world, - ecs_query_event_t *event); +/* Clear ordered children storage. */ +void flecs_ordered_children_clear( + ecs_component_record_t *cr); -void flecs_register_table( +/* Reparent entities in ordered children storage. */ +void flecs_ordered_children_reparent( ecs_world_t *world, - ecs_table_t *table); + const ecs_table_t *dst, + const ecs_table_t *src, + int32_t row, + int32_t count); -void flecs_unregister_table( +/* Unparent entities in ordered children storage. */ +void flecs_ordered_children_unparent( ecs_world_t *world, - ecs_table_t *table); + const ecs_table_t *src, + int32_t row, + int32_t count); -void flecs_table_set_empty( +/* Reorder entities in ordered children storage. */ +void flecs_ordered_children_reorder( ecs_world_t *world, - ecs_table_t *table); + ecs_entity_t parent, + const ecs_entity_t *children, + int32_t child_count); -ecs_id_record_t* flecs_ensure_id_record( +/* Directly add child to ordered children array. */ +void flecs_ordered_entities_append( ecs_world_t *world, - ecs_id_t id); + ecs_component_record_t *cr, + ecs_entity_t e); -void flecs_register_for_id_record( +/* Directly remove child from ordered children array. */ +void flecs_ordered_entities_remove( ecs_world_t *world, - ecs_id_t id, - const ecs_table_t *table, - ecs_table_record_t *tr); + ecs_component_record_t *cr, + ecs_entity_t e); -ecs_id_record_t* flecs_get_id_record( - const ecs_world_t *world, - ecs_id_t id); +#endif -ecs_hashmap_t* flecs_ensure_id_name_index( - ecs_world_t *world, - ecs_id_t id); +/** + * @file storage/non_fragmenting_childof.h + * @brief Non-fragmenting storage for hierarchies. + */ -ecs_hashmap_t* flecs_get_id_name_index( - const ecs_world_t *world, - ecs_id_t id); +#ifndef FLECS_NON_FRAGMENTING_CHILDOF +#define FLECS_NON_FRAGMENTING_CHILDOF -ecs_table_record_t* flecs_get_table_record( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id); +void flecs_bootstrap_parent_component( + ecs_world_t *world); -const ecs_table_record_t* flecs_id_record_table( - ecs_id_record_t *idr, - ecs_table_t *table); +void flecs_on_non_fragmenting_child_move_add( + ecs_world_t *world, + const ecs_table_t *dst, + const ecs_table_t *src, + int32_t row, + int32_t count); -void flecs_process_pending_tables( - const ecs_world_t *world); +void flecs_on_non_fragmenting_child_move_remove( + ecs_world_t *world, + const ecs_table_t *dst, + const ecs_table_t *src, + int32_t row, + int32_t count, + bool update_parent_records); -ecs_id_record_t* flecs_table_iter( +void flecs_non_fragmenting_childof_reparent( ecs_world_t *world, - ecs_id_t id, - ecs_table_cache_iter_t *out); + const ecs_table_t *dst, + const ecs_table_t *src, + int32_t row, + int32_t count); -ecs_id_record_t* flecs_empty_table_iter( +void flecs_non_fragmenting_childof_unparent( ecs_world_t *world, - ecs_id_t id, - ecs_table_cache_iter_t *out); + const ecs_table_t *dst, + const ecs_table_t *src, + int32_t row, + int32_t count); -bool flecs_table_cache_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out); +bool flecs_component_has_non_fragmenting_childof( + ecs_component_record_t *cr); -bool flecs_table_cache_empty_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out); +int flecs_add_non_fragmenting_child_w_records( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t entity, + ecs_component_record_t *cr, + const ecs_record_t *r); -ecs_table_cache_hdr_t* _flecs_table_cache_next( - ecs_table_cache_iter_t *it); +#endif -#define flecs_table_cache_next(it, T)\ - (ECS_CAST(T*, _flecs_table_cache_next(it))) + /** + * @file query/query.h + * @brief Query implementation. + */ -void flecs_clear_id_record( - ecs_world_t *world, - ecs_id_t id, - ecs_id_record_t *idr); +/** + * @file query/compiler/compiler.h + * @brief Query compiler functions. + */ -void flecs_remove_id_record( - ecs_world_t *world, - ecs_id_t id, - ecs_id_record_t *idr); + /** + * @file query/types.h + * @brief Internal types and functions for queries. + */ -void flecs_name_index_erase( - ecs_hashmap_t *map, - ecs_entity_t entity, - uint64_t hash); +#ifndef FLECS_QUERY_TYPES +#define FLECS_QUERY_TYPES + +typedef struct ecs_query_impl_t ecs_query_impl_t; +typedef uint8_t ecs_var_id_t; +typedef int16_t ecs_query_lbl_t; +typedef ecs_flags64_t ecs_write_flags_t; + +#define flecs_query_impl(query) (ECS_CONST_CAST(ecs_query_impl_t*, query)) +#define EcsQueryMaxVarCount (64) +#define EcsVarNone ((ecs_var_id_t)-1) +#define EcsThisName "this" + +/* -- Variable types -- */ +typedef enum { + EcsVarEntity, /* Variable that stores an entity id */ + EcsVarTable, /* Variable that stores a table */ + EcsVarAny /* Used when requesting either entity or table var */ +} ecs_var_kind_t; + +typedef struct ecs_query_var_t { + int8_t kind; /* variable kind (EcsVarEntity or EcsVarTable) */ + bool anonymous; /* variable is anonymous */ + ecs_var_id_t id; /* variable id */ + ecs_var_id_t table_id; /* id to table variable, if any */ + ecs_var_id_t base_id; /* id to base entity variable, for lookups */ + const char *name; /* variable name */ + const char *lookup; /* Lookup string for variable */ +#ifdef FLECS_DEBUG + const char *label; /* for debugging */ +#endif +} ecs_query_var_t; + +/* Placeholder values for queries with only $this variable */ +extern ecs_query_var_t flecs_this_array; +extern char *flecs_this_name_array; + +/* -- Instruction kinds -- */ +typedef enum { + EcsQueryAll, /* Yield all tables */ + EcsQueryAnd, /* And operator: find or match id against variable source */ + EcsQueryAndAny, /* And operator with support for matching Any src/id */ + EcsQueryAndWcTgt, /* And operator for (*, T) queries */ + EcsQueryTriv, /* Trivial search (batches multiple terms) */ + EcsQueryCache, /* Cached search */ + EcsQueryIsCache, /* Cached search for queries that are entirely cached */ + EcsQueryUp, /* Up traversal */ + EcsQuerySelfUp, /* Self|up traversal */ + EcsQueryWith, /* Match id against fixed or variable source */ + EcsQueryWithWcTgt, /* Match (*, T) id against fixed or variable source */ + EcsQueryTrav, /* Support for transitive/reflexive queries */ + EcsQueryAndFrom, /* AndFrom operator */ + EcsQueryOrFrom, /* OrFrom operator */ + EcsQueryNotFrom, /* NotFrom operator */ + EcsQueryIds, /* Test for existence of ids matching wildcard */ + EcsQueryIdsRight, /* Find ids in use that match (R, *) wildcard */ + EcsQueryIdsLeft, /* Find ids in use that match (*, T) wildcard */ + EcsQueryEach, /* Iterate entities in table, populate entity variable */ + EcsQueryStore, /* Store table or entity in variable */ + EcsQueryReset, /* Reset value of variable to wildcard (*) */ + EcsQueryOr, /* Or operator */ + EcsQueryOptional, /* Optional operator */ + EcsQueryIfVar, /* Conditional execution on whether variable is set */ + EcsQueryIfSet, /* Conditional execution on whether term is set */ + EcsQueryNot, /* Sets iterator state after term was not matched */ + EcsQueryEnd, /* End of control flow block */ + EcsQueryPredEq, /* Test if variable is equal to, or assign to if not set */ + EcsQueryPredNeq, /* Test if variable is not equal to */ + EcsQueryPredEqName, /* Same as EcsQueryPredEq but with matching by name */ + EcsQueryPredNeqName, /* Same as EcsQueryPredNeq but with matching by name */ + EcsQueryPredEqMatch, /* Same as EcsQueryPredEq but with fuzzy matching by name */ + EcsQueryPredNeqMatch, /* Same as EcsQueryPredNeq but with fuzzy matching by name */ + EcsQueryMemberEq, /* Compare member value */ + EcsQueryMemberNeq, /* Compare member value */ + EcsQueryToggle, /* Evaluate toggle bitset, if present */ + EcsQueryToggleOption, /* Toggle for optional terms */ + EcsQuerySparse, /* Evaluate sparse component */ + EcsQuerySparseNot, /* Evaluate sparse component with not operator */ + EcsQuerySparseSelfUp, + EcsQuerySparseUp, + EcsQuerySparseWith, /* Evaluate sparse component against fixed or variable source */ + EcsQueryTree, + EcsQueryTreeWildcard, + EcsQueryTreeWith, /* Evaluate (ChildOf, tgt) against fixed or variable source */ + EcsQueryTreeUp, /* Return union of up(ChildOf) and tables with Parent */ + EcsQueryTreeSelfUp, + EcsQueryTreePre, /* Tree instruction that doesn't filter Parent component / returns entire tables. */ + EcsQueryTreePost, /* Tree instruction that applies filter to Parent component. */ + EcsQueryTreeUpPre, /* Up traversal for ChildOf that doesn't filter Parent component / returns entire tables */ + EcsQueryTreeSelfUpPre, /* Up traversal for ChildOf that doesn't filter Parent component / returns entire tables */ + EcsQueryTreeUpPost, /* Up traversal for ChildOf that filters cached tables w/Parent component. */ + EcsQueryTreeSelfUpPost, /* Up traversal for ChildOf that filters cached tables w/Parent component. */ + EcsQueryChildren, /* Return children for parent, if possible in order */ + EcsQueryChildrenWc, /* Return children for parents, if possible in order */ + EcsQueryLookup, /* Lookup relative to variable */ + EcsQuerySetVars, /* Populate it.sources from variables */ + EcsQuerySetThis, /* Populate This entity variable */ + EcsQuerySetFixed, /* Set fixed source entity ids */ + EcsQuerySetIds, /* Set fixed (component) ids */ + EcsQuerySetId, /* Set id if not set */ + EcsQueryContain, /* Test if table contains entity */ + EcsQueryPairEq, /* Test if both elements of pair are the same */ + EcsQueryYield, /* Yield result back to application */ + EcsQueryNothing /* Must be last */ +} ecs_query_op_kind_t; + +/* Op flags to indicate if ecs_query_ref_t is entity or variable */ +#define EcsQueryIsEntity (1 << 0) +#define EcsQueryIsVar (1 << 1) +#define EcsQueryIsSelf (1 << 6) + +/* Op flags used to shift EcsQueryIsEntity and EcsQueryIsVar */ +#define EcsQuerySrc 0 +#define EcsQueryFirst 2 +#define EcsQuerySecond 4 + +/* References to variable or entity */ +typedef union { + ecs_var_id_t var; + ecs_entity_t entity; +} ecs_query_ref_t; + +/* Query instruction */ +typedef struct ecs_query_op_t { + uint8_t kind; /* Instruction kind */ + ecs_flags8_t flags; /* Flags storing whether 1st/2nd are variables */ + int8_t field_index; /* Query field corresponding with operation */ + int8_t term_index; /* Query term corresponding with operation */ + ecs_query_lbl_t prev; /* Backtracking label (no data) */ + ecs_query_lbl_t next; /* Forwarding label. Must come after prev */ + ecs_query_lbl_t other; /* Misc register used for control flow */ + ecs_flags16_t match_flags; /* Flags that modify matching behavior */ + ecs_query_ref_t src; + ecs_query_ref_t first; + ecs_query_ref_t second; + ecs_flags64_t written; /* Bitset with variables written by op */ +} ecs_query_op_t; + +/* All context */ +typedef struct { + int32_t cur; + ecs_table_record_t dummy_tr; +} ecs_query_all_ctx_t; -void flecs_triggers_notify( - ecs_iter_t *it, - ecs_observable_t *observable, - ecs_ids_t *ids, - ecs_entity_t event); +/* And context */ +typedef struct { + ecs_component_record_t *cr; + ecs_table_cache_iter_t it; + int16_t column; + int16_t remaining; + bool non_fragmenting; +} ecs_query_and_ctx_t; -void flecs_set_triggers_notify( - ecs_iter_t *it, - ecs_observable_t *observable, - ecs_ids_t *ids, - ecs_entity_t event, - ecs_id_t set_id); +/* Sparse context */ +typedef struct { + ecs_query_and_ctx_t and_; /* For mixed results */ -bool flecs_check_triggers_for_event( - const ecs_poly_t *world, - ecs_id_t id, - ecs_entity_t event); + ecs_sparse_t *sparse; + ecs_table_range_t range; + int32_t cur; + bool self; + bool exclusive; -void flecs_trigger_fini( - ecs_world_t *world, - ecs_trigger_t *trigger); + ecs_component_record_t *cr; + ecs_table_range_t prev_range; + int32_t prev_cur; +} ecs_query_sparse_ctx_t; -void flecs_observer_fini( - ecs_world_t *world, - ecs_observer_t *observer); +typedef enum ecs_query_tree_iter_state_t { + EcsQueryTreeIterNext = 1, + EcsQueryTreeIterTables = 2, + EcsQueryTreeIterEntities = 3 +} ecs_query_tree_iter_state_t; -/* Suspend/resume readonly state. To fully support implicit registration of - * components, it should be possible to register components while the world is - * in readonly mode. It is not uncommon that a component is used first from - * within a system, which are often ran while in readonly mode. - * - * Suspending readonly mode is only allowed when the world is not multithreaded. - * When a world is multithreaded, it is not safe to (even temporarily) leave - * readonly mode, so a multithreaded application should always explicitly - * register components in advance. - * - * These operations also suspend deferred mode. - */ typedef struct { - bool is_readonly; - bool is_deferred; - int32_t defer_count; - ecs_entity_t scope; - ecs_entity_t with; - ecs_vector_t *defer_queue; - ecs_stage_t *stage; -} ecs_suspend_readonly_state_t; + ecs_query_and_ctx_t and_; /* For mixed results */ + ecs_component_record_t *cr; + ecs_entity_t tgt; + ecs_entity_t *entities; + const EcsParent *parents; + ecs_table_range_t range; + int32_t cur; + ecs_query_tree_iter_state_t state; +} ecs_query_tree_ctx_t; -ecs_world_t* flecs_suspend_readonly( - const ecs_world_t *world, - ecs_suspend_readonly_state_t *state); +typedef struct { + ecs_component_record_t *cr; + ecs_table_cache_iter_t it; + ecs_entity_t *entities; + int32_t count; + int32_t cur; + ecs_query_tree_iter_state_t state; +} ecs_query_tree_wildcard_ctx_t; -void flecs_resume_readonly( - ecs_world_t *world, - ecs_suspend_readonly_state_t *state); +/* Down traversal cache (for resolving up queries w/unknown source) */ +typedef struct { + ecs_table_range_t range; + bool leaf; /* Table owns and inherits id (for Up queries without Self) */ +} ecs_trav_down_elem_t; -void flecs_emit( - ecs_world_t *world, - ecs_world_t *stage, - ecs_event_desc_t *desc); +typedef struct { + ecs_vec_t elems; /* vector */ + bool ready; +} ecs_trav_down_t; -ecs_entity_t flecs_get_oneof( - const ecs_world_t *world, - ecs_entity_t e); +typedef struct { + ecs_entity_t src; + ecs_id_t id; + ecs_table_record_t *tr; + bool ready; +} ecs_trav_up_t; -//////////////////////////////////////////////////////////////////////////////// -//// Stage API -//////////////////////////////////////////////////////////////////////////////// +typedef enum { + EcsTravUp = 1, + EcsTravDown = 2 +} ecs_trav_direction_t; -/* Initialize stage data structures */ -void flecs_stage_init( - ecs_world_t *world, - ecs_stage_t *stage); +typedef struct { + ecs_map_t src; /* map or map */ + ecs_id_t with; + ecs_trav_direction_t dir; +} ecs_trav_up_cache_t; -/* Deinitialize stage */ -void flecs_stage_deinit( - ecs_world_t *world, - ecs_stage_t *stage); +/* And up context */ +typedef struct { + ecs_table_t *table; + int32_t row; + int32_t end; + ecs_entity_t trav; + ecs_id_t with; + ecs_id_t matched; + ecs_component_record_t *cr_with; + ecs_component_record_t *cr_trav; + + /* If queried for component is a ChilOf pair that uses the non-fragmenting + * ChildOf storage, iterate ordered children vector instead of tables with + * ChildOf pairs as roots for down cache. */ + ecs_entity_t *entities; + int32_t entities_cur; + int32_t entities_count; -/* Post-frame merge actions */ -void flecs_stage_merge_post_frame( - ecs_world_t *world, - ecs_stage_t *stage); + ecs_trav_down_t *down; + int32_t cache_elem; + ecs_trav_up_cache_t cache; +} ecs_query_up_impl_t; -/* Delete table from stage */ -void flecs_delete_table( - ecs_world_t *world, - ecs_table_t *table); +typedef struct { + union { + ecs_query_and_ctx_t and; + ecs_query_sparse_ctx_t sparse_; + } is; + /* Indirection because otherwise the ctx struct gets too large */ + ecs_query_up_impl_t *impl; -//////////////////////////////////////////////////////////////////////////////// -//// Defer API -//////////////////////////////////////////////////////////////////////////////// + /* Data for returning tables with non-fragmenting ChildOf */ + const EcsParent *parents; + ecs_table_range_t range; + int32_t cur; +} ecs_query_up_ctx_t; -bool flecs_defer_none( - ecs_world_t *world, - ecs_stage_t *stage); +typedef struct { + union { + ecs_query_and_ctx_t and; + ecs_query_up_ctx_t up_; + } is; + ecs_query_tree_iter_state_t state; +} ecs_query_tree_pre_ctx_t; -bool flecs_defer_modified( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t component); +/* Cache for storing results of upward/downward "all" traversal. This type of + * traversal iterates and caches the entire tree. */ +typedef struct { + ecs_entity_t entity; + ecs_component_record_t *cr; + const ecs_table_record_t *tr; +} ecs_trav_elem_t; -bool flecs_defer_new( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id); +typedef struct { + ecs_id_t id; + ecs_component_record_t *cr; + ecs_vec_t entities; + bool up; +} ecs_trav_cache_t; -bool flecs_defer_clone( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t src, - bool clone_value); +/* Trav context */ +typedef struct { + ecs_query_and_ctx_t and; + int32_t index; + int32_t offset; + int32_t count; + ecs_trav_cache_t cache; + bool yield_reflexive; +} ecs_query_trav_ctx_t; -bool flecs_defer_bulk_new( - ecs_world_t *world, - ecs_stage_t *stage, - int32_t count, - ecs_id_t id, - const ecs_entity_t **ids_out); + /* Eq context */ +typedef struct { + ecs_table_range_t range; + int32_t index; + int16_t name_col; + bool redo; +} ecs_query_eq_ctx_t; -bool flecs_defer_delete( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity); + /* Each context */ +typedef struct { + int32_t row; +} ecs_query_each_ctx_t; -bool flecs_defer_clear( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity); + /* Setthis context */ +typedef struct { + ecs_table_range_t range; +} ecs_query_setthis_ctx_t; -bool flecs_defer_on_delete_action( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_id_t id, - ecs_entity_t action); +/* Ids context */ +typedef struct { + ecs_component_record_t *cur; +} ecs_query_ids_ctx_t; -bool flecs_defer_enable( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t component, - bool enable); +/* Control flow context */ +typedef struct { + ecs_query_lbl_t op_index; + ecs_id_t field_id; + bool is_set; +} ecs_query_ctrl_ctx_t; -bool flecs_defer_add( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id); +/* Trivial iterator context */ +typedef struct { + ecs_table_cache_iter_t it; + const ecs_table_record_t *tr; + int32_t start_from; + int32_t first_to_eval; +} ecs_query_trivial_ctx_t; -bool flecs_defer_remove( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id); +/* *From operator iterator context */ +typedef struct { + ecs_query_and_ctx_t and; + ecs_entity_t type_id; + ecs_type_t *type; + int32_t first_id_index; + int32_t cur_id_index; +} ecs_query_xfrom_ctx_t; + +/* Member equality context */ +typedef struct { + ecs_query_each_ctx_t each; + void *data; +} ecs_query_membereq_ctx_t; -bool flecs_defer_set( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_defer_op_kind_t op_kind, - ecs_entity_t entity, - ecs_entity_t component, - ecs_size_t size, - const void *value, - void **value_out, - bool *is_added); +/* Toggle context */ +typedef struct { + ecs_table_range_t range; + int32_t cur; + int32_t block_index; + ecs_flags64_t block; + ecs_termset_t prev_set_fields; + bool optional_not; + bool has_bitset; +} ecs_query_toggle_ctx_t; + +/* Optional context */ +typedef struct { + ecs_table_range_t range; +} ecs_query_optional_ctx_t; -bool flecs_defer_flush( - ecs_world_t *world, - ecs_stage_t *stage); +typedef struct ecs_query_op_ctx_t { + union { + ecs_query_all_ctx_t all; + ecs_query_and_ctx_t and; + ecs_query_xfrom_ctx_t xfrom; + ecs_query_up_ctx_t up; + ecs_query_trav_ctx_t trav; + ecs_query_ids_ctx_t ids; + ecs_query_eq_ctx_t eq; + ecs_query_each_ctx_t each; + ecs_query_setthis_ctx_t setthis; + ecs_query_ctrl_ctx_t ctrl; + ecs_query_trivial_ctx_t trivial; + ecs_query_membereq_ctx_t membereq; + ecs_query_toggle_ctx_t toggle; + ecs_query_sparse_ctx_t sparse; + ecs_query_tree_ctx_t tree; + ecs_query_tree_pre_ctx_t tree_pre; + ecs_query_tree_wildcard_ctx_t tree_wildcard; + ecs_query_optional_ctx_t optional; + } is; +} ecs_query_op_ctx_t; -bool flecs_defer_purge( - ecs_world_t *world, - ecs_stage_t *stage); +typedef struct { + /* Labels used for control flow */ + ecs_query_lbl_t lbl_query; /* Used to find the op that does the actual searching */ + ecs_query_lbl_t lbl_begin; + ecs_query_lbl_t lbl_cond_eval; + ecs_write_flags_t written_or; /* Cond written flags at start of or chain */ + ecs_write_flags_t cond_written_or; /* Cond written flags at start of or chain */ + ecs_query_ref_t src_or; /* Source for terms in current or chain */ + bool src_written_or; /* Was src populated before OR chain */ + bool in_or; /* Whether we're in an or chain */ +} ecs_query_compile_ctrlflow_t; + +/* Query compiler state */ +typedef struct { + ecs_vec_t *ops; + ecs_write_flags_t written; /* Bitmask to check which variables have been written */ + ecs_write_flags_t cond_written; /* Track conditional writes (optional operators) */ + /* Maintain control flow per scope */ + ecs_query_compile_ctrlflow_t ctrlflow[FLECS_QUERY_SCOPE_NESTING_MAX]; + ecs_query_compile_ctrlflow_t *cur; /* Current scope */ -//////////////////////////////////////////////////////////////////////////////// -//// Notifications -//////////////////////////////////////////////////////////////////////////////// + int32_t scope; /* Nesting level of query scopes */ + ecs_flags32_t scope_is_not; /* Whether scope is prefixed with not */ + ecs_oper_kind_t oper; /* Temp storage to track current operator for term */ + int32_t skipped; /* Term skipped during compilation */ +} ecs_query_compile_ctx_t; -void flecs_notify_on_remove( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_table_diff_t *diff); +/* Query run state */ +typedef struct { + uint64_t *written; /* Bitset to check which variables have been written */ + ecs_query_lbl_t op_index; /* Currently evaluated operation */ + ecs_var_t *vars; /* Variable storage */ + ecs_iter_t *it; /* Iterator */ + ecs_query_op_ctx_t *op_ctx; /* Operation context (stack) */ + ecs_world_t *world; /* Reference to world */ + const ecs_query_impl_t *query; /* Reference to query */ + const ecs_query_var_t *query_vars; /* Reference to query variable array */ + ecs_query_iter_t *qit; +} ecs_query_run_ctx_t; + +struct ecs_query_impl_t { + ecs_query_t pub; /* Public query data */ + + ecs_stage_t *stage; /* Stage used for allocations */ + + /* Variables */ + ecs_query_var_t *vars; /* Variables */ + int32_t var_count; /* Number of variables */ + int32_t var_size; /* Size of variable array */ + ecs_hashmap_t tvar_index; /* Name index for table variables */ + ecs_hashmap_t evar_index; /* Name index for entity variables */ + ecs_var_id_t *src_vars; /* Array with ids to source variables for fields */ + + /* Query plan */ + ecs_query_op_t *ops; /* Operations */ + int32_t op_count; /* Number of operations */ + + /* Misc */ + int16_t tokens_len; /* Length of tokens buffer */ + char *tokens; /* Buffer with string tokens used by terms */ + int32_t *monitor; /* Change monitor for fields with fixed src */ -void flecs_notify_on_set( - ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - ecs_ids_t *ids, - bool owned); +#ifdef FLECS_DEBUG + ecs_termset_t final_terms; /* Terms that don't use component inheritance */ +#endif + /* Query cache */ + struct ecs_query_cache_t *cache; /* Cache, if query contains cached terms */ -//////////////////////////////////////////////////////////////////////////////// -//// Table API -//////////////////////////////////////////////////////////////////////////////// + /* User context */ + ecs_ctx_free_t ctx_free; /* Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ -/** Find or create table for a set of components */ -ecs_table_t* flecs_table_find_or_create( - ecs_world_t *world, - const ecs_ids_t *type); + /* Mixins */ + flecs_poly_dtor_t dtor; +}; -/* Initialize columns for data */ -void flecs_table_init_data( - ecs_world_t *world, - ecs_table_t *table); +#endif -/* Clear all entities from a table. */ -void flecs_table_clear_entities( - ecs_world_t *world, - ecs_table_t *table); -/* Reset a table to its initial state */ -void flecs_table_reset( +/* Compile query to list of operations */ +int flecs_query_compile( ecs_world_t *world, - ecs_table_t *table); + ecs_stage_t *stage, + ecs_query_impl_t *query); -/* Clear all entities from the table. Do not invoke OnRemove systems */ -void flecs_table_clear_entities_silent( +/* Compile single term */ +int flecs_query_compile_term( ecs_world_t *world, - ecs_table_t *table); + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx); -/* Clear table data. Don't call OnRemove handlers. */ -void flecs_table_clear_data( +/* Compile term ref (first, second or src) */ +void flecs_query_compile_term_ref( ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data); + ecs_query_impl_t *query, + ecs_query_op_t *op, + ecs_term_ref_t *term_ref, + ecs_query_ref_t *ref, + ecs_flags8_t ref_kind, + ecs_var_kind_t kind, + ecs_query_compile_ctx_t *ctx, + bool create_wildcard_vars); + +/* Mark variable as written */ +void flecs_query_write( + ecs_var_id_t var_id, + uint64_t *written); + +/* Mark variable as written in compiler context */ +void flecs_query_write_ctx( + ecs_var_id_t var_id, + ecs_query_compile_ctx_t *ctx, + bool cond_write); + +/* Add operation to query plan */ +ecs_query_lbl_t flecs_query_op_insert( + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx); + +/* Insert each instruction */ +void flecs_query_insert_each( + ecs_var_id_t tvar, + ecs_var_id_t evar, + ecs_query_compile_ctx_t *ctx, + bool cond_write); + +/* Insert instruction that populates field */ +void flecs_query_insert_populate( + ecs_query_impl_t *query, + ecs_query_compile_ctx_t *ctx, + ecs_flags64_t populated); + +/* Add discovered variable */ +ecs_var_id_t flecs_query_add_var( + ecs_query_impl_t *query, + const char *name, + ecs_vec_t *vars, + ecs_var_kind_t kind); -/* Return number of entities in data */ -int32_t flecs_table_data_count( - const ecs_data_t *data); +/* Find variable by name/kind */ +ecs_var_id_t flecs_query_find_var_id( + const ecs_query_impl_t *query, + const char *name, + ecs_var_kind_t kind); -/* Add a new entry to the table for the specified entity */ -int32_t flecs_table_append( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - ecs_entity_t entity, - ecs_record_t *record, - bool construct); +ecs_query_op_t* flecs_query_begin_block( + ecs_query_op_kind_t kind, + ecs_query_compile_ctx_t *ctx); -/* Delete an entity from the table. */ -void flecs_table_delete( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t index, - bool destruct); +void flecs_query_end_block( + ecs_query_compile_ctx_t *ctx, + bool reset); -/* Increase refcount of table (prevents deletion) */ -void flecs_table_claim( - ecs_world_t *world, - ecs_table_t *table); -/* Decrease refcount of table (may delete) */ -bool flecs_table_release( - ecs_world_t *world, - ecs_table_t *table); + /** + * @file query/cache/cache.h + * @brief Query cache functions. + */ -/* Unregister table cache records */ -void flecs_table_records_unregister( - ecs_world_t *world, - ecs_table_t *table); -/* Make sure table records are in correct table cache list */ -bool flecs_table_records_update_empty( - ecs_table_t *table); +/** Table match data. + * Each table matched by the query is represented by an ecs_query_cache_match_t + * instance, which are linked together in a list. A table may match a query + * multiple times (due to wildcard queries) with different columns being matched + * by the query. */ +typedef struct ecs_query_triv_cache_match_t { + ecs_table_t *table; /* The current table. */ + const ecs_table_record_t **trs; /* Information about where to find field in table. */ + void **ptrs; /* Cached column pointers for match. */ + uint32_t table_version; /* Used to check if pointers need to be revalidated. */ + ecs_termset_t set_fields; /* Fields that are set (used by fields with Optional/Not). */ +} ecs_query_triv_cache_match_t; + +struct ecs_query_cache_match_t { + ecs_query_triv_cache_match_t base; + int32_t _offset; /* Starting point in table. */ + int32_t _count; /* Number of entities to iterate in table. */ + ecs_id_t *_ids; /* Resolved (component) ids for current table. */ + ecs_entity_t *_sources; /* Subjects (sources) of ids. */ + ecs_table_t **_tables; /* Tables for fields with non-$this source. */ + ecs_termset_t _up_fields; /* Fields that are matched through traversal. */ + int32_t *_monitor; /* Used to monitor table for changes. */ + int32_t rematch_count; /* Track whether table was rematched. */ + ecs_vec_t *wildcard_matches; /* Additional matches for table for wildcard queries. */ +}; -/* Move a row from one table to another */ -void flecs_table_move( - ecs_world_t *world, - ecs_entity_t dst_entity, - ecs_entity_t src_entity, - ecs_table_t *new_table, - ecs_data_t *new_data, - int32_t new_index, - ecs_table_t *old_table, - ecs_data_t *old_data, - int32_t old_index, - bool construct); +/** Query group */ +struct ecs_query_cache_group_t { + ecs_vec_t tables; /* vec */ + ecs_query_group_info_t info; /* Group info available to application. */ + ecs_query_cache_group_t *next; /* Next group to iterate (only set for queries with group_by). */ +}; -/* Grow table with specified number of records. Populate table with entities, - * starting from specified entity id. */ -int32_t flecs_table_appendn( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t count, - const ecs_entity_t *ids); +/** Table record type for query table cache. A query only has one per table. */ +typedef struct ecs_query_cache_table_t { + ecs_query_cache_group_t *group; /* Group the table is added to. */ + int32_t index; /* Index into group->tables. */ +} ecs_query_cache_table_t; -/* Set table to a fixed size. Useful for preallocating memory in advance. */ -void flecs_table_set_size( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t count); +/* Query level block allocators have sizes that depend on query field count */ +typedef struct ecs_query_cache_allocators_t { + ecs_block_allocator_t pointers; + ecs_block_allocator_t ids; + ecs_block_allocator_t monitors; +} ecs_query_cache_allocators_t; -/* Shrink table to contents */ -bool flecs_table_shrink( - ecs_world_t *world, - ecs_table_t *table); +/** Query that is automatically matched against tables */ +typedef struct ecs_query_cache_t { + /* Uncached query used to populate the cache */ + ecs_query_t *query; -/* Get dirty state for table columns */ -int32_t* flecs_table_get_dirty_state( - ecs_table_t *table); + /* Observer to keep the cache in sync */ + ecs_observer_t *observer; -/* Get monitor for monitoring table changes */ -int32_t* flecs_table_get_monitor( - ecs_table_t *table); + /* Tables matched with query */ + ecs_map_t tables; -/* Initialize root table */ -void flecs_init_root_table( - ecs_world_t *world); + /* Query groups, if group_by is used */ + ecs_map_t groups; -/* Unset components in table */ -void flecs_table_remove_actions( - ecs_world_t *world, - ecs_table_t *table); + /* Default query group */ + ecs_query_cache_group_t default_group; -/* Free table */ -void flecs_table_free( - ecs_world_t *world, - ecs_table_t *table); + /* Groups in iteration order */ + ecs_query_cache_group_t *first_group; -/* Free table */ -void flecs_table_free_type( - ecs_table_t *table); - -/* Replace data */ -void flecs_table_replace_data( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data); + /* Table sorting */ + ecs_entity_t order_by; + ecs_order_by_action_t order_by_callback; + ecs_sort_table_action_t order_by_table_callback; + ecs_vec_t table_slices; + int32_t order_by_term; -/* Merge data of one table into another table */ -void flecs_table_merge( - ecs_world_t *world, - ecs_table_t *new_table, - ecs_table_t *old_table, - ecs_data_t *new_data, - ecs_data_t *old_data); + /* Table grouping */ + ecs_entity_t group_by; + ecs_group_by_action_t group_by_callback; + ecs_group_create_action_t on_group_create; + ecs_group_delete_action_t on_group_delete; + void *group_by_ctx; + ecs_ctx_free_t group_by_ctx_free; -void flecs_table_swap( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t row_1, - int32_t row_2); + /* Monitor generation */ + int32_t monitor_generation; -ecs_table_t *flecs_table_traverse_add( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t *id_ptr, - ecs_table_diff_t *diff); + int32_t cascade_by; /* Identify cascade term */ + int32_t match_count; /* How often have tables been (un)matched */ + int32_t prev_match_count; /* Track if sorting is needed */ + int32_t rematch_count; /* Track which tables were added during rematch */ + + ecs_entity_t entity; /* Entity associated with query */ -ecs_table_t *flecs_table_traverse_remove( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t *id_ptr, - ecs_table_diff_t *diff); + /* Zero'd out sources array, used for results that only match on $this */ + ecs_entity_t *sources; -void flecs_table_mark_dirty( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t component); + /* Map field indices from cache query to actual query */ + int8_t *field_map; -const EcsComponent* flecs_component_from_id( - const ecs_world_t *world, - ecs_entity_t e); + /* Query-level allocators */ + ecs_query_cache_allocators_t allocators; +} ecs_query_cache_t; -int32_t flecs_table_switch_from_case( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_entity_t add); +ecs_query_cache_t* flecs_query_cache_init( + ecs_query_impl_t *impl, + const ecs_query_desc_t *desc); -void flecs_table_notify( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_event_t *event); +void flecs_query_cache_fini( + ecs_query_impl_t *impl); -void flecs_table_clear_edges( +void flecs_query_cache_sort_tables( ecs_world_t *world, - ecs_table_t *table); + ecs_query_impl_t *impl); -void flecs_table_delete_entities( - ecs_world_t *world, - ecs_table_t *table); +void flecs_query_cache_build_sorted_tables( + ecs_query_cache_t *cache); -ecs_column_t *ecs_table_column_for_id( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id); +bool flecs_query_cache_is_trivial( + const ecs_query_cache_t *cache); +ecs_size_t flecs_query_cache_elem_size( + const ecs_query_cache_t *cache); -//////////////////////////////////////////////////////////////////////////////// -//// Query API -//////////////////////////////////////////////////////////////////////////////// +/** + * @file query/cache/cache_iter.h + * @brief Cache iterator functions. + */ -/* Match table with term */ -bool flecs_term_match_table( - ecs_world_t *world, - const ecs_term_t *term, - const ecs_table_t *table, - ecs_type_t type, - ecs_id_t *id_out, - int32_t *column_out, - ecs_entity_t *subject_out, - int32_t *match_indices, - bool first, - ecs_flags32_t iter_flags); -/* Match table with filter */ -bool flecs_filter_match_table( - ecs_world_t *world, - const ecs_filter_t *filter, - const ecs_table_t *table, - ecs_id_t *ids, - int32_t *columns, - ecs_entity_t *subjects, - int32_t *match_indices, - int32_t *matches_left, - bool first, - int32_t skip_term, - ecs_flags32_t iter_flags); +void flecs_query_cache_iter_init( + ecs_iter_t *it, + ecs_query_iter_t *qit, + ecs_query_impl_t *impl); -void flecs_query_notify( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_event_t *event); +/* Cache search */ +bool flecs_query_cache_search( + const ecs_query_run_ctx_t *ctx); -void flecs_iter_init( - ecs_iter_t *it, - ecs_flags8_t fields); +/* Cache search where entire query is cached */ +bool flecs_query_is_cache_search( + const ecs_query_run_ctx_t *ctx); -void flecs_iter_validate( - ecs_iter_t *it); +/* Cache test */ +bool flecs_query_cache_test( + const ecs_query_run_ctx_t *ctx, + bool redo); -void flecs_iter_populate_data( - ecs_world_t *world, - ecs_iter_t *it, - ecs_table_t *table, - int32_t offset, - int32_t count, - void **ptrs, - ecs_size_t *sizes); +/* Cache test where entire query is cached */ +bool flecs_query_is_cache_test( + const ecs_query_run_ctx_t *ctx, + bool redo); -bool flecs_iter_next_row( - ecs_iter_t *it); +bool flecs_query_is_trivial_cache_search( + const ecs_query_run_ctx_t *ctx); -bool flecs_iter_next_instanced( - ecs_iter_t *it, - bool result); +bool flecs_query_is_trivial_cache_test( + const ecs_query_run_ctx_t *ctx, + bool redo); +/** + * @file query/cache/group.h + * @brief Adding/removing tables to query groups + */ -//////////////////////////////////////////////////////////////////////////////// -//// Poly API -//////////////////////////////////////////////////////////////////////////////// +ecs_query_cache_group_t* flecs_query_cache_get_group( + const ecs_query_cache_t *cache, + uint64_t group_id); -/* Initialize object header & mixins for specified type */ -void* _ecs_poly_init( - ecs_poly_t *object, - int32_t kind, - ecs_size_t size, - ecs_mixins_t *mixins); +ecs_query_cache_match_t* flecs_query_cache_add_table( + ecs_query_cache_t *cache, + ecs_table_t *table); -#define ecs_poly_init(object, type)\ - _ecs_poly_init(object, type##_magic, sizeof(type), &type##_mixins) +ecs_query_cache_match_t* flecs_query_cache_ensure_table( + ecs_query_cache_t *cache, + ecs_table_t *table); -/* Deinitialize object for specified type */ -void _ecs_poly_fini( - ecs_poly_t *object, - int32_t kind); +void flecs_query_cache_remove_table( + ecs_query_cache_t *cache, + ecs_table_t *table); -#define ecs_poly_fini(object, type)\ - _ecs_poly_fini(object, type##_magic) +void flecs_query_cache_remove_all_tables( + ecs_query_cache_t *cache); -/* Utility functions for creating an object on the heap */ -#define ecs_poly_new(type)\ - (type*)ecs_poly_init(ecs_os_calloc_t(type), type) +ecs_query_cache_table_t* flecs_query_cache_get_table( + const ecs_query_cache_t *cache, + ecs_table_t *table); -#define ecs_poly_free(obj, type)\ - ecs_poly_fini(obj, type);\ - ecs_os_free(obj) +ecs_query_cache_match_t* flecs_query_cache_match_from_table( + const ecs_query_cache_t *cache, + const ecs_query_cache_table_t *qt); -/* Utilities for testing/asserting an object type */ -#ifndef FLECS_NDEBUG -void _ecs_poly_assert( - const ecs_poly_t *object, - int32_t type, - const char *file, - int32_t line); +/** + * @file query/cache/match.h + * @brief Match table one or more times with query. + */ -#define ecs_poly_assert(object, type)\ - _ecs_poly_assert(object, type##_magic, __FILE__, __LINE__) -#else -#define ecs_poly_assert(object, type) -#endif +void flecs_query_cache_match_fini( + ecs_query_cache_t *cache, + ecs_query_cache_match_t *qm); -bool _ecs_poly_is( - const ecs_poly_t *object, - int32_t type); +bool flecs_query_cache_match_next( + ecs_query_cache_t *cache, + ecs_iter_t *it); -#define ecs_poly_is(object, type)\ - _ecs_poly_is(object, type##_magic) +/** + * @file query/cache/change_detection.h + * @brief Query change detection implementation. + */ -/* Utility functions for getting a mixin from an object */ -ecs_iterable_t* ecs_get_iterable( - const ecs_poly_t *poly); +void flecs_query_sync_match_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_match_t *match); -ecs_observable_t* ecs_get_observable( - const ecs_poly_t *object); +void flecs_query_mark_fields_dirty( + ecs_query_impl_t *impl, + ecs_iter_t *it); +bool flecs_query_check_table_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_match_t *qm, + int32_t term); -//////////////////////////////////////////////////////////////////////////////// -//// Observables -//////////////////////////////////////////////////////////////////////////////// +void flecs_query_mark_fixed_fields_dirty( + ecs_query_impl_t *impl, + ecs_iter_t *it); -void flecs_observable_init( - ecs_observable_t *observable); +bool flecs_query_update_fixed_monitor( + ecs_query_impl_t *impl); -void flecs_observable_fini( - ecs_observable_t *observable); +bool flecs_query_check_fixed_monitor( + ecs_query_impl_t *impl); -//////////////////////////////////////////////////////////////////////////////// -//// Safe(r) integer casting -//////////////////////////////////////////////////////////////////////////////// +/** + * @file query/engine/engine.h + * @brief Query engine functions. + */ -#define FLECS_CONVERSION_ERR(T, value)\ - "illegal conversion from value " #value " to type " #T +/** + * @file query/engine/trav_cache.h + * @brief Traversal cache functions + */ -#define flecs_signed_char__ (CHAR_MIN < 0) -#define flecs_signed_short__ true -#define flecs_signed_int__ true -#define flecs_signed_long__ true -#define flecs_signed_size_t__ false -#define flecs_signed_int8_t__ true -#define flecs_signed_int16_t__ true -#define flecs_signed_int32_t__ true -#define flecs_signed_int64_t__ true -#define flecs_signed_intptr_t__ true -#define flecs_signed_uint8_t__ false -#define flecs_signed_uint16_t__ false -#define flecs_signed_uint32_t__ false -#define flecs_signed_uint64_t__ false -#define flecs_signed_uintptr_t__ false -#define flecs_signed_ecs_size_t__ true -#define flecs_signed_ecs_entity_t__ false -uint64_t _flecs_ito( - size_t dst_size, - bool dst_signed, - bool lt_zero, - uint64_t value, - const char *err); +/* Traversal cache for transitive queries. Finds all reachable entities by + * following a relationship */ -#ifndef FLECS_NDEBUG -#define flecs_ito(T, value)\ - (T)_flecs_ito(\ - sizeof(T),\ - flecs_signed_##T##__,\ - (value) < 0,\ - (uint64_t)(value),\ - FLECS_CONVERSION_ERR(T, (value))) +/* Find all entities when traversing downwards */ +void flecs_query_get_trav_down_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity); -#define flecs_uto(T, value)\ - (T)_flecs_ito(\ - sizeof(T),\ - flecs_signed_##T##__,\ - false,\ - (uint64_t)(value),\ - FLECS_CONVERSION_ERR(T, (value))) -#else -#define flecs_ito(T, value) (T)(value) -#define flecs_uto(T, value) (T)(value) -#endif +/* Find all entities when traversing upwards */ +void flecs_query_get_trav_up_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table); -#define flecs_itosize(value) flecs_ito(size_t, (value)) -#define flecs_utosize(value) flecs_uto(ecs_size_t, (value)) -#define flecs_itoi16(value) flecs_ito(int16_t, (value)) -#define flecs_itoi32(value) flecs_ito(int32_t, (value)) +/* Free traversal cache */ +void flecs_query_trav_cache_fini( + ecs_allocator_t *a, + ecs_trav_cache_t *cache); -//////////////////////////////////////////////////////////////////////////////// -//// Utilities -//////////////////////////////////////////////////////////////////////////////// +/* Traversal caches for up traversal. Enables searching upwards until an entity + * with the queried for id has been found. */ -uint64_t flecs_hash( - const void *data, - ecs_size_t length); +/* Traverse downwards from starting entity to find all tables for which the + * specified entity is the source of the queried for id ('with'). */ +ecs_trav_down_t* flecs_query_get_down_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_component_record_t *cr_with, + bool self, + bool empty); -/* Get next power of 2 */ -int32_t flecs_next_pow_of_2( - int32_t n); +/* Free down traversal cache */ +void flecs_query_down_cache_fini( + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache); -/* Convert 64bit value to ecs_record_t type. ecs_record_t is stored as 64bit int in the - * entity index */ -ecs_record_t flecs_to_row( - uint64_t value); +ecs_trav_up_t* flecs_query_get_up_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_table_t *table, + int32_t row, + ecs_id_t with, + ecs_entity_t trav, + ecs_component_record_t *cr_with, + ecs_component_record_t *cr_trav); -/* Get 64bit integer from ecs_record_t */ -uint64_t flecs_from_row( - ecs_record_t record); +/* Free up traversal cache */ +void flecs_query_up_cache_fini( + ecs_trav_up_cache_t *cache); -/* Get actual row from record row */ -uint32_t flecs_record_to_row( - uint32_t row, - bool *is_watched_out); +/** + * @file query/engine/trivial_iter.h + * @brief Trivial iterator functions. + */ -/* Convert actual row to record row */ -uint32_t flecs_row_to_record( - uint32_t row, - bool is_watched); -/* Convert type to entity array */ -ecs_ids_t flecs_type_to_ids( - ecs_type_t type); +/* Iterator for queries with trivial terms. */ +bool flecs_query_trivial_search( + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + bool redo, + ecs_flags64_t field_set); -/* Convert a symbol name to an entity name by removing the prefix */ -const char* flecs_name_from_symbol( - ecs_world_t *world, - const char *type_name); +/* Iterator for queries with only trivial terms. */ +bool flecs_query_is_trivial_search( + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + bool redo); -/* Compare function for entity ids */ -int flecs_entity_compare( - ecs_entity_t e1, - const void *ptr1, - ecs_entity_t e2, - const void *ptr2); +/* Trivial test for constrained $this. */ +bool flecs_query_trivial_test( + const ecs_query_run_ctx_t *ctx, + bool first, + ecs_flags64_t field_set); -/* Compare function for entity ids which can be used with qsort */ -int flecs_entity_compare_qsort( - const void *e1, - const void *e2); -/* Convert floating point to string */ -char * ecs_ftoa( - double f, - char * buf, - int precision); +/* Query evaluation utilities */ -/* Create allocated string from format */ -char* ecs_vasprintf( - const char *fmt, - va_list args); +void flecs_query_set_iter_this( + ecs_iter_t *it, + const ecs_query_run_ctx_t *ctx); -/* Create allocated string from format */ -char* ecs_asprintf( - const char *fmt, - ...); +ecs_query_op_ctx_t* flecs_op_ctx_( + const ecs_query_run_ctx_t *ctx); -uint64_t flecs_string_hash( - const void *ptr); +#define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) -void flecs_table_hashmap_init( - ecs_hashmap_t *hm); +void flecs_query_op_ctx_fini( + ecs_iter_t *it, + const ecs_query_op_t *op, + ecs_query_op_ctx_t *ctx); -#define assert_func(cond) _assert_func(cond, #cond, __FILE__, __LINE__, __func__) -void _assert_func( - bool cond, - const char *cond_str, - const char *file, - int32_t line, - const char *func); +void flecs_reset_source_set_flag( + ecs_iter_t *it, + int32_t field_index); -#endif +void flecs_set_source_set_flag( + ecs_iter_t *it, + int32_t field_index); +ecs_table_range_t flecs_query_var_get_range( + int32_t var_id, + const ecs_query_run_ctx_t *ctx); -/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as - * this can severly slow down many ECS operations. */ -#ifdef FLECS_SANITIZE -static -void check_table_sanity(ecs_table_t *table) { - int32_t size = ecs_vector_size(table->storage.entities); - int32_t count = ecs_vector_count(table->storage.entities); - - ecs_assert(size == ecs_vector_size(table->storage.record_ptrs), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(count == ecs_vector_count(table->storage.record_ptrs), - ECS_INTERNAL_ERROR, NULL); +ecs_table_t* flecs_query_var_get_table( + int32_t var_id, + const ecs_query_run_ctx_t *ctx); + +ecs_table_t* flecs_query_get_table( + const ecs_query_op_t *op, + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_query_run_ctx_t *ctx); + +ecs_table_range_t flecs_query_get_range( + const ecs_query_op_t *op, + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_query_run_ctx_t *ctx); + +ecs_entity_t flecs_query_var_get_entity( + ecs_var_id_t var_id, + const ecs_query_run_ctx_t *ctx); + +void flecs_query_var_reset( + ecs_var_id_t var_id, + const ecs_query_run_ctx_t *ctx); + +void flecs_query_var_set_range( + const ecs_query_op_t *op, + ecs_var_id_t var_id, + ecs_table_t *table, + int32_t offset, + int32_t count, + const ecs_query_run_ctx_t *ctx); - int32_t i; - int32_t sw_offset = table->sw_column_offset; - int32_t sw_count = table->sw_column_count; - int32_t bs_offset = table->bs_column_offset; - int32_t bs_count = table->bs_column_count; - int32_t type_count = ecs_vector_count(table->type); - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - - ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); - ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); +void flecs_query_src_set_single( + const ecs_query_op_t *op, + int32_t row, + const ecs_query_run_ctx_t *ctx); - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - ecs_assert(table->storage_count == ecs_vector_count(storage_table->type), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->storage_ids == ecs_vector_first( - storage_table->type, ecs_id_t), ECS_INTERNAL_ERROR, NULL); +void flecs_query_src_set_range( + const ecs_query_op_t *op, + const ecs_table_range_t *range, + const ecs_query_run_ctx_t *ctx); + +void flecs_query_var_set_entity( + const ecs_query_op_t *op, + ecs_var_id_t var_id, + ecs_entity_t entity, + const ecs_query_run_ctx_t *ctx); - int32_t storage_count = table->storage_count; - ecs_assert(type_count >= storage_count, ECS_INTERNAL_ERROR, NULL); +void flecs_query_set_vars( + const ecs_query_op_t *op, + ecs_id_t id, + const ecs_query_run_ctx_t *ctx); - int32_t *storage_map = table->storage_map; - ecs_assert(storage_map != NULL, ECS_INTERNAL_ERROR, NULL); +ecs_table_range_t flecs_get_ref_range( + const ecs_query_ref_t *ref, + ecs_flags16_t flag, + const ecs_query_run_ctx_t *ctx); - ecs_id_t *storage_ids = table->storage_ids; - for (i = 0; i < type_count; i ++) { - if (storage_map[i] != -1) { - ecs_assert(ids[i] == storage_ids[storage_map[i]], - ECS_INTERNAL_ERROR, NULL); - } - } +ecs_entity_t flecs_get_ref_entity( + const ecs_query_ref_t *ref, + ecs_flags16_t flag, + const ecs_query_run_ctx_t *ctx); - ecs_assert(table->storage.columns != NULL, ECS_INTERNAL_ERROR, NULL); +ecs_id_t flecs_query_op_get_id_w_written( + const ecs_query_op_t *op, + uint64_t written, + const ecs_query_run_ctx_t *ctx); - for (i = 0; i < storage_count; i ++) { - ecs_type_info_t *ti = &table->type_info[i]; - ecs_column_t *column = &table->storage.columns[i]; +ecs_id_t flecs_query_op_get_id( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx); - ecs_vector_assert_size(column->data, ti->size); - ecs_assert(size == ecs_vector_size(column->data), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(count == ecs_vector_count(column->data), - ECS_INTERNAL_ERROR, NULL); - int32_t storage_map_id = storage_map[i + type_count]; - ecs_assert(storage_map_id >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ids[storage_map_id] == storage_ids[i], - ECS_INTERNAL_ERROR, NULL); - } - } else { - ecs_assert(table->storage_count == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->storage_ids == NULL, ECS_INTERNAL_ERROR, NULL); - } +int16_t flecs_query_next_column( + ecs_table_t *table, + ecs_id_t id, + int32_t column); - if (sw_count) { - ecs_assert(table->storage.sw_columns != NULL, - ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < sw_count; i ++) { - ecs_sw_column_t *sw = &table->storage.sw_columns[i]; - ecs_assert(sw->data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_count(sw->data->values) == count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert((ids[i + sw_offset] & ECS_ROLE_MASK) == - ECS_SWITCH, ECS_INTERNAL_ERROR, NULL); - } - } +void flecs_query_it_set_tr( + ecs_iter_t *it, + int32_t field_index, + const ecs_table_record_t *tr); - if (bs_count) { - ecs_assert(table->storage.bs_columns != NULL, - ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < bs_count; i ++) { - ecs_bs_column_t *bs = &table->storage.bs_columns[i]; - ecs_assert(flecs_bitset_count(&bs->data) == count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert((ids[i + bs_offset] & ECS_ROLE_MASK) == - ECS_DISABLED, ECS_INTERNAL_ERROR, NULL); - } - } -} -#else -#define check_table_sanity(table) -#endif +ecs_id_t flecs_query_it_set_id( + ecs_iter_t *it, + ecs_table_t *table, + int32_t field_index, + int32_t column); -static -void init_storage_map( - ecs_table_t *table) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (!table->storage_table) { - return; - } +void flecs_query_set_match( + const ecs_query_op_t *op, + ecs_table_t *table, + int32_t column, + const ecs_query_run_ctx_t *ctx); - ecs_type_t type = table->type; - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t t, ids_count = ecs_vector_count(type); - ecs_id_t *storage_ids = table->storage_ids; - int32_t s, storage_ids_count = table->storage_count; +void flecs_query_set_trav_match( + const ecs_query_op_t *op, + const ecs_table_record_t *tr, + ecs_entity_t trav, + ecs_entity_t second, + const ecs_query_run_ctx_t *ctx); - if (!ids_count) { - table->storage_map = NULL; - return; - } +bool flecs_query_table_filter( + ecs_table_t *table, + ecs_query_lbl_t other, + ecs_flags32_t filter_mask); + +bool flecs_query_setids( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + +bool flecs_query_run_until( + bool redo, + ecs_query_run_ctx_t *ctx, + const ecs_query_op_t *ops, + ecs_query_lbl_t first, + ecs_query_lbl_t cur, + int32_t last); + + +/* And evaluation */ + +bool flecs_query_and( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + +bool flecs_query_and_any( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + +/* Select evaluation */ + +bool flecs_query_select( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + +bool flecs_query_select_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_flags32_t table_filter); + +bool flecs_query_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + +bool flecs_query_with_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + +bool flecs_query_select_w_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_id_t id, + ecs_flags32_t filter_mask); - table->storage_map = ecs_os_malloc_n( - int32_t, ids_count + storage_ids_count); - int32_t *t2s = table->storage_map; - int32_t *s2t = &table->storage_map[ids_count]; +/* Sparse evaluation */ + +bool flecs_query_sparse( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + +bool flecs_query_sparse_select( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_flags32_t table_mask); + +bool flecs_query_sparse_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool not); + +bool flecs_query_sparse_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + +bool flecs_query_sparse_self_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + + +/* Hierarchy evaluation */ - for (s = 0, t = 0; (t < ids_count) && (s < storage_ids_count); ) { - ecs_id_t id = ids[t]; - ecs_id_t storage_id = storage_ids[s]; +const EcsParent* flecs_query_tree_get_parents( + ecs_table_range_t range); + +bool flecs_query_tree_and( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + +bool flecs_query_tree_and_wildcard( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool bulk_return); - if (id == storage_id) { - t2s[t] = s; - s2t[s] = t; - } else { - t2s[t] = -1; - } +bool flecs_query_tree_pre( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); - /* Ids can never get ahead of storage id, as ids are a superset of the - * storage ids */ - ecs_assert(id <= storage_id, ECS_INTERNAL_ERROR, NULL); +bool flecs_query_tree_post( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); - t += (id <= storage_id); - s += (id == storage_id); - } +bool flecs_query_tree_up_pre( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool self); + +bool flecs_query_tree_up_post( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool self); + +bool flecs_query_tree_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + +bool flecs_query_children( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); + +/* Toggle evaluation*/ + +bool flecs_query_toggle( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + +bool flecs_query_toggle_option( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + + +/* Equality predicate evaluation */ - /* Storage ids is always a subset of ids, so all should be iterated */ - ecs_assert(s == storage_ids_count, ECS_INTERNAL_ERROR, NULL); +bool flecs_query_pred_eq_match( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); - /* Initialize remainder of type -> storage_type map */ - for (; (t < ids_count); t ++) { - t2s[t] = -1; - } -} +bool flecs_query_pred_neq_match( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + +bool flecs_query_pred_eq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + +bool flecs_query_pred_eq_name( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + +bool flecs_query_pred_neq_w_range( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx, + ecs_table_range_t r); -static -void init_storage_table( - ecs_world_t *world, - ecs_table_t *table) -{ - if (table->storage_table) { - return; - } +bool flecs_query_pred_neq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); - ecs_type_t type = table->type; - int32_t i, count = ecs_vector_count(type); - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - ecs_table_record_t *records = table->records; +bool flecs_query_pred_neq_name( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); - ecs_id_t array[ECS_ID_CACHE_SIZE]; - ecs_ids_t storage_ids = { .array = array }; - if (count > ECS_ID_CACHE_SIZE) { - storage_ids.array = ecs_os_malloc_n(ecs_id_t, count); - } - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert(idr->flags & ECS_TYPE_INFO_INITIALIZED, - ECS_INTERNAL_ERROR, NULL); +/* Component member evaluation */ + +bool flecs_query_member_eq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + +bool flecs_query_member_neq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + + +/* Up traversal */ + +typedef enum ecs_query_up_select_trav_kind_t { + FlecsQueryUpSelectUp, + FlecsQueryUpSelectSelfUp +} ecs_query_up_select_trav_kind_t; - if (idr->type_info == NULL) { - ecs_assert(ecs_get_typeid(world, ids[i]) == 0, - ECS_INTERNAL_ERROR, NULL); - continue; /* not a component */ - } +typedef enum ecs_query_up_select_kind_t { + FlecsQueryUpSelectDefault, + FlecsQueryUpSelectId, + FlecsQueryUpSelectSparse +} ecs_query_up_select_kind_t; - storage_ids.array[storage_ids.count ++] = ids[i]; - } - - if (storage_ids.count && storage_ids.count != count) { - ecs_table_t *storage_table = flecs_table_find_or_create(world, - &storage_ids); - table->storage_table = storage_table; - table->storage_count = flecs_ito(uint16_t, storage_ids.count); - table->storage_ids = ecs_vector_first(storage_table->type, ecs_id_t); - storage_table->refcount ++; - } else if (storage_ids.count) { - table->storage_table = table; - table->storage_count = flecs_ito(uint16_t, count); - table->storage_ids = ecs_vector_first(type, ecs_id_t); - } +bool flecs_query_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); - if (storage_ids.array != array) { - ecs_os_free(storage_ids.array); - } +bool flecs_query_self_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); - if (!table->storage_map) { - init_storage_map(table); - } -} +bool flecs_query_up_select( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_query_up_select_trav_kind_t trav_kind, + ecs_query_up_select_kind_t kind); -static -ecs_flags32_t type_info_flags( - const ecs_type_info_t *ti) -{ - ecs_flags32_t flags = 0; +bool flecs_query_up_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); - if (ti->lifecycle.ctor) { - flags |= EcsTableHasCtors; - } - if (ti->lifecycle.on_add) { - flags |= EcsTableHasCtors; - } - if (ti->lifecycle.dtor) { - flags |= EcsTableHasDtors; - } - if (ti->lifecycle.on_remove) { - flags |= EcsTableHasDtors; - } - if (ti->lifecycle.copy) { - flags |= EcsTableHasCopy; - } - if (ti->lifecycle.move) { - flags |= EcsTableHasMove; - } +bool flecs_query_self_up_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); - return flags; -} -static -void init_type_info( - ecs_table_t *table) -{ - ecs_table_t *storage_table = table->storage_table; - if (!storage_table) { - return; - } +/* Transitive relationship traversal */ - if (storage_table != table) { - /* Because the storage table is guaranteed to have the same components - * (but not tags) as this table, we can share the type info cache */ - table->type_info = storage_table->type_info; - table->flags |= storage_table->flags; - return; - } +bool flecs_query_trav( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx); - if (table->type_info) { - return; - } - ecs_table_record_t *records = table->records; - int32_t i, count = ecs_vector_count(table->type); - table->type_info = ecs_os_calloc_n(ecs_type_info_t, count); +/** + * @file query/util.h + * @brief Utility functions + */ - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert(idr->flags & ECS_TYPE_INFO_INITIALIZED, - ECS_INTERNAL_ERROR, NULL); - - /* All ids in the storage table must be components with type info */ - const ecs_type_info_t *ti = idr->type_info; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - table->flags |= type_info_flags(ti); - table->type_info[i] = *ti; - } -} -void flecs_table_init_data( - ecs_world_t *world, - ecs_table_t *table) -{ - init_storage_table(world, table); - init_type_info(table); +/* Helper type for passing around context required for error messages */ +typedef struct { + const ecs_world_t *world; + const ecs_query_desc_t *desc; + ecs_query_t *query; + ecs_term_t *term; + int32_t term_index; +} ecs_query_validator_ctx_t; - int32_t sw_count = table->sw_column_count; - int32_t bs_count = table->bs_column_count; +/* Fill out all term fields, check for consistency. */ +int flecs_term_finalize( + const ecs_world_t *world, + ecs_term_t *term, + ecs_query_validator_ctx_t *ctx); + +/* Convert integer to label */ +ecs_query_lbl_t flecs_itolbl( + int64_t val); + +/* Convert integer to variable id */ +ecs_var_id_t flecs_itovar( + int64_t val); + +/* Convert unsigned integer to variable id */ +ecs_var_id_t flecs_utovar( + uint64_t val); + +/* Get name for term ref */ +const char* flecs_term_ref_var_name( + ecs_term_ref_t *ref); + +/* Is term ref wildcard */ +bool flecs_term_ref_is_wildcard( + ecs_term_ref_t *ref); + +/* Does term use builtin predicates (eq, neq, ...)*/ +bool flecs_term_is_builtin_pred( + ecs_term_t *term); + +/* Does term have fixed id */ +bool flecs_term_is_fixed_id( + ecs_query_t *q, + ecs_term_t *term); + +/* Is term part of OR chain */ +bool flecs_term_is_or( + const ecs_query_t *q, + const ecs_term_t *term); + +/* Get ref flags (IsEntity) or IsVar) for ref (Src, First, Second) */ +ecs_flags16_t flecs_query_ref_flags( + ecs_flags16_t flags, + ecs_flags16_t kind); + +/* Check if variable is written */ +bool flecs_query_is_written( + ecs_var_id_t var_id, + uint64_t written); + +/* Check if ref is written (calls flecs_query_is_written)*/ +bool flecs_ref_is_written( + const ecs_query_op_t *op, + const ecs_query_ref_t *ref, + ecs_flags16_t kind, + uint64_t written); + +/* Get allocator from iterator */ +ecs_allocator_t* flecs_query_get_allocator( + const ecs_iter_t *it); + +/* Convert instruction kind to string */ +const char* flecs_query_op_str( + uint16_t kind); + +/* Convert term to string */ +void flecs_term_to_buf( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_strbuf_t *buf, + int32_t t); - ecs_data_t *storage = &table->storage; - int32_t i, count = table->storage_count; +/* Apply iterator flags from query */ +void flecs_query_apply_iter_flags( + ecs_iter_t *it, + const ecs_query_t *query); - /* Root tables don't have columns */ - if (!count && !sw_count && !bs_count) { - storage->columns = NULL; - } +ecs_id_t flecs_query_iter_set_id( + ecs_iter_t *it, + int8_t field, + ecs_id_t id); - if (count) { - storage->columns = ecs_os_calloc_n(ecs_column_t, count); - } - if (sw_count) { - ecs_entity_t *ids = ecs_vector_first(table->type, ecs_entity_t); - int32_t sw_offset = table->sw_column_offset; - storage->sw_columns = ecs_os_calloc_n(ecs_sw_column_t, sw_count); - for (i = 0; i < sw_count; i ++) { - ecs_entity_t e = ids[i + sw_offset]; - ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); - e = e & ECS_COMPONENT_MASK; - const EcsType *switch_type = ecs_get(world, e, EcsType); - ecs_assert(switch_type != NULL, - ECS_INVALID_PARAMETER, "not a switch"); - ecs_table_t *sw_table = switch_type->normalized; - ecs_type_t sw_type = sw_table->type; +#ifdef FLECS_DEBUG +#define flecs_set_var_label(var, lbl) (var)->label = lbl +#else +#define flecs_set_var_label(var, lbl) +#endif - ecs_entity_t *sw_array = ecs_vector_first(sw_type, ecs_entity_t); - int32_t sw_array_count = ecs_vector_count(sw_type); +/* Fast function for finalizing simple queries */ +bool flecs_query_finalize_simple( + ecs_world_t *world, + ecs_query_t *q, + const ecs_query_desc_t *desc); - ecs_switch_t *sw = flecs_switch_new( - sw_array[0], - sw_array[sw_array_count - 1], - 0); +/* Finalize query data & validate */ +int flecs_query_finalize_query( + ecs_world_t *world, + ecs_query_t *q, + const ecs_query_desc_t *desc); - storage->sw_columns[i].data = sw; - storage->sw_columns[i].type = sw_table; - } - } +/* Copy terms, sizes and ids arrays from stack to heap */ +void flecs_query_copy_arrays( + ecs_query_t *q); - if (bs_count) { - storage->bs_columns = ecs_os_calloc_n(ecs_bs_column_t, bs_count); - for (i = 0; i < bs_count; i ++) { - flecs_bitset_init(&storage->bs_columns[i].data); - } - } -} +/* Free terms, sizes and ids arrays */ +void flecs_query_free_arrays( + ecs_query_t *q); -static -void notify_trigger( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t event) -{ - (void)world; +/* Internal function for creating iterator, doesn't run aperiodic tasks */ +ecs_iter_t flecs_query_iter( + const ecs_world_t *world, + const ecs_query_t *q); - if (event == EcsOnAdd) { - table->flags |= EcsTableHasOnAdd; - } else if (event == EcsOnRemove) { - table->flags |= EcsTableHasOnRemove; - } else if (event == EcsOnSet) { - table->flags |= EcsTableHasOnSet; - } else if (event == EcsUnSet) { - table->flags |= EcsTableHasUnSet; - } -} +/* Internal function for initializing an iterator after vars are constrained */ +void flecs_query_iter_constrain( + ecs_iter_t *it); -static -void run_on_remove( +/* Rematch query after cache could have been invalidated */ +void flecs_query_rematch( ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data) -{ - int32_t count = ecs_vector_count(data->entities); - if (count) { - ecs_ids_t removed = { - .array = ecs_vector_first(table->type, ecs_id_t), - .count = ecs_vector_count(table->type) - }; + ecs_query_t *q); - ecs_table_diff_t diff = { - .removed = removed, - .un_set = removed - }; - - flecs_notify_on_remove(world, table, NULL, 0, count, &diff); - } -} +/* Reclaim memory from queries */ +void flecs_query_reclaim( + ecs_query_t *query); -/* -- Private functions -- */ -static -void on_component_callback( +/** + * @file component_actions.c + * @brief Logic executed after adding/removing a component. + */ + + +/* Invoke component hook. */ +void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, - ecs_iter_action_t callback, + const ecs_component_record_t *cr, + const ecs_table_record_t *tr, + int32_t count, + int32_t row, + const ecs_entity_t *entities, + ecs_id_t id, + const ecs_type_info_t *ti, ecs_entity_t event, - ecs_column_t *column, - ecs_entity_t *entities, + ecs_iter_action_t hook); + +/* Invoke replace hook */ +void flecs_invoke_replace_hook( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, ecs_id_t id, + const void *old_ptr, + const void *new_ptr, + const ecs_type_info_t *ti); + +/* Add action for sparse components. */ +bool flecs_sparse_on_add( + ecs_world_t *world, + ecs_table_t *table, int32_t row, int32_t count, - ecs_type_info_t *ti) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_iter_t it = { .term_count = 1 }; - it.entities = entities; - - ecs_size_t size = ti->size; - void *ptr = ecs_vector_get_t(column->data, size, ti->alignment, row); - - flecs_iter_init(&it, flecs_iter_cache_all); - it.world = world; - it.real_world = world; - it.table = table; - it.type = table->type; - it.ptrs[0] = ptr; - it.sizes[0] = size; - it.ids[0] = id; - it.event = event; - it.event_id = id; - it.ctx = ti->lifecycle.ctx; - it.count = count; - flecs_iter_validate(&it); - callback(&it); -} + const ecs_type_t *added, + bool construct); -static -void ctor_component( - ecs_type_info_t *ti, - ecs_column_t *column, +/* Add action for single sparse component. */ +bool flecs_sparse_on_add_cr( + ecs_world_t *world, + ecs_table_t *table, int32_t row, - int32_t count) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_xtor_t ctor = ti->lifecycle.ctor; - if (ctor) { - void *ptr = ecs_vector_get_t( - column->data, ti->size, ti->alignment, row); - ctor(ptr, count, ti); - } -} + ecs_component_record_t *cr, + bool construct, + void **ptr_out); -static -void add_component( +/* Run actions for creating new entity in table. */ +void flecs_actions_new( ecs_world_t *world, ecs_table_t *table, - ecs_type_info_t *ti, - ecs_column_t *column, - ecs_entity_t *entities, - ecs_id_t id, int32_t row, - int32_t count) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - - ctor_component(ti, column, row, count); - - ecs_iter_action_t on_add = ti->lifecycle.on_add; - if (on_add) { - on_component_callback(world, table, on_add, EcsOnAdd, column, - entities, id, row, count, ti); - } -} + int32_t count, + const ecs_table_diff_t *diff, + ecs_flags32_t flags, + bool construct, + bool sparse); -static -void dtor_component( - ecs_type_info_t *ti, - ecs_column_t *column, +/* Run actions for deleting an entity. */ +void flecs_actions_delete( + ecs_world_t *world, + ecs_table_t *table, int32_t row, - int32_t count) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_xtor_t dtor = ti->lifecycle.dtor; - if (dtor) { - void *ptr = ecs_vector_get_t( - column->data, ti->size, ti->alignment, row); - dtor(ptr, count, ti); - } -} + int32_t count, + const ecs_table_diff_t *diff); -static -void remove_component( +/* Same as flecs_actions_delete, but for entities whose parent is also deleted */ +void flecs_actions_delete_tree( ecs_world_t *world, ecs_table_t *table, - ecs_type_info_t *ti, - ecs_column_t *column, - ecs_entity_t *entities, - ecs_id_t id, int32_t row, - int32_t count) -{ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t count, + const ecs_table_diff_t *diff); - ecs_iter_action_t on_remove = ti->lifecycle.on_remove; - if (on_remove) { - on_component_callback(world, table, on_remove, EcsOnRemove, column, - entities, id, row, count, ti); - } - - dtor_component(ti, column, row, count); -} +/* Run actions for added components in table move */ +void flecs_actions_move_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff, + ecs_flags32_t flags, + bool construct, + bool sparse); -static -void dtor_all_components( +/* Run actions for removed components in table move */ +void flecs_actions_move_remove( ecs_world_t *world, ecs_table_t *table, - ecs_data_t *data, + ecs_table_t *other_table, int32_t row, int32_t count, - bool update_entity_index, - bool is_delete) -{ - /* Can't delete and not update the entity index */ - ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); + const ecs_table_diff_t *diff); - ecs_id_t *ids = table->storage_ids; - int32_t ids_count = table->storage_count; - ecs_record_t **records = ecs_vector_first(data->record_ptrs, ecs_record_t*); - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - int32_t i, c, end = row + count; +/* Run on set actions. */ +void flecs_notify_on_set( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_id_t id, + bool invoke_hook); - (void)records; +/* Same as flecs_notify_on_set but for multiple component ids. */ +void flecs_notify_on_set_ids( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_type_t *ids); - /* If table has components with destructors, iterate component columns */ - if (table->flags & EcsTableHasDtors) { - /* Prevent the storage from getting modified while deleting */ - ecs_defer_begin(world); +/** + * @file entity_name.h + * @brief Utilities for looking up entities by name. + */ - /* Throw up a lock just to be sure */ - table->lock = true; - /* Run on_remove callbacks in bulk for improved performance */ - for (c = 0; c < ids_count; c++) { - ecs_column_t *column = &data->columns[c]; - ecs_type_info_t *ti = &table->type_info[c]; - ecs_iter_action_t on_remove = ti->lifecycle.on_remove; - if (on_remove) { - on_component_callback(world, table, on_remove, EcsOnRemove, - column, &entities[row], ids[c], row, count, ti); - } - } +/* Called during bootstrap to register entity name entities with world. */ +void flecs_bootstrap_entity_name( + ecs_world_t *world); - /* Iterate entities first, then components. This ensures that only one - * entity is invalidated at a time, which ensures that destructors can - * safely access other entities. */ - for (i = row; i < end; i ++) { - for (c = 0; c < ids_count; c++) { - ecs_column_t *column = &data->columns[c]; - dtor_component(&table->type_info[c], column, i, 1); - } +/* Update lookup index for entity names. */ +void flecs_reparent_name_index( + ecs_world_t *world, + ecs_table_t *dst, + ecs_table_t *src, + int32_t offset, + int32_t count); - /* Update entity index after invoking destructors so that entity can - * be safely used in destructor callbacks. */ - if (update_entity_index) { - ecs_entity_t e = entities[i]; - ecs_assert(!e || ecs_is_valid(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i] == ecs_eis_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); +void flecs_unparent_name_index( + ecs_world_t *world, + ecs_table_t *src, + ecs_table_t *dst, + int32_t offset, + int32_t count); - if (is_delete) { - ecs_eis_delete(world, e); - ecs_assert(ecs_is_valid(world, e) == false, - ECS_INTERNAL_ERROR, NULL); - } else { - // If this is not a delete, clear the entity index record - records[i]->table = NULL; - records[i]->row = 0; - } - } else { - /* This should only happen in rare cases, such as when the data - * cleaned up is not part of the world (like with snapshots) */ - } - } +/* Hook (on_set/on_remove) for updating lookup index for entity names. */ +void ecs_on_set(EcsIdentifier)( + ecs_iter_t *it); - table->lock = false; +/** + * @file commands.h + * @brief Command queue implementation. + */ - ecs_defer_end(world); +#ifndef FLECS_COMMANDS_H +#define FLECS_COMMANDS_H - /* If table does not have destructors, just update entity index */ - } else if (update_entity_index) { - if (is_delete) { - for (i = row; i < end; i ++) { - ecs_entity_t e = entities[i]; - ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i] == ecs_eis_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); +/** Types for deferred operations */ +typedef enum ecs_cmd_kind_t { + EcsCmdClone, + EcsCmdBulkNew, + EcsCmdAdd, + EcsCmdRemove, + EcsCmdSet, + EcsCmdEmplace, + EcsCmdEnsure, + EcsCmdModified, + EcsCmdModifiedNoHook, + EcsCmdAddModified, + EcsCmdPath, + EcsCmdDelete, + EcsCmdClear, + EcsCmdOnDeleteAction, + EcsCmdEnable, + EcsCmdDisable, + EcsCmdEvent, + EcsCmdSkip +} ecs_cmd_kind_t; + +/* Entity specific metadata for command in queue */ +typedef struct ecs_cmd_entry_t { + int32_t first; + int32_t last; /* If -1, a delete command was inserted */ +} ecs_cmd_entry_t; - ecs_eis_delete(world, e); - ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - } - } else { - for (i = row; i < end; i ++) { - ecs_entity_t e = entities[i]; - ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i] == ecs_eis_get(world, e), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!e || records[i]->table == table, - ECS_INTERNAL_ERROR, NULL); - records[i]->table = NULL; - records[i]->row = records[i]->row & ECS_ROW_FLAGS_MASK; - (void)e; - } - } - } -} +typedef struct ecs_cmd_1_t { + void *value; /* Component value (used by set / ensure) */ + ecs_size_t size; /* Size of value */ + bool clone_value; /* Clone entity with value (used for clone) */ +} ecs_cmd_1_t; -static -void fini_data( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - bool do_on_remove, - bool update_entity_index, - bool is_delete, - bool deactivate) -{ - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); +typedef struct ecs_cmd_n_t { + ecs_entity_t *entities; + int32_t count; +} ecs_cmd_n_t; - if (!data) { - return; - } +typedef struct ecs_cmd_t { + ecs_cmd_kind_t kind; /* Command kind */ + int32_t next_for_entity; /* Next operation for entity */ + ecs_id_t id; /* (Component) id */ + ecs_cmd_entry_t *entry; + ecs_entity_t entity; /* Entity id */ - ecs_flags32_t flags = table->flags; + union { + ecs_cmd_1_t _1; /* Data for single entity operation */ + ecs_cmd_n_t _n; /* Data for multi entity operation */ + } is; - if (do_on_remove && (flags & EcsTableHasOnRemove)) { - run_on_remove(world, table, data); - } + ecs_entity_t system; /* System that enqueued the command */ +} ecs_cmd_t; - int32_t count = flecs_table_data_count(data); - if (count) { - dtor_all_components(world, table, data, 0, count, - update_entity_index, is_delete); - } +/** Callback used to capture commands of a frame */ +typedef void (*ecs_on_commands_action_t)( + const ecs_stage_t *stage, + const ecs_vec_t *commands, + void *ctx); - /* Sanity check */ - ecs_assert(ecs_vector_count(data->record_ptrs) == - ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); +/* Initialize command queue data structure for stage. */ +void flecs_commands_init( + ecs_stage_t *stage, + ecs_commands_t *cmd); - ecs_column_t *columns = data->columns; - if (columns) { - int32_t c, column_count = table->storage_count; - for (c = 0; c < column_count; c ++) { - /* Sanity check */ - ecs_assert(!columns[c].data || (ecs_vector_count(columns[c].data) == - ecs_vector_count(data->entities)), ECS_INTERNAL_ERROR, NULL); +/* Free command queue data structure for stage. */ +void flecs_commands_fini( + ecs_stage_t *stage, + ecs_commands_t *cmd); - ecs_vector_free(columns[c].data); - } - ecs_os_free(columns); - data->columns = NULL; - } +/* Begin deferring, or return whether already deferred. */ +bool flecs_defer_cmd( + ecs_stage_t *stage); - ecs_sw_column_t *sw_columns = data->sw_columns; - if (sw_columns) { - int32_t c, column_count = table->sw_column_count; - for (c = 0; c < column_count; c ++) { - flecs_switch_free(sw_columns[c].data); - } - ecs_os_free(sw_columns); - data->sw_columns = NULL; - } +/* Begin deferred mode. */ +bool flecs_defer_begin( + ecs_world_t *world, + ecs_stage_t *stage); - ecs_bs_column_t *bs_columns = data->bs_columns; - if (bs_columns) { - int32_t c, column_count = table->bs_column_count; - for (c = 0; c < column_count; c ++) { - flecs_bitset_fini(&bs_columns[c].data); - } - ecs_os_free(bs_columns); - data->bs_columns = NULL; - } +/* Purge command queue without executing commands. */ +bool flecs_defer_purge( + ecs_world_t *world, + ecs_stage_t *stage); - ecs_vector_free(data->entities); - ecs_vector_free(data->record_ptrs); +/* Insert modified command. */ +bool flecs_defer_modified( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component); - data->entities = NULL; - data->record_ptrs = NULL; +/* Insert clone command. */ +bool flecs_defer_clone( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value); - if (deactivate && count) { - flecs_table_set_empty(world, table); - } -} +/* Insert bulk_new command. */ +bool flecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, + int32_t count, + ecs_id_t id, + const ecs_entity_t **ids_out); + +/* Insert path command (sets entity path name). */ +bool flecs_defer_path( + ecs_stage_t *stage, + ecs_entity_t parent, + ecs_entity_t entity, + const char *name); + +/* Insert delete command. */ +bool flecs_defer_delete( + ecs_stage_t *stage, + ecs_entity_t entity); + +/* Insert clear command. */ +bool flecs_defer_clear( + ecs_stage_t *stage, + ecs_entity_t entity); + +/* Insert delete_with/remove_all command*/ +bool flecs_defer_on_delete_action( + ecs_stage_t *stage, + ecs_id_t id, + ecs_entity_t action); + +/* Insert enable command (component toggling). */ +bool flecs_defer_enable( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t component, + bool enable); + +/* Insert add component command. */ +bool flecs_defer_add( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id); + +/* Insert remove component command. */ +bool flecs_defer_remove( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id); -/* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ -void flecs_table_clear_data( +void* flecs_defer_emplace( ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data) -{ - fini_data(world, table, data, false, false, false, false); -} + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + ecs_size_t size, + bool *is_new); -/* Cleanup, no OnRemove, clear entity index, deactivate table */ -void flecs_table_clear_entities_silent( +void* flecs_defer_ensure( ecs_world_t *world, - ecs_table_t *table) -{ - fini_data(world, table, &table->storage, false, true, false, true); -} + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + ecs_size_t size); -/* Cleanup, run OnRemove, clear entity index, deactivate table */ -void flecs_table_clear_entities( +/* Insert set component command. */ +void* flecs_defer_set( ecs_world_t *world, - ecs_table_t *table) -{ - fini_data(world, table, &table->storage, true, true, false, true); -} + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + ecs_size_t size, + void *value); -/* Cleanup, run OnRemove, delete from entity index, deactivate table */ -void flecs_table_delete_entities( +void* flecs_defer_cpp_set( ecs_world_t *world, - ecs_table_t *table) -{ - fini_data(world, table, &table->storage, true, true, true, true); -} + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + ecs_size_t size, + const void *value); -/* Unset all components in table. This function is called before a table is - * deleted, and invokes all UnSet handlers, if any */ -void flecs_table_remove_actions( +void* flecs_defer_cpp_assign( ecs_world_t *world, - ecs_table_t *table) -{ - (void)world; - run_on_remove(world, table, &table->storage); -} + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + ecs_size_t size, + const void *value); -/* Free table resources. */ -void flecs_table_free( +/* Insert event command. */ +void flecs_enqueue( ecs_world_t *world, - ecs_table_t *table) -{ - bool is_root = table == &world->store.root; - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), - ECS_INTERNAL_ERROR, NULL); - (void)world; + ecs_stage_t *stage, + ecs_event_desc_t *desc); + +#endif - ecs_assert(table->refcount == 0, ECS_INTERNAL_ERROR, NULL); +/** + * @file entity.h + * @brief Internal functions for dealing with entities. + */ - if (!is_root) { - flecs_notify_queries( - world, &(ecs_query_event_t){ - .kind = EcsQueryTableUnmatch, - .table = table - }); - } +#ifndef FLECS_ENTITY_H +#define FLECS_ENTITY_H - if (ecs_should_log_2()) { - char *expr = ecs_type_str(world, table->type); - ecs_dbg_2( - "#[green]table#[normal] [%s] #[red]deleted#[normal] with id %d // %p", - expr, table->id, table); - ecs_os_free(expr); - ecs_log_push_2(); +#define ecs_get_low_id(table, r, id)\ + ecs_assert(table->component_map != NULL, ECS_INTERNAL_ERROR, NULL);\ + int16_t column_index = table->component_map[id];\ + if (column_index > 0) {\ + ecs_column_t *column = &table->data.columns[column_index - 1];\ + return ECS_ELEM(column->data, column->ti->size, \ + ECS_RECORD_TO_ROW(r->row));\ } - world->info.empty_table_count -= (ecs_table_count(table) == 0); +typedef struct { + const ecs_type_info_t *ti; + void *ptr; +} flecs_component_ptr_t; - /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ - fini_data(world, table, &table->storage, false, true, true, false); +flecs_component_ptr_t flecs_ensure( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component, + ecs_record_t *r, + ecs_size_t size); - flecs_table_clear_edges(world, table); +flecs_component_ptr_t flecs_get_mut( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t id, + ecs_record_t *r, + ecs_size_t size); - if (!is_root) { - ecs_ids_t ids = { - .array = ecs_vector_first(table->type, ecs_id_t), - .count = ecs_vector_count(table->type) - }; +/* Get component pointer with type info. */ +flecs_component_ptr_t flecs_get_component_ptr( + const ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_component_record_t *cr); - flecs_hashmap_remove(&world->store.table_map, &ids, ecs_table_t*); - } +/* Get component pointer. */ +void* flecs_get_component( + const ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_component_record_t *cr); - ecs_os_free(table->dirty_state); - ecs_os_free(table->storage_map); +/* Create new entity. */ +ecs_entity_t flecs_new_id( + const ecs_world_t *world); - flecs_table_records_unregister(world, table); +/* Create new entities in bulk. */ +const ecs_entity_t* flecs_bulk_new( + ecs_world_t *world, + ecs_table_t *table, + const ecs_entity_t *entities, + ecs_type_t *component_ids, + int32_t count, + void **c_info, + bool move, + int32_t *row_out, + ecs_table_diff_t *diff); - ecs_table_t *storage_table = table->storage_table; - if (storage_table == table) { - if (table->type_info) { - ecs_os_free(table->type_info); - } - } else if (storage_table) { - flecs_table_release(world, storage_table); - } +/* Add new entity id to root table. */ +void flecs_add_to_root_table( + ecs_world_t *world, + ecs_entity_t e); - /* Update counters */ - world->info.table_count --; - world->info.table_record_count -= table->record_count; - world->info.table_storage_count -= table->storage_count; - world->info.table_delete_total ++; +/* Mark an entity as being watched. This is used to trigger automatic rematching + * when entities used in system expressions change their components. */ +void flecs_add_flag( + ecs_world_t *world, + ecs_entity_t entity, + uint32_t flag); - if (!table->storage_count) { - world->info.tag_table_count --; - } else { - world->info.trivial_table_count -= !(table->flags & EcsTableIsComplex); - } +/* Same as flecs_add_flag but for ecs_record_t. */ +void flecs_record_add_flag( + ecs_record_t *record, + uint32_t flag); - if (!world->is_fini) { - ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); - flecs_table_free_type(table); - flecs_sparse_remove(&world->store.tables, table->id); - } +/* Get entity that should be used for OneOf constraint from relationship. */ +ecs_entity_t flecs_get_oneof( + const ecs_world_t *world, + ecs_entity_t e); - ecs_log_pop_2(); -} +/* Compute relationship depth for table */ +int32_t flecs_relation_depth( + const ecs_world_t *world, + ecs_entity_t r, + const ecs_table_t *table); + +/* Get component from base entity (follows IsA relationship) */ +void* flecs_get_base_component( + const ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_component_record_t *table_index, + int32_t recur_depth); -void flecs_table_claim( +/* Commit entity to (new) table */ +void flecs_commit( ecs_world_t *world, - ecs_table_t *table) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); - table->refcount ++; - (void)world; -} + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *dst_table, + ecs_table_diff_t *diff, + bool construct, + ecs_flags32_t evt_flags); -bool flecs_table_release( +/* Add multiple component ids to entity */ +void flecs_add_ids( ecs_world_t *world, - ecs_table_t *table) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t entity, + ecs_id_t *ids, + int32_t count); - if (--table->refcount == 0) { - flecs_table_free(world, table); - return true; - } - - return false; -} +/* Like regular modified, but doesn't assert if entity doesn't have component */ +void flecs_modified_id_if( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool invoke_hook); -/* Free table type. Do this separately from freeing the table as types can be - * in use by application destructors. */ -void flecs_table_free_type( - ecs_table_t *table) -{ - ecs_vector_free((ecs_vector_t*)table->type); -} +/* Like regular set, but uses move instead of copy */ +void flecs_set_id_move( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + void *ptr, + ecs_cmd_kind_t cmd_kind); -/* Reset a table to its initial state. */ -void flecs_table_reset( +/* Add single component id */ +void flecs_add_id( ecs_world_t *world, - ecs_table_t *table) -{ - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - flecs_table_clear_edges(world, table); -} + ecs_entity_t entity, + ecs_id_t id); -static -void mark_table_dirty( +/* Remove single component id */ +void flecs_remove_id( ecs_world_t *world, - ecs_table_t *table, - int32_t index) -{ - (void)world; - if (table->dirty_state) { - table->dirty_state[index] ++; - } -} + ecs_entity_t entity, + ecs_id_t id); -void flecs_table_mark_dirty( +/* Run on delete action */ +void flecs_on_delete( ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t component) -{ - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id, + ecs_entity_t action, + bool delete_id, + bool force_delete); - if (table->dirty_state) { - int32_t index = ecs_search(world, table->storage_table, component, 0); - ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - table->dirty_state[index + 1] ++; - } -} +/* Remove non-fragmenting components from entity */ +void flecs_entity_remove_non_fragmenting( + ecs_world_t *world, + ecs_entity_t e, + ecs_record_t *r); -static -void move_switch_columns( - ecs_table_t *dst_table, - ecs_data_t *dst_data, - int32_t dst_index, - ecs_table_t *src_table, - ecs_data_t *src_data, - int32_t src_index, - int32_t count, - bool clear) -{ - int32_t i_old = 0, src_column_count = src_table->sw_column_count; - int32_t i_new = 0, dst_column_count = dst_table->sw_column_count; +const char* flecs_entity_invalid_reason( + const ecs_world_t *world, + ecs_entity_t entity); - if (!src_column_count && !dst_column_count) { - return; - } +ecs_table_range_t flecs_range_from_entity( + const ecs_world_t *world, + ecs_entity_t e); - ecs_sw_column_t *src_columns = src_data->sw_columns; - ecs_sw_column_t *dst_columns = dst_data->sw_columns; +ecs_entity_t flecs_set_identifier( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t tag, + const char *name); - ecs_type_t dst_type = dst_table->type; - ecs_type_t src_type = src_table->type; +#endif - int32_t offset_new = dst_table->sw_column_offset; - int32_t offset_old = src_table->sw_column_offset; +/** + * @file instantiate.h + * @brief Functions for instantiating prefabs (IsA relationship). + */ - ecs_id_t *dst_ids = ecs_vector_first(dst_type, ecs_id_t); - ecs_id_t *src_ids = ecs_vector_first(src_type, ecs_id_t); +#ifndef FLECS_INSTANTIATE_H +#define FLECS_INSTANTIATE_H - for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_entity_t dst_id = dst_ids[i_new + offset_new]; - ecs_entity_t src_id = src_ids[i_old + offset_old]; +typedef struct ecs_instantiate_ctx_t { + ecs_entity_t root_prefab; + ecs_entity_t root_instance; +} ecs_instantiate_ctx_t; - if (dst_id == src_id) { - ecs_switch_t *src_switch = src_columns[i_old].data; - ecs_switch_t *dst_switch = dst_columns[i_new].data; +void flecs_instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t instance, + const ecs_instantiate_ctx_t *ctx); - flecs_switch_ensure(dst_switch, dst_index + count); +void flecs_instantiate_dont_fragment( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t instance); - int i; - for (i = 0; i < count; i ++) { - uint64_t value = flecs_switch_get(src_switch, src_index + i); - flecs_switch_set(dst_switch, dst_index + i, value); - } +void flecs_instantiate_sparse( + ecs_world_t *world, + const ecs_table_range_t *base_child_range, + const ecs_entity_t *base_children, + ecs_table_t *instance_table, + const ecs_entity_t *instance_children, + int32_t row_offset); - if (clear) { - ecs_assert(count == flecs_switch_count(src_switch), - ECS_INTERNAL_ERROR, NULL); - flecs_switch_clear(src_switch); - } - } +#endif - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; - } +/** + * @file observable.h + * @brief Functions for emitting events. + */ - /* Clear remaining columns */ - if (clear) { - for (; (i_old < src_column_count); i_old ++) { - ecs_switch_t *src_switch = src_columns[i_old].data; - ecs_assert(count == flecs_switch_count(src_switch), - ECS_INTERNAL_ERROR, NULL); - flecs_switch_clear(src_switch); - } - } -} +#ifndef FLECS_OBSERVABLE_H +#define FLECS_OBSERVABLE_H -static -void move_bitset_columns( - ecs_table_t *dst_table, - ecs_data_t *dst_data, - int32_t dst_index, - ecs_table_t *src_table, - ecs_data_t *src_data, - int32_t src_index, - int32_t count, - bool clear) -{ - int32_t i_old = 0, src_column_count = src_table->bs_column_count; - int32_t i_new = 0, dst_column_count = dst_table->bs_column_count; +/** All observers for a specific (component) id */ +typedef struct ecs_event_id_record_t { + /* Triggers for Self */ + ecs_map_t self; /* map */ + ecs_map_t self_up; /* map */ + ecs_map_t up; /* map */ - if (!src_column_count && !dst_column_count) { - return; - } + /* Number of active observers for (component) id */ + int32_t observer_count; +} ecs_event_id_record_t; - ecs_bs_column_t *src_columns = src_data->bs_columns; - ecs_bs_column_t *dst_columns = dst_data->bs_columns; +typedef struct ecs_observer_impl_t { + ecs_observer_t pub; - ecs_type_t dst_type = dst_table->type; - ecs_type_t src_type = src_table->type; + int32_t *last_event_id; /**< Last handled event id */ + int32_t last_event_id_storage; - int32_t offset_new = dst_table->bs_column_offset; - int32_t offset_old = src_table->bs_column_offset; + ecs_flags32_t flags; /**< Observer flags */ - ecs_entity_t *dst_components = ecs_vector_first(dst_type, ecs_entity_t); - ecs_entity_t *src_components = ecs_vector_first(src_type, ecs_entity_t); + int8_t term_index; /**< Index of the term in parent observer (single term observers only) */ + ecs_id_t register_id; /**< Id observer is registered with (single term observers only) */ + uint64_t id; /**< Internal id (not entity id) */ - for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_entity_t dst_component = dst_components[i_new + offset_new]; - ecs_entity_t src_component = src_components[i_old + offset_old]; + ecs_vec_t children; /**< If multi observer, vector stores child observers */ - if (dst_component == src_component) { - ecs_bitset_t *src_bs = &src_columns[i_old].data; - ecs_bitset_t *dst_bs = &dst_columns[i_new].data; + ecs_query_t *not_query; /**< Query used to populate observer data when a + term with a not operator triggers. */ - flecs_bitset_ensure(dst_bs, dst_index + count); + /* Mixins */ + flecs_poly_dtor_t dtor; +} ecs_observer_impl_t; - int i; - for (i = 0; i < count; i ++) { - uint64_t value = flecs_bitset_get(src_bs, src_index + i); - flecs_bitset_set(dst_bs, dst_index + i, value); - } +#define flecs_observer_impl(observer) (ECS_CONST_CAST(ecs_observer_impl_t*, observer)) - if (clear) { - ecs_assert(count == flecs_bitset_count(src_bs), - ECS_INTERNAL_ERROR, NULL); - flecs_bitset_fini(src_bs); - } - } +/* Get event record (all observers for an event). */ +ecs_event_record_t* flecs_event_record_get( + const ecs_observable_t *o, + ecs_entity_t event); - i_new += dst_component <= src_component; - i_old += dst_component >= src_component; - } +/* Get or create event record. */ +ecs_event_record_t* flecs_event_record_ensure( + ecs_observable_t *o, + ecs_entity_t event); - /* Clear remaining columns */ - if (clear) { - for (; (i_old < src_column_count); i_old ++) { - ecs_bitset_t *src_bs = &src_columns[i_old].data; - ecs_assert(count == flecs_bitset_count(src_bs), - ECS_INTERNAL_ERROR, NULL); - flecs_bitset_fini(src_bs); - } - } -} +/* Get event id record (all observers for an event/component). */ +ecs_event_id_record_t* flecs_event_id_record_get( + const ecs_event_record_t *er, + ecs_id_t id); -static -void* grow_column( - ecs_column_t *column, - ecs_type_info_t *ti, - int32_t to_add, - int32_t dst_size, - bool construct) -{ - ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); +/* Get or create event id record. */ +ecs_event_id_record_t* flecs_event_id_record_ensure( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_id_t id); - ecs_vector_t *vec = column->data; - int32_t size = ti->size; - int32_t alignment = ti->alignment; - int32_t count = ecs_vector_count(vec); - int32_t src_size = ecs_vector_size(vec); - int32_t dst_count = count + to_add; - bool can_realloc = dst_size != src_size; - void *result = NULL; +/* Remove event id record. */ +void flecs_event_id_record_remove( + ecs_event_record_t *er, + ecs_id_t id); - ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); +/* Initialize observable (typically the world). */ +void flecs_observable_init( + ecs_observable_t *observable); - /* If the array could possibly realloc and the component has a move action - * defined, move old elements manually */ - ecs_move_t move_ctor; - if (count && can_realloc && (move_ctor = ti->lifecycle.move_ctor)) - { - ecs_xtor_t ctor = ti->lifecycle.ctor; - ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); +/* Free observable. */ +void flecs_observable_fini( + ecs_observable_t *observable); - /* Create vector */ - ecs_vector_t *dst_vec = ecs_vector_new_t(size, alignment, dst_size); - ecs_vector_set_count_t(&dst_vec, size, alignment, dst_count); +/* Check if any observers exist for event/component. */ +bool flecs_observers_exist( + const ecs_observable_t *observable, + ecs_id_t id, + ecs_entity_t event); - void *src_buffer = ecs_vector_first_t(vec, size, alignment); - void *dst_buffer = ecs_vector_first_t(dst_vec, size, alignment); +/* Initialize observer. */ +ecs_observer_t* flecs_observer_init( + ecs_world_t *world, + ecs_entity_t entity, + const ecs_observer_desc_t *desc); - /* Move (and construct) existing elements to new vector */ - move_ctor(dst_buffer, src_buffer, count, ti); +/* Free observer. */ +void flecs_observer_fini( + ecs_observer_t *observer); - if (construct) { - /* Construct new element(s) */ - result = ECS_OFFSET(dst_buffer, size * count); - ctor(result, to_add, ti); - } +/* Emit event. */ +void flecs_emit( + ecs_world_t *world, + ecs_world_t *stage, + ecs_event_desc_t *desc); - /* Free old vector */ - ecs_vector_free(vec); +/* Default function to set in iter::next */ +bool flecs_default_next_callback( + ecs_iter_t *it); - column->data = dst_vec; - } else { - /* If array won't realloc or has no move, simply add new elements */ - if (can_realloc) { - ecs_vector_set_size_t(&vec, size, alignment, dst_size); - } +/* Invoke observers. */ +void flecs_observers_invoke( + ecs_world_t *world, + ecs_map_t *observers, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav); - result = ecs_vector_addn_t(&vec, size, alignment, to_add); +/* Invalidate reachable cache. */ +void flecs_emit_propagate_invalidate( + ecs_world_t *world, + ecs_table_t *table, + int32_t offset, + int32_t count); - ecs_xtor_t ctor; - if (construct && (ctor = ti->lifecycle.ctor)) { - /* If new elements need to be constructed and component has a - * constructor, construct */ - ctor(result, to_add, ti); - } +/* Set bit indicating that observer is disabled. */ +void flecs_observer_set_disable_bit( + ecs_world_t *world, + ecs_entity_t e, + ecs_flags32_t bit, + bool cond); - column->data = vec; - } +#endif - ecs_assert(ecs_vector_size(column->data) == dst_size, - ECS_INTERNAL_ERROR, NULL); +/** + * @file iter.h + * @brief Iterator utilities. + */ - return result; -} +#ifndef FLECS_ITER_H +#define FLECS_ITER_H -static -int32_t grow_data( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t to_add, - int32_t size, - const ecs_entity_t *ids) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t cur_count = flecs_table_data_count(data); - int32_t column_count = table->storage_count; - int32_t sw_column_count = table->sw_column_count; - int32_t bs_column_count = table->bs_column_count; - ecs_column_t *columns = data->columns; - ecs_sw_column_t *sw_columns = data->sw_columns; - ecs_bs_column_t *bs_columns = data->bs_columns; - - /* Add record to record ptr array */ - ecs_vector_set_size(&data->record_ptrs, ecs_record_t*, size); - ecs_record_t **r = ecs_vector_addn(&data->record_ptrs, ecs_record_t*, to_add); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - if (ecs_vector_size(data->record_ptrs) > size) { - size = ecs_vector_size(data->record_ptrs); - } +/* Initialize iterator. */ +void flecs_iter_init( + const ecs_world_t *world, + ecs_iter_t *it, + bool alloc_resources); - /* Add entity to column with entity ids */ - ecs_vector_set_size(&data->entities, ecs_entity_t, size); - ecs_entity_t *e = ecs_vector_addn(&data->entities, ecs_entity_t, to_add); - ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_size(data->entities) == size, ECS_INTERNAL_ERROR, NULL); +/* Deinitialize iterator. */ +void flecs_iter_free( + void *ptr, + ecs_size_t size); - /* Initialize entity ids and record ptrs */ - int32_t i; - if (ids) { - for (i = 0; i < to_add; i ++) { - e[i] = ids[i]; - } - } else { - ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); - } - ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); +/* Allocate zero initialized memory from iterator allocator. */ +void* flecs_iter_calloc( + ecs_iter_t *it, + ecs_size_t size, + ecs_size_t align); - /* Add elements to each column array */ - ecs_type_info_t *type_info = table->type_info; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_type_info_t *ti = &type_info[i]; - grow_column(column, ti, to_add, size, true); - ecs_assert(ecs_vector_size(columns[i].data) == size, - ECS_INTERNAL_ERROR, NULL); - } +#define flecs_iter_calloc_t(it, T)\ + flecs_iter_calloc(it, ECS_SIZEOF(T), ECS_ALIGNOF(T)) - /* Add elements to each switch column */ - for (i = 0; i < sw_column_count; i ++) { - ecs_switch_t *sw = sw_columns[i].data; - flecs_switch_addn(sw, to_add); - } +#define flecs_iter_calloc_n(it, T, count)\ + flecs_iter_calloc(it, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) - /* Add elements to each bitset column */ - for (i = 0; i < bs_column_count; i ++) { - ecs_bitset_t *bs = &bs_columns[i].data; - flecs_bitset_addn(bs, to_add); - } +#define flecs_iter_free_t(ptr, T)\ + flecs_iter_free(ptr, ECS_SIZEOF(T)) - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(world, table, 0); +#define flecs_iter_free_n(ptr, T, count)\ + flecs_iter_free(ptr, ECS_SIZEOF(T) * count) - if (!world->is_readonly && !cur_count) { - flecs_table_set_empty(world, table); - } +#endif - table->alloc_count ++; +/** + * @file poly.h + * @brief Functions for managing poly objects. + */ - /* Return index of first added entity */ - return cur_count; -} +#ifndef FLECS_POLY_H +#define FLECS_POLY_H -static -void fast_append( - ecs_type_info_t *type_info, - ecs_column_t *columns, - int32_t count) -{ - /* Add elements to each column array */ - int32_t i; - for (i = 0; i < count; i ++) { - ecs_type_info_t *ti = &type_info[i]; - ecs_column_t *column = &columns[i]; - ecs_vector_add_t(&column->data, ti->size, ti->alignment); - } -} +/* Tags associated with poly for (Poly, tag) components */ +#define ecs_world_t_tag invalid +#define ecs_stage_t_tag invalid +#define ecs_query_t_tag EcsQuery +#define ecs_observer_t_tag EcsObserver -int32_t flecs_table_append( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - ecs_entity_t entity, - ecs_record_t *record, - bool construct) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); +/* Mixin kinds */ +typedef enum ecs_mixin_kind_t { + EcsMixinWorld, + EcsMixinEntity, + EcsMixinObservable, + EcsMixinDtor, + EcsMixinMax +} ecs_mixin_kind_t; - check_table_sanity(table); +/* The mixin array contains pointers to mixin members for different kinds of + * flecs objects. This allows the API to retrieve data from an object regardless + * of its type. Each mixin array is only stored once per type */ +struct ecs_mixins_t { + const char *type_name; /* Include name of mixin type so debug code doesn't + * need to know about every object */ + ecs_size_t elems[EcsMixinMax]; +}; - /* Get count & size before growing entities array. This tells us whether the - * arrays will realloc */ - int32_t count = ecs_vector_count(data->entities); - int32_t size = ecs_vector_size(data->entities); - int32_t column_count = table->storage_count; - ecs_column_t *columns = table->storage.columns; - - /* Grow buffer with entity ids, set new element to new entity */ - ecs_entity_t *e = ecs_vector_add(&data->entities, ecs_entity_t); - ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); - *e = entity; +/* Mixin tables */ +extern ecs_mixins_t ecs_world_t_mixins; +extern ecs_mixins_t ecs_stage_t_mixins; +extern ecs_mixins_t ecs_query_t_mixins; +extern ecs_mixins_t ecs_observer_t_mixins; - /* Keep track of alloc count. This allows references to check if cached - * pointers need to be updated. */ - table->alloc_count += (count == size); +/* Types that have no mixins */ +#define ecs_table_t_mixins (&(ecs_mixins_t){ NULL }) - /* Add record ptr to array with record ptrs */ - ecs_record_t **r = ecs_vector_add(&data->record_ptrs, ecs_record_t*); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - *r = record; - - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(world, table, 0); - ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); +/* Initialize poly */ +void* flecs_poly_init_( + ecs_poly_t *object, + int32_t kind, + ecs_size_t size, + ecs_mixins_t *mixins); - ecs_type_info_t *type_info = table->type_info; +#define flecs_poly_init(object, type)\ + flecs_poly_init_(object, type##_magic, sizeof(type), &type##_mixins) - /* Fast path: no switch columns, no lifecycle actions */ - if (!(table->flags & EcsTableIsComplex)) { - fast_append(type_info, columns, column_count); - if (!count) { - flecs_table_set_empty(world, table); /* See below */ - } - return count; - } +/* Deinitialize object for specified type */ +void flecs_poly_fini_( + ecs_poly_t *object, + int32_t kind); - int32_t sw_column_count = table->sw_column_count; - int32_t bs_column_count = table->bs_column_count; - ecs_sw_column_t *sw_columns = data->sw_columns; - ecs_bs_column_t *bs_columns = data->bs_columns; - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); +#define flecs_poly_fini(object, type)\ + flecs_poly_fini_(object, type##_magic) - /* Reobtain size to ensure that the columns have the same size as the - * entities and record vectors. This keeps reasoning about when allocations - * occur easier. */ - size = ecs_vector_size(data->entities); +/* Utility functions for creating an object on the heap */ +#define flecs_poly_new(type)\ + (type*)flecs_poly_init(ecs_os_calloc_t(type), type) - /* Grow component arrays with 1 element */ - int32_t i; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_type_info_t *ti = &type_info[i]; - grow_column(column, ti, 1, size, construct); +#define flecs_poly_free(obj, type)\ + flecs_poly_fini(obj, type);\ + ecs_os_free(obj) - ecs_iter_action_t on_add = ti->lifecycle.on_add; - if (on_add) { - on_component_callback(world, table, on_add, EcsOnAdd, column, - &entities[count], table->storage_ids[i], count, 1, ti); - } +/* Get or create poly component for an entity */ +EcsPoly* flecs_poly_bind_( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag); - ecs_assert(ecs_vector_size(columns[i].data) == - ecs_vector_size(data->entities), ECS_INTERNAL_ERROR, NULL); - ecs_assert( ecs_vector_count(columns[i].data) == - ecs_vector_count(data->entities), ECS_INTERNAL_ERROR, NULL); - } +#define flecs_poly_bind(world, entity, T) \ + flecs_poly_bind_(world, entity, T##_tag) - /* Add element to each switch column */ - for (i = 0; i < sw_column_count; i ++) { - ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = sw_columns[i].data; - flecs_switch_add(sw); - } +/* Send modified event for (Poly, Tag) pair. */ +void flecs_poly_modified_( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag); - /* Add element to each bitset column */ - for (i = 0; i < bs_column_count; i ++) { - ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &bs_columns[i].data; - flecs_bitset_addn(bs, 1); - } +#define flecs_poly_modified(world, entity, T) \ + flecs_poly_modified_(world, entity, T##_tag) - /* If this is the first entity in this table, signal queries so that the - * table moves from an inactive table to an active table. */ - if (!count) { - flecs_table_set_empty(world, table); - } +/* Get poly component for an entity */ +const EcsPoly* flecs_poly_bind_get_( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag); - check_table_sanity(table); +#define flecs_poly_bind_get(world, entity, T) \ + flecs_poly_bind_get_(world, entity, T##_tag) - return count; -} +/* Get (Poly, Tag) poly object from entity. */ +ecs_poly_t* flecs_poly_get_( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag); -static -void fast_delete_last( - ecs_column_t *columns, - int32_t column_count) -{ - int i; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_vector_remove_last(column->data); - } -} +#define flecs_poly_get(world, entity, T) \ + ((T*)flecs_poly_get_(world, entity, T##_tag)) -static -void fast_delete( - ecs_type_info_t *type_info, - ecs_column_t *columns, - int32_t column_count, - int32_t index) -{ - int i; - for (i = 0; i < column_count; i ++) { - ecs_type_info_t *ti = &type_info[i]; - ecs_column_t *column = &columns[i]; - ecs_vector_remove_t(column->data, ti->size, ti->alignment, index); - } -} +/* Utilities for testing/asserting an object type */ +#ifndef FLECS_NDEBUG +#define flecs_poly_assert(object, ty)\ + do {\ + ecs_assert(object != NULL, ECS_INVALID_PARAMETER, NULL);\ + const ecs_header_t *hdr = (const ecs_header_t *)object;\ + const char *type_name = hdr->mixins->type_name;\ + ecs_assert(hdr->type == ty##_magic, ECS_INVALID_PARAMETER, type_name);\ + } while (0) +#else +#define flecs_poly_assert(object, ty) +#endif -void flecs_table_delete( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t index, - bool destruct) -{ - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); +/* Get observable mixin from poly object. */ +ecs_observable_t* flecs_get_observable( + const ecs_poly_t *object); - check_table_sanity(table); +/* Get dtor mixin from poly object. */ +flecs_poly_dtor_t* flecs_get_dtor( + const ecs_poly_t *poly); - ecs_vector_t *v_entities = data->entities; - int32_t count = ecs_vector_count(v_entities); +#endif - ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); - count --; - ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); +/** + * @file tree_spawner.h + * @brief Data structure used to speed up the creation of hierarchies. + */ - /* Move last entity id to index */ - ecs_entity_t *entities = ecs_vector_first(v_entities, ecs_entity_t); - ecs_entity_t entity_to_move = entities[count]; - ecs_entity_t entity_to_delete = entities[index]; - entities[index] = entity_to_move; - ecs_vector_remove_last(v_entities); +#ifndef FLECS_SPAWNER_H +#define FLECS_SPAWNER_H - /* Move last record ptr to index */ - ecs_vector_t *v_records = data->record_ptrs; - ecs_assert(count < ecs_vector_count(v_records), ECS_INTERNAL_ERROR, NULL); +/* Called during bootstrap to register spawner entities with world. */ +void flecs_bootstrap_spawner( + ecs_world_t *world); - ecs_record_t **records = ecs_vector_first(v_records, ecs_record_t*); - ecs_record_t *record_to_move = records[count]; - records[index] = record_to_move; - ecs_vector_remove_last(v_records); +EcsTreeSpawner* flecs_prefab_spawner_build( + ecs_world_t *world, + ecs_entity_t base); - /* Update record of moved entity in entity index */ - if (index != count) { - if (record_to_move) { - uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; - record_to_move->row = ECS_ROW_TO_RECORD(index, row_flags); - ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); - } - } +void flecs_spawner_instantiate( + ecs_world_t *world, + EcsTreeSpawner *spawner, + ecs_entity_t instance); - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(world, table, 0); +#endif - /* If table is empty, deactivate it */ - if (!count) { - flecs_table_set_empty(world, table); - } +/** + * @file stage.h + * @brief Stage functions. + */ - /* Destruct component data */ - ecs_type_info_t *type_info = table->type_info; - ecs_column_t *columns = data->columns; - int32_t column_count = table->storage_count; - int32_t i; +#ifndef FLECS_STAGE_H +#define FLECS_STAGE_H + +/* Stage level allocators are for operations that can be multithreaded */ +typedef struct ecs_stage_allocators_t { + ecs_stack_t iter_stack; + ecs_block_allocator_t cmd_entry_chunk; + ecs_block_allocator_t query_impl; + ecs_block_allocator_t query_cache; +} ecs_stage_allocators_t; + +/** A stage is a context that allows for safely using the API from multiple + * threads. Stage pointers can be passed to the world argument of API + * operations, which causes the operation to be ran on the stage instead of the + * world. The features provided by a stage are: + * + * - A command queue for deferred ECS operations and events + * - Thread specific allocators + * - Thread specific world state (like current scope, with, current system) + * - Thread specific buffers for preventing allocations + */ +struct ecs_stage_t { + ecs_header_t hdr; - /* If this is a table without lifecycle callbacks or special columns, take - * fast path that just remove an element from the array(s) */ - if (!(table->flags & EcsTableIsComplex)) { - if (index == count) { - fast_delete_last(columns, column_count); - } else { - fast_delete(type_info, columns, column_count, index); - } + /* Unique id that identifies the stage */ + int32_t id; - check_table_sanity(table); - return; - } + /* Zero if not deferred, positive if deferred, negative if suspended */ + int32_t defer; - ecs_id_t *ids = table->storage_ids; + /* Command queue */ + ecs_commands_t *cmd; + ecs_commands_t cmd_stack[2]; /* Two so we can flush one & populate the other */ + bool cmd_flushing; /* Ensures only one defer_end call flushes */ - /* Last element, destruct & remove */ - if (index == count) { - /* If table has component destructors, invoke */ - if (destruct && (table->flags & EcsTableHasDtors)) { - for (i = 0; i < column_count; i ++) { - remove_component(world, table, &type_info[i], &columns[i], - &entity_to_delete, ids[i], index, 1); - } - } + /* Thread context */ + ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ + ecs_world_t *world; /* Reference to world */ + ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ - fast_delete_last(columns, column_count); + /* One-shot actions to be executed after the merge */ + ecs_vec_t post_frame_actions; - /* Not last element, move last element to deleted element & destruct */ - } else { - /* If table has component destructors, invoke */ - if (destruct && (table->flags & (EcsTableHasDtors | EcsTableHasMove))) { - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_type_info_t *ti = &type_info[i]; - ecs_size_t size = ti->size; - ecs_size_t align = ti->alignment; - ecs_vector_t *vec = column->data; - void *dst = ecs_vector_get_t(vec, size, align, index); - void *src = ecs_vector_last_t(vec, size, align); - - ecs_iter_action_t on_remove = ti->lifecycle.on_remove; - if (on_remove) { - on_component_callback(world, table, on_remove, EcsOnRemove, - column, &entity_to_delete, ids[i], index, 1, ti); - } + /* Namespacing */ + ecs_entity_t scope; /* Entity of current scope */ + ecs_entity_t with; /* Id to add by default to new entities */ + ecs_entity_t base; /* Currently instantiated top-level base */ + const ecs_entity_t *lookup_path; /* Search path used by lookup operations */ - ecs_move_t move_dtor = ti->lifecycle.move_dtor; - if (move_dtor) { - move_dtor(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, size); - } + /* Running system */ + ecs_entity_t system; - ecs_vector_remove_last(vec); - } - } else { - fast_delete(type_info, columns, column_count, index); - } - } + /* Thread specific allocators */ + ecs_stage_allocators_t allocators; + ecs_allocator_t allocator; - /* Remove elements from switch columns */ - ecs_sw_column_t *sw_columns = data->sw_columns; - int32_t sw_column_count = table->sw_column_count; - for (i = 0; i < sw_column_count; i ++) { - flecs_switch_remove(sw_columns[i].data, index); - } + /* Caches for query creation */ + ecs_vec_t variables; + ecs_vec_t operations; - /* Remove elements from bitset columns */ - ecs_bs_column_t *bs_columns = data->bs_columns; - int32_t bs_column_count = table->bs_column_count; - for (i = 0; i < bs_column_count; i ++) { - flecs_bitset_remove(&bs_columns[i].data, index); - } +#ifdef FLECS_SCRIPT + /* Thread specific runtime for script execution */ + ecs_script_runtime_t *runtime; +#endif +}; - check_table_sanity(table); -} +/* Post-frame merge actions. */ +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage); -static -void fast_move( - ecs_table_t *dst_table, - ecs_data_t *dst_data, - int32_t dst_index, - ecs_table_t *src_table, - ecs_data_t *src_data, - int32_t src_index) -{ - int32_t i_new = 0, dst_column_count = dst_table->storage_count; - int32_t i_old = 0, src_column_count = src_table->storage_count; - ecs_id_t *dst_ids = dst_table->storage_ids; - ecs_id_t *src_ids = src_table->storage_ids; +/* Set system id for debugging which system inserted which commands. */ +ecs_entity_t flecs_stage_set_system( + ecs_stage_t *stage, + ecs_entity_t system); - ecs_column_t *src_columns = src_data->columns; - ecs_column_t *dst_columns = dst_data->columns; +/* Get allocator from stage/world. */ +ecs_allocator_t* flecs_stage_get_allocator( + ecs_world_t *world); - ecs_type_info_t *dst_type_info = dst_table->type_info; +/* Get stack allocator from stage/world. */ +ecs_stack_t* flecs_stage_get_stack_allocator( + ecs_world_t *world); - for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_id_t dst_id = dst_ids[i_new]; - ecs_id_t src_id = src_ids[i_old]; +/* Shrink memory for stage data structures. */ +void ecs_stage_shrink( + ecs_stage_t *stage); - if (dst_id == src_id) { - ecs_column_t *dst_column = &dst_columns[i_new]; - ecs_column_t *src_column = &src_columns[i_old]; - ecs_type_info_t *ti = &dst_type_info[i_new]; - int32_t size = ti->size; - int32_t alignment = ti->alignment; - void *dst = ecs_vector_get_t( - dst_column->data, size, alignment, dst_index); - void *src = ecs_vector_get_t( - src_column->data, size, alignment, src_index); - - ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_memcpy(dst, src, size); - } +#endif - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; - } -} +/** + * @file world.h + * @brief World functions. + */ -void flecs_table_move( - ecs_world_t *world, - ecs_entity_t dst_entity, - ecs_entity_t src_entity, - ecs_table_t *dst_table, - ecs_data_t *dst_data, - int32_t dst_index, - ecs_table_t *src_table, - ecs_data_t *src_data, - int32_t src_index, - bool construct) -{ - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!dst_table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!src_table->lock, ECS_LOCKED_STORAGE, NULL); +#ifndef FLECS_WORLD_H +#define FLECS_WORLD_H - ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); +/* The bitmask used when determining the table version array index */ +#define ECS_TABLE_VERSION_ARRAY_BITMASK (0xff) - ecs_assert(src_data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dst_data != NULL, ECS_INTERNAL_ERROR, NULL); +/* The number of table versions to split tables across */ +#define ECS_TABLE_VERSION_ARRAY_SIZE (ECS_TABLE_VERSION_ARRAY_BITMASK + 1) - check_table_sanity(dst_table); - check_table_sanity(src_table); +/* World level allocators are for operations that are not multithreaded */ +typedef struct ecs_world_allocators_t { + ecs_block_allocator_t graph_edge_lo; + ecs_block_allocator_t graph_edge; + ecs_block_allocator_t component_record; + ecs_block_allocator_t pair_record; + ecs_block_allocator_t table_diff; + ecs_block_allocator_t sparse_chunk; - if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { - fast_move(dst_table, dst_data, dst_index, src_table, src_data, - src_index); - check_table_sanity(dst_table); - check_table_sanity(src_table); - return; - } + /* Temporary vectors used for creating table diff id sequences */ + ecs_table_diff_builder_t diff_builder; - move_switch_columns(dst_table, dst_data, dst_index, src_table, src_data, - src_index, 1, false); - move_bitset_columns(dst_table, dst_data, dst_index, src_table, src_data, - src_index, 1, false); + /* Temporary vector for tree spawner */ + ecs_vec_t tree_spawner; +} ecs_world_allocators_t; - bool same_entity = dst_entity == src_entity; +/* Component monitor */ +typedef struct ecs_monitor_t { + ecs_vec_t queries; /* vector */ + bool is_dirty; /* Should queries be rematched? */ +} ecs_monitor_t; - ecs_type_info_t *dst_type_info = dst_table->type_info; - ecs_type_info_t *src_type_info = src_table->type_info; +/* Component monitors */ +typedef struct ecs_monitor_set_t { + ecs_map_t monitors; /* map */ + bool is_dirty; /* Should monitors be evaluated? */ +} ecs_monitor_set_t; - int32_t i_new = 0, dst_column_count = dst_table->storage_count; - int32_t i_old = 0, src_column_count = src_table->storage_count; - ecs_id_t *dst_ids = dst_table->storage_ids; - ecs_id_t *src_ids = src_table->storage_ids; +/* Data stored for id marked for deletion */ +typedef struct ecs_marked_id_t { + ecs_component_record_t *cr; + ecs_id_t id; + ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ + bool delete_id; +} ecs_marked_id_t; - ecs_column_t *src_columns = src_data->columns; - ecs_column_t *dst_columns = dst_data->columns; +typedef struct ecs_store_t { + /* Entity lookup */ + ecs_entity_index_t entity_index; - for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_id_t dst_id = dst_ids[i_new]; - ecs_id_t src_id = src_ids[i_old]; + /* Tables */ + ecs_sparse_t tables; /* sparse */ - if (dst_id == src_id) { - ecs_column_t *dst_column = &dst_columns[i_new]; - ecs_column_t *src_column = &src_columns[i_old]; - ecs_type_info_t *ti = &dst_type_info[i_new]; - int32_t size = ti->size; - int32_t alignment = ti->alignment; + /* Table lookup by hash */ + ecs_hashmap_t table_map; /* hashmap */ - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - void *dst = ecs_vector_get_t( - dst_column->data, size, alignment, dst_index); - void *src = ecs_vector_get_t( - src_column->data, size, alignment, src_index); - - ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); - - if (same_entity) { - ecs_move_t move = ti->lifecycle.ctor_move_dtor; - if (move) { - /* ctor + move + dtor */ - move(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, size); - } - } else { - ecs_copy_t copy = ti->lifecycle.copy_ctor; - if (copy) { - copy(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, size); - } - } - } else { - if (dst_id < src_id) { - if (construct) { - add_component(world, dst_table, &dst_type_info[i_new], - &dst_columns[i_new], &dst_entity, dst_id, - dst_index, 1); - } - } else { - remove_component(world, src_table, &src_type_info[i_old], - &src_columns[i_old], &src_entity, src_id, - src_index, 1); - } - } + /* Root table */ + ecs_table_t root; - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; - } + /* Records cache */ + ecs_vec_t records; - if (construct) { - for (; (i_new < dst_column_count); i_new ++) { - add_component(world, dst_table, &dst_type_info[i_new], - &dst_columns[i_new], &dst_entity, dst_ids[i_new], dst_index, 1); - } - } + /* Stack of ids being deleted during cleanup action. */ + ecs_vec_t marked_ids; /* vector */ - for (; (i_old < src_column_count); i_old ++) { - remove_component(world, src_table, &src_type_info[i_old], - &src_columns[i_old], &src_entity, src_ids[i_old], - src_index, 1); - } + /* Components deleted during cleanup action. Used to delay cleaning up of + * type info so it's guaranteed that this data is available while the + * storage is cleaning up tables. */ + ecs_vec_t deleted_components; /* vector */ +} ecs_store_t; - check_table_sanity(dst_table); - check_table_sanity(src_table); -} +/* fini actions */ +typedef struct ecs_action_elem_t { + ecs_fini_action_t action; + void *ctx; +} ecs_action_elem_t; -int32_t flecs_table_appendn( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t to_add, - const ecs_entity_t *ids) -{ - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); +typedef struct ecs_pipeline_state_t ecs_pipeline_state_t; - check_table_sanity(table); +/** The world stores and manages all ECS data. An application can have more than + * one world, but data is not shared between worlds. */ +struct ecs_world_t { + ecs_header_t hdr; - int32_t cur_count = flecs_table_data_count(data); - int32_t result = grow_data( - world, table, data, to_add, cur_count + to_add, ids); - check_table_sanity(table); - return result; -} + /* -- Type metadata -- */ + ecs_component_record_t **id_index_lo; + ecs_map_t id_index_hi; /* map */ + ecs_map_t type_info; /* map */ -void flecs_table_set_size( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t size) -{ - ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); +#ifdef FLECS_DEBUG + /* Locked components. When a component is queried for, it is no longer + * possible to change traits and/or to delete the component. */ + ecs_map_t locked_components; /* map */ - check_table_sanity(table); + /* Locked entities. This is used for queried for pair targets. It is + * possible to add traits, but entities cannot be deleted. */ + ecs_map_t locked_entities; /* map */ +#endif - int32_t cur_count = flecs_table_data_count(data); + /* -- Cached handle to id records -- */ + ecs_component_record_t *cr_wildcard; + ecs_component_record_t *cr_wildcard_wildcard; + ecs_component_record_t *cr_any; + ecs_component_record_t *cr_isa_wildcard; + ecs_component_record_t *cr_childof_0; + ecs_component_record_t *cr_childof_wildcard; + ecs_component_record_t *cr_identifier_name; - if (cur_count < size) { - grow_data(world, table, data, 0, size, NULL); - check_table_sanity(table); - } -} + /* Head of list that points to all non-fragmenting component ids */ + ecs_component_record_t *cr_non_fragmenting_head; -bool flecs_table_shrink( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - (void)world; + /* -- Mixins -- */ + ecs_world_t *self; + ecs_observable_t observable; + /* Unique id per generated event used to prevent duplicate notifications */ + int32_t event_id; - check_table_sanity(table); + /* Array of table versions used with component refs to determine if the + * cached pointer is still valid. */ + uint32_t table_version[ECS_TABLE_VERSION_ARRAY_SIZE]; - ecs_data_t *data = &table->storage; - bool has_payload = data->entities != NULL; - ecs_vector_reclaim(&data->entities, ecs_entity_t); - ecs_vector_reclaim(&data->record_ptrs, ecs_record_t*); + /* Same as table_version, but only increases after the column pointers of + * a table change. */ + uint32_t table_column_version[ECS_TABLE_VERSION_ARRAY_SIZE]; - int32_t i, count = table->storage_count; - ecs_type_info_t *type_info = table->type_info; - for (i = 0; i < count; i ++) { - ecs_column_t *column = &data->columns[i]; - ecs_type_info_t *ti = &type_info[i]; - ecs_vector_reclaim_t(&column->data, ti->size, ti->alignment); - } + /* Array for checking if components can be looked up trivially */ + ecs_flags8_t non_trivial_lookup[FLECS_HI_COMPONENT_ID]; - table->alloc_count ++; + /* Array for checking if components can be set trivially */ + ecs_flags8_t non_trivial_set[FLECS_HI_COMPONENT_ID]; - return has_payload; -} + /* -- Data storage -- */ + ecs_store_t store; -int32_t flecs_table_data_count( - const ecs_data_t *data) -{ - return data ? ecs_vector_count(data->entities) : 0; -} + /* Used to track when cache needs to be updated */ + ecs_monitor_set_t monitors; /* map */ -static -void swap_switch_columns( - ecs_table_t *table, - ecs_data_t *data, - int32_t row_1, - int32_t row_2) -{ - int32_t i = 0, column_count = table->sw_column_count; - if (!column_count) { - return; - } + /* -- Systems -- */ + ecs_entity_t pipeline; /* Current pipeline */ - ecs_sw_column_t *columns = data->sw_columns; + /* -- Identifiers -- */ + ecs_hashmap_t aliases; + ecs_hashmap_t symbols; - for (i = 0; i < column_count; i ++) { - ecs_switch_t *sw = columns[i].data; - flecs_switch_swap(sw, row_1, row_2); - } -} + /* -- Staging -- */ + ecs_stage_t **stages; /* Stages */ + int32_t stage_count; /* Number of stages */ -static -void swap_bitset_columns( - ecs_table_t *table, - ecs_data_t *data, - int32_t row_1, - int32_t row_2) -{ - int32_t i = 0, column_count = table->bs_column_count; - if (!column_count) { - return; - } + /* -- Component ids -- */ + ecs_vec_t component_ids; /* World local component ids */ - ecs_bs_column_t *columns = data->bs_columns; + /* Index of prefab children in ordered children vector. Used by ecs_get_target. */ + ecs_map_t prefab_child_indices; - for (i = 0; i < column_count; i ++) { - ecs_bitset_t *bs = &columns[i].data; - flecs_bitset_swap(bs, row_1, row_2); - } -} + /* Is entity range checking enabled? */ + bool range_check_enabled; -void flecs_table_swap( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t row_1, - int32_t row_2) -{ - (void)world; + /* Internal callback for command inspection. Only one callback can be set at + * a time. After assignment the action will become active at the start of + * the next frame, set by ecs_frame_begin, and will be reset by + * ecs_frame_end. */ + ecs_on_commands_action_t on_commands; + ecs_on_commands_action_t on_commands_active; + void *on_commands_ctx; + void *on_commands_ctx_active; - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); + /* -- Multithreading -- */ + ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ + ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ + ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ + int32_t workers_running; /* Number of threads running */ + int32_t workers_waiting; /* Number of workers waiting on sync */ + ecs_pipeline_state_t* pq; /* Pointer to the pipeline for the workers to execute */ + bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */ + + /* -- Exclusive access */ + ecs_os_thread_id_t exclusive_access; /* If set, world can only be mutated by thread */ + const char *exclusive_thread_name; /* Name of thread with exclusive access (used for debugging) */ - check_table_sanity(table); - - if (row_1 == row_2) { - return; - } + /* -- Time management -- */ + ecs_time_t world_start_time; /* Timestamp of simulation start */ + ecs_time_t frame_start_time; /* Timestamp of frame start */ + ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */ - /* If the table is monitored indicate that there has been a change */ - mark_table_dirty(world, table, 0); + /* -- Metrics -- */ + ecs_world_info_t info; - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - ecs_entity_t e1 = entities[row_1]; - ecs_entity_t e2 = entities[row_2]; + /* -- World flags -- */ + ecs_flags32_t flags; - ecs_record_t **record_ptrs = ecs_vector_first(data->record_ptrs, ecs_record_t*); - ecs_record_t *record_ptr_1 = record_ptrs[row_1]; - ecs_record_t *record_ptr_2 = record_ptrs[row_2]; + /* -- Default query flags -- */ + ecs_flags32_t default_query_flags; - ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); + /* Count that increases when component monitors change */ + int32_t monitor_generation; - /* Keep track of whether entity is watched */ - uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); - uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); + /* -- Allocators -- */ + ecs_world_allocators_t allocators; /* Static allocation sizes */ + ecs_allocator_t allocator; /* Dynamic allocation sizes */ - /* Swap entities & records */ - entities[row_1] = e2; - entities[row_2] = e1; - record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); - record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); - record_ptrs[row_1] = record_ptr_2; - record_ptrs[row_2] = record_ptr_1; + void *ctx; /* Application context */ + void *binding_ctx; /* Binding-specific context */ - swap_switch_columns(table, data, row_1, row_2); - swap_bitset_columns(table, data, row_1, row_2); + ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ - ecs_column_t *columns = data->columns; - if (!columns) { - check_table_sanity(table); - return; - } + ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ +}; - ecs_type_info_t *type_info = table->type_info; +/* Get current stage. */ +ecs_stage_t* flecs_stage_from_world( + ecs_world_t **world_ptr); - /* Swap columns */ - int32_t i, column_count = table->storage_count; - for (i = 0; i < column_count; i ++) { - ecs_type_info_t *ti = &type_info[i]; - int32_t size = ti->size; - int32_t alignment = ti->alignment; +/* Get current thread-specific stage from readonly world. */ +ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world); - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); +/* Get component callbacks. */ +const ecs_type_info_t *flecs_type_info_get( + const ecs_world_t *world, + ecs_entity_t component); - void *ptr = ecs_vector_first_t(columns[i].data, size, alignment); - void *tmp = ecs_os_alloca(size); +/* Get or create component callbacks. */ +ecs_type_info_t* flecs_type_info_ensure( + ecs_world_t *world, + ecs_entity_t component); - void *el_1 = ECS_OFFSET(ptr, size * row_1); - void *el_2 = ECS_OFFSET(ptr, size * row_2); +/* Initialize type info for builtin components. */ +bool flecs_type_info_init_id( + ecs_world_t *world, + ecs_entity_t component, + ecs_size_t size, + ecs_size_t alignment, + const ecs_type_hooks_t *li); - ecs_os_memcpy(tmp, el_1, size); - ecs_os_memcpy(el_1, el_2, size); - ecs_os_memcpy(el_2, tmp, size); - } +#define flecs_type_info_init(world, T, ...)\ + flecs_type_info_init_id(world, ecs_id(T), ECS_SIZEOF(T), ECS_ALIGNOF(T),\ + &(ecs_type_hooks_t)__VA_ARGS__) - check_table_sanity(table); -} +/* Free type info for component id. */ +void flecs_type_info_free( + ecs_world_t *world, + ecs_entity_t component); -static -void merge_vector( - ecs_vector_t **dst_out, - ecs_vector_t *src, - int16_t size, - int16_t alignment) -{ - ecs_vector_t *dst = *dst_out; - int32_t dst_count = ecs_vector_count(dst); +/* Check component monitors (triggers query cache revalidation, not related to + * EcsMonitor). */ +void flecs_eval_component_monitors( + ecs_world_t *world); - if (!dst_count) { - if (dst) { - ecs_vector_free(dst); - } +/* Register component monitor. */ +void flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query); - *dst_out = src; - - /* If the new table is not empty, copy the contents from the - * src into the dst. */ - } else { - int32_t src_count = ecs_vector_count(src); - ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); - - void *dst_ptr = ecs_vector_first_t(dst, size, alignment); - void *src_ptr = ecs_vector_first_t(src, size, alignment); +/* Unregister component monitor. */ +void flecs_monitor_unregister( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query); - dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); - - ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); +/* Update component monitors for added/removed components. */ +void flecs_update_component_monitors( + ecs_world_t *world, + ecs_type_t *added, + ecs_type_t *removed); - ecs_vector_free(src); - *dst_out = dst; - } -} +/* Notify tables with component of event (or all tables if id is 0). */ +void flecs_notify_tables( + ecs_world_t *world, + ecs_id_t id, + ecs_table_event_t *event); -static -void merge_column( - ecs_type_info_t *ti, - int32_t size, - int32_t alignment, - ecs_data_t *data, - int32_t column_id, - ecs_vector_t *src) -{ - ecs_column_t *column = &data->columns[column_id]; - ecs_vector_t *dst = column->data; - int32_t dst_count = ecs_vector_count(dst); +/* Increase table version (used for invalidating ecs_ref_t's). */ +void flecs_increment_table_version( + ecs_world_t *world, + ecs_table_t *table); - if (!dst_count) { - if (dst) { - ecs_vector_free(dst); - } +/* Same as flecs_increment_table_version, but for column version. */ +void flecs_increment_table_column_version( + ecs_world_t *world, + ecs_table_t *table); - column->data = src; - - /* If the new table is not empty, copy the contents from the - * src into the dst. */ - } else { - int32_t src_count = ecs_vector_count(src); - ecs_vector_set_count_t(&dst, size, alignment, dst_count + src_count); - column->data = dst; +/* Get table version. */ +uint32_t flecs_get_table_version_fast( + const ecs_world_t *world, + const uint64_t table_id); - /* Construct new values */ - ctor_component(ti, column, dst_count, src_count); - - void *dst_ptr = ecs_vector_first_t(dst, size, alignment); - void *src_ptr = ecs_vector_first_t(src, size, alignment); +/* Get table version for column pointer validation. */ +uint32_t flecs_get_table_column_version( + const ecs_world_t *world, + const uint64_t table_id); - dst_ptr = ECS_OFFSET(dst_ptr, size * dst_count); - - /* Move values into column */ - ecs_move_t move = ti->lifecycle.move; - if (move) { - move(dst_ptr, src_ptr, src_count, ti); - } else { - ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); - } +/* Throws error when (OnDelete*, Panic) constraint is violated. */ +void flecs_throw_invalid_delete( + ecs_world_t *world, + ecs_id_t id); - ecs_vector_free(src); - } -} +#ifdef FLECS_DEBUG +void flecs_component_lock( + ecs_world_t *world, + ecs_id_t component); -static -void merge_table_data( +void flecs_component_unlock( ecs_world_t *world, - ecs_table_t *dst_table, - ecs_table_t *src_table, - int32_t src_count, - int32_t dst_count, - ecs_data_t *src_data, - ecs_data_t *dst_data) -{ - int32_t i_new = 0, dst_column_count = dst_table->storage_count; - int32_t i_old = 0, src_column_count = src_table->storage_count; - ecs_id_t *dst_ids = dst_table->storage_ids; - ecs_id_t *src_ids = src_table->storage_ids; + ecs_id_t component); + +bool flecs_component_is_trait_locked( + ecs_world_t *world, + ecs_id_t component); - ecs_type_info_t *dst_type_info = dst_table->type_info; - ecs_type_info_t *src_type_info = src_table->type_info; +bool flecs_component_is_delete_locked( + ecs_world_t *world, + ecs_id_t component); - ecs_column_t *src_columns = src_data->columns; - ecs_column_t *dst_columns = dst_data->columns; +#else +#define flecs_component_lock(world, component) (void)world; (void)component +#define flecs_component_unlock(world, component) (void)world; (void)component +#define flecs_component_is_trait_locked(world, component) (false) +#define flecs_component_is_delete_locked(world, component) (false) +#endif - if (!dst_columns && !dst_data->entities) { - dst_columns = dst_data->columns; +/* Convenience macro's for world allocator */ +#define flecs_walloc(world, size)\ + flecs_alloc(&world->allocator, size) +#define flecs_walloc_t(world, T)\ + flecs_alloc_t(&world->allocator, T) +#define flecs_walloc_n(world, T, count)\ + flecs_alloc_n(&world->allocator, T, count) +#define flecs_wcalloc(world, size)\ + flecs_calloc(&world->allocator, size) +#define flecs_wfree_t(world, T, ptr)\ + flecs_free_t(&world->allocator, T, ptr) +#define flecs_wcalloc_n(world, T, count)\ + flecs_calloc_n(&world->allocator, T, count) +#define flecs_wfree(world, size, ptr)\ + flecs_free(&world->allocator, size, ptr) +#define flecs_wfree_n(world, T, count, ptr)\ + flecs_free_n(&world->allocator, T, count, ptr) +#define flecs_wrealloc(world, size_dst, size_src, ptr)\ + flecs_realloc(&world->allocator, size_dst, size_src, ptr) +#define flecs_wrealloc_n(world, T, count_dst, count_src, ptr)\ + flecs_realloc_n(&world->allocator, T, count_dst, count_src, ptr) +#define flecs_wdup(world, size, ptr)\ + flecs_dup(&world->allocator, size, ptr) +#define flecs_wdup_n(world, T, count, ptr)\ + flecs_dup_n(&world->allocator, T, count, ptr) + +/* Convenience macro that iterates over all component records in the world */ +#define FLECS_EACH_COMPONENT_RECORD(cr, ...)\ + for (int32_t _i = 0; _i < FLECS_HI_ID_RECORD_ID; _i++) {\ + ecs_component_record_t *cr = world->id_index_lo[_i];\ + if (cr) {\ + __VA_ARGS__\ + }\ + }\ + {\ + ecs_map_iter_t _it = ecs_map_iter(&world->id_index_hi);\ + while (ecs_map_next(&_it)) {\ + ecs_component_record_t *cr = ecs_map_ptr(&_it);\ + __VA_ARGS__\ + }\ } - - ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); - if (!src_count) { - return; +/* Convenience macro that iterates over all queries in the world */ +#define FLECS_EACH_QUERY(query, ...)\ + {\ + ecs_iter_t _it = ecs_each_pair(world, ecs_id(EcsPoly), EcsQuery);\ + while (ecs_each_next(&_it)) {\ + EcsPoly *queries = ecs_field(&_it, EcsPoly, 0);\ + for (int32_t _i = 0; _i < _it.count; _i++) {\ + ecs_query_t *query = queries[_i].poly;\ + if (!query) {\ + continue;\ + }\ + flecs_poly_assert(query, ecs_query_t);\ + ecs_assert(query->entity != 0, ECS_INTERNAL_ERROR, NULL);\ + __VA_ARGS__\ + }\ + }\ } - /* Merge entities */ - merge_vector(&dst_data->entities, src_data->entities, ECS_SIZEOF(ecs_entity_t), - ECS_ALIGNOF(ecs_entity_t)); - src_data->entities = NULL; - ecs_assert(ecs_vector_count(dst_data->entities) == src_count + dst_count, - ECS_INTERNAL_ERROR, NULL); +#endif - /* Merge entity index record pointers */ - merge_vector(&dst_data->record_ptrs, src_data->record_ptrs, - ECS_SIZEOF(ecs_record_t*), ECS_ALIGNOF(ecs_record_t*)); - src_data->record_ptrs = NULL; +/** + * @file addons/journal.h + * @brief Journaling addon that logs API functions. + * + * The journaling addon traces API calls. The trace is formatted as runnable + * C code, which allows for (partially) reproducing the behavior of an app + * with the journaling trace. + * + * The journaling addon is disabled by default. Enabling it can have a + * significant impact on performance. + */ - for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { - ecs_id_t dst_id = dst_ids[i_new]; - ecs_id_t src_id = src_ids[i_old]; - ecs_type_info_t *dst_ti = &dst_type_info[i_new]; - int32_t size = dst_ti->size; - int32_t alignment = dst_ti->alignment; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); +#ifdef FLECS_JOURNAL - if (dst_id == src_id) { - merge_column(dst_ti, size, alignment, dst_data, - i_new, src_columns[i_old].data); - src_columns[i_old].data = NULL; +#ifndef FLECS_LOG +#define FLECS_LOG +#endif - /* Mark component column as dirty */ - mark_table_dirty(world, dst_table, i_new + 1); - - i_new ++; - i_old ++; - } else if (dst_id < src_id) { - /* New column does not occur in old table, make sure vector is large - * enough. */ - ecs_column_t *column = &dst_columns[i_new]; - ecs_vector_set_count_t(&column->data, size, alignment, - src_count + dst_count); - - /* Construct new values */ - ctor_component(dst_ti, column, 0, src_count + dst_count); - - i_new ++; - } else if (dst_id > src_id) { - ecs_column_t *column = &src_columns[i_old]; - - /* Destruct old values */ - dtor_component(&src_type_info[i_old], column, 0, src_count); +#ifndef FLECS_JOURNAL_H +#define FLECS_JOURNAL_H - /* Old column does not occur in new table, remove */ - ecs_vector_free(column->data); - column->data = NULL; +/** + * @defgroup c_addons_journal Journal + * @ingroup c_addons + * Journaling addon (disabled by default). + * + * + * @{ + */ - i_old ++; - } - } +/* Trace when log level is at or higher than level */ +#define FLECS_JOURNAL_LOG_LEVEL (0) - move_switch_columns(dst_table, dst_data, dst_count, src_table, src_data, 0, - src_count, true); - move_bitset_columns(dst_table, dst_data, dst_count, src_table, src_data, 0, - src_count, true); +#ifdef __cplusplus +extern "C" { +#endif - /* Initialize remaining columns */ - for (; i_new < dst_column_count; i_new ++) { - ecs_column_t *column = &dst_columns[i_new]; - ecs_type_info_t *ti = &dst_type_info[i_new]; - int32_t size = ti->size; - int32_t alignment = ti->alignment; - - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - ecs_vector_set_count_t(&column->data, size, alignment, - src_count + dst_count); +/* Journaling API, meant to be used by internals. */ - /* Construct new values */ - ctor_component(ti, column, 0, src_count + dst_count); - } +typedef enum ecs_journal_kind_t { + EcsJournalNew, + EcsJournalMove, + EcsJournalClear, + EcsJournalDelete, + EcsJournalSetParent, + EcsJournalDeleteWith, + EcsJournalRemoveAll, + EcsJournalTableEvents +} ecs_journal_kind_t; - /* Destroy remaining columns */ - for (; i_old < src_column_count; i_old ++) { - ecs_column_t *column = &src_columns[i_old]; +FLECS_DBG_API +void flecs_journal_begin( + ecs_world_t *world, + ecs_journal_kind_t kind, + ecs_entity_t entity, + ecs_type_t *add, + ecs_type_t *remove); - /* Destruct old values */ - dtor_component(&src_type_info[i_old], column, 0, src_count); +FLECS_DBG_API +void flecs_journal_end(void); - /* Old column does not occur in new table, remove */ - ecs_vector_free(column->data); - column->data = NULL; - } +#define flecs_journal(...)\ + flecs_journal_begin(__VA_ARGS__);\ + flecs_journal_end(); - /* Mark entity column as dirty */ - mark_table_dirty(world, dst_table, 0); -} - -int32_t ecs_table_count( - const ecs_table_t *table) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return flecs_table_data_count(&table->storage); +#ifdef __cplusplus } +#endif // __cplusplus +/** @} */ +#endif // FLECS_JOURNAL_H +#else +#define flecs_journal_begin(...) +#define flecs_journal_end(...) +#define flecs_journal(...) -void flecs_table_merge( - ecs_world_t *world, - ecs_table_t *dst_table, - ecs_table_t *src_table, - ecs_data_t *dst_data, - ecs_data_t *src_data) -{ - ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!src_table->lock, ECS_LOCKED_STORAGE, NULL); +#endif // FLECS_JOURNAL - check_table_sanity(dst_table); - check_table_sanity(src_table); - - bool move_data = false; - - /* If there is nothing to merge to, just clear the old table */ - if (!dst_table) { - flecs_table_clear_data(world, src_table, src_data); - check_table_sanity(src_table); - return; - } else { - ecs_assert(!dst_table->lock, ECS_LOCKED_STORAGE, NULL); - } - /* If there is no data to merge, drop out */ - if (!src_data) { - return; - } +/* Used in id records to keep track of entities used with id flags */ +extern const ecs_entity_t EcsFlag; - if (!dst_data) { - dst_data = &dst_table->storage; - if (dst_table == src_table) { - move_data = true; - } - } +//////////////////////////////////////////////////////////////////////////////// +//// Bootstrap API +//////////////////////////////////////////////////////////////////////////////// - ecs_entity_t *src_entities = ecs_vector_first(src_data->entities, ecs_entity_t); - int32_t src_count = ecs_vector_count(src_data->entities); - int32_t dst_count = ecs_vector_count(dst_data->entities); +/* Bootstrap world */ +void flecs_bootstrap( + ecs_world_t *world); - ecs_record_t **src_records = ecs_vector_first( - src_data->record_ptrs, ecs_record_t*); +#define flecs_bootstrap_component(world, id_)\ + ecs_component_init(world, &(ecs_component_desc_t){\ + .entity = ecs_entity(world, { .id = ecs_id(id_), .name = #id_, .symbol = #id_ }),\ + .type.size = sizeof(id_),\ + .type.alignment = ECS_ALIGNOF(id_)\ + }); - /* First, update entity index so old entities point to new type */ - int32_t i; - for(i = 0; i < src_count; i ++) { - ecs_record_t *record; - if (dst_table != src_table) { - record = src_records[i]; - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - } else { - record = ecs_eis_ensure(world, src_entities[i]); - } +#define flecs_bootstrap_tag(world, name)\ + ecs_make_alive(world, name);\ + ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ + ecs_set_name(world, name, (const char*)&#name[ecs_os_strlen(world->info.name_prefix)]); - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); - record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); - record->table = dst_table; - } +#define flecs_bootstrap_trait(world, name)\ + flecs_bootstrap_tag(world, name)\ + ecs_add_id(world, name, EcsTrait) - /* Merge table columns */ - if (move_data) { - *dst_data = *src_data; - } else { - merge_table_data(world, dst_table, src_table, src_count, dst_count, - src_data, dst_data); - } - dst_table->alloc_count ++; +//////////////////////////////////////////////////////////////////////////////// +//// Safe(r) integer casting +//////////////////////////////////////////////////////////////////////////////// - if (src_count) { - if (!dst_count) { - flecs_table_set_empty(world, dst_table); - } - flecs_table_set_empty(world, src_table); - } +#define FLECS_CONVERSION_ERR(T, value)\ + "illegal conversion from value " #value " to type " #T - check_table_sanity(src_table); - check_table_sanity(dst_table); -} +#define flecs_signed_char__ (CHAR_MIN < 0) +#define flecs_signed_short__ true +#define flecs_signed_int__ true +#define flecs_signed_long__ true +#define flecs_signed_size_t__ false +#define flecs_signed_int8_t__ true +#define flecs_signed_int16_t__ true +#define flecs_signed_int32_t__ true +#define flecs_signed_int64_t__ true +#define flecs_signed_intptr_t__ true +#define flecs_signed_uint8_t__ false +#define flecs_signed_uint16_t__ false +#define flecs_signed_uint32_t__ false +#define flecs_signed_uint64_t__ false +#define flecs_signed_uintptr_t__ false +#define flecs_signed_ecs_size_t__ true +#define flecs_signed_ecs_entity_t__ false -void flecs_table_replace_data( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data) -{ - int32_t prev_count = 0; - ecs_data_t *table_data = &table->storage; - ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); +uint64_t flecs_ito_( + size_t dst_size, + bool dst_signed, + bool lt_zero, + uint64_t value, + const char *err); - check_table_sanity(table); +#ifndef FLECS_NDEBUG +#define flecs_ito(T, value)\ + (T)flecs_ito_(\ + sizeof(T),\ + flecs_signed_##T##__,\ + (value) < 0,\ + (uint64_t)(value),\ + FLECS_CONVERSION_ERR(T, (value))) - prev_count = ecs_vector_count(table_data->entities); - run_on_remove(world, table, table_data); - flecs_table_clear_data(world, table, table_data); +#define flecs_uto(T, value)\ + (T)flecs_ito_(\ + sizeof(T),\ + flecs_signed_##T##__,\ + false,\ + (uint64_t)(value),\ + FLECS_CONVERSION_ERR(T, (value))) +#else +#define flecs_ito(T, value) (T)(value) +#define flecs_uto(T, value) (T)(value) +#endif - if (data) { - table->storage = *data; - } else { - flecs_table_init_data(world, table); - } +#define flecs_itosize(value) flecs_ito(size_t, (value)) +#define flecs_utosize(value) flecs_uto(ecs_size_t, (value)) +#define flecs_itoi16(value) flecs_ito(int16_t, (value)) +#define flecs_itoi32(value) flecs_ito(int32_t, (value)) - int32_t count = ecs_table_count(table); - if (!prev_count && count) { - flecs_table_set_empty(world, table); - } else if (prev_count && !count) { - flecs_table_set_empty(world, table); - } +//////////////////////////////////////////////////////////////////////////////// +//// Utilities +//////////////////////////////////////////////////////////////////////////////// - table->alloc_count ++; +/* Check if component is valid, return reason if it's not */ +const char* flecs_id_invalid_reason( + const ecs_world_t *world, + ecs_id_t id); - check_table_sanity(table); -} +/* Generate 64bit hash from buffer. */ +uint64_t flecs_hash( + const void *data, + ecs_size_t length); -int32_t* flecs_table_get_dirty_state( - ecs_table_t *table) -{ - if (!table->dirty_state) { - int32_t column_count = table->storage_count; - table->dirty_state = ecs_os_malloc_n( int32_t, column_count + 1); - ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - - for (int i = 0; i < column_count + 1; i ++) { - table->dirty_state[i] = 1; - } - } - return table->dirty_state; -} +/* Get next power of 2 */ +int32_t flecs_next_pow_of_2( + int32_t n); -int32_t* flecs_table_get_monitor( - ecs_table_t *table) -{ - int32_t *dirty_state = flecs_table_get_dirty_state(table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); +/* Compare function for entity ids used for order_by */ +int flecs_entity_compare( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2); - int32_t column_count = table->storage_count; - return ecs_os_memdup(dirty_state, (column_count + 1) * ECS_SIZEOF(int32_t)); -} +/* Compare function for component ids used for qsort */ +int flecs_id_qsort_cmp( + const void *a, + const void *b); -void flecs_table_notify( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_event_t *event) -{ - if (world->is_fini) { - return; - } +/* Load file contents into string */ +char* flecs_load_from_file( + const char *filename); - switch(event->kind) { - case EcsTableTriggersForId: - notify_trigger(world, table, event->event); - break; - case EcsTableNoTriggersForId: - break; - } -} +/* Test whether entity name is an entity id (starts with a #). */ +bool flecs_name_is_id( + const char *name); -void ecs_table_lock( - ecs_world_t *world, - ecs_table_t *table) -{ - if (table) { - if (ecs_poly_is(world, ecs_world_t) && !world->is_readonly) { - table->lock ++; - } - } -} +/* Convert entity name to entity id. */ +ecs_entity_t flecs_name_to_id( + const char *name); -void ecs_table_unlock( - ecs_world_t *world, - ecs_table_t *table) -{ - if (table) { - if (ecs_poly_is(world, ecs_world_t) && !world->is_readonly) { - table->lock --; - ecs_assert(table->lock >= 0, ECS_INVALID_OPERATION, NULL); - } - } -} +/* Convert floating point to string */ +char * ecs_ftoa( + double f, + char * buf, + int precision); -bool ecs_table_has_module( - ecs_table_t *table) -{ - return table->flags & EcsTableHasModule; -} +/* Replace #[color] tokens with terminal color symbols. */ +void flecs_colorize_buf( + char *msg, + bool enable_colors, + ecs_strbuf_t *buf); -ecs_column_t* ecs_table_column_for_id( +/* Check whether id can be inherited. */ +bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, - ecs_id_t id) -{ - ecs_table_t *storage_table = table->storage_table; - if (!storage_table) { - return NULL; - } - - ecs_table_record_t *tr = flecs_get_table_record(world, storage_table, id); - if (tr) { - return &table->storage.columns[tr->column]; - } + const ecs_component_record_t *cr, + ecs_id_t id); - return NULL; -} +/* Cleanup type info data. */ +void flecs_fini_type_info( + ecs_world_t *world); -ecs_type_t ecs_table_get_type( - const ecs_table_t *table) -{ - if (table) { - return table->type; - } else { - return NULL; - } -} +const ecs_type_info_t* flecs_determine_type_info_for_component( + const ecs_world_t *world, + ecs_id_t component); -ecs_table_t* ecs_table_get_storage_table( - const ecs_table_t *table) -{ - return table->storage_table; -} +ecs_size_t flecs_type_size( + ecs_world_t *world, + ecs_entity_t type); + +/* Utility for using allocated strings in assert/error messages */ +const char* flecs_errstr( + char *str); +const char* flecs_errstr_1( + char *str); +const char* flecs_errstr_2( + char *str); +const char* flecs_errstr_3( + char *str); +const char* flecs_errstr_4( + char *str); +const char* flecs_errstr_5( + char *str); -int32_t ecs_table_type_to_storage_index( - const ecs_table_t *table, - int32_t index) -{ - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(index < ecs_vector_count(table->type), - ECS_INVALID_PARAMETER, NULL); - int32_t *storage_map = table->storage_map; - if (storage_map) { - return storage_map[index]; - } -error: - return -1; -} +#endif -int32_t ecs_table_storage_to_type_index( - const ecs_table_t *table, - int32_t index) -{ - ecs_check(index < table->storage_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t offset = ecs_vector_count(table->type); - return table->storage_map[offset + index]; -error: - return -1; -} -ecs_record_t* ecs_record_find( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); +/* -- Identifier Component -- */ +static ECS_DTOR(EcsIdentifier, ptr, { + ecs_os_strset(&ptr->value, NULL); +}) - world = ecs_get_world(world); +static ECS_COPY(EcsIdentifier, dst, src, { + ecs_os_strset(&dst->value, src->value); + dst->hash = src->hash; + dst->length = src->length; + dst->index_hash = src->index_hash; + dst->index = src->index; +}) - ecs_record_t *r = ecs_eis_get(world, entity); - if (r) { - return r; - } -error: - return NULL; -} +static ECS_MOVE(EcsIdentifier, dst, src, { + ecs_os_strset(&dst->value, NULL); + dst->value = src->value; + dst->hash = src->hash; + dst->length = src->length; + dst->index_hash = src->index_hash; + dst->index = src->index; -void* ecs_record_get_column( - ecs_record_t *r, - int32_t column, - size_t c_size) -{ - (void)c_size; - ecs_table_t *table = r->table; + src->value = NULL; + src->hash = 0; + src->index_hash = 0; + src->index = 0; + src->length = 0; +}) - ecs_check(column < table->storage_count, ECS_INVALID_PARAMETER, NULL); - ecs_type_info_t *ti = &table->type_info[column]; - ecs_column_t *c = &table->storage.columns[column]; - ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == ti->size, - ECS_INVALID_PARAMETER, NULL); +/* -- Poly component -- */ - return ecs_vector_get_t(c->data, ti->size, ti->alignment, - ECS_RECORD_TO_ROW(r->row)); -error: - return NULL; -} +static ECS_COPY(EcsPoly, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied"); +}) -#include +static ECS_MOVE(EcsPoly, dst, src, { + if (dst->poly && (dst->poly != src->poly)) { + flecs_poly_dtor_t *dtor = flecs_get_dtor(dst->poly); + ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); + dtor[0](dst->poly); + } -static const char* mixin_kind_str[] = { - [EcsMixinBase] = "base (should never be requested by application)", - [EcsMixinWorld] = "world", - [EcsMixinObservable] = "observable", - [EcsMixinIterable] = "iterable", - [EcsMixinMax] = "max (should never be requested by application)" -}; + dst->poly = src->poly; + src->poly = NULL; +}) -ecs_mixins_t ecs_world_t_mixins = { - .type_name = "ecs_world_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_world_t, self), - [EcsMixinObservable] = offsetof(ecs_world_t, observable), - [EcsMixinIterable] = offsetof(ecs_world_t, iterable) +static ECS_DTOR(EcsPoly, ptr, { + if (ptr->poly) { + flecs_poly_dtor_t *dtor = flecs_get_dtor(ptr->poly); + ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); + dtor[0](ptr->poly); } -}; +}) -ecs_mixins_t ecs_stage_t_mixins = { - .type_name = "ecs_stage_t", - .elems = { - [EcsMixinBase] = offsetof(ecs_stage_t, world), - [EcsMixinWorld] = offsetof(ecs_stage_t, world) + +/* -- Builtin triggers -- */ + +static +void flecs_assert_relation_unused( + ecs_world_t *world, + ecs_entity_t rel, + ecs_entity_t trait) +{ + if (world->flags & (EcsWorldInit|EcsWorldFini)) { + return; } -}; -ecs_mixins_t ecs_query_t_mixins = { - .type_name = "ecs_query_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_query_t, world), - [EcsMixinIterable] = offsetof(ecs_query_t, iterable) + ecs_vec_t *marked_ids = &world->store.marked_ids; + int32_t i, count = ecs_vec_count(marked_ids); + for (i = 0; i < count; i ++) { + ecs_marked_id_t *mid = ecs_vec_get_t(marked_ids, ecs_marked_id_t, i); + if (mid->id == ecs_pair(rel, EcsWildcard)) { + /* If id is being cleaned up, no need to throw error as tables will + * be cleaned up */ + return; + } } -}; -ecs_mixins_t ecs_filter_t_mixins = { - .type_name = "ecs_filter_t", - .elems = { - [EcsMixinIterable] = offsetof(ecs_filter_t, iterable) + bool in_use = ecs_id_in_use(world, ecs_pair(rel, EcsWildcard)); + in_use |= ecs_id_in_use(world, rel); + + if (in_use) { + char *r_str = ecs_get_path(world, rel); + char *p_str = ecs_get_path(world, trait); + + ecs_throw(ECS_INVALID_OPERATION, + "cannot change trait '%s' for '%s': component is already in use", + p_str, r_str); + + ecs_os_free(r_str); + ecs_os_free(p_str); } -}; + +error: + return; +} static -void* get_mixin( - const ecs_poly_t *poly, - ecs_mixin_kind_t kind) +bool flecs_set_id_flag( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_flags32_t flag, + ecs_entity_t trait) { - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); - - const ecs_header_t *hdr = poly; - ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); + (void)trait; - const ecs_mixins_t *mixins = hdr->mixins; - if (!mixins) { - /* Object has no mixins */ - goto not_found; - } + if (!(cr->flags & flag)) { + cr->flags |= flag; + if (flag == EcsIdSparse) { + flecs_component_init_sparse(world, cr); + } - ecs_size_t offset = mixins->elems[kind]; - if (offset == 0) { - /* Object has mixins but not the requested one. Try to find the mixin - * in the poly's base */ - goto find_in_base; + if (flag == EcsIdDontFragment) { + flecs_component_record_init_dont_fragment(world, cr); + } + + if (flag == EcsIdExclusive) { + flecs_component_record_init_exclusive(world, cr); + } + + return true; } - /* Object has mixin, return its address */ - return ECS_OFFSET(hdr, offset); + return false; +} -find_in_base: - if (offset) { - /* If the poly has a base, try to find the mixin in the base */ - ecs_poly_t *base = *(ecs_poly_t**)ECS_OFFSET(hdr, offset); - if (base) { - return get_mixin(base, kind); - } +static +bool flecs_unset_id_flag( + ecs_component_record_t *cr, + ecs_flags32_t flag) +{ + if (cr->flags & EcsIdMarkedForDelete) { + /* Don't change flags for record that's about to be deleted */ + return false; } - -not_found: - /* Mixin wasn't found for poly */ - return NULL; + + if ((cr->flags & flag)) { + cr->flags &= ~flag; + return true; + } + return false; } +typedef struct ecs_on_trait_ctx_t { + ecs_flags32_t flag, not_flag; +} ecs_on_trait_ctx_t; + static -void* assert_mixin( - const ecs_poly_t *poly, - ecs_mixin_kind_t kind) +bool flecs_trait_can_add_after_query( + ecs_entity_t trait) { - void *ptr = get_mixin(poly, kind); - if (!ptr) { - const ecs_header_t *header = poly; - const ecs_mixins_t *mixins = header->mixins; - ecs_err("%s not available for type %s", - mixin_kind_str[kind], - mixins ? mixins->type_name : "unknown"); - ecs_os_abort(); + if (trait == EcsWith) { + return true; } - return ptr; + return false; } -void* _ecs_poly_init( - ecs_poly_t *poly, - int32_t type, - ecs_size_t size, - ecs_mixins_t *mixins) +static +void flecs_register_flag_for_trait( + ecs_iter_t *it, + ecs_entity_t trait, + ecs_flags32_t flag, + ecs_flags32_t not_flag, + ecs_flags32_t entity_flag) { - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_world_t *world = it->world; + ecs_entity_t event = it->event; - ecs_header_t *hdr = poly; - ecs_os_memset(poly, 0, size); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + bool changed = false; - hdr->magic = ECS_OBJECT_MAGIC; - hdr->type = type; - hdr->mixins = mixins; + if (event == EcsOnAdd) { + if (flag == EcsIdOnInstantiateInherit) { + if (e < FLECS_HI_COMPONENT_ID) { + world->non_trivial_lookup[e] |= EcsNonTrivialIdInherit; + } + } - return poly; -} + if (!(world->flags & EcsWorldInit) && !flecs_trait_can_add_after_query(trait)) { + ecs_check(!flecs_component_is_trait_locked(world, e), ECS_INVALID_OPERATION, + "cannot set '%s' trait for component '%s' because it is already" + " queried for (apply traits before creating queries)", + flecs_errstr(ecs_get_path(world, trait)), + flecs_errstr_1(ecs_id_str(world, e))); + } -void _ecs_poly_fini( - ecs_poly_t *poly, - int32_t type) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - (void)type; + ecs_component_record_t *cr = flecs_components_get(world, e); + if (cr) { + changed |= flecs_set_id_flag(world, cr, flag, trait); + } - ecs_header_t *hdr = poly; + cr = flecs_components_get(world, ecs_pair(e, EcsWildcard)); + if (cr) { + do { + changed |= flecs_set_id_flag(world, cr, flag, trait); + } while ((cr = flecs_component_first_next(cr))); + } - /* Don't deinit poly that wasn't initialized */ - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); - ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, NULL); - hdr->magic = 0; -} + if (entity_flag) flecs_add_flag(world, e, entity_flag); + } else if (event == EcsOnRemove) { + ecs_component_record_t *cr = flecs_components_get(world, e); + + if (cr) changed |= flecs_unset_id_flag(cr, not_flag); + cr = flecs_components_get(world, ecs_pair(e, EcsWildcard)); + if (cr) { + do { + changed |= flecs_unset_id_flag(cr, not_flag); + } while ((cr = flecs_component_first_next(cr))); + } + } -#define assert_object(cond, file, line)\ - _ecs_assert((cond), ECS_INVALID_PARAMETER, #cond, file, line, NULL);\ - assert(cond) + if (changed) { + flecs_assert_relation_unused(world, e, trait); + } + } +error: + return; +} -#ifndef FLECS_NDEBUG -void _ecs_poly_assert( - const ecs_poly_t *poly, - int32_t type, - const char *file, - int32_t line) -{ - assert_object(poly != NULL, file, line); +static +void flecs_register_final(ecs_iter_t *it) { + ecs_world_t *world = it->world; - const ecs_header_t *hdr = poly; - assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line); - assert_object(hdr->type == type, file, line); -} -#endif + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + if (flecs_components_get(world, ecs_pair(EcsIsA, e)) != NULL) { + ecs_throw(ECS_INVALID_OPERATION, + "cannot change trait 'Final' for '%s': already inherited from", + flecs_errstr(ecs_get_path(world, e))); + } -bool _ecs_poly_is( - const ecs_poly_t *poly, - int32_t type) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!flecs_component_is_trait_locked(world, e), ECS_INVALID_OPERATION, "cannot change " + "trait 'Final' for '%s': already queried for (apply traits " + "before creating queries)", + flecs_errstr(ecs_get_path(world, e))); - const ecs_header_t *hdr = poly; - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); - return hdr->type == type; + error: + continue; + } } -ecs_iterable_t* ecs_get_iterable( - const ecs_poly_t *poly) -{ - return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); +static +void flecs_register_tag(ecs_iter_t *it) { + flecs_register_flag_for_trait(it, EcsPairIsTag, EcsIdPairIsTag, EcsIdPairIsTag, 0); + + /* Ensure that all id records for tag have type info set to NULL */ + ecs_world_t *world = it->real_world; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (it->event == EcsOnAdd) { + ecs_component_record_t *cr = flecs_components_get(world, + ecs_pair(e, EcsWildcard)); + if (cr) { + do { + if (cr->type_info != NULL) { + flecs_assert_relation_unused(world, e, EcsPairIsTag); + } + cr->type_info = NULL; + } while ((cr = flecs_component_first_next(cr))); + } + } + } } -ecs_observable_t* ecs_get_observable( - const ecs_poly_t *poly) -{ - return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); +static +void flecs_register_on_delete(ecs_iter_t *it) { + ecs_id_t id = ecs_field_id(it, 0); + flecs_register_flag_for_trait(it, EcsOnDelete, + ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), + EcsIdOnDeleteMask, + EcsEntityIsId); } -const ecs_world_t* ecs_get_world( - const ecs_poly_t *poly) -{ - return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); +static +void flecs_register_on_delete_object(ecs_iter_t *it) { + ecs_id_t id = ecs_field_id(it, 0); + flecs_register_flag_for_trait(it, EcsOnDeleteTarget, + ECS_ID_ON_DELETE_TARGET_FLAG(ECS_PAIR_SECOND(id)), + EcsIdOnDeleteTargetMask, + EcsEntityIsId); } +static +void flecs_register_on_instantiate(ecs_iter_t *it) { + ecs_id_t id = ecs_field_id(it, 0); + flecs_register_flag_for_trait(it, EcsOnInstantiate, + ECS_ID_ON_INSTANTIATE_FLAG(ECS_PAIR_SECOND(id)), + 0, 0); +} -#include +static +void flecs_register_trait(ecs_iter_t *it) { + ecs_on_trait_ctx_t *ctx = it->ctx; + flecs_register_flag_for_trait( + it, it->ids[0], ctx->flag, ctx->not_flag, 0); +} static -void flecs_notify_on_add( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - ecs_data_t *data, - int32_t row, - int32_t count, - ecs_table_diff_t *diff, - bool run_on_set); +void flecs_register_trait_pair(ecs_iter_t *it) { + ecs_on_trait_ctx_t *ctx = it->ctx; + flecs_register_flag_for_trait( + it, ecs_pair_first(it->world, it->ids[0]), ctx->flag, ctx->not_flag, 0); +} static -const ecs_entity_t* new_w_data( - ecs_world_t *world, - ecs_table_t *table, - const ecs_entity_t *entities, - ecs_ids_t *component_ids, - int32_t count, - void **c_info, - bool move, - int32_t *row_out, - ecs_table_diff_t *diff); +void flecs_register_slot_of(ecs_iter_t *it) { + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_add_id(it->world, it->entities[i], EcsDontFragment); + ecs_add_id(it->world, it->entities[i], EcsExclusive); + } +} static -void* get_component_w_index( - ecs_table_t *table, - int32_t column_index, - int32_t row) -{ - ecs_check(column_index < table->storage_count, ECS_NOT_A_COMPONENT, NULL); +void flecs_on_symmetric_add_remove(ecs_iter_t *it) { + ecs_entity_t pair = ecs_field_id(it, 0); - ecs_type_info_t *ti = &table->type_info[column_index]; - ecs_column_t *column = &table->storage.columns[column_index]; + if (!ECS_HAS_ID_FLAG(pair, PAIR)) { + /* If relationship was not added as a pair, there's nothing to do */ + return; + } - /* If size is 0, component does not have a value. This is likely caused by - * an application trying to call ecs_get with a tag. */ - int32_t size = ti->size; - ecs_check(size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_world_t *world = it->world; + ecs_entity_t rel = ECS_PAIR_FIRST(pair); + ecs_entity_t tgt = ecs_pair_second(world, pair); + ecs_entity_t event = it->event; - void *ptr = ecs_vector_first_t(column->data, size, ti->alignment); - return ECS_OFFSET(ptr, size * row); -error: - return NULL; + + if (tgt) { + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t subj = it->entities[i]; + if (event == EcsOnAdd) { + if (!ecs_has_id(it->real_world, tgt, ecs_pair(rel, subj))) { + ecs_add_pair(it->world, tgt, rel, subj); + } + } else { + if (ecs_has_id(it->real_world, tgt, ecs_pair(rel, subj))) { + ecs_remove_pair(it->world, tgt, rel, subj); + } + } + } + } } static -void* get_component( - const ecs_world_t *world, - ecs_table_t *table, - int32_t row, - ecs_id_t id) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - - if (!table->storage_table) { - ecs_check(ecs_search(world, table, id, 0) == -1, - ECS_NOT_A_COMPONENT, NULL); - return NULL; - } +void flecs_register_symmetric(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; - ecs_table_record_t *tr = flecs_get_table_record( - world, table->storage_table, id); - if (!tr) { - ecs_check(ecs_search(world, table, id, 0) == -1, - ECS_NOT_A_COMPONENT, NULL); - return NULL; - } + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t r = it->entities[i]; + flecs_assert_relation_unused(world, r, EcsSymmetric); - return get_component_w_index(table, tr->column, row); -error: - return NULL; + /* Create observer that adds the reverse relationship when R(X, Y) is + * added, or remove the reverse relationship when R(X, Y) is removed. */ + ecs_observer(world, { + .entity = ecs_entity(world, { .parent = r }), + .query.terms[0] = { .id = ecs_pair(r, EcsWildcard) }, + .callback = flecs_on_symmetric_add_remove, + .events = {EcsOnAdd, EcsOnRemove} + }); + } } +#ifdef FLECS_DEBUG static -void* get_base_component( - const ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_id_record_t *table_index, - int32_t recur_depth) -{ - /* Cycle detected in IsA relation */ - ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); - - /* Table (and thus entity) does not have component, look for base */ - if (!(table->flags & EcsTableHasIsA)) { - return NULL; - } +void flecs_on_singleton_add_remove(ecs_iter_t *it) { + ecs_entity_t component = ecs_field_id(it, 0); + ecs_world_t *world = it->real_world; - /* Exclude Name */ - if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { - return NULL; - } + (void)world; - /* Table should always be in the table index for (IsA, *), otherwise the - * HasBase flag should not have been set */ - const ecs_table_record_t *tr_isa = flecs_id_record_table( - world->idr_isa_wildcard, table); - ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; - ecs_type_t type = table->type; - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i = tr_isa->column, end = tr_isa->count + tr_isa->column; - void *ptr = NULL; + if (ECS_IS_PAIR(component)) { + ecs_entity_t relationship = ECS_PAIR_FIRST(component); + e = (uint32_t)e; + ecs_check(relationship == e, ECS_CONSTRAINT_VIOLATED, + "cannot add singleton pair '%s' to entity '%s': singleton " + "component '%s' must be added to itself", + flecs_errstr(ecs_id_str(world, component)), + flecs_errstr_1(ecs_get_path(world, it->entities[i])), + flecs_errstr_2(ecs_get_path(it->world, relationship))); + (void)relationship; + } else { + ecs_check(component == e, ECS_CONSTRAINT_VIOLATED, + "cannot add singleton component '%s' to entity '%s': singleton" + " component must be added to itself", + flecs_errstr(ecs_get_path(it->world, component)), + flecs_errstr_1(ecs_get_path(it->world, it->entities[i]))); + } - do { - ecs_id_t pair = ids[i ++]; - ecs_entity_t base = ecs_pair_second(world, pair); + error: + continue; + } +} +#endif - ecs_record_t *r = ecs_eis_get(world, base); - if (!r) { - continue; - } +static +void flecs_register_singleton(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; - table = r->table; - if (!table) { - continue; - } + (void)world; - const ecs_table_record_t *tr = NULL; + flecs_register_flag_for_trait(it, EcsSingleton, EcsIdSingleton, 0, 0); - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - tr = flecs_id_record_table(table_index, storage_table); - } else { - ecs_check(!ecs_owns_id(world, base, id), - ECS_NOT_A_COMPONENT, NULL); - } + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t component = it->entities[i]; + (void)component; - if (!tr) { - ptr = get_base_component(world, table, id, table_index, - recur_depth + 1); - } else { - int32_t row = ECS_RECORD_TO_ROW(r->row); - ptr = get_component_w_index(table, tr->column, row); - } - } while (!ptr && (i < end)); + /* Create observer that enforces that singleton is only added to self */ +#ifdef FLECS_DEBUG + ecs_observer(world, { + .entity = ecs_entity(world, { .parent = component }), + .query.terms[0] = { + .id = component, .src.id = EcsThis|EcsSelf + }, + .callback = flecs_on_singleton_add_remove, + .events = {EcsOnAdd} + }); - return ptr; -error: - return NULL; + ecs_observer(world, { + .entity = ecs_entity(world, { .parent = component }), + .query.terms[0] = { + .id = ecs_pair(component, EcsWildcard), .src.id = EcsThis|EcsSelf + }, + .callback = flecs_on_singleton_add_remove, + .events = {EcsOnAdd} + }); +#endif + } } -/* Utility to compute actual row from row in record */ static -void set_row_info( - ecs_entity_info_t *info, - uint32_t row) -{ - info->row = ECS_RECORD_TO_ROW(row); - info->row_flags = ECS_RECORD_TO_ROW_FLAGS(row); -} +void flecs_on_component(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsComponent *c = ecs_field(it, EcsComponent, 0); -/* Utility to set info from main stage record */ -static -void set_info_from_record( - ecs_entity_info_t *info, - ecs_record_t *record) -{ - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; - info->record = record; + uint32_t component_id = (uint32_t)e; /* Strip generation */ + ecs_assert(component_id < ECS_MAX_COMPONENT_ID, ECS_OUT_OF_RANGE, + "component id must be smaller than %u", ECS_MAX_COMPONENT_ID); + (void)component_id; - ecs_table_t *table = record->table; + if (it->event != EcsOnRemove) { + ecs_entity_t parent = ecs_get_parent(world, e); + if (parent) { + ecs_record_t *parent_record = flecs_entities_get(world, parent); + ecs_table_t *parent_table = parent_record->table; + if (!ecs_table_has_id(world, parent_table, EcsModule)) { + if (!ecs_table_has_id(world, parent_table, ecs_id(EcsComponent))) { + ecs_add_id(world, parent, EcsModule); + } + } - set_row_info(info, record->row); + } + } - info->table = table; - if (!info->table) { - return; + if (it->event == EcsOnSet) { + if (flecs_type_info_init_id( + world, e, c[i].size, c[i].alignment, NULL)) + { + flecs_assert_relation_unused(world, e, ecs_id(EcsComponent)); + } + } else if (it->event == EcsOnRemove) { + #ifdef FLECS_DEBUG + if (ecs_should_log(0)) { + char *path = ecs_get_path(world, e); + ecs_trace("unregistering component '%s'", path); + ecs_os_free(path); + } + #endif + if (!ecs_vec_count(&world->store.marked_ids)) { + flecs_type_info_free(world, e); + } else { + ecs_vec_append_t(&world->allocator, + &world->store.deleted_components, ecs_entity_t)[0] = e; + } + } } +} - info->data = &table->storage; +static +void flecs_ensure_module_tag(ecs_iter_t *it) { + ecs_world_t *world = it->world; - ecs_assert(ecs_vector_count(table->storage.entities) > info->row, - ECS_INTERNAL_ERROR, NULL); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); + if (parent) { + ecs_add_id(world, parent, EcsModule); + } + } } static -const ecs_type_info_t *get_c_info( - ecs_world_t *world, - ecs_entity_t component) +void flecs_disable_observer( + ecs_iter_t *it) { - ecs_entity_t real_id = ecs_get_typeid(world, component); - if (real_id) { - return flecs_get_type_info(world, real_id); - } else { - return NULL; + ecs_world_t *world = it->world; + ecs_entity_t evt = it->event; + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + flecs_observer_set_disable_bit(world, it->entities[i], + EcsObserverIsDisabled, evt == EcsOnAdd); } } static -void ids_merge( - ecs_ids_t *ids, - ecs_ids_t *add) +void flecs_disable_module_observers( + ecs_world_t *world, + ecs_entity_t module, + bool should_disable) { - if (!add || !add->count) { - return; - } - - int32_t new_count = ids->count + add->count; - if (new_count >= ids->size) { - ids->size = flecs_next_pow_of_2(new_count); - ecs_id_t *arr = ecs_os_malloc(ids->size * ECS_SIZEOF(ecs_id_t)); - ecs_os_memcpy_n(arr, ids->array, ecs_id_t, ids->count); + ecs_iter_t child_it = ecs_children(world, module); + while (ecs_children_next(&child_it)) { + ecs_table_t *table = child_it.table; + bool table_disabled = table->flags & EcsTableIsDisabled; + int32_t i; - if (ids->count >= ECS_ID_CACHE_SIZE) { - ecs_os_free(ids->array); + /* Recursively walk modules, don't propagate to disabled modules */ + if (ecs_table_has_id(world, table, EcsModule) && !table_disabled) { + for (i = 0; i < child_it.count; i ++) { + flecs_disable_module_observers( + world, child_it.entities[i], should_disable); + } + continue; } - - ids->array = arr; - } - ecs_os_memcpy_n(&ids->array[ids->count], add->array, ecs_id_t, add->count); - ids->count += add->count; -} + /* Only disable observers */ + if (!ecs_table_has_id(world, table, EcsObserver)) { + continue; + } -#define ECS_TABLE_DIFF_INIT {\ - .added = {.array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}, .size = ECS_ID_CACHE_SIZE},\ - .removed = {.array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}, .size = ECS_ID_CACHE_SIZE},\ - .on_set = {.array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}, .size = ECS_ID_CACHE_SIZE},\ - .un_set = {.array = (ecs_id_t[ECS_ID_CACHE_SIZE]){0}, .size = ECS_ID_CACHE_SIZE},\ + for (i = 0; i < child_it.count; i ++) { + flecs_observer_set_disable_bit(world, child_it.entities[i], + EcsObserverIsParentDisabled, should_disable); + } + } } static -void diff_append( - ecs_table_diff_t *dst, - ecs_table_diff_t *src) -{ - ids_merge(&dst->added, &src->added); - ids_merge(&dst->removed, &src->removed); - ids_merge(&dst->on_set, &src->on_set); - ids_merge(&dst->un_set, &src->un_set); +void flecs_disable_module(ecs_iter_t *it) { + int32_t i; + for (i = 0; i < it->count; i ++) { + flecs_disable_module_observers( + it->real_world, it->entities[i], it->event == EcsOnAdd); + } } static -void diff_free( - ecs_table_diff_t *diff) -{ - if (diff->added.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->added.array); - } - if (diff->removed.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->removed.array); - } - if (diff->on_set.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->on_set.array); - } - if (diff->un_set.count > ECS_ID_CACHE_SIZE) { - ecs_os_free(diff->un_set.array); +void flecs_on_add_prefab(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + for (int32_t i = 0; i < it->count; i ++) { + ecs_entity_t p = it->entities[i]; + + ecs_iter_t cit = ecs_children(world, p); + while (ecs_children_next(&cit)) { + for (int32_t j = 0; j < cit.count; j ++) { + ecs_add_id(world, cit.entities[j], EcsPrefab); + } + } } } static -ecs_table_t* table_append( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_table_diff_t *diff) -{ - ecs_table_diff_t temp_diff; - table = flecs_table_traverse_add(world, table, &id, &temp_diff); - ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); - diff_append(diff, &temp_diff); - return table; -error: - return NULL; +void flecs_register_ordered_children(ecs_iter_t *it) { + int32_t i; + if (it->event == EcsOnAdd) { + for (i = 0; i < it->count; i ++) { + ecs_entity_t parent = it->entities[i]; + ecs_component_record_t *cr = flecs_components_ensure( + it->world, ecs_childof(parent)); + if (!(cr->flags & EcsIdOrderedChildren)) { + flecs_ordered_children_init(it->world, cr); + flecs_ordered_children_populate(it->world, cr); + cr->flags |= EcsIdOrderedChildren; + } + } + } else if (!(it->real_world->flags & EcsWorldFini) && it->other_table) { + ecs_assert(it->event == EcsOnRemove, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < it->count; i ++) { + ecs_entity_t parent = it->entities[i]; + ecs_component_record_t *cr = flecs_components_get( + it->world, ecs_childof(parent)); + if (cr && (cr->flags & EcsIdOrderedChildren)) { + flecs_ordered_children_clear(cr); + cr->flags &= ~EcsIdOrderedChildren; + } + } + } } -static -void notify( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_entity_t event, - ecs_ids_t *ids, - ecs_entity_t relation) -{ - flecs_emit(world, world, &(ecs_event_desc_t) { - .event = event, - .ids = ids, - .table = table, - .other_table = other_table, - .offset = row, - .count = count, - .observable = world, - .relation = relation - }); -} +/* -- Bootstrapping -- */ -static -void instantiate( - ecs_world_t *world, - ecs_entity_t base, - ecs_table_t *table, - ecs_data_t *data, - int32_t row, - int32_t count); +#define flecs_bootstrap_builtin_t(world, table, name)\ + flecs_bootstrap_builtin(world, table, ecs_id(name), #name, sizeof(name),\ + ECS_ALIGNOF(name)) static -void instantiate_children( +void flecs_bootstrap_builtin( ecs_world_t *world, - ecs_entity_t base, ecs_table_t *table, - ecs_data_t *data, - int32_t row, - int32_t count, - ecs_table_t *child_table) + ecs_entity_t entity, + const char *symbol, + ecs_size_t size, + ecs_size_t alignment) { - if (!ecs_table_count(child_table)) { - return; - } - - ecs_type_t type = child_table->type; - ecs_data_t *child_data = &child_table->storage; - - ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); - int32_t type_count = ecs_vector_count(type); - - /* Instantiate child table for each instance */ - - /* Create component array for creating the table */ - ecs_ids_t components = { - .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) - }; - - void **component_data = ecs_os_alloca_n(void*, type_count + 1); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - /* Copy in component identifiers. Find the base index in the component - * array, since we'll need this to replace the base with the instance id */ - int j, i, childof_base_index = -1, pos = 0; - for (i = 0; i < type_count; i ++) { - ecs_id_t id = ids[i]; + ecs_column_t *columns = table->data.columns; + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); - /* Make sure instances don't have EcsPrefab */ - if (id == EcsPrefab) { - continue; - } + ecs_record_t *record = flecs_entities_ensure(world, entity); + ecs_assert(record->table == &world->store.root, ECS_INTERNAL_ERROR, NULL); + flecs_table_delete( + world, &world->store.root, ECS_RECORD_TO_ROW(record->row), false); + record->table = table; - /* Keep track of the element that creates the ChildOf relationship with - * the prefab parent. We need to replace this element to make sure the - * created children point to the instance and not the prefab */ - if (ECS_HAS_RELATION(id, EcsChildOf) && (ECS_PAIR_SECOND(id) == base)) { - childof_base_index = pos; - } + int32_t index = flecs_table_append(world, table, entity, false, false); + record->row = ECS_ROW_TO_RECORD(index, 0); - int32_t storage_index = ecs_table_type_to_storage_index(child_table, i); - if (storage_index != -1) { - ecs_type_info_t *ti = &child_table->type_info[storage_index]; - ecs_column_t *column = &child_data->columns[storage_index]; - component_data[pos] = ecs_vector_first_t( - column->data, ti->size, ti->alignment); - } else { - component_data[pos] = NULL; - } + EcsComponent *component = columns[0].data; + component[index].size = size; + component[index].alignment = alignment; - components.array[pos] = id; - pos ++; - } + const char *name = &symbol[3]; /* Strip 'Ecs' */ + ecs_size_t symbol_length = ecs_os_strlen(symbol); + ecs_size_t name_length = symbol_length - 3; - /* Table must contain children of base */ - ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); + EcsIdentifier *name_col = columns[1].data; + uint64_t name_hash = flecs_hash(name, name_length); + name_col[index].value = ecs_os_strdup(name); + name_col[index].length = name_length; + name_col[index].hash = name_hash; + name_col[index].index_hash = 0; - /* If children are added to a prefab, make sure they are prefabs too */ - if (table->flags & EcsTableIsPrefab) { - components.array[pos] = EcsPrefab; - component_data[pos] = NULL; - pos ++; - } + ecs_hashmap_t *name_index = flecs_table_get_name_index(world, table); + name_col[index].index = name_index; + flecs_name_index_ensure(name_index, entity, name, name_length, name_hash); - components.count = pos; + EcsIdentifier *symbol_col = columns[2].data; + symbol_col[index].value = ecs_os_strdup(symbol); + symbol_col[index].length = symbol_length; + symbol_col[index].hash = flecs_hash(symbol, symbol_length); + symbol_col[index].index_hash = 0; + symbol_col[index].index = NULL; +} - /* Instantiate the prefab child table for each new instance */ - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - int32_t child_count = ecs_vector_count(child_data->entities); +/** Initialize component table. This table is manually constructed to bootstrap + * flecs. After this function has been called, the builtin components can be + * created. + * The reason this table is constructed manually is because it requires the size + * and alignment of the EcsComponent and EcsIdentifier components, which haven't + * been created yet */ +static +ecs_table_t* flecs_bootstrap_component_table( + ecs_world_t *world) +{ + /* Before creating table, manually set flags for ChildOf/Identifier, as this + * can no longer be done after they are in use. */ - for (i = row; i < count + row; i ++) { - ecs_entity_t instance = entities[i]; - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; - ecs_table_t *i_table = NULL; - - /* Replace ChildOf element in the component array with instance id */ - components.array[childof_base_index] = ecs_pair(EcsChildOf, instance); + /* Initialize id records cached on world */ + world->cr_childof_wildcard = flecs_components_ensure(world, + ecs_pair(EcsChildOf, EcsWildcard)); + world->cr_childof_wildcard->flags |= EcsIdOnDeleteTargetDelete | + EcsIdOnInstantiateDontInherit | EcsIdTraversable | EcsIdPairIsTag | + EcsIdExclusive; + + ecs_component_record_t *cr = flecs_components_ensure( + world, ecs_pair_t(EcsIdentifier, EcsWildcard)); + cr->flags |= EcsIdOnInstantiateDontInherit; + world->cr_identifier_name = + flecs_components_ensure(world, ecs_pair_t(EcsIdentifier, EcsName)); + world->cr_childof_0 = flecs_components_ensure(world, + ecs_pair(EcsChildOf, 0)); - /* Find or create table */ - for (j = 0; j < components.count; j ++) { - i_table = table_append(world, i_table, components.array[j], &diff); - } + /* Initialize root table */ + flecs_init_root_table(world); - ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_count(i_table->type) == components.count, - ECS_INTERNAL_ERROR, NULL); + ecs_id_t ids[] = { + ecs_id(EcsComponent), + ecs_pair_t(EcsIdentifier, EcsName), + ecs_pair_t(EcsIdentifier, EcsSymbol), + ecs_pair(EcsChildOf, EcsFlecsCore), + }; - /* The instance is trying to instantiate from a base that is also - * its parent. This would cause the hierarchy to instantiate itself - * which would cause infinite recursion. */ - ecs_entity_t *children = ecs_vector_first( - child_data->entities, ecs_entity_t); + ecs_type_t array = { + .array = ids, + .count = 4 + }; -#ifdef FLECS_DEBUG - for (j = 0; j < child_count; j ++) { - ecs_entity_t child = children[j]; - ecs_check(child != instance, ECS_INVALID_PARAMETER, NULL); - } -#else - /* Bit of boilerplate to ensure that we don't get warnings about the - * error label not being used. */ - ecs_check(true, ECS_INVALID_OPERATION, NULL); -#endif + ecs_table_t *result = flecs_table_find_or_create(world, &array); - /* Create children */ - int32_t child_row; - new_w_data(world, i_table, NULL, &components, child_count, - component_data, false, &child_row, &diff); - diff_free(&diff); + /* Preallocate enough memory for initial components */ + ecs_vec_t v_entities = ecs_vec_from_entities(result); + ecs_vec_init_t(NULL, &v_entities, ecs_entity_t, EcsFirstUserComponentId); + + { + ecs_column_t *column = &result->data.columns[0]; + ecs_vec_t v = ecs_vec_from_column_t(column, result, EcsComponent); + ecs_vec_init_t(NULL, &v, EcsComponent, EcsFirstUserComponentId); + ecs_assert(v.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); + column->data = v.array; + } + { + ecs_column_t *column = &result->data.columns[1]; + ecs_vec_t v = ecs_vec_from_column_t(column, result, EcsIdentifier); + ecs_vec_init_t(NULL, &v, EcsIdentifier, EcsFirstUserComponentId); + ecs_assert(v.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); + column->data = v.array; + } + { + ecs_column_t *column = &result->data.columns[2]; + ecs_vec_t v = ecs_vec_from_column_t(column, result, EcsIdentifier); + ecs_vec_init_t(NULL, &v, EcsIdentifier, EcsFirstUserComponentId); + ecs_assert(v.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); + column->data = v.array; + } - /* If prefab child table has children itself, recursively instantiate */ - ecs_data_t *i_data = &i_table->storage; - for (j = 0; j < child_count; j ++) { - ecs_entity_t child = children[j]; - instantiate(world, child, i_table, i_data, child_row + j, 1); - } - } -error: - return; + result->data.entities = v_entities.array; + result->data.count = 0; + result->data.size = v_entities.size; + + return result; } +/* Make entities alive before the root table is initialized */ static -void instantiate( +void flecs_bootstrap_make_alive( ecs_world_t *world, - ecs_entity_t base, - ecs_table_t *table, - ecs_data_t *data, - int32_t row, - int32_t count) + ecs_entity_t e) { - ecs_table_t *base_table = ecs_get_table(world, ecs_get_alive(world, base)); - if (!base_table || !(base_table->flags & EcsTableIsPrefab)) { - /* Don't instantiate children from base entities that aren't prefabs */ - return; - } + ecs_table_t *root = &world->store.root; + flecs_entities_make_alive(world, e); - ecs_table_cache_iter_t it; - if (flecs_table_iter(world, ecs_pair(EcsChildOf, base), &it) != NULL) { - const ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - instantiate_children( - world, base, table, data, row, count, tr->hdr.table); - } + ecs_record_t *r = flecs_entities_ensure(world, e); + ecs_assert(r->table == NULL || r->table == root, + ECS_INTERNAL_ERROR, NULL); + + if (r->table == NULL) { + r->table = root; + r->row = flecs_ito(uint32_t, root->data.count); + + ecs_vec_t v_entities = ecs_vec_from_entities(root); + ecs_entity_t *array = ecs_vec_append_t(NULL, &v_entities, ecs_entity_t); + array[0] = e; + + root->data.entities = v_entities.array; + root->data.count = v_entities.count; + root->data.size = v_entities.size; } } static -bool override_component( - ecs_world_t *world, - ecs_entity_t component, - ecs_type_t type, - ecs_table_t *table, - ecs_table_t *other_table, - ecs_data_t *data, - const ecs_type_info_t *ti, - ecs_column_t *column, - int32_t row, - int32_t count, - bool notify_on_set); - -static -bool override_from_base( +void flecs_bootstrap_entity( ecs_world_t *world, - ecs_entity_t base, - ecs_entity_t component, - ecs_table_t *table, - ecs_table_t *other_table, - ecs_data_t *data, - const ecs_type_info_t *ti, - ecs_column_t *column, - int32_t row, - int32_t count, - bool notify_on_set) -{ - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t id, + const char *name, + ecs_entity_t parent) +{ + flecs_bootstrap_make_alive(world, id); + ecs_add_pair(world, id, EcsChildOf, parent); + ecs_set_name(world, id, name); - ecs_entity_info_t base_info; - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); - if (!flecs_get_info(world, base, &base_info) || !base_info.table) { - return false; + if (!parent || parent == EcsFlecsCore) { + ecs_assert(ecs_lookup(world, name) == id, + ECS_INTERNAL_ERROR, NULL); } +} - void *base_ptr = get_component( - world, base_info.table, base_info.row, component); - if (base_ptr) { - int32_t data_size = ti->size; - void *data_ptr = ecs_vector_get_t( - column->data, data_size, ti->alignment, row); - - int32_t index; +static +void flecs_bootstrap_sanity_check( + ecs_world_t *world) +{ + (void)world; +#ifdef FLECS_DEBUG + int32_t i, e, count = flecs_sparse_count(&world->store.tables); + int32_t total_count = 0; - ecs_copy_t copy = ti->lifecycle.copy; - if (copy) { - for (index = 0; index < count; index ++) { - copy(data_ptr, base_ptr, 1, ti); - data_ptr = ECS_OFFSET(data_ptr, data_size); - } + for (i = -1; i < count; i ++) { + ecs_table_t *table; + if (i == -1) { + table = &world->store.root; } else { - for (index = 0; index < count; index ++) { - ecs_os_memcpy(data_ptr, base_ptr, data_size); - data_ptr = ECS_OFFSET(data_ptr, data_size); - } + table = flecs_sparse_get_dense_t( + &world->store.tables, ecs_table_t, i); } - - ecs_ids_t ids = { - .array = (ecs_id_t[]){ component }, - .count = 1 - }; - - if (notify_on_set) { - /* Check if the component was available for the previous table. If - * the override is caused by an add operation, it does not introduce - * a new component value, and the application should not be - * notified. - * - * If the override is the result if adding a IsA relation - * with an entity that has components with the OVERRIDE flag, an - * event should be generated, since this represents a new component - * (and component value) for the entity. - * - * Note that this is an edge case, regular (self) triggers won't be - * notified because the event id is not the component but an IsA - * relationship. Superset triggers will not be invoked because the - * component is owned. */ - int32_t c = ecs_search_relation(world, other_table, 0, component, - EcsIsA, 1, 0, 0, 0, 0, 0); - if (c == -1) { - notify( - world, table, other_table, row, count, EcsOnSet, &ids, 0); - } + for (e = 0; e < table->data.count; e ++) { + ecs_record_t *r = flecs_entities_get( + world, table->data.entities[e]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_RECORD_TO_ROW(r->row) == e, + ECS_INTERNAL_ERROR, NULL); + total_count ++; } + } - return true; - } else { - /* If component not found on base, check if base itself inherits */ - ecs_type_t base_type = base_info.table->type; - return override_component(world, component, base_type, table, - other_table, data, ti, column, row, count, notify_on_set); + count = flecs_entities_count(world); + ecs_assert(count == total_count, ECS_INTERNAL_ERROR, NULL); + + for (i = 1; i < count; i ++) { + ecs_entity_t entity = flecs_entities_ids(world)[i]; + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->dense == (i + 1), ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table->data.entities[ECS_RECORD_TO_ROW(r->row)] == entity, + ECS_INTERNAL_ERROR, NULL); } +#endif } -static -bool override_component( - ecs_world_t *world, - ecs_entity_t component, - ecs_type_t type, - ecs_table_t *table, - ecs_table_t *other_table, - ecs_data_t *data, - const ecs_type_info_t *ti, - ecs_column_t *column, - int32_t row, - int32_t count, - bool notify_on_set) +void flecs_bootstrap( + ecs_world_t *world) { - ecs_entity_t *type_array = ecs_vector_first(type, ecs_entity_t); - int32_t i, type_count = ecs_vector_count(type); - - /* Walk prefabs */ - i = type_count - 1; - do { - ecs_entity_t e = type_array[i]; + ecs_log_push(); - if (!(e & ECS_ROLE_MASK)) { - break; - } + ecs_set_name_prefix(world, "Ecs"); - if (ECS_HAS_RELATION(e, EcsIsA)) { - if (override_from_base(world, ecs_pair_second(world, e), component, - table, other_table, data, ti, column, row, count, - notify_on_set)) - { - return true; - } - } - } while (--i >= 0); + /* Ensure builtin ids are alive */ + flecs_bootstrap_make_alive(world, ecs_id(EcsComponent)); + flecs_bootstrap_make_alive(world, ecs_id(EcsIdentifier)); + flecs_bootstrap_make_alive(world, ecs_id(EcsPoly)); + flecs_bootstrap_make_alive(world, ecs_id(EcsParent)); + flecs_bootstrap_make_alive(world, ecs_id(EcsTreeSpawner)); + flecs_bootstrap_make_alive(world, ecs_id(EcsDefaultChildComponent)); + flecs_bootstrap_make_alive(world, EcsFinal); + flecs_bootstrap_make_alive(world, EcsName); + flecs_bootstrap_make_alive(world, EcsSymbol); + flecs_bootstrap_make_alive(world, EcsAlias); + flecs_bootstrap_make_alive(world, EcsChildOf); + flecs_bootstrap_make_alive(world, EcsFlecs); + flecs_bootstrap_make_alive(world, EcsFlecsCore); + flecs_bootstrap_make_alive(world, EcsOnAdd); + flecs_bootstrap_make_alive(world, EcsOnRemove); + flecs_bootstrap_make_alive(world, EcsOnSet); + flecs_bootstrap_make_alive(world, EcsOnDelete); + flecs_bootstrap_make_alive(world, EcsPanic); + flecs_bootstrap_make_alive(world, EcsFlag); + flecs_bootstrap_make_alive(world, EcsIsA); + flecs_bootstrap_make_alive(world, EcsWildcard); + flecs_bootstrap_make_alive(world, EcsAny); + flecs_bootstrap_make_alive(world, EcsCanToggle); + flecs_bootstrap_make_alive(world, EcsTrait); + flecs_bootstrap_make_alive(world, EcsRelationship); + flecs_bootstrap_make_alive(world, EcsTarget); + flecs_bootstrap_make_alive(world, EcsSparse); + flecs_bootstrap_make_alive(world, EcsDontFragment); + flecs_bootstrap_make_alive(world, EcsObserver); + flecs_bootstrap_make_alive(world, EcsPairIsTag); + + /* Register type information for builtin components */ + flecs_type_info_init(world, EcsComponent, { + .ctor = flecs_default_ctor, + .on_set = flecs_on_component, + .on_remove = flecs_on_component + }); - return false; -} + flecs_type_info_init(world, EcsIdentifier, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsIdentifier), + .copy = ecs_copy(EcsIdentifier), + .move = ecs_move(EcsIdentifier), + .on_set = ecs_on_set(EcsIdentifier), + .on_remove = ecs_on_set(EcsIdentifier) + }); -static -void components_override( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - ecs_data_t *data, - int32_t row, - int32_t count, - ecs_ids_t *added, - bool notify_on_set) -{ - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_type_info_init(world, EcsPoly, { + .ctor = flecs_default_ctor, + .copy = ecs_copy(EcsPoly), + .move = ecs_move(EcsPoly), + .dtor = ecs_dtor(EcsPoly) + }); - ecs_column_t *columns = data->columns; - ecs_type_t type = table->type; - ecs_table_t *storage_table = table->storage_table; + flecs_type_info_init(world, EcsParent, { + .ctor = flecs_default_ctor + }); - int i; - for (i = 0; i < added->count; i ++) { - ecs_entity_t id = added->array[i]; + flecs_type_info_init(world, EcsDefaultChildComponent, { + .ctor = flecs_default_ctor, + }); - if (ECS_HAS_RELATION(id, EcsIsA)) { - ecs_entity_t base = ECS_PAIR_SECOND(id); + /* Create and cache often used id records on world */ + flecs_components_init(world); - /* Cannot inherit from base if base is final */ - ecs_check(!ecs_has_id(world, ecs_get_alive(world, base), EcsFinal), - ECS_CONSTRAINT_VIOLATED, NULL); - ecs_check(base != 0, ECS_INVALID_PARAMETER, NULL); + /* Create table for builtin components. This table temporarily stores the + * entities associated with builtin components, until they get moved to + * other tables once properties are added (see below) */ + ecs_table_t *table = flecs_bootstrap_component_table(world); + assert(table != NULL); - if (!world->stage.base) { - /* Setting base prevents instantiating the hierarchy multiple - * times. The instantiate function recursively iterates the - * hierarchy to instantiate children. While this is happening, - * new tables are created which end up calling this function, - * which would call instantiate multiple times for the same - * level in the hierarchy. */ - world->stage.base = base; - instantiate(world, base, table, data, row, count); - world->stage.base = 0; - } - } + /* Bootstrap builtin components */ + flecs_bootstrap_builtin_t(world, table, EcsIdentifier); + flecs_bootstrap_builtin_t(world, table, EcsComponent); + flecs_bootstrap_builtin_t(world, table, EcsPoly); + flecs_bootstrap_builtin_t(world, table, EcsParent); + flecs_bootstrap_builtin_t(world, table, EcsTreeSpawner); + flecs_bootstrap_builtin_t(world, table, EcsDefaultChildComponent); - if (!storage_table) { - continue; - } + /* Initialize default entity id range */ + world->info.last_component_id = EcsFirstUserComponentId; + flecs_entities_max_id(world) = EcsFirstUserEntityId; + world->info.min_id = 0; + world->info.max_id = 0; - ecs_table_record_t *tr = flecs_get_table_record( - world, storage_table, id); - if (!tr) { - continue; - } + /* Register observer for trait before adding EcsPairIsTag */ + ecs_observer(world, { + .query.terms[0] = { .id = EcsPairIsTag }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_tag, + .yield_existing = true, + .global_observer = true + }); - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & ECS_ID_DONT_INHERIT) { - continue; - } + /* Populate core module */ + ecs_set_scope(world, EcsFlecsCore); - const ecs_type_info_t *ti = idr->type_info; - if (!ti->size) { - continue; - } + flecs_bootstrap_tag(world, EcsName); + flecs_bootstrap_tag(world, EcsSymbol); + flecs_bootstrap_tag(world, EcsAlias); - ecs_column_t *column = &columns[tr->column]; - override_component(world, id, type, table, other_table, data, ti, - column, row, count, notify_on_set); - } -error: - return; -} + flecs_bootstrap_tag(world, EcsParentDepth); + flecs_bootstrap_tag(world, EcsQuery); + flecs_bootstrap_tag(world, EcsObserver); -static -void set_switch( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t row, - int32_t count, - ecs_ids_t *entities, - bool reset) -{ - ecs_entity_t *array = entities->array; - int32_t i, comp_count = entities->count; + flecs_bootstrap_tag(world, EcsModule); + flecs_bootstrap_tag(world, EcsPrefab); + flecs_bootstrap_tag(world, EcsSlotOf); + flecs_bootstrap_tag(world, EcsDisabled); + flecs_bootstrap_tag(world, EcsNotQueryable); + flecs_bootstrap_tag(world, EcsEmpty); - for (i = 0; i < comp_count; i ++) { - ecs_entity_t e = array[i]; + /* Initialize builtin modules */ + ecs_set_name(world, EcsFlecs, "flecs"); + ecs_add_id(world, EcsFlecs, EcsModule); + ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); - if (ECS_HAS_ROLE(e, CASE)) { - e = e & ECS_COMPONENT_MASK; + ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); + ecs_set_name(world, EcsFlecsCore, "core"); + ecs_add_id(world, EcsFlecsCore, EcsModule); - ecs_entity_t sw_case = 0; - if (!reset) { - sw_case = e; - ecs_assert(sw_case != 0, ECS_INTERNAL_ERROR, NULL); - } + /* Self check */ + ecs_record_t *r = flecs_entities_get(world, EcsFlecs); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->row & EcsEntityIsTraversable, ECS_INTERNAL_ERROR, NULL); + (void)r; - int32_t sw_index = flecs_table_switch_from_case(world, table, e); - ecs_assert(sw_index != -1, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = data->sw_columns[sw_index].data; - ecs_assert(sw != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t r; - for (r = 0; r < count; r ++) { - flecs_switch_set(sw, row + r, sw_case); - } - } - } -} + /* Initialize builtin entities */ + flecs_bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsAny, "_", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsThis, "this", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsFlag, "Flag", EcsFlecsCore); -static -void ecs_components_switch( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - int32_t row, - int32_t count, - ecs_ids_t *added, - ecs_ids_t *removed) -{ - if (added) { - set_switch(world, table, data, row, count, added, false); - } - if (removed) { - set_switch(world, table, data, row, count, removed, true); - } -} + /* Component/relationship properties */ + flecs_bootstrap_trait(world, EcsTransitive); + flecs_bootstrap_trait(world, EcsReflexive); + flecs_bootstrap_trait(world, EcsSymmetric); + flecs_bootstrap_trait(world, EcsSingleton); + flecs_bootstrap_trait(world, EcsFinal); + flecs_bootstrap_trait(world, EcsInheritable); + flecs_bootstrap_trait(world, EcsPairIsTag); + flecs_bootstrap_trait(world, EcsExclusive); + flecs_bootstrap_trait(world, EcsAcyclic); + flecs_bootstrap_trait(world, EcsTraversable); + flecs_bootstrap_trait(world, EcsWith); + flecs_bootstrap_trait(world, EcsOneOf); + flecs_bootstrap_trait(world, EcsCanToggle); + flecs_bootstrap_trait(world, EcsTrait); + flecs_bootstrap_trait(world, EcsRelationship); + flecs_bootstrap_trait(world, EcsTarget); + flecs_bootstrap_trait(world, EcsOnDelete); + flecs_bootstrap_trait(world, EcsOnDeleteTarget); + flecs_bootstrap_trait(world, EcsOnInstantiate); + flecs_bootstrap_trait(world, EcsSparse); + flecs_bootstrap_trait(world, EcsDontFragment); -static -int32_t new_entity( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_info_t *info, - ecs_table_t *new_table, - ecs_table_diff_t *diff, - bool construct, - bool notify_on_set) -{ - ecs_record_t *record = info->record; - ecs_data_t *new_data = &new_table->storage; - int32_t new_row; + flecs_bootstrap_tag(world, EcsRemove); + flecs_bootstrap_tag(world, EcsDelete); + flecs_bootstrap_tag(world, EcsPanic); - if (!record) { - record = ecs_eis_ensure(world, entity); - } + flecs_bootstrap_tag(world, EcsOverride); + flecs_bootstrap_tag(world, EcsInherit); + flecs_bootstrap_tag(world, EcsDontInherit); - new_row = flecs_table_append( - world, new_table, new_data, entity, record, construct); + flecs_bootstrap_tag(world, EcsOrderedChildren); - record->table = new_table; - record->row = ECS_ROW_TO_RECORD(new_row, record->row & ECS_ROW_FLAGS_MASK); + /* Builtin predicates */ + flecs_bootstrap_tag(world, EcsPredEq); + flecs_bootstrap_tag(world, EcsPredMatch); + flecs_bootstrap_tag(world, EcsPredLookup); + flecs_bootstrap_tag(world, EcsScopeOpen); + flecs_bootstrap_tag(world, EcsScopeClose); - ecs_assert(ecs_vector_count(new_data[0].entities) > new_row, - ECS_INTERNAL_ERROR, NULL); + /* Builtin relationships */ + flecs_bootstrap_tag(world, EcsIsA); + flecs_bootstrap_tag(world, EcsChildOf); + flecs_bootstrap_tag(world, EcsDependsOn); - if (new_table->flags & EcsTableHasAddActions) { - flecs_notify_on_add( - world, new_table, NULL, new_data, new_row, 1, diff, notify_on_set); - } + /* Builtin events */ + flecs_bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsMonitor, "EcsMonitor", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnTableCreate, "OnTableCreate", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnTableDelete, "OnTableDelete", EcsFlecsCore); + + /* Constant tag */ + flecs_bootstrap_entity(world, EcsConstant, "constant", EcsFlecsCore); + + /* Sync properties of ChildOf and Identifier with bootstrapped flags */ + ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); + ecs_add_id(world, EcsChildOf, EcsTrait); + ecs_add_id(world, EcsChildOf, EcsAcyclic); + ecs_add_id(world, EcsChildOf, EcsTraversable); + ecs_add_pair(world, EcsChildOf, EcsOnInstantiate, EcsDontInherit); + ecs_add_pair(world, ecs_id(EcsIdentifier), EcsOnInstantiate, EcsDontInherit); + + /* Register observers for components/relationship properties. Most observers + * set flags on an component record when a trait is added to a component, which + * allows for quick trait testing in various operations. */ + ecs_observer(world, { + .query.terms = {{ .id = EcsFinal }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_final, + .global_observer = true + }); - info->data = new_data; + static ecs_on_trait_ctx_t inheritable_trait = { EcsIdInheritable, 0 }; + ecs_observer(world, { + .query.terms = {{ .id = EcsInheritable }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_trait, + .ctx = &inheritable_trait, + .global_observer = true + }); - return new_row; -} + ecs_observer(world, { + .query.terms = { + { .id = ecs_pair(EcsOnDelete, EcsWildcard) } + }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_on_delete, + .global_observer = true + }); -static -int32_t move_entity( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_info_t *info, - ecs_table_t *src_table, - ecs_data_t *src_data, - int32_t src_row, - ecs_table_t *dst_table, - ecs_table_diff_t *diff, - bool construct, - bool notify_on_set) -{ - ecs_data_t *dst_data = &dst_table->storage; - ecs_assert(src_data != dst_data, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src_data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_vector_count(src_data->entities) > src_row, - ECS_INTERNAL_ERROR, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_observer(world, { + .query.terms = { + { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard) } + }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_on_delete_object, + .global_observer = true + }); - ecs_record_t *record = info->record; - ecs_assert(!record || record == ecs_eis_get(world, entity), - ECS_INTERNAL_ERROR, NULL); + ecs_observer(world, { + .query.terms = { + { .id = ecs_pair(EcsOnInstantiate, EcsWildcard) } + }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_on_instantiate, + .global_observer = true + }); - int32_t dst_row = flecs_table_append(world, dst_table, dst_data, entity, - record, false); + ecs_observer(world, { + .query.terms = {{ .id = EcsSymmetric }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_symmetric, + .global_observer = true + }); - ecs_assert(ecs_vector_count(src_data->entities) > src_row, - ECS_INTERNAL_ERROR, NULL); + ecs_observer(world, { + .query.terms = {{ .id = EcsSingleton }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_singleton, + .global_observer = true + }); - /* Copy entity & components from src_table to dst_table */ - if (src_table->type) { - flecs_notify_on_remove( - world, src_table, dst_table, src_row, 1, diff); + static ecs_on_trait_ctx_t traversable_trait = { EcsIdTraversable, EcsIdTraversable }; + ecs_observer(world, { + .query.terms = {{ .id = EcsTraversable }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_trait, + .ctx = &traversable_trait, + .global_observer = true + }); - flecs_table_move(world, entity, entity, dst_table, dst_data, dst_row, - src_table, src_data, src_row, construct); - } + static ecs_on_trait_ctx_t exclusive_trait = { EcsIdExclusive, 0 }; + ecs_observer(world, { + .query.terms = {{ .id = EcsExclusive }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_trait, + .ctx = &exclusive_trait, + .global_observer = true + }); - /* Update entity index & delete old data after running remove actions */ - record->table = dst_table; - record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); + static ecs_on_trait_ctx_t toggle_trait = { EcsIdCanToggle, 0 }; + ecs_observer(world, { + .query.terms = {{ .id = EcsCanToggle }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_trait, + .ctx = &toggle_trait, + .global_observer = true + }); + + static ecs_on_trait_ctx_t with_trait = { EcsIdWith, 0 }; + ecs_observer(world, { + .query.terms = { + { .id = ecs_pair(EcsWith, EcsWildcard) }, + }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_trait_pair, + .ctx = &with_trait, + .global_observer = true + }); + + static ecs_on_trait_ctx_t sparse_trait = { EcsIdSparse, 0 }; + ecs_observer(world, { + .query.terms = {{ .id = EcsSparse }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_trait, + .ctx = &sparse_trait, + .global_observer = true + }); + + static ecs_on_trait_ctx_t dont_fragment_trait = { EcsIdDontFragment, 0 }; + ecs_observer(world, { + .query.terms = {{ .id = EcsDontFragment }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_trait, + .ctx = &dont_fragment_trait, + .global_observer = true + }); + + ecs_observer(world, { + .query.terms = {{ .id = EcsOrderedChildren }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_ordered_children, + .global_observer = true + }); + + /* Entities used as slot are marked as exclusive to ensure a slot can always + * only point to a single entity. */ + ecs_observer(world, { + .query.terms = { + { .id = ecs_pair(EcsSlotOf, EcsWildcard) } + }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_slot_of, + .global_observer = true + }); + + /* Define observer to make sure that adding a module to a child entity also + * adds it to the parent. */ + ecs_observer(world, { + .query.terms = {{ .id = EcsModule } }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_ensure_module_tag, + .global_observer = true + }); + + /* Observer that tracks whether observers are disabled */ + ecs_observer(world, { + .query.terms = { + { .id = EcsObserver }, + { .id = EcsDisabled }, + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_disable_observer, + .global_observer = true + }); + + /* Observer that tracks whether modules are disabled */ + ecs_observer(world, { + .query.terms = { + { .id = EcsModule }, + { .id = EcsDisabled }, + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_disable_module, + .global_observer = true + }); + + /* Observer that ensures children of a prefab are also prefabs */ + ecs_observer(world, { + .query.terms = { + { .id = EcsPrefab }, + }, + .events = {EcsOnAdd}, + .callback = flecs_on_add_prefab, + .global_observer = true + }); + + /* Exclusive properties */ + ecs_add_id(world, EcsChildOf, EcsExclusive); + ecs_add_id(world, EcsOnDelete, EcsExclusive); + ecs_add_id(world, EcsOnDeleteTarget, EcsExclusive); + ecs_add_id(world, EcsOnInstantiate, EcsExclusive); + ecs_add_id(world, EcsParentDepth, EcsExclusive); + + /* Unqueryable entities */ + ecs_add_id(world, EcsThis, EcsNotQueryable); + ecs_add_id(world, EcsWildcard, EcsNotQueryable); + ecs_add_id(world, EcsAny, EcsNotQueryable); + ecs_add_id(world, EcsVariable, EcsNotQueryable); + + /* Tag relationships (relationships that should never have data) */ + ecs_add_id(world, EcsIsA, EcsPairIsTag); + ecs_add_id(world, EcsChildOf, EcsPairIsTag); + ecs_add_id(world, EcsSlotOf, EcsPairIsTag); + ecs_add_id(world, EcsDependsOn, EcsPairIsTag); + ecs_add_id(world, EcsFlag, EcsPairIsTag); + ecs_add_id(world, EcsWith, EcsPairIsTag); + + /* Relationships */ + ecs_add_id(world, EcsChildOf, EcsRelationship); + ecs_add_id(world, EcsIsA, EcsRelationship); + ecs_add_id(world, EcsSlotOf, EcsRelationship); + ecs_add_id(world, EcsDependsOn, EcsRelationship); + ecs_add_id(world, EcsWith, EcsRelationship); + ecs_add_id(world, EcsOnDelete, EcsRelationship); + ecs_add_id(world, EcsOnDeleteTarget, EcsRelationship); + ecs_add_id(world, EcsOnInstantiate, EcsRelationship); + ecs_add_id(world, ecs_id(EcsIdentifier), EcsRelationship); + + /* Targets */ + ecs_add_id(world, EcsOverride, EcsTarget); + ecs_add_id(world, EcsInherit, EcsTarget); + ecs_add_id(world, EcsDontInherit, EcsTarget); + + /* Traversable relationships are always acyclic */ + ecs_add_pair(world, EcsTraversable, EcsWith, EcsAcyclic); + + /* Transitive relationships are always Traversable */ + ecs_add_pair(world, EcsTransitive, EcsWith, EcsTraversable); + + /* DontFragment components are always sparse */ + ecs_add_pair(world, EcsDontFragment, EcsWith, EcsSparse); - flecs_table_delete(world, src_table, src_data, src_row, false); + /* Modules are singletons */ + ecs_add_pair(world, EcsModule, EcsWith, EcsSingleton); + + /* DontInherit components */ + ecs_add_pair(world, EcsPrefab, EcsOnInstantiate, EcsDontInherit); + ecs_add_pair(world, ecs_id(EcsComponent), EcsOnInstantiate, EcsDontInherit); + ecs_add_pair(world, EcsOnDelete, EcsOnInstantiate, EcsDontInherit); + ecs_add_pair(world, EcsExclusive, EcsOnInstantiate, EcsDontInherit); + ecs_add_pair(world, EcsDontFragment, EcsOnInstantiate, EcsDontInherit); + + /* Acyclic/Traversable components */ + ecs_add_id(world, EcsIsA, EcsTraversable); + ecs_add_id(world, EcsDependsOn, EcsTraversable); + ecs_add_id(world, EcsWith, EcsAcyclic); - /* If components were added, invoke add actions */ - if (src_table != dst_table || diff->added.count) { - if (diff->added.count && (dst_table->flags & EcsTableHasAddActions)) { - flecs_notify_on_add(world, dst_table, src_table, dst_data, - dst_row, 1, diff, notify_on_set); - } - } + /* Transitive relationships */ + ecs_add_id(world, EcsIsA, EcsTransitive); + ecs_add_id(world, EcsIsA, EcsReflexive); - info->data = dst_data; - return dst_row; -error: - return -1; -} + /* Exclusive properties */ + ecs_add_id(world, EcsSlotOf, EcsExclusive); + ecs_add_id(world, EcsOneOf, EcsExclusive); -static -void delete_entity( - ecs_world_t *world, - ecs_table_t *src_table, - ecs_data_t *src_data, - int32_t src_row, - ecs_table_diff_t *diff) -{ - if (src_table) { - /* Invoke remove actions before deleting */ - if (src_table->flags & EcsTableHasRemoveActions) { - flecs_notify_on_remove(world, src_table, NULL, src_row, 1, diff); - } - } + /* Inherited components */ + ecs_add_pair(world, EcsIsA, EcsOnInstantiate, EcsInherit); + ecs_add_pair(world, EcsDependsOn, EcsOnInstantiate, EcsInherit); + + /* Run bootstrap functions for other parts of the code */ + flecs_bootstrap_entity_name(world); + flecs_bootstrap_parent_component(world); + flecs_bootstrap_spawner(world); + + ecs_set_scope(world, 0); + ecs_set_name_prefix(world, NULL); + + ecs_assert(world->cr_childof_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world->cr_isa_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Verify that all entities are where they're supposed to be */ + flecs_bootstrap_sanity_check(world); - flecs_table_delete(world, src_table, src_data, src_row, true); + ecs_log_pop(); } -/* Updating component monitors is a relatively expensive operation that only - * happens for entities that are monitored. The approach balances the amount of - * processing between the operation on the entity vs the amount of work that - * needs to be done to rematch queries, as a simple brute force approach does - * not scale when there are many tables / queries. Therefore we need to do a bit - * of bookkeeping that is more intelligent than simply flipping a flag */ +/** + * @file commands.c + * @brief Command queue implementation. + */ + + static -void update_component_monitor_w_array( +ecs_table_t* flecs_find_table_remove( ecs_world_t *world, - ecs_ids_t *entities) + ecs_table_t *table, + ecs_id_t id, + ecs_table_diff_builder_t *diff) { - if (!entities) { - return; - } - - int i; - for (i = 0; i < entities->count; i ++) { - ecs_entity_t id = entities->array[i]; - if (ECS_HAS_ROLE(id, PAIR)) { - flecs_monitor_mark_dirty(world, - ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); - } - - flecs_monitor_mark_dirty(world, id); - } + ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; + table = flecs_table_traverse_remove(world, table, &id, &temp_diff); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_table_diff_build_append_table(world, diff, &temp_diff); + return table; +error: + return NULL; } static -void update_component_monitors( - ecs_world_t *world, - ecs_ids_t *added, - ecs_ids_t *removed) +ecs_cmd_t* flecs_cmd_new( + ecs_stage_t *stage) { - update_component_monitor_w_array(world, added); - update_component_monitor_w_array(world, removed); + ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->cmd->queue, + ecs_cmd_t); + cmd->is._1.value = NULL; + cmd->id = 0; + cmd->next_for_entity = 0; + cmd->entry = NULL; + cmd->system = stage->system; + return cmd; } static -void commit( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_info_t *info, - ecs_table_t *dst_table, - ecs_table_diff_t *diff, - bool construct, - bool notify_on_set) +ecs_cmd_t* flecs_cmd_new_batched( + ecs_stage_t *stage, + ecs_entity_t e) { - ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); - - ecs_table_t *src_table = info->table; - if (src_table == dst_table) { - /* If source and destination table are the same no action is needed * - * However, if a component was added in the process of traversing a - * table, this suggests that a case switch could have occured. */ - if (((diff->added.count) || (diff->removed.count)) && - src_table && src_table->flags & EcsTableHasSwitch) - { - ecs_components_switch( - world, src_table, info->data, info->row, 1, - &diff->added, &diff->removed); + ecs_vec_t *cmds = &stage->cmd->queue; + ecs_cmd_entry_t *entry = flecs_sparse_get_t( + &stage->cmd->entries, ecs_cmd_entry_t, e); + + int32_t cur = ecs_vec_count(cmds); + ecs_cmd_t *cmd = flecs_cmd_new(stage); + bool is_new = false; + if (entry) { + if (entry->first == -1) { + /* Existing but invalidated entry */ + entry->first = cur; + cmd->entry = entry; + } else { + int32_t last = entry->last; + ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); + if (arr[last].entity == e) { + ecs_cmd_t *last_op = &arr[last]; + last_op->next_for_entity = cur; + if (last == entry->first) { + /* Flip sign bit so flush logic can tell which command + * is the first for an entity */ + last_op->next_for_entity *= -1; + } + } else { + /* Entity with different version was in the same queue. Discard + * the old entry and create a new one. */ + is_new = true; + } } + } else { + is_new = true; + } - return; + if (is_new) { + cmd->entry = entry = flecs_sparse_ensure_fast_t( + &stage->cmd->entries, ecs_cmd_entry_t, e); + entry->first = cur; } - if (src_table) { - ecs_data_t *src_data = info->data; - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + entry->last = cur; - if (dst_table->type) { - info->row = move_entity(world, entity, info, src_table, - src_data, info->row, dst_table, diff, construct, notify_on_set); - info->table = dst_table; - } else { - delete_entity(world, src_table, src_data, info->row, diff); + return cmd; +} - ecs_eis_set(world, entity, &(ecs_record_t){ - NULL, info->row_flags - }); - } - } else { - if (dst_table->type) { - info->row = new_entity( - world, entity, info, dst_table, diff, construct, notify_on_set); - info->table = dst_table; - } - } +bool flecs_defer_begin( + ecs_world_t *world, + ecs_stage_t *stage) +{ + flecs_poly_assert(world, ecs_world_t); + flecs_poly_assert(stage, ecs_stage_t); + flecs_check_exclusive_world_access_write(world); + (void)world; + if (stage->defer < 0) return false; + return (++ stage->defer) == 1; +} - /* If the entity is being watched, it is being monitored for changes and - * requires rematching systems when components are added or removed. This - * ensures that systems that rely on components from containers or prefabs - * update the matched tables when the application adds or removes a - * component from, for example, a container. */ - if (info->row_flags) { - update_component_monitors(world, &diff->added, &diff->removed); +bool flecs_defer_cmd( + ecs_stage_t *stage) +{ + if (stage->defer) { + return (stage->defer > 0); } - if ((!src_table || !src_table->type) && world->range_check_enabled) { - ecs_check(!world->info.max_id || entity <= world->info.max_id, - ECS_OUT_OF_RANGE, 0); - ecs_check(entity >= world->info.min_id, - ECS_OUT_OF_RANGE, 0); - } -error: - return; + stage->defer ++; + return false; } -static -void new( - ecs_world_t *world, +bool flecs_defer_modified( + ecs_stage_t *stage, ecs_entity_t entity, - ecs_ids_t *to_add) + ecs_id_t id) { - ecs_entity_info_t info = {0}; - int32_t i, count = to_add->count; - ecs_table_t *table = &world->store.root; - - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; - for (i = 0; i < count; i ++) { - table = table_append(world, table, to_add->array[i], &diff); + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + if (cmd) { + cmd->kind = EcsCmdModified; + cmd->id = id; + cmd->entity = entity; + } + return true; } + return false; +} - new_entity(world, entity, &info, table, &diff, true, true); - - diff_free(&diff); +bool flecs_defer_clone( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t src, + bool clone_value) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdClone; + cmd->id = src; + cmd->entity = entity; + cmd->is._1.clone_value = clone_value; + return true; + } + return false; } -static -const ecs_entity_t* new_w_data( - ecs_world_t *world, - ecs_table_t *table, - const ecs_entity_t *entities, - ecs_ids_t *component_ids, - int32_t count, - void **component_data, - bool is_move, - int32_t *row_out, - ecs_table_diff_t *diff) +bool flecs_defer_path( + ecs_stage_t *stage, + ecs_entity_t parent, + ecs_entity_t entity, + const char *name) { - int32_t sparse_count = 0; - if (!entities) { - sparse_count = ecs_eis_count(world); - entities = flecs_sparse_new_ids(ecs_eis(world), count); + if (stage->defer > 0) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdPath; + cmd->entity = entity; + cmd->id = parent; + cmd->is._1.value = ecs_os_strdup(name); + return true; } + return false; +} - if (!table) { - return entities; +bool flecs_defer_delete( + ecs_stage_t *stage, + ecs_entity_t entity) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdDelete; + cmd->entity = entity; + return true; } + return false; +} - ecs_type_t type = table->type; - if (!type) { - return entities; +bool flecs_defer_clear( + ecs_stage_t *stage, + ecs_entity_t entity) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->kind = EcsCmdClear; + cmd->entity = entity; + return true; } + return false; +} - ecs_ids_t component_array = { 0 }; - if (!component_ids) { - component_ids = &component_array; - component_array.array = ecs_vector_first(type, ecs_entity_t); - component_array.count = ecs_vector_count(type); +bool flecs_defer_on_delete_action( + ecs_stage_t *stage, + ecs_id_t id, + ecs_entity_t action) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdOnDeleteAction; + cmd->id = id; + cmd->entity = action; + return true; } + return false; +} - ecs_data_t *data = &table->storage; - int32_t row = flecs_table_appendn(world, table, data, count, entities); - - /* Update entity index. */ - int i; - ecs_record_t **record_ptrs = ecs_vector_first( - data->record_ptrs, ecs_record_t*); - for (i = 0; i < count; i ++) { - record_ptrs[row + i] = ecs_eis_set(world, entities[i], - &(ecs_record_t){ - .table = table, - .row = ECS_ROW_TO_RECORD(row + i, 0) - }); +bool flecs_defer_enable( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + bool enable) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = enable ? EcsCmdEnable : EcsCmdDisable; + cmd->entity = entity; + cmd->id = id; + return true; } + return false; +} - flecs_defer_none(world, &world->stage); - - flecs_notify_on_add(world, table, NULL, data, row, count, diff, - component_data == NULL); - - if (component_data) { - /* Set components that we're setting in the component mask so the init - * actions won't call OnSet triggers for them. This ensures we won't - * call OnSet triggers multiple times for the same component */ - int32_t c_i; - ecs_table_t *storage_table = table->storage_table; - for (c_i = 0; c_i < component_ids->count; c_i ++) { - void *src_ptr = component_data[c_i]; - if (!src_ptr) { - continue; - } - - /* Find component in storage type */ - ecs_entity_t id = component_ids->array[c_i]; - const ecs_table_record_t *tr = flecs_get_table_record( - world, storage_table, id); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); +bool flecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, + int32_t count, + ecs_id_t id, + const ecs_entity_t **ids_out) +{ + if (flecs_defer_cmd(stage)) { + ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); - int32_t index = tr->column; - ecs_type_info_t *ti = &table->type_info[index]; - ecs_column_t *column = &table->storage.columns[index]; - int32_t size = ti->size; - int32_t alignment = ti->alignment; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + /* Use ecs_new_id as this is thread safe */ + int i; + for (i = 0; i < count; i ++) { + ids[i] = ecs_new(world); + } - void *ptr = ecs_vector_first_t(column->data, size, alignment); - ptr = ECS_OFFSET(ptr, size * row); + *ids_out = ids; - ecs_copy_t copy; - ecs_move_t move; - if (is_move && (move = ti->lifecycle.move)) { - move(ptr, src_ptr, count, ti); - } else if (!is_move && (copy = ti->lifecycle.copy)) { - copy(ptr, src_ptr, count, ti); - } else { - ecs_os_memcpy(ptr, src_ptr, size * count); - } - }; + /* Store data in op */ + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdBulkNew; + cmd->id = id; + cmd->is._n.entities = ids; + cmd->is._n.count = count; + cmd->entity = 0; + return true; + } + return false; +} - flecs_notify_on_set(world, table, row, count, NULL, true); - flecs_notify_on_set(world, table, row, count, &diff->on_set, false); +bool flecs_defer_add( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + if (flecs_defer_cmd(stage)) { + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->kind = EcsCmdAdd; + cmd->id = id; + cmd->entity = entity; + return true; } + return false; +} - flecs_defer_flush(world, &world->stage); +bool flecs_defer_remove( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + if (flecs_defer_cmd(stage)) { + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->kind = EcsCmdRemove; + cmd->id = id; + cmd->entity = entity; - if (row_out) { - *row_out = row; - } + /* If an override is removed, restore to the component to the value of + * the overridden component. This serves to purposes: + * + * - the application immediately sees the correct component value + * - if a remove command is followed up by an add command, the override + * will still be applied vs. getting cancelled out because of + * command batching. + */ + ecs_world_t *world = stage->world; + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; + ecs_table_overrides_t *o = table->data.overrides; + if (o) { + ecs_component_record_t *cr = flecs_components_get(world, id); + const ecs_type_info_t *ti; + if (cr && (ti = cr->type_info)) { + const ecs_table_record_t *tr = flecs_component_get_table( + cr, table); + if (tr) { + ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_ref_t *ref = &o->refs[tr->column]; + if (ref->entity) { + void *dst = ECS_OFFSET( + table->data.columns[tr->column].data, + ti->size * ECS_RECORD_TO_ROW(r->row)); + const void *src = ecs_ref_get_id( + world, &o->refs[tr->column], id); + ecs_copy_t copy = ti->hooks.copy; + if (copy) { + copy(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); + } + } + } + } + } - if (sparse_count) { - entities = flecs_sparse_ids(ecs_eis(world)); - return &entities[sparse_count]; - } else { - return entities; + return true; } + return false; } +/* Return existing component pointer & type info */ static -void add_id_w_info( +flecs_component_ptr_t flecs_defer_get_existing( ecs_world_t *world, ecs_entity_t entity, - ecs_entity_info_t *info, + ecs_record_t *r, ecs_id_t id, - bool construct) + ecs_size_t size) { - ecs_table_diff_t diff; + flecs_component_ptr_t ptr = flecs_get_mut(world, entity, id, r, size); - ecs_table_t *src_table = info->table; - ecs_table_t *dst_table = flecs_table_traverse_add( - world, src_table, &id, &diff); - - commit(world, entity, info, dst_table, &diff, construct, - false); /* notify_on_set = false, this function is only called from - * functions that are about to set the component. */ + /* Make sure we return type info, even if entity doesn't have component */ + if (!ptr.ti) { + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { + /* If cr doesn't exist yet, create it but only if the + * application is not multithreaded. */ + if (world->flags & EcsWorldMultiThreaded) { + ptr.ti = ecs_get_type_info(world, id); + } else { + /* When not in multi threaded mode, it's safe to find or + * create the component record. */ + cr = flecs_components_ensure(world, id); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get type_info from component record. We could have called + * ecs_get_type_info directly, but since this function can be + * expensive for pairs, creating the component record ensures we + * can find the type_info quickly for subsequent operations. */ + ptr.ti = cr->type_info; + } + } else { + ptr.ti = cr->type_info; + } + } + return ptr; } -static -void add_id( +void* flecs_defer_emplace( ecs_world_t *world, + ecs_stage_t *stage, ecs_entity_t entity, - ecs_id_t id) -{ - ecs_stage_t *stage = flecs_stage_from_world(&world); - - if (flecs_defer_add(world, stage, entity, id)) { - return; + ecs_id_t id, + ecs_size_t size, + bool *is_new) +{ + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->entity = entity; + cmd->id = id; + + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t ptr = flecs_defer_get_existing( + world, entity, r, id, size); + + const ecs_type_info_t *ti = ptr.ti; + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "provided component is not a type"); + ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, + "mismatching size specified for component in ensure/emplace/set"); + size = ti->size; + + void *cmd_value = ptr.ptr; + if (!ptr.ptr) { + ecs_stack_t *stack = &stage->cmd->stack; + cmd_value = flecs_stack_alloc(stack, size, ti->alignment); + + cmd->kind = EcsCmdEmplace; + cmd->is._1.size = size; + cmd->is._1.value = cmd_value; + if (is_new) *is_new = true; + } else { + cmd->kind = EcsCmdAdd; + if (is_new) *is_new = false; } - ecs_entity_info_t info; - flecs_get_info(world, entity, &info); - - ecs_table_diff_t diff; - ecs_table_t *src_table = info.table; - ecs_table_t *dst_table = flecs_table_traverse_add( - world, src_table, &id, &diff); - - commit(world, entity, &info, dst_table, &diff, true, true); - - flecs_defer_flush(world, stage); + return cmd_value; +error: + return NULL; } -static -void remove_id( +void* flecs_defer_ensure( ecs_world_t *world, + ecs_stage_t *stage, ecs_entity_t entity, - ecs_id_t id) + ecs_id_t id, + ecs_size_t size) { - ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - if (flecs_defer_remove(world, stage, entity, id)) { - return; - } + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->entity = entity; + cmd->id = id; - ecs_entity_info_t info; - flecs_get_info(world, entity, &info); + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t ptr = flecs_defer_get_existing( + world, entity, r, id, size); - ecs_table_diff_t diff; - ecs_table_t *src_table = info.table; - ecs_table_t *dst_table = flecs_table_traverse_remove( - world, src_table, &id, &diff); + const ecs_type_info_t *ti = ptr.ti; + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "provided component is not a type"); + ecs_assert(size == ti->size, ECS_INVALID_PARAMETER, + "bad size for component in ensure"); - commit(world, entity, &info, dst_table, &diff, true, true); + ecs_table_t *table = r->table; + if (!ptr.ptr) { + ecs_stack_t *stack = &stage->cmd->stack; + cmd->kind = EcsCmdEnsure; + cmd->is._1.size = size; + cmd->is._1.value = ptr.ptr = + flecs_stack_alloc(stack, size, ti->alignment); + + /* Check if entity inherits component */ + void *base = NULL; + if (table && (table->flags & EcsTableHasIsA)) { + ecs_component_record_t *cr = flecs_components_get(world, id); + base = flecs_get_base_component(world, table, id, cr, 0); + } + + if (!base) { + /* Normal ctor */ + ecs_xtor_t ctor = ti->hooks.ctor; + if (ctor) { + ctor(ptr.ptr, 1, ti); + } + } else { + /* Override */ + ecs_copy_t copy = ti->hooks.copy_ctor; + if (copy) { + copy(ptr.ptr, base, 1, ti); + } else { + ecs_os_memcpy(ptr.ptr, base, size); + } + } + } else { + cmd->kind = EcsCmdAdd; + } - flecs_defer_flush(world, stage); + return ptr.ptr; +error: + return NULL; } -static -void *get_mutable( +void* flecs_defer_set( ecs_world_t *world, + ecs_stage_t *stage, ecs_entity_t entity, - ecs_entity_t component, - ecs_entity_info_t *info, - bool *is_added) + ecs_id_t id, + ecs_size_t size, + void *value) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(component != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check((component & ECS_COMPONENT_MASK) == component || - ECS_HAS_ROLE(component, PAIR), ECS_INVALID_PARAMETER, NULL); + ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + ecs_assert(cmd != NULL, ECS_INTERNAL_ERROR, NULL); + cmd->entity = entity; + cmd->id = id; + + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t ptr = flecs_defer_get_existing( + world, entity, r, id, size); + + if (world->stage_count != 1) { + /* If world has multiple stages we need to insert a set command + * with temporary storage, as the value could be lost otherwise + * by a command in another stage. */ + ptr.ptr = NULL; + } + + const ecs_type_info_t *ti = ptr.ti; + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "provided component is not a type"); + ecs_assert(size == ti->size, ECS_INVALID_PARAMETER, + "mismatching size specified for component in ensure/emplace/set (%u vs %u)", + size, ti->size); + + /* Handle trivial set command (no hooks, OnSet observers) */ + if (id < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_set[id]) { + if (!ptr.ptr) { + ptr.ptr = flecs_stack_alloc( + &stage->cmd->stack, size, ti->alignment); + + /* No OnSet observers, so ensure is enough */ + cmd->kind = EcsCmdEnsure; + cmd->is._1.size = size; + cmd->is._1.value = ptr.ptr; + } else { + /* No OnSet observers, so only thing we need to do is make sure + * that a preceding remove command doesn't cause the entity to + * end up without the component. */ + cmd->kind = EcsCmdAdd; + } - void *dst = NULL; - if (flecs_get_info(world, entity, info) && info->table) { - dst = get_component(world, info->table, info->row, component); + ecs_os_memcpy(ptr.ptr, value, size); + return ptr.ptr; + } } - if (!dst) { - ecs_table_t *table = info->table; - add_id_w_info(world, entity, info, component, true); - flecs_get_info(world, entity, info); - - ecs_assert(info->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(info->table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); - dst = get_component(world, info->table, info->row, component); + ecs_copy_t copy; + if (!ptr.ptr) { + cmd->kind = EcsCmdSet; + cmd->is._1.size = size; + ptr.ptr = cmd->is._1.value = + flecs_stack_alloc(&stage->cmd->stack, size, ti->alignment); + copy = ti->hooks.copy_ctor; + } else { + cmd->kind = EcsCmdAddModified; - if (is_added) { - *is_added = table != info->table; + /* Call on_replace hook before copying the new value. */ + if (ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, entity, id, ptr.ptr, value, ti); } - return dst; - } else { - if (is_added) { - *is_added = false; - } + copy = ti->hooks.copy; + } - return dst; + if (copy) { + copy(ptr.ptr, value, 1, ti); + } else { + ecs_os_memcpy(ptr.ptr, value, size); } + + return ptr.ptr; error: return NULL; } -/* -- Private functions -- */ -static -void flecs_notify_on_add( +/* Same as flecs_defer_set, but doesn't copy value into storage. */ +void* flecs_defer_cpp_set( ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - ecs_data_t *data, - int32_t row, - int32_t count, - ecs_table_diff_t *diff, - bool run_on_set) + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + ecs_size_t size, + const void *value) { - ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + ecs_assert(cmd != NULL, ECS_INTERNAL_ERROR, NULL); + cmd->entity = entity; + cmd->id = id; + + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t ptr = flecs_defer_get_existing( + world, entity, r, id, size); + + const ecs_type_info_t *ti = ptr.ti; + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "provided component is not a type"); + ecs_assert(size == ti->size, ECS_INVALID_PARAMETER, + "mismatching size specified for component in ensure/emplace/set"); + + /* Handle trivial set command (no hooks, OnSet observers) */ + if (id < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_set[id]) { + if (!ptr.ptr) { + ptr.ptr = flecs_stack_alloc( + &stage->cmd->stack, size, ti->alignment); + + /* No OnSet observers, so ensure is enough */ + cmd->kind = EcsCmdEnsure; + cmd->is._1.size = size; + cmd->is._1.value = ptr.ptr; + } else { + /* No OnSet observers, so only thing we need to do is make sure + * that a preceding remove command doesn't cause the entity to + * end up without the component. */ + cmd->kind = EcsCmdAdd; + } - if (diff->added.count) { - if (table->flags & EcsTableHasIsA) { - components_override(world, table, other_table, data, row, count, - &diff->added, run_on_set); + ecs_os_memcpy(ptr.ptr, value, size); + return ptr.ptr; } + } + + if (!ptr.ptr) { + cmd->kind = EcsCmdSet; + cmd->is._1.size = size; + ptr.ptr = cmd->is._1.value = + flecs_stack_alloc(&stage->cmd->stack, size, ti->alignment); - if (table->flags & EcsTableHasSwitch) { - ecs_components_switch( - world, table, data, row, count, &diff->added, NULL); + ecs_xtor_t ctor = ti->hooks.ctor; + if (ctor) { + ctor(ptr.ptr, 1, ti); } + } else { + cmd->kind = EcsCmdAddModified; - if (table->flags & EcsTableHasOnAdd) { - notify(world, table, other_table, row, count, EcsOnAdd, - &diff->added, 0); + /* Call on_replace hook before copying the new value. */ + if (ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, entity, id, ptr.ptr, value, ti); } } - /* When a IsA relation is added to an entity, that entity inherits the - * components from the base. Send OnSet notifications so that an application - * can respond to these new components. */ - if (run_on_set && diff->on_set.count) { - notify(world, table, other_table, row, count, EcsOnSet, &diff->on_set, - EcsIsA); - } + return ptr.ptr; +error: + return NULL; } -void flecs_notify_on_remove( +void* flecs_defer_cpp_assign( ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_table_diff_t *diff) + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + ecs_size_t size, + const void *value) { - ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); - if (count) { - if (diff->un_set.count) { - notify(world, table, other_table, row, count, EcsUnSet, &diff->un_set, 0); - } + flecs_component_ptr_t ptr = flecs_get_mut(world, entity, id, r, size); + ecs_assert(ptr.ptr != NULL, ECS_INVALID_PARAMETER, + "entity does not have component, use set() instead"); - if (table->flags & EcsTableHasOnRemove && diff->removed.count) { - notify(world, table, other_table, row, count, EcsOnRemove, - &diff->removed, 0); + if (id < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_set[id]) { + return ptr.ptr; /* Nothing more to do */ } + } - if (table->flags & EcsTableHasIsA && diff->on_set.count) { - notify(world, table, other_table, row, count, EcsOnSet, - &diff->on_set, 0); - } + /* Call on_replace hook before copying the new value. */ + ecs_iter_action_t on_replace = ptr.ti->hooks.on_replace; + if (on_replace) { + flecs_invoke_replace_hook( + world, r->table, entity, id, ptr.ptr, value, ptr.ti); } + + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdModified; + cmd->entity = entity; + cmd->id = id; + + return ptr.ptr; } -void flecs_notify_on_set( +void flecs_enqueue( ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - ecs_ids_t *ids, - bool owned) + ecs_stage_t *stage, + ecs_event_desc_t *desc) { - ecs_data_t *data = &table->storage; + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdEvent; + cmd->entity = desc->entity; + + ecs_stack_t *stack = &stage->cmd->stack; + ecs_event_desc_t *desc_cmd = flecs_stack_alloc_t(stack, ecs_event_desc_t); + ecs_os_memcpy_t(desc_cmd, desc, ecs_event_desc_t); + + if (desc->ids && desc->ids->count != 0) { + ecs_type_t *type_cmd = flecs_stack_alloc_t(stack, ecs_type_t); + int32_t id_count = desc->ids->count; + type_cmd->count = id_count; + type_cmd->array = flecs_stack_alloc_n(stack, ecs_id_t, id_count); + ecs_os_memcpy_n(type_cmd->array, desc->ids->array, ecs_id_t, id_count); + desc_cmd->ids = type_cmd; + } else { + desc_cmd->ids = NULL; + } - ecs_entity_t *entities = ecs_vector_get(data->entities, ecs_entity_t, row); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(row < ecs_vector_count(data->entities), - ECS_INTERNAL_ERROR, NULL); - ecs_assert((row + count) <= ecs_vector_count(data->entities), - ECS_INTERNAL_ERROR, NULL); + cmd->is._1.value = desc_cmd; + cmd->is._1.size = ECS_SIZEOF(ecs_event_desc_t); - ecs_ids_t local_ids; - if (!ids) { - local_ids.array = table->storage_ids; - local_ids.count = table->storage_count; - ids = &local_ids; - } + if (desc->param || desc->const_param) { + ecs_assert(!(desc->const_param && desc->param), ECS_INVALID_PARAMETER, + "cannot set param and const_param at the same time"); - if (owned) { - ecs_table_t *storage_table = table->storage_table; - int i; - for (i = 0; i < ids->count; i ++) { - ecs_id_t id = ids->array[i]; - const ecs_table_record_t *tr = flecs_get_table_record(world, - storage_table, id); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); - int32_t column = tr->column; - const ecs_type_info_t *ti = &table->type_info[column]; - ecs_iter_action_t on_set = ti->lifecycle.on_set; - if (on_set) { - ecs_column_t *c = &table->storage.columns[column]; - ecs_size_t size = ti->size; - void *ptr = ecs_vector_get_t(c->data, size, ti->alignment, row); - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = ecs_get_type_info(world, desc->event); + ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, + "can only enqueue events with data for events that are components"); - ecs_iter_t it = {.term_count = 1}; - it.entities = entities; - - flecs_iter_init(&it, flecs_iter_cache_all); - it.world = world; - it.real_world = world; - it.table = table; - it.type = table->type; - it.ptrs[0] = ptr; - it.sizes[0] = size; - it.ids[0] = id; - it.event = EcsOnSet; - it.event_id = id; - it.ctx = ti->lifecycle.ctx; - it.count = count; - flecs_iter_validate(&it); - on_set(&it); + void *param_cmd = flecs_stack_alloc(stack, ti->size, ti->alignment); + ecs_assert(param_cmd != NULL, ECS_INTERNAL_ERROR, NULL); + if (desc->param) { + if (ti->hooks.move_ctor) { + ti->hooks.move_ctor(param_cmd, desc->param, 1, ti); + } else { + ecs_os_memcpy(param_cmd, desc->param, ti->size); + } + } else { + if (ti->hooks.copy_ctor) { + ti->hooks.copy_ctor(param_cmd, desc->const_param, 1, ti); + } else { + ecs_os_memcpy(param_cmd, desc->const_param, ti->size); } } - } - /* Run OnSet notifications */ - if (table->flags & EcsTableHasOnSet && ids->count) { - notify(world, table, NULL, row, count, EcsOnSet, ids, 0); + desc_cmd->param = param_cmd; + desc_cmd->const_param = NULL; } } -bool flecs_get_info( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_info_t *info) +static +void flecs_flush_bulk_new( + ecs_world_t *world, + ecs_cmd_t *cmd) { - info->table = NULL; - info->record = NULL; - info->data = NULL; - info->row_flags = 0; - - if (entity & ECS_ROLE) { - return false; - } - - ecs_record_t *record = ecs_eis_get(world, entity); + ecs_entity_t *entities = cmd->is._n.entities; - if (!record) { - return false; + if (cmd->id) { + int i, count = cmd->is._n.count; + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_ensure(world, entities[i]); + if (!r->table) { + flecs_add_to_root_table(world, entities[i]); + } + flecs_add_id(world, entities[i], cmd->id); + } } - set_info_from_record(info, record); - - return true; -} - -uint32_t flecs_record_to_row( - uint32_t row, - bool *is_watched_out) -{ - *is_watched_out = (row & ECS_ROW_FLAGS_MASK) != 0; - return row & ECS_ROW_MASK; -} - -uint32_t flecs_row_to_record( - uint32_t row, - bool is_watched) -{ - return row | (ECS_FLAG_OBSERVED * is_watched); -} - -ecs_ids_t flecs_type_to_ids( - ecs_type_t type) -{ - return (ecs_ids_t){ - .array = ecs_vector_first(type, ecs_entity_t), - .count = ecs_vector_count(type) - }; + ecs_os_free(entities); } -void flecs_add_flag( +static +void flecs_dtor_value( ecs_world_t *world, - ecs_entity_t entity, - uint32_t flag) -{ - (void)world; - - ecs_record_t *record = ecs_eis_get(world, entity); - if (!record) { - ecs_record_t new_record = {.row = flag, .table = NULL}; - ecs_eis_set(world, entity, &new_record); - } else { - record->row |= flag; + ecs_id_t id, + void *value) +{ + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + dtor(value, 1, ti); } } - -/* -- Public functions -- */ - -bool ecs_commit( +static +void flecs_free_cmd_event( ecs_world_t *world, - ecs_entity_t entity, - ecs_record_t *record, - ecs_table_t *table, - ecs_ids_t *added, - ecs_ids_t *removed) + ecs_event_desc_t *desc) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_table_t *src_table = NULL; - if (!record) { - record = ecs_eis_get(world, entity); - src_table = record->table; + if (desc->ids) { + flecs_stack_free_n(desc->ids->array, ecs_id_t, desc->ids->count); } - ecs_entity_info_t info = {0}; - if (record) { - set_info_from_record(&info, record); - } + /* Safe const cast, command makes a copy of type object */ + flecs_stack_free_t(ECS_CONST_CAST(ecs_type_t*, desc->ids), + ecs_type_t); - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; - if (added) { - diff.added = *added; - } - if (removed) { - diff.added = *removed; + if (desc->param) { + flecs_dtor_value(world, desc->event, + /* Safe const cast, command makes copy of value */ + ECS_CONST_CAST(void*, desc->param)); } - - commit(world, entity, &info, table, &diff, true, true); - - return src_table != table; -error: - return false; } -ecs_entity_t ecs_set_with( +static +void flecs_discard_cmd( ecs_world_t *world, - ecs_id_t id) + ecs_cmd_t *cmd) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_id_t prev = stage->with; - stage->with = id; - return prev; -error: - return 0; + if (cmd->kind == EcsCmdBulkNew) { + ecs_os_free(cmd->is._n.entities); + } else if (cmd->kind == EcsCmdEvent) { + flecs_free_cmd_event(world, cmd->is._1.value); + } else { + ecs_assert(cmd->kind != EcsCmdEvent, ECS_INTERNAL_ERROR, NULL); + void *value = cmd->is._1.value; + if (value) { + flecs_dtor_value(world, cmd->id, value); + flecs_stack_free(value, cmd->is._1.size); + } + } } -ecs_id_t ecs_get_with( - const ecs_world_t *world) +static +bool flecs_remove_invalid( + ecs_world_t *world, + ecs_id_t id, + ecs_id_t *id_out) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - return stage->with; -error: - return 0; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + if (!flecs_entities_is_valid(world, rel)) { + /* After relationship is deleted we can no longer see what its + * delete action was, so pretend this never happened */ + *id_out = 0; + return true; + } else { + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + if (!flecs_entities_is_valid(world, tgt)) { + /* Check the relationship's policy for deleted objects */ + ecs_component_record_t *cr = flecs_components_get(world, + ecs_pair(rel, EcsWildcard)); + if (cr) { + ecs_entity_t action = ECS_ID_ON_DELETE_TARGET(cr->flags); + if (action == EcsDelete) { + /* Entity should be deleted, don't bother checking + * other ids */ + return false; + } else if (action == EcsPanic) { + /* If policy is throw this object should not have + * been deleted */ + flecs_throw_invalid_delete(world, id); + } else { + *id_out = 0; + return true; + } + } else { + *id_out = 0; + return true; + } + } + } + } else { + id &= ECS_COMPONENT_MASK; + if (!flecs_entities_is_valid(world, id)) { + /* After relationship is deleted we can no longer see what its + * delete action was, so pretend this never happened */ + *id_out = 0; + return true; + } + } + + return true; } -ecs_entity_t ecs_new_id( - ecs_world_t *world) +static +void flecs_cmd_batch_for_entity( + ecs_world_t *world, + ecs_table_diff_builder_t *diff, + ecs_entity_t entity, + ecs_cmd_t *cmds, + int32_t start) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + world->info.cmd.batched_entity_count ++; - /* It is possible that the world passed to this function is a stage, so - * make sure we have the actual world. Cast away const since this is one of - * the few functions that may modify the world while it is in readonly mode, - * since it is thread safe (uses atomic inc when in threading mode) */ - ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); + bool has_set = false; + ecs_table_t *start_table = table; + ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ + int32_t cur = start; + int32_t next_for_entity; - ecs_entity_t entity; - int32_t stage_count = ecs_get_stage_count(unsafe_world); - if (stage->asynchronous || (ecs_os_has_threading() && stage_count > 1)) { - /* Can't atomically increase number above max int */ - ecs_assert(unsafe_world->info.last_id < UINT_MAX, - ECS_INVALID_OPERATION, NULL); - entity = (ecs_entity_t)ecs_os_ainc( - (int32_t*)&unsafe_world->info.last_id); - } else { - entity = ecs_eis_recycle(unsafe_world); - } + do { + ecs_cmd_t *cmd = &cmds[cur]; + ecs_id_t id = cmd->id; + next_for_entity = cmd->next_for_entity; + if (next_for_entity < 0) { + /* First command for an entity has a negative index, flip sign */ + next_for_entity *= -1; + } - ecs_assert(!unsafe_world->info.max_id || - ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, - ECS_OUT_OF_RANGE, NULL); + /* Check if added id is still valid (like is the parent of a ChildOf + * pair still alive), if not run cleanup actions for entity */ + if (id) { + ecs_component_record_t *cr = NULL; + if ((id < FLECS_HI_COMPONENT_ID)) { + if (world->non_trivial_lookup[id]) { + cr = flecs_components_get(world, id); + } + } else { + cr = flecs_components_get(world, id); + } + + if (cr && cr->flags & EcsIdDontFragment) { + /* Nothing to batch for non-fragmenting components */ + continue; + } - return entity; -error: - return 0; -} + if (flecs_remove_invalid(world, id, &id)) { + if (!id) { + /* Entity should remain alive but id should not be added */ + cmd->kind = EcsCmdSkip; + continue; + } + /* Entity should remain alive and id is still valid */ + } else { + /* Id was no longer valid and had a Delete policy */ + cmd->kind = EcsCmdSkip; + ecs_delete(world, entity); + flecs_table_diff_builder_clear(diff); + return; + } + } -ecs_entity_t ecs_new_low_id( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_cmd_kind_t kind = cmd->kind; + switch(kind) { + case EcsCmdAddModified: + /* Add is batched, but keep Modified */ + cmd->kind = EcsCmdModified; + table = flecs_find_table_add(world, table, id, diff); + world->info.cmd.batched_command_count ++; + break; + case EcsCmdAdd: + table = flecs_find_table_add(world, table, id, diff); + world->info.cmd.batched_command_count ++; + cmd->kind = EcsCmdSkip; + break; + case EcsCmdSet: + case EcsCmdEnsure: { + table = flecs_find_table_add(world, table, id, diff); + world->info.cmd.batched_command_count ++; + has_set = true; + break; + } + case EcsCmdEmplace: + /* Don't add for emplace, as this requires a special call to ensure + * the constructor is not invoked for the component */ + break; + case EcsCmdRemove: { + table = flecs_find_table_remove(world, table, id, diff); + world->info.cmd.batched_command_count ++; + cmd->kind = EcsCmdSkip; + break; + } + case EcsCmdClear: + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (table->type.count) { + ecs_id_t *ids = ecs_vec_grow_t(&world->allocator, + &diff->removed, ecs_id_t, table->type.count); + ecs_os_memcpy_n(ids, table->type.array, ecs_id_t, + table->type.count); + diff->removed_flags |= table->flags & EcsTableRemoveEdgeFlags; + } + table = &world->store.root; + world->info.cmd.batched_command_count ++; + cmd->kind = EcsCmdSkip; + break; + case EcsCmdClone: + case EcsCmdBulkNew: + case EcsCmdPath: + case EcsCmdDelete: + case EcsCmdOnDeleteAction: + case EcsCmdEnable: + case EcsCmdDisable: + case EcsCmdEvent: + case EcsCmdSkip: + case EcsCmdModifiedNoHook: + case EcsCmdModified: + break; + } + } while ((cur = next_for_entity)); - /* It is possible that the world passed to this function is a stage, so - * make sure we have the actual world. Cast away const since this is one of - * the few functions that may modify the world while it is in readonly mode, - * but only if single threaded. */ - ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); - if (unsafe_world->is_readonly) { - /* Can't issue new comp id while iterating when in multithreaded mode */ - ecs_check(ecs_get_stage_count(world) <= 1, - ECS_INVALID_WHILE_ITERATING, NULL); - } + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t id = 0; - if (unsafe_world->info.last_component_id < ECS_HI_COMPONENT_ID) { + /* Invoke OnAdd handlers after commit. This ensures that observers with + * mixed OnAdd/OnSet events won't get called with uninitialized values for + * an OnSet field. */ + ecs_type_t added = { diff->added.array, diff->added.count }; + diff->added.array = NULL; + diff->added.count = 0; + + /* Move entity to destination table in single operation */ + flecs_table_diff_build_noalloc(diff, &table_diff); + flecs_defer_begin(world, world->stages[0]); + flecs_commit(world, entity, r, table, &table_diff, true, 0); + flecs_defer_end(world, world->stages[0]); + + /* If destination table has new sparse components, make sure they're created + * for the entity. */ + if ((table_diff.added_flags & (EcsTableHasSparse|EcsTableHasDontFragment)) && added.count) { + flecs_sparse_on_add( + world, table, ECS_RECORD_TO_ROW(r->row), 1, &added, true); + } + + /* If the batch contains set commands, copy the component value from the + * temporary command storage to the actual component storage before OnSet + * observers are invoked. This ensures that for multi-component OnSet + * observers all components are assigned a valid value before the observer + * is invoked. + * This only happens for entities that didn't have the assigned component + * yet, as for entities that did have the component already the value will + * have been assigned directly to the component storage. */ + if (has_set) { + cur = start; do { - id = unsafe_world->info.last_component_id ++; - } while (ecs_exists(unsafe_world, id) && id <= ECS_HI_COMPONENT_ID); - } + ecs_cmd_t *cmd = &cmds[cur]; + next_for_entity = cmd->next_for_entity; + if (next_for_entity < 0) { + next_for_entity *= -1; + } - if (!id || id >= ECS_HI_COMPONENT_ID) { - /* If the low component ids are depleted, return a regular entity id */ - id = ecs_new_id(unsafe_world); + switch(cmd->kind) { + case EcsCmdSet: + case EcsCmdEnsure: { + flecs_component_ptr_t dst = flecs_get_mut( + world, entity, cmd->id, r, cmd->is._1.size); + + /* It's possible that even though the component was set, the + * command queue also contained a remove command, so before we + * do anything ensure the entity actually has the component. */ + if (dst.ptr) { + void *ptr = cmd->is._1.value; + const ecs_type_info_t *ti = dst.ti; + if (ti->hooks.on_replace) { + ecs_table_t *prev_table = r->table; + flecs_invoke_replace_hook(world, prev_table, entity, + cmd->id, dst.ptr, ptr, ti); + if (prev_table != r->table) { + if (!r->table) { + /* Entity was deleted */ + goto done; + } + dst = flecs_get_mut( + world, entity, cmd->id, r, cmd->is._1.size); + } + } + + ecs_move_t move = ti->hooks.move; + if (move) { + move(dst.ptr, ptr, 1, ti); + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + dtor(ptr, 1, ti); + } + } else { + ecs_os_memcpy(dst.ptr, ptr, ti->size); + } + + flecs_stack_free(ptr, cmd->is._1.size); + cmd->is._1.value = NULL; + + if (cmd->kind == EcsCmdSet) { + /* A set operation is add + copy + modified. We just did + * the add and copy, so the only thing that's left is a + * modified command, which will call the OnSet + * observers. */ + cmd->kind = EcsCmdModified; + } else { + /* If this was an ensure, nothing's left to be done */ + cmd->kind = EcsCmdSkip; + } + } else { + /* The entity no longer has the component which means that + * there was a remove command for the component in the + * command queue. In that case skip the command. */ + cmd->kind = EcsCmdSkip; + } + break; + } + case EcsCmdRemove: + case EcsCmdClone: + case EcsCmdBulkNew: + case EcsCmdAdd: + case EcsCmdEmplace: + case EcsCmdModified: + case EcsCmdModifiedNoHook: + case EcsCmdAddModified: + case EcsCmdPath: + case EcsCmdDelete: + case EcsCmdClear: + case EcsCmdOnDeleteAction: + case EcsCmdEnable: + case EcsCmdDisable: + case EcsCmdEvent: + case EcsCmdSkip: + break; + } + } while ((cur = next_for_entity)); + } + + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (added.count) { + ecs_table_diff_t add_diff = ECS_TABLE_DIFF_INIT; + add_diff.added = added; + add_diff.added_flags = diff->added_flags; + + if (r->row & EcsEntityIsTraversable) { + /* Update monitors since we didn't do this in flecs_commit. Do this + * before calling flecs_actions_move_add() since this can trigger + * prefab instantiation logic. When that happens, prefab children + * can be created for this instance which would mean that the table + * count of r->cr would always be >0. + * Since those tables are new, we don't have to invoke component + * monitors since queries will have correctly matched them. */ + ecs_component_record_t *cr = flecs_components_get( + world, ecs_pair(EcsWildcard, entity)); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + if (ecs_map_count(&cr->cache.index)) { + flecs_update_component_monitors(world, &added, NULL); + } + } + + flecs_defer_begin(world, world->stages[0]); + flecs_actions_move_add(world, r->table, start_table, + ECS_RECORD_TO_ROW(r->row), 1, &add_diff, 0, true, false); + flecs_defer_end(world, world->stages[0]); } - return id; -error: - return 0; +done: + diff->added.array = added.array; + diff->added.count = added.count; + flecs_table_diff_builder_clear(diff); } -ecs_entity_t ecs_new_w_id( +/* Leave safe section. Run all deferred commands. */ +bool flecs_defer_end( ecs_world_t *world, - ecs_id_t id) + ecs_stage_t *stage) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_poly_assert(world, ecs_world_t); + flecs_poly_assert(stage, ecs_stage_t); - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_entity_t entity = ecs_new_id(world); + flecs_check_exclusive_world_access_write(world); - ecs_id_t ids[3]; - ecs_ids_t to_add = { .array = ids, .count = 0 }; - - if (id) { - ids[to_add.count ++] = id; + if (stage->defer < 0) { + /* Defer suspending makes it possible to do operations on the storage + * without flushing the commands in the queue */ + return false; } - ecs_id_t with = stage->with; - if (with) { - ids[to_add.count ++] = with; - } + ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t scope = stage->scope; - if (scope) { - if (!id || !ECS_HAS_RELATION(id, EcsChildOf)) { - ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); - } - } - if (to_add.count) { - if (flecs_defer_new(world, stage, entity, to_add.array[0])) { - int i; - for (i = 1; i < to_add.count; i ++) { - flecs_defer_add(world, stage, entity, to_add.array[i]); - } - return entity; - } + if (!--stage->defer && !stage->cmd_flushing) { + ecs_os_perf_trace_push("flecs.commands.merge"); - new(world, entity, &to_add); - } else { - if (flecs_defer_new(world, stage, entity, 0)) { - return entity; + /* Test whether we're flushing to another queue or whether we're + * flushing to the storage */ + bool merge_to_world = false; + if (flecs_poly_is(world, ecs_world_t)) { + merge_to_world = world->stages[0]->defer == 0; } - ecs_eis_set(world, entity, &(ecs_record_t){ 0 }); - } - flecs_defer_flush(world, stage); - - return entity; -error: - return 0; -} + do { + ecs_stage_t *dst_stage = flecs_stage_from_world(&world); + ecs_commands_t *commands = stage->cmd; + ecs_vec_t *queue = &commands->queue; -#ifdef FLECS_PARSER + if (stage->cmd == &stage->cmd_stack[0]) { + stage->cmd = &stage->cmd_stack[1]; + } else { + stage->cmd = &stage->cmd_stack[0]; + } -/* Traverse table graph by either adding or removing identifiers parsed from the - * passed in expression. */ -static -ecs_table_t *traverse_from_expr( - ecs_world_t *world, - ecs_table_t *table, - const char *name, - const char *expr, - ecs_table_diff_t *diff, - bool replace_and, - bool *error) -{ - const char *ptr = expr; - if (ptr) { - ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ - if (!ecs_term_is_initialized(&term)) { + if (!ecs_vec_count(queue)) { break; } - if (term.pred.set.mask == EcsDefaultSet) { - term.pred.set.mask = EcsSelf; - } - if (term.subj.set.mask == EcsDefaultSet) { - term.subj.set.mask = EcsSelf; - } - if (term.obj.set.mask == EcsDefaultSet) { - term.obj.set.mask = EcsSelf; + stage->cmd_flushing = true; + + /* Internal callback for capturing commands */ + if (world->on_commands_active) { + world->on_commands_active(stage, queue, + world->on_commands_ctx_active); } - if (ecs_term_finalize(world, name, &term)) { - ecs_term_fini(&term); - if (error) { - *error = true; + ecs_cmd_t *cmds = ecs_vec_first(queue); + int32_t i, count = ecs_vec_count(queue); + + ecs_table_diff_builder_t diff = {0}; + bool diff_builder_used = false; + + for (i = 0; i < count; i ++) { + ecs_cmd_t *cmd = &cmds[i]; + ecs_entity_t e = cmd->entity; + bool is_alive = flecs_entities_is_alive(world, e); + + /* A negative index indicates the first command for an entity */ + if (merge_to_world && (cmd->next_for_entity < 0)) { + diff.added_flags = 0; + diff.removed_flags = 0; + + /* Batch commands for entity to limit archetype moves */ + if (is_alive) { + if (!diff_builder_used) { + flecs_table_diff_builder_init(world, &diff); + diff_builder_used = true; + } + + flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); + } else { + world->info.cmd.discard_count ++; + } + } + + /* Invalidate entry */ + if (cmd->entry) { + cmd->entry->first = -1; } - return NULL; - } - if (!ecs_term_is_trivial(&term)) { - ecs_term_fini(&term); - if (error) { - *error = true; + /* If entity is no longer alive, this could be because the queue + * contained both a delete and a subsequent add/remove/set which + * should be ignored. */ + ecs_cmd_kind_t kind = cmd->kind; + if ((kind != EcsCmdPath) && ((kind == EcsCmdSkip) || (e && !is_alive))) { + world->info.cmd.discard_count ++; + flecs_discard_cmd(world, cmd); + continue; } - ecs_parser_error(name, expr, (ptr - expr), - "invalid non-trivial term in add expression"); - return NULL; - } - if (term.role == ECS_CASE) { - table = table_append(world, table, - ECS_SWITCH | ECS_PAIR_FIRST(term.id), diff); - term.id = ECS_CASE | ECS_PAIR_SECOND(term.id); - } + ecs_id_t id = cmd->id; - if (term.oper == EcsAnd || !replace_and) { - /* Regular AND expression */ - table = table_append(world, table, term.id, diff); - } else if (term.oper == EcsAndFrom) { - /* Add all components from the specified type */ - const EcsType *t = ecs_get(world, term.id, EcsType); - if (!t) { - if (error) { - *error = true; + switch(kind) { + case EcsCmdAdd: + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + if (flecs_remove_invalid(world, id, &id)) { + if (id) { + world->info.cmd.add_count ++; + flecs_add_id(world, e, id); + } else { + world->info.cmd.discard_count ++; + } + } else { + world->info.cmd.discard_count ++; + ecs_delete(world, e); } - ecs_parser_error(name, expr, (ptr - expr), - "expected type for AND role"); - return NULL; + break; + case EcsCmdRemove: + flecs_remove_id(world, e, id); + world->info.cmd.remove_count ++; + break; + case EcsCmdClone: + ecs_clone(world, e, id, cmd->is._1.clone_value); + world->info.cmd.other_count ++; + break; + case EcsCmdSet: + flecs_set_id_move(world, dst_stage, e, + cmd->id, flecs_itosize(cmd->is._1.size), + cmd->is._1.value, kind); + world->info.cmd.set_count ++; + break; + case EcsCmdEmplace: + if (merge_to_world) { + bool is_new; + ecs_emplace_id(world, e, id, + flecs_itosize(cmd->is._1.size), &is_new); + if (!is_new) { + kind = EcsCmdEnsure; + } + } + flecs_set_id_move(world, dst_stage, e, + cmd->id, flecs_itosize(cmd->is._1.size), + cmd->is._1.value, kind); + world->info.cmd.ensure_count ++; + break; + case EcsCmdEnsure: + flecs_set_id_move(world, dst_stage, e, + cmd->id, flecs_itosize(cmd->is._1.size), + cmd->is._1.value, kind); + world->info.cmd.ensure_count ++; + break; + case EcsCmdModified: + flecs_modified_id_if(world, e, id, true); + world->info.cmd.modified_count ++; + break; + case EcsCmdModifiedNoHook: + flecs_modified_id_if(world, e, id, false); + world->info.cmd.modified_count ++; + break; + case EcsCmdAddModified: + flecs_add_id(world, e, id); + flecs_modified_id_if(world, e, id, true); + world->info.cmd.set_count ++; + break; + case EcsCmdDelete: { + ecs_delete(world, e); + world->info.cmd.delete_count ++; + break; } - - ecs_id_t *ids = ecs_vector_first(t->normalized->type, ecs_id_t); - int32_t i, count = ecs_vector_count(t->normalized->type); - for (i = 0; i < count; i ++) { - table = table_append(world, table, ids[i], diff); + case EcsCmdClear: + ecs_clear(world, e); + world->info.cmd.clear_count ++; + break; + case EcsCmdOnDeleteAction: + ecs_defer_begin(world); + flecs_on_delete(world, id, e, false, false); + ecs_defer_end(world); + world->info.cmd.other_count ++; + break; + case EcsCmdEnable: + ecs_enable_id(world, e, id, true); + world->info.cmd.other_count ++; + break; + case EcsCmdDisable: + ecs_enable_id(world, e, id, false); + world->info.cmd.other_count ++; + break; + case EcsCmdBulkNew: + flecs_flush_bulk_new(world, cmd); + world->info.cmd.other_count ++; + continue; + case EcsCmdPath: { + bool keep_alive = true; + ecs_make_alive(world, e); + if (cmd->id) { + if (ecs_is_alive(world, cmd->id)) { + ecs_add_pair(world, e, EcsChildOf, cmd->id); + } else { + ecs_delete(world, e); + keep_alive = false; + } + } + if (keep_alive) { + ecs_set_name(world, e, cmd->is._1.value); + } + ecs_os_free(cmd->is._1.value); + cmd->is._1.value = NULL; + world->info.cmd.other_count ++; + break; + } + case EcsCmdEvent: { + ecs_event_desc_t *desc = cmd->is._1.value; + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_emit((ecs_world_t*)stage, desc); + flecs_free_cmd_event(world, desc); + world->info.cmd.event_count ++; + break; + } + case EcsCmdSkip: + break; + } + + if (cmd->is._1.value) { + flecs_stack_free(cmd->is._1.value, cmd->is._1.size); } } - ecs_term_fini(&term); - } + stage->cmd_flushing = false; - if (!ptr) { - if (error) { - *error = true; + flecs_stack_reset(&commands->stack); + ecs_vec_clear(queue); + + if (diff_builder_used) { + flecs_table_diff_builder_fini(world, &diff); } - return NULL; - } + + /* Internal callback for capturing commands, signal queue is done */ + if (world->on_commands_active) { + world->on_commands_active(stage, NULL, + world->on_commands_ctx_active); + } + } while (true); + + ecs_os_perf_trace_pop("flecs.commands.merge"); + + return true; } - return table; + return false; } -/* Add/remove components based on the parsed expression. This operation is - * slower than traverse_from_expr, but safe to use from a deferred context. */ -static -void defer_from_expr( +/* Delete operations from queue without executing them. */ +bool flecs_defer_purge( ecs_world_t *world, - ecs_entity_t entity, - const char *name, - const char *expr, - bool is_add, - bool replace_and) + ecs_stage_t *stage) { - const char *ptr = expr; - if (ptr) { - ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ - if (!ecs_term_is_initialized(&term)) { - break; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - if (ecs_term_finalize(world, name, &term)) { - return; - } + if (!--stage->defer) { + ecs_vec_t commands = stage->cmd->queue; - if (!ecs_term_is_trivial(&term)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid non-trivial term in add expression"); - return; + if (ecs_vec_count(&commands)) { + ecs_cmd_t *cmds = ecs_vec_first(&commands); + int32_t i, count = ecs_vec_count(&commands); + for (i = 0; i < count; i ++) { + flecs_discard_cmd(world, &cmds[i]); } - if (term.oper == EcsAnd || !replace_and) { - /* Regular AND expression */ - if (is_add) { - ecs_add_id(world, entity, term.id); - } else { - ecs_remove_id(world, entity, term.id); - } - } else if (term.oper == EcsAndFrom) { - /* Add all components from the specified type */ - const EcsType *t = ecs_get(world, term.id, EcsType); - if (!t) { - ecs_parser_error(name, expr, (ptr - expr), - "expected type for AND role"); - return; - } - - ecs_id_t *ids = ecs_vector_first(t->normalized->type, ecs_id_t); - int32_t i, count = ecs_vector_count(t->normalized->type); - for (i = 0; i < count; i ++) { - if (is_add) { - ecs_add_id(world, entity, ids[i]); - } else { - ecs_remove_id(world, entity, ids[i]); - } - } - } + ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); - ecs_term_fini(&term); + ecs_vec_clear(&commands); + flecs_stack_reset(&stage->cmd->stack); + flecs_sparse_clear(&stage->cmd->entries); } + + return true; } + +error: + return false; } -#endif -/* If operation is not deferred, add components by finding the target - * table and moving the entity towards it. */ -static -int traverse_add( - ecs_world_t *world, - ecs_entity_t result, - const char *name, - const ecs_entity_desc_t *desc, - ecs_entity_t scope, - ecs_id_t with, - bool new_entity, - bool name_assigned) +void flecs_commands_init( + ecs_stage_t *stage, + ecs_commands_t *cmd) { - const char *sep = desc->sep; - const char *root_sep = desc->root_sep; + flecs_stack_init(&cmd->stack); + ecs_vec_init_t(&stage->allocator, &cmd->queue, ecs_cmd_t, 0); + flecs_sparse_init_t(&cmd->entries, &stage->allocator, + &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); +} - /* Find existing table */ - ecs_entity_info_t info = {0}; - ecs_table_t *src_table = NULL, *table = NULL; - if (!new_entity) { - if (flecs_get_info(world, result, &info)) { - table = info.table; - } - } +void flecs_commands_fini( + ecs_stage_t *stage, + ecs_commands_t *cmd) +{ + /* Make sure stage has no unmerged data */ + ecs_assert(ecs_vec_count(&cmd->queue) == 0, ECS_INTERNAL_ERROR, NULL); - /* Find destination table */ - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + flecs_stack_fini(&cmd->stack); + ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); + flecs_sparse_fini(&cmd->entries); +} - /* If this is a new entity without a name, add the scope. If a name is - * provided, the scope will be added by the add_path_w_sep function */ - if (new_entity) { - if (new_entity && scope && !name && !name_assigned) { - table = table_append( - world, table, ecs_pair(EcsChildOf, scope), &diff); - } - if (with) { - table = table_append(world, table, with, &diff); - } - } +bool ecs_defer_begin( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + return flecs_defer_begin(world, stage); +error: + return false; +} - /* If a name is provided but not yet assigned, add the Name component */ - if (name && !name_assigned) { - table = table_append(world, table, - ecs_pair(ecs_id(EcsIdentifier), EcsName), &diff); - } +bool ecs_defer_end( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + return flecs_defer_end(world, stage); +error: + return false; +} - /* Add components from the 'add' id array */ - int32_t i = 0; - ecs_id_t id; - const ecs_id_t *ids = desc->add; - while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { - bool should_add = true; - if (ECS_HAS_ROLE(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { - scope = ECS_PAIR_SECOND(id); - if (!desc->entity || (name && !name_assigned)) { - /* If name is added to entity, pass scope to add_path instead - * of adding it to the table. The provided name may have nested - * elements, in which case the parent provided here is not the - * parent the entity will end up with. */ - should_add = false; - } - } - if (should_add) { - table = table_append(world, table, id, &diff); - } - } +void ecs_defer_suspend( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, + "world/stage must be deferred before it can be suspended"); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(stage->defer > 0, ECS_INVALID_OPERATION, + "world/stage is already suspended"); + stage->defer = -stage->defer; +error: + return; +} - /* Add components from the 'add_expr' expression */ - if (desc->add_expr) { -#ifdef FLECS_PARSER - bool error = false; - table = traverse_from_expr( - world, table, name, desc->add_expr, &diff, true, &error); - if (error) { - return -1; - } -#else - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); -#endif - } +void ecs_defer_resume( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(stage->defer < 0, ECS_INVALID_OPERATION, + "world/stage must be suspended before it can be resumed"); + stage->defer = -stage->defer; +error: + return; +} - /* Commit entity to destination table */ - if (src_table != table) { - ecs_defer_begin(world); - commit(world, result, &info, table, &diff, true, true); - ecs_defer_end(world); - } +/** + * @file component_actions.c + * @brief Logic executed after adding/removing a component. + * + * After a component is added to an entity there can be additional things that + * need to be done, such as: + * + * - Invoking hooks + * - Notifying observers + * - Updating sparse storage + * - Update name lookup index + */ - /* Set name */ - if (name && !name_assigned) { - ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); - ecs_assert(ecs_get_name(world, result) != NULL, - ECS_INTERNAL_ERROR, NULL); + +void flecs_invoke_hook( + ecs_world_t *world, + ecs_table_t *table, + const ecs_component_record_t *cr, + const ecs_table_record_t *tr, + int32_t count, + int32_t row, + const ecs_entity_t *entities, + ecs_id_t id, + const ecs_type_info_t *ti, + ecs_entity_t event, + ecs_iter_action_t hook) +{ + int32_t defer = world->stages[0]->defer; + if (defer < 0) { + world->stages[0]->defer *= -1; } - if (desc->symbol && desc->symbol[0]) { - const char *sym = ecs_get_symbol(world, result); - if (sym) { - ecs_assert(!ecs_os_strcmp(desc->symbol, sym), - ECS_INCONSISTENT_NAME, desc->symbol); - } else { - ecs_set_symbol(world, result, desc->symbol); - } + ecs_iter_t it = { .field_count = 1 }; + it.entities = entities; + + ecs_table_record_t dummy_tr; + if (!tr) { + dummy_tr.hdr.cr = ECS_CONST_CAST(ecs_component_record_t*, cr); + dummy_tr.hdr.table = table; + dummy_tr.index = -1; + dummy_tr.column = -1; + dummy_tr.count = 0; + tr = &dummy_tr; } - diff_free(&diff); + ecs_entity_t dummy_src = 0; - return 0; + it.world = world; + it.real_world = world; + it.table = table; + it.trs = &tr; + it.row_fields = !!(tr->hdr.cr->flags & EcsIdSparse); + it.ref_fields = it.row_fields; + it.sizes = ECS_CONST_CAST(ecs_size_t*, &ti->size); + it.ids = &id; + it.sources = &dummy_src; + it.event = event; + it.event_id = id; + it.ctx = ti->hooks.ctx; + it.callback_ctx = ti->hooks.binding_ctx; + it.count = count; + it.offset = row; + it.flags = EcsIterIsValid; + + hook(&it); + + world->stages[0]->defer = defer; } -/* When in deferred mode, we need to add/remove components one by one using - * the regular operations. */ -static -void deferred_add_remove( +void flecs_invoke_replace_hook( ecs_world_t *world, + ecs_table_t *table, ecs_entity_t entity, - const char *name, - const ecs_entity_desc_t *desc, - ecs_entity_t scope, - ecs_id_t with, - bool new_entity, - bool name_assigned) + ecs_id_t id, + const void *old_ptr, + const void *new_ptr, + const ecs_type_info_t *ti) { - const char *sep = desc->sep; - const char *root_sep = desc->root_sep; + int32_t defer = world->stages[0]->defer; + if (defer < 0) { + world->stages[0]->defer *= -1; + } - /* If this is a new entity without a name, add the scope. If a name is - * provided, the scope will be added by the add_path_w_sep function */ - if (new_entity) { - if (new_entity && scope && !name && !name_assigned) { - ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); - } + ecs_iter_t it = { .field_count = 2 }; + it.entities = &entity; - if (with) { - ecs_add_id(world, entity, with); - } - } + const ecs_table_record_t *trs[] = {NULL, NULL}; + ecs_size_t sizes[] = {ti->size, ti->size}; + ecs_id_t ids[] = {id, id}; + ecs_entity_t srcs[] = {0, 0}; + const void *ptrs[] = {old_ptr, new_ptr}; - /* Add components from the 'add' id array */ - int32_t i = 0; - ecs_id_t id; - const ecs_id_t *ids = desc->add; - while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { - bool defer = true; - if (ECS_HAS_ROLE(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { - scope = ECS_PAIR_SECOND(id); - if (!desc->entity || (name && !name_assigned)) { - /* New named entities are created by temporarily going out of - * readonly mode to ensure no duplicates are created. */ - defer = false; - } - } - if (defer) { - ecs_add_id(world, entity, id); - } - } + it.world = world; + it.real_world = world; + it.table = table; + it.trs = trs; + it.row_fields = 0; + it.ref_fields = it.row_fields; + it.sizes = sizes; + it.ptrs = ECS_CONST_CAST(void**, ptrs); + it.ids = ids; + it.sources = srcs; + it.event = 0; + it.event_id = id; + it.ctx = ti->hooks.ctx; + it.callback_ctx = ti->hooks.binding_ctx; + it.count = 1; + it.offset = 0; /* Don't set row because we don't want to offset ptrs */ + it.flags = EcsIterIsValid; - /* Add components from the 'add_expr' expression */ - if (desc->add_expr) { -#ifdef FLECS_PARSER - defer_from_expr(world, entity, name, desc->add_expr, true, true); -#else - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); -#endif + ti->hooks.on_replace(&it); + + world->stages[0]->defer = defer; +} + +static +void flecs_on_reparent( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count) +{ + flecs_reparent_name_index(world, table, other_table, row, count); + flecs_ordered_children_reparent(world, table, other_table, row, count); + flecs_non_fragmenting_childof_reparent(world, table, other_table, row, count); +} + +static +void flecs_on_unparent( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count) +{ + if (other_table) { + flecs_unparent_name_index(world, table, other_table, row, count); } + flecs_ordered_children_unparent(world, table, row, count); + flecs_non_fragmenting_childof_unparent(world, other_table, table, row, count); +} - int32_t thread_count = ecs_get_stage_count(world); +bool flecs_sparse_on_add_cr( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_component_record_t *cr, + bool construct, + void **ptr_out) +{ + bool is_new = false; - /* Set name */ - if (name && !name_assigned) { - /* To prevent creating two entities with the same name, temporarily go - * out of readonly mode if it's safe to do so. */ - ecs_suspend_readonly_state_t state; - if (thread_count <= 1) { - /* When not running on multiple threads we can temporarily leave - * readonly mode which ensures that we don't accidentally create - * two entities with the same name. */ - ecs_world_t *real_world = flecs_suspend_readonly(world, &state); - ecs_add_path_w_sep(real_world, entity, scope, name, sep, root_sep); - flecs_resume_readonly(real_world, &state); + if (cr && cr->flags & EcsIdSparse) { + void *result = NULL; + int32_t sparse_count = flecs_sparse_count(cr->sparse); + + if (construct) { + result = flecs_component_sparse_insert( + world, cr, table, row); } else { - /* In multithreaded mode we can't leave readonly mode, which means - * there is a risk of creating two entities with the same name. - * Future improvements will be able to detect this. */ - ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); + result = flecs_component_sparse_emplace( + world, cr, table, row); } - } - /* Set symbol */ - if (desc->symbol) { - const char *sym = ecs_get_symbol(world, entity); - if (!sym || ecs_os_strcmp(sym, desc->symbol)) { - if (thread_count <= 1) { /* See above */ - ecs_suspend_readonly_state_t state; - ecs_world_t *real_world = flecs_suspend_readonly(world, &state); - ecs_set_symbol(world, entity, desc->symbol); - flecs_resume_readonly(real_world, &state); - } else { - ecs_set_symbol(world, entity, desc->symbol); + if (ptr_out) { + *ptr_out = result; + } + + if (cr->flags & EcsIdDontFragment) { + is_new = sparse_count != flecs_sparse_count(cr->sparse); + if (is_new) { + const ecs_entity_t *entities = ecs_table_entities(table); + ecs_record_t *r = flecs_entities_get(world, entities[row]); + r->row |= EcsEntityHasDontFragment; } } } + + return is_new; } -ecs_entity_t ecs_entity_init( +bool flecs_sparse_on_add( ecs_world_t *world, - const ecs_entity_desc_t *desc) + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_type_t *added, + bool construct) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + bool is_new = false; - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_entity_t scope = stage->scope; - ecs_id_t with = ecs_get_with(world); + int32_t i, j; + for (i = 0; i < added->count; i ++) { + ecs_id_t id = added->array[i]; + ecs_component_record_t *cr = flecs_components_get(world, id); - const char *name = desc->name; - const char *sep = desc->sep; - if (!sep) { - sep = "."; + for (j = 0; j < count; j ++) { + is_new |= flecs_sparse_on_add_cr( + world, table, row + j, cr, construct, NULL); + } } - if (name && !name[0]) { - name = NULL; - } + return is_new; +} - const char *root_sep = desc->root_sep; - bool new_entity = false; - bool name_assigned = false; +static +void flecs_sparse_on_remove( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_type_t *removed) +{ + int32_t i, j; + for (i = 0; i < removed->count; i ++) { + ecs_id_t id = removed->array[i]; + ecs_component_record_t *cr = flecs_components_get(world, id); + if (cr && cr->flags & EcsIdSparse) { + for (j = 0; j < count; j ++) { + flecs_component_sparse_remove(world, cr, table, row + j); + } + } + } +} - /* Remove optional prefix from name. Entity names can be derived from - * language identifiers, such as components (typenames) and systems - * function names). Because C does not have namespaces, such identifiers - * often encode the namespace as a prefix. - * To ensure interoperability between C and C++ (and potentially other - * languages with namespacing) the entity must be stored without this prefix - * and with the proper namespace, which is what the name_prefix is for */ - const char *prefix = world->name_prefix; - if (name && prefix) { - ecs_size_t len = ecs_os_strlen(prefix); - if (!ecs_os_strncmp(name, prefix, len) && - (isupper(name[len]) || name[len] == '_')) - { - if (name[len] == '_') { - name = name + len + 1; - } else { - name = name + len; +static +bool flecs_dont_fragment_on_remove( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_type_t *removed) +{ + int32_t i, j; + for (i = 0; i < removed->count; i ++) { + ecs_id_t id = removed->array[i]; + ecs_component_record_t *cr = flecs_components_get(world, id); + if (cr && cr->flags & EcsIdDontFragment) { + const ecs_entity_t *entities = ecs_table_entities(table); + for (j = 0; j < count; j ++) { + ecs_entity_t e = entities[row + j]; + if (flecs_component_sparse_has(cr, e)) { + return true; + } } } } - /* Find or create entity */ - ecs_entity_t result = desc->entity; - if (!result) { - if (name) { - /* If add array contains a ChildOf pair, use it as scope instead */ - const ecs_id_t *ids = desc->add; - ecs_id_t id; - int32_t i = 0; - while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { - if (ECS_HAS_ROLE(id, PAIR) && - (ECS_PAIR_FIRST(id) == EcsChildOf)) - { - scope = ECS_PAIR_SECOND(id); - } - } - - result = ecs_lookup_path_w_sep( - world, scope, name, sep, root_sep, false); - if (result) { - name_assigned = true; - } - } + return false; +} - if (!result) { - if (desc->use_low_id) { - result = ecs_new_low_id(world); - } else { - result = ecs_new_id(world); - } - new_entity = true; - ecs_assert(ecs_get_type(world, result) == NULL, - ECS_INTERNAL_ERROR, NULL); - } - } else { - /* Make sure provided id is either alive or revivable */ - ecs_ensure(world, result); +void flecs_entity_remove_non_fragmenting( + ecs_world_t *world, + ecs_entity_t e, + ecs_record_t *r) +{ + if (!r) { + r = flecs_entities_get(world, e); + } - name_assigned = ecs_has_pair( - world, result, ecs_id(EcsIdentifier), EcsName); - if (name && name_assigned) { - /* If entity has name, verify that name matches */ - char *path = ecs_get_path_w_sep(world, scope, result, sep, NULL); - if (path) { - if (ecs_os_strcmp(path, name)) { - /* Mismatching name */ - ecs_os_free(path); - return 0; - } - ecs_os_free(path); - } - } + if (!r || !(r->row & EcsEntityHasDontFragment)) { + return; } - ecs_assert(name_assigned == ecs_has_pair( - world, result, ecs_id(EcsIdentifier), EcsName), - ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *cur = world->cr_non_fragmenting_head; + while (cur) { + ecs_assert(cur->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); + if (cur->sparse && !(ecs_id_is_wildcard(cur->id))) { + if (flecs_sparse_has(cur->sparse, e)) { + ecs_type_t type = { .count = 1, .array = &cur->id }; - if (stage->defer) { - deferred_add_remove((ecs_world_t*)stage, result, name, desc, - scope, with, new_entity, name_assigned); - } else { - if (traverse_add(world, result, name, desc, - scope, with, new_entity, name_assigned)) - { - return 0; + flecs_emit(world, world, &(ecs_event_desc_t) { + .event = EcsOnRemove, + .ids = &type, + .table = r->table, + .other_table = r->table, + .offset = ECS_RECORD_TO_ROW(r->row), + .count = 1, + .observable = world + }); + + flecs_component_sparse_remove( + world, cur, r->table, ECS_RECORD_TO_ROW(r->row)); + } } + + cur = cur->non_fragmenting.next; } - return result; -error: - return 0; + r->row &= ~EcsEntityHasDontFragment; } -const ecs_entity_t* ecs_bulk_init( +static +void flecs_actions_on_add_intern( ecs_world_t *world, - const ecs_bulk_desc_t *desc) + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff, + ecs_flags32_t flags, + bool construct, + bool sparse) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + ecs_flags32_t diff_flags = diff->added_flags; + if (!diff_flags) { + return; + } - const ecs_entity_t *entities = desc->entities; - int32_t count = desc->count; + const ecs_type_t *added = &diff->added; - int32_t sparse_count = 0; - if (!entities) { - sparse_count = ecs_eis_count(world); - entities = flecs_sparse_new_ids(ecs_eis(world), count); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - } else { - int i; - for (i = 0; i < count; i ++) { - ecs_ensure(world, entities[i]); - } + if (diff_flags & EcsTableEdgeReparent) { + flecs_on_reparent(world, table, other_table, row, count); } - ecs_ids_t ids; - ecs_table_t *table = desc->table; - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; - if (!table) { - int32_t i = 0; - ecs_id_t id; - while ((id = desc->ids[i])) { - table = table_append(world, table, id, &diff); - i ++; + if (sparse && (diff_flags & EcsTableHasSparse)) { + if (flecs_sparse_on_add(world, table, row, count, added, construct)) { + diff_flags |= EcsTableHasOnAdd; } - - ids.array = (ecs_id_t*)desc->ids; - ids.count = i; - } else { - diff.added.array = ecs_vector_first(table->type, ecs_id_t); - diff.added.count = ecs_vector_count(table->type); - - ids = diff.added; } - new_w_data( - world, table, entities, &ids, count, desc->data, true, NULL, &diff); - - if (!sparse_count) { - return entities; - } else { - /* Refetch entity ids, in case the underlying array was reallocated */ - entities = flecs_sparse_ids(ecs_eis(world)); - return &entities[sparse_count]; + if (diff_flags & (EcsTableHasOnAdd|EcsTableHasTraversable)) { + flecs_emit(world, world, &(ecs_event_desc_t){ + .event = EcsOnAdd, + .ids = added, + .table = table, + .other_table = other_table, + .offset = row, + .count = count, + .observable = world, + .flags = flags + }); } -error: - return NULL; } -ecs_entity_t ecs_component_init( +static +void flecs_actions_on_remove_intern( ecs_world_t *world, - const ecs_component_desc_t *desc) + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff, + ecs_flags32_t diff_flags) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - - ecs_suspend_readonly_state_t readonly_state; - world = flecs_suspend_readonly(world, &readonly_state); - - ecs_entity_desc_t entity_desc = desc->entity; - entity_desc.use_low_id = true; - if (!entity_desc.symbol) { - entity_desc.symbol = entity_desc.name; - } + const ecs_type_t *removed = &diff->removed; - ecs_entity_t e = desc->entity.entity; - ecs_entity_t result = ecs_entity_init(world, &entity_desc); - if (!result) { - goto error; - } - - bool added = false; - EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent, &added); + ecs_assert(diff_flags != 0, ECS_INTERNAL_ERROR, NULL); - if (added) { - ptr->size = flecs_utosize(desc->size); - ptr->alignment = flecs_utosize(desc->alignment); - if (!ptr->size) { - ecs_trace("#[green]tag#[reset] %s created", - ecs_get_name(world, result)); - } else { - ecs_trace("#[green]component#[reset] %s created", - ecs_get_name(world, result)); - } - } else { - if (ptr->size != flecs_utosize(desc->size)) { - ecs_abort(ECS_INVALID_COMPONENT_SIZE, desc->entity.name); - } - if (ptr->alignment != flecs_utosize(desc->alignment)) { - ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, desc->entity.name); + if (diff_flags & EcsTableHasDontFragment) { + if (flecs_dont_fragment_on_remove( + world, table, row, count, removed)) + { + diff_flags |= EcsTableHasOnRemove; } } - ecs_modified(world, result, EcsComponent); - - if (e >= world->info.last_component_id && e < ECS_HI_COMPONENT_ID) { - world->info.last_component_id = e + 1; + if (diff_flags & EcsTableHasOnRemove) { + flecs_emit(world, world, &(ecs_event_desc_t) { + .event = EcsOnRemove, + .ids = removed, + .table = table, + .other_table = other_table, + .offset = row, + .count = count, + .observable = world + }); } - /* Ensure components cannot be deleted */ - ecs_add_pair(world, result, EcsOnDelete, EcsThrow); - - flecs_resume_readonly(world, &readonly_state); - - ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); - - return result; -error: - return 0; + if (diff_flags & EcsTableHasSparse) { + flecs_sparse_on_remove(world, table, row, count, removed); + } } -ecs_entity_t ecs_type_init( +static +void flecs_actions_on_remove_intern_w_reparent( ecs_world_t *world, - const ecs_type_desc_t *desc) + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - - ecs_entity_t result = ecs_entity_init(world, &desc->entity); - if (!result) { - goto error; + if (!(world->flags & EcsWorldFini)) { + ecs_check(!(table->flags & EcsTableHasBuiltins), + ECS_INVALID_OPERATION, + "removing components from builtin entities is not allowed"); } - ecs_table_t *table = NULL, *normalized = NULL; - ecs_table_diff_t temp_diff, diff = ECS_TABLE_DIFF_INIT; - - /* Find destination table (and type) */ - - /* Add components from the 'add' id array */ - int32_t i = 0; - ecs_id_t id; - const ecs_id_t *ids = desc->ids; - while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { - normalized = flecs_table_traverse_add( - world, normalized, &id, &temp_diff); - table = flecs_table_traverse_add(world, table, &id, NULL); - ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); - diff_append(&diff, &temp_diff); + ecs_flags32_t diff_flags = diff->removed_flags; + if (!diff_flags) { + return; } - /* If expression is set, add it to the table */ - if (desc->ids_expr) { -#ifdef FLECS_PARSER - bool error = false; - - normalized = traverse_from_expr(world, normalized, desc->entity.name, - desc->ids_expr, &diff, true, &error); - if (error) { - goto error; - } - - table = traverse_from_expr(world, table, desc->entity.name, - desc->ids_expr, &diff, false, &error); - if (error) { - goto error; + if (diff_flags & (EcsTableEdgeReparent|EcsTableHasOrderedChildren)) { + if (!other_table || !(other_table->flags & EcsTableHasChildOf)) { + flecs_on_unparent(world, table, other_table, row, count); } -#else - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); -#endif - } - - diff_free(&diff); - - ecs_type_t type = NULL; - - if (table) { - type = table->type; } - bool add = false; - EcsType *type_ptr = ecs_get_mut(world, result, EcsType, &add); - if (add) { - type_ptr->type = type; - type_ptr->normalized = normalized; - - /* This will allow the type to show up in debug tools */ - if (type) { - ecs_map_set(&world->type_handles, (uintptr_t)type, &result); - } - - ecs_modified(world, result, EcsType); - } else { - ecs_check(type_ptr->type == type, ECS_ALREADY_DEFINED, - desc->entity.name); - ecs_check(type_ptr->normalized == normalized, ECS_ALREADY_DEFINED, - desc->entity.name); - } + flecs_actions_on_remove_intern( + world, table, other_table, row, count, diff, diff_flags); - return result; error: - return 0; + return; } -const ecs_entity_t* ecs_bulk_new_w_id( +void flecs_actions_new( ecs_world_t *world, - ecs_id_t id, - int32_t count) + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff, + ecs_flags32_t flags, + bool construct, + bool sparse) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - - const ecs_entity_t *ids; - if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { - return ids; - } + flecs_actions_on_add_intern( + world, table, NULL, row, count, diff, flags, construct, sparse); +} - ecs_table_t *table = &world->store.root; - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; - - if (id) { - table = table_append(world, table, id, &diff); +void flecs_actions_delete( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff) +{ + if (diff->removed.count) { + flecs_actions_on_remove_intern_w_reparent( + world, table, NULL, row, count, diff); } - - ids = new_w_data(world, table, NULL, NULL, count, NULL, false, NULL, &diff); - flecs_defer_flush(world, stage); - - return ids; -error: - return NULL; } -void ecs_clear( +void flecs_actions_delete_tree( ecs_world_t *world, - ecs_entity_t entity) + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + if (diff->removed.count) { + ecs_flags32_t diff_flags = diff->removed_flags; + if (!diff_flags) { + return; + } - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_clear(world, stage, entity)) { - return; + flecs_actions_on_remove_intern( + world, table, NULL, row, count, diff, diff_flags); } +} - ecs_entity_info_t info; - info.table = NULL; +void flecs_actions_move_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff, + ecs_flags32_t flags, + bool construct, + bool sparse) +{ + ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_t *added = &diff->added; - flecs_get_info(world, entity, &info); + if (added->count) { + ecs_flags32_t table_flags = table->flags; - ecs_table_t *table = info.table; - if (table) { - ecs_table_diff_t diff = { - .removed = flecs_type_to_ids(table->type), - .un_set = { table->storage_ids, table->storage_count, 0 } - }; + if (table_flags & EcsTableHasTraversable) { + flecs_emit_propagate_invalidate(world, table, row, count); + } - delete_entity(world, table, &table->storage, info.row, &diff); - info.record->table = NULL; - info.record->row = 0; - } + if (table_flags & EcsTableHasParent) { + flecs_on_non_fragmenting_child_move_add( + world, table, other_table, row, count); + } - flecs_defer_flush(world, stage); -error: - return; + flecs_actions_on_add_intern(world, table, other_table, row, count, diff, + flags, construct, sparse); + } } -static -void on_delete_any_w_entity( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t action, - uint32_t flags); - -static -void throw_invalid_delete( +void flecs_actions_move_remove( ecs_world_t *world, - ecs_id_t id) + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff) { - char *id_str = ecs_id_str(world, id); - ecs_throw(ECS_CONSTRAINT_VIOLATED, id_str); -error: - ecs_os_free(id_str); + ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + if (diff->removed.count) { + ecs_flags32_t table_flags = table->flags; + if (table_flags & EcsTableHasTraversable) { + flecs_emit_propagate_invalidate(world, table, row, count); + } + + if (table_flags & EcsTableHasParent) { + bool update_parent_records = true; + if (diff->added.count && (table->flags & EcsTableHasParent)) { + update_parent_records = false; + } + + flecs_on_non_fragmenting_child_move_remove( + world, other_table, table, row, count, update_parent_records); + } + + flecs_actions_on_remove_intern_w_reparent( + world, table, other_table, row, count, diff); + } } -static -void remove_from_table( +void flecs_notify_on_set_ids( ecs_world_t *world, - ecs_table_t *src_table, - ecs_id_t id, - int32_t column, - int32_t column_count) + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_type_t *ids) { - ecs_table_diff_t temp_diff, diff = ECS_TABLE_DIFF_INIT; - ecs_table_t *dst_table = src_table; - ecs_id_t *ids = ecs_vector_first(src_table->type, ecs_id_t); - - /* If id is pair but the column pointed to is not a pair, the record is - * pointing to an instance of the id that has a (non-PAIR) role. */ - bool is_pair = ECS_HAS_ROLE(id, PAIR); - bool is_role = is_pair && !ECS_HAS_ROLE(ids[column], PAIR); - ecs_assert(!is_role || ((ids[column] & ECS_ROLE_MASK) != 0), + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = &ecs_table_entities(table)[row]; + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); - bool is_wildcard = ecs_id_is_wildcard(id); - - int32_t i, count = ecs_vector_count(src_table->type), removed_count = 0; - ecs_entity_t entity = ECS_PAIR_FIRST(id); - for (i = column; i < count; i ++) { - ecs_id_t e = ids[i]; + bool dont_fragment = false; - if (is_role) { - if ((e & ECS_COMPONENT_MASK) != entity) { - continue; - } - } else if (is_wildcard && !ecs_id_match(e, id)) { + int i; + for (i = 0; i < ids->count; i ++) { + ecs_id_t id = ids->array[i]; + ecs_component_record_t *cr = flecs_components_get(world, id); + dont_fragment |= (cr->flags & EcsIdDontFragment) != 0; + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = cr->type_info; + ecs_iter_action_t on_set = ti->hooks.on_set; + if (!on_set) { continue; } - dst_table = flecs_table_traverse_remove( - world, dst_table, &e, &temp_diff); - diff_append(&diff, &temp_diff); - - removed_count ++; - if (removed_count == column_count) { - break; - } - } - - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - - if (!dst_table->type) { - ecs_dbg_3("clear entities from table %u", (uint32_t)src_table->id); - /* If this removes all components, clear table */ - flecs_table_clear_entities(world, src_table); - } else { - ecs_dbg_3("move entities from table %u to %u", - (uint32_t)src_table->id, - (uint32_t)dst_table->id); - /* Otherwise, merge table into dst_table */ - if (dst_table != src_table) { - ecs_data_t *src_data = &src_table->storage; - int32_t src_count = ecs_table_count(src_table); - if (diff.removed.count) { - flecs_notify_on_remove(world, src_table, NULL, - 0, src_count, &diff); + ecs_table_record_t dummy_tr; + const ecs_table_record_t *tr = + flecs_component_get_table(cr, table); + if (!tr) { + dummy_tr.hdr.cr = cr; + dummy_tr.hdr.table = table; + dummy_tr.column = -1; + dummy_tr.index = -1; + dummy_tr.count = 0; + tr = &dummy_tr; + } + + if (cr->flags & EcsIdSparse) { + int32_t j; + for (j = 0; j < count; j ++) { + flecs_invoke_hook(world, table, cr, tr, 1, row, + &entities[j], id, ti, EcsOnSet, on_set); + } + } else { + ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + if (on_set) { + flecs_invoke_hook(world, table, cr, tr, count, row, + entities, id, ti, EcsOnSet, on_set); } - flecs_table_merge(world, dst_table, src_table, - &dst_table->storage, src_data); } } - diff_free(&diff); + /* Run OnSet notifications */ + if ((dont_fragment || table->flags & EcsTableHasOnSet) && ids->count) { + flecs_emit(world, world, &(ecs_event_desc_t) { + .event = EcsOnSet, + .ids = ids, + .table = table, + .offset = row, + .count = count, + .observable = world + }); + } } -static -void delete_objects( +void flecs_notify_on_set( ecs_world_t *world, - ecs_table_t *table) + ecs_table_t *table, + int32_t row, + ecs_id_t id, + bool invoke_hook) { - ecs_data_t *data = &table->storage; - if (data) { - ecs_entity_t *entities = ecs_vector_first( - data->entities, ecs_entity_t); - ecs_record_t **records = ecs_vector_first( - data->record_ptrs, ecs_record_t*); - - int32_t i, count = ecs_vector_count(data->entities); - for (i = 0; i < count; i ++) { - ecs_record_t *r = records[i]; - - /* If entity is flagged, it could have delete actions. */ - uint32_t flags; - if (r && (flags = (r->row & ECS_ROW_FLAGS_MASK))) { - /* Strip mask to prevent infinite recursion */ - r->row = r->row & ECS_ROW_MASK; - - /* Run delete actions for object */ - on_delete_any_w_entity(world, entities[i], 0, flags); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = &ecs_table_entities(table)[row]; + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row <= ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + + ecs_component_record_t *cr = flecs_components_get(world, id); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + bool dont_fragment = (cr->flags & EcsIdDontFragment) != 0; + + if (invoke_hook) { + const ecs_type_info_t *ti = cr->type_info; + ecs_iter_action_t on_set = ti->hooks.on_set; + if (on_set) { + ecs_table_record_t dummy_tr; + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + dummy_tr.hdr.cr = cr; + dummy_tr.hdr.table = table; + dummy_tr.column = -1; + dummy_tr.index = -1; + dummy_tr.count = 0; + tr = &dummy_tr; + } + + if (cr->flags & EcsIdSparse) { + flecs_invoke_hook(world, table, cr, tr, 1, row, + entities, id, ti, EcsOnSet, on_set); + } else { + ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + if (on_set) { + flecs_invoke_hook(world, table, cr, tr, 1, row, + entities, id, ti, EcsOnSet, on_set); + } } } + } - /* Clear components from table (invokes destructors, OnRemove) */ - flecs_table_delete_entities(world, table); - } + if ((dont_fragment || table->flags & EcsTableHasOnSet)) { + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_emit(world, world, &(ecs_event_desc_t) { + .event = EcsOnSet, + .ids = &ids, + .table = table, + .offset = row, + .count = 1, + .observable = world + }); + } } -static -void on_delete_object_action( - ecs_world_t *world, - ecs_id_t id, - ecs_entity_t action) -{ - ecs_table_cache_iter_t it; - ecs_id_record_t *idr = flecs_get_id_record(world, id); +/** + * @file query/each.c + * @brief Simple iterator for a single component id. + */ - if (idr) { - bool deleted; - do { - deleted = false; +static +bool flecs_each_component_record( + ecs_iter_t *it, + ecs_component_record_t *cr, + ecs_id_t id) +{ + ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(id == cr->id, ECS_INTERNAL_ERROR, NULL); - /* Make sure records are in the right list (empty/non-empty) */ - ecs_force_aperiodic(world); + ecs_each_iter_t *each_iter = &it->priv_.iter.each; + each_iter->ids = id; + each_iter->sizes = 0; + if (cr->type_info) { + each_iter->sizes = cr->type_info->size; + } - if (!flecs_table_cache_iter(&idr->cache, &it)) { - continue; - } + each_iter->sources = 0; + each_iter->trs = NULL; + flecs_table_cache_iter((ecs_table_cache_t*)cr, &each_iter->it); - /* First move entities to tables without the id (action = Remove) or - * delete entities with id (action = Delete) */ - const ecs_table_record_t *tr; - if ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - do { - ecs_table_t *table = tr->hdr.table; + return true; +error: + return false; +} - if (!ecs_table_count(table)) { - continue; - } +ecs_iter_t ecs_each_id( + const ecs_world_t *stage, + ecs_id_t id) +{ + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_dbg_3("cleanup table %u", (uint32_t)table->id); - ecs_log_push_3(); - - /* Prevent table from getting deleted in next step, which - * could happen if store contains cyclic relationships */ - flecs_table_claim(world, table); - - /* If store contains cyclic relationships it's possible that - * a table we were about to cleanup already got emptied */ - ecs_id_t *rel_id = ecs_vector_get( - table->type, ecs_id_t, tr->column); - ecs_assert(rel_id != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_entity_t rel = ECS_PAIR_FIRST(*rel_id); - /* delete_object_action should be invoked for pairs */ - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - - /* Initialize with original value in case action = 0 */ - ecs_entity_t cur_action = action; - - /* Find delete action for relation */ - if (!cur_action) { - ecs_id_record_t *idrr = flecs_get_id_record( - world, rel); - if (idrr) { - cur_action = - ECS_ID_ON_DELETE_OBJECT(idrr->flags); - } - } + const ecs_world_t *world = ecs_get_world(stage); - if (!cur_action || cur_action == EcsRemove) { - remove_from_table( - world, table, id, tr->column, tr->count); - } else if (cur_action == EcsDelete) { - delete_objects(world, table); - - /* Delete actions can cause cyclic cleanup, which - * requires special attention */ - deleted = true; - } else if (cur_action == EcsThrow) { - throw_invalid_delete(world, id); - } + flecs_check_exclusive_world_access_write(world); - ecs_log_pop_3(); + ecs_iter_t it = { + .real_world = ECS_CONST_CAST(ecs_world_t*, world), + .world = ECS_CONST_CAST(ecs_world_t*, stage), + .field_count = 1, + .next = ecs_each_next + }; - /* It is possible that the current record has been moved to - * the empty list, as result of a cyclic cleanup action. In - * that case, break out of the loop and re-obtain an - * iterator to the non-empty list, as the current record - * will be pointing to the next empty table. */ - if (tr->hdr.empty) { - flecs_table_release(world, table); - break; - } + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { + return it; + } - /* Manually set next in case the next element has been - * deleted by the previous cleanup operations */ - it.next = tr->hdr.next; - - /* It is possible that a cyclic relationship has emptied a - * table that was already in this list. There is one edge - * case where releasing the current table could invalidate - * the next (empty) record, which is if the (emptied) record - * belongs to the table's storage table. - * To make sure the next record is valid, first move to a - * non-empty record before releasing the table. */ - while (it.next && !ecs_table_count(it.next->table)) { - it.next = it.next->next; - } - - /* Release table. This may remove the table record from the - * current list */ - flecs_table_release(world, table); + if (!flecs_each_component_record(&it, cr, id)) { + return (ecs_iter_t){0}; + } - } while ((tr = flecs_table_cache_next( - &it, ecs_table_record_t))); - } + return it; +error: + return (ecs_iter_t){0}; +} - /* Keep repeating while cleanup actions were performed. This ensures - * that even when tables are moved between empty/non-empty lists as - * cleanup actions are performed, eventually all tables with the id - * are correctly cleaned up. */ - } while (deleted); +bool ecs_each_next( + ecs_iter_t *it) +{ + ecs_each_iter_t *each_iter = &it->priv_.iter.each; + const ecs_table_record_t *next = flecs_table_cache_next( + &each_iter->it, ecs_table_record_t); + it->flags |= EcsIterIsValid; + if (next) { + each_iter->trs = next; + ecs_table_t *table = next->hdr.table; + it->table = table; + it->count = ecs_table_count(table); + it->entities = ecs_table_entities(table); + if (next->index != -1) { + it->ids = &table->type.array[next->index]; + } else { + it->ids = NULL; + } + it->trs = &each_iter->trs; + it->sources = &each_iter->sources; + it->sizes = &each_iter->sizes; + it->set_fields = 1; - /* Delete all remaining (empty) tables with id */ - flecs_remove_id_record(world, id, idr); + return true; + } else { + return false; } } static -void on_delete_id_action( - ecs_world_t *world, - ecs_id_t id, - ecs_entity_t action) +bool flecs_children_next_ordered( + ecs_iter_t *it) { - ecs_table_cache_iter_t it; - ecs_id_record_t *idr = flecs_table_iter(world, id, &it); - if (idr) { - if (!action) { - action = ECS_ID_ON_DELETE(idr->flags); - } + return ecs_children_next(it); +} - if (action == EcsThrow) { - throw_invalid_delete(world, id); - } +ecs_iter_t ecs_children_w_rel( + const ecs_world_t *stage, + ecs_entity_t relationship, + ecs_entity_t parent) +{ + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - /* Delete non-empty tables */ - const ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; + const ecs_world_t *world = ecs_get_world(stage); - if (!action || action == EcsRemove) { - remove_from_table(world, table, id, tr->column, tr->count); - } else if (action == EcsDelete) { - delete_objects(world, table); - } - } + flecs_check_exclusive_world_access_read(world); - flecs_remove_id_record(world, id, idr); - } -} + ecs_iter_t it = { + .real_world = ECS_CONST_CAST(ecs_world_t*, world), + .world = ECS_CONST_CAST(ecs_world_t*, stage), + .field_count = 1, + .next = ecs_children_next + }; -static -void on_delete_action( - ecs_world_t *world, - ecs_id_t id, - ecs_entity_t action) -{ - if (ecs_should_log_1()) { - char *id_str = ecs_id_str(world, id); - ecs_dbg("#[red]delete#[reset] tables with id %s", id_str); - ecs_os_free(id_str); + ecs_component_record_t *cr = flecs_components_get( + world, ecs_pair(relationship, parent)); + if (!cr) { + return (ecs_iter_t){0}; } - ecs_log_push_1(); - - if (ecs_id_is_wildcard(id)) { - /* If id is wildcard, check if the relation or object is a wildcard. - * Relation wildcard ids are implemented differently as relations - * with the same object aren't guaranteed to occupy neighboring - * elements in the type, other wildcards with the same relation. */ - if (ECS_PAIR_FIRST(id) == EcsWildcard) { - on_delete_object_action(world, id, action); - } else { - on_delete_id_action(world, id, action); - } - } else { - /* If the id is not a wildcard it's simple, as a table can only have - * at most one instance of the id */ - on_delete_id_action(world, id, action); + if (cr->flags & EcsIdOrderedChildren) { + ecs_vec_t *v = &cr->pair->ordered_children; + it.entities = ecs_vec_first_t(v, ecs_entity_t); + it.count = ecs_vec_count(v); + it.next = flecs_children_next_ordered; + return it; + } else if (cr->flags & EcsIdSparse) { + it.entities = flecs_sparse_ids(cr->sparse); + it.count = flecs_sparse_count(cr->sparse); + it.next = flecs_children_next_ordered; + return it; } - - ecs_log_pop_1(); + + return ecs_each_id(stage, ecs_pair(relationship, parent)); +error: + return (ecs_iter_t){0}; } -static -void on_delete_any_w_entity( - ecs_world_t *world, - ecs_entity_t e, - ecs_entity_t action, - uint32_t flags) +ecs_iter_t ecs_children( + const ecs_world_t *stage, + ecs_entity_t parent) { - /* Make sure any references to the entity are cleaned up */ - if (flags & ECS_FLAG_OBSERVED_ID) { - on_delete_action(world, e, action); - on_delete_action(world, ecs_pair(e, EcsWildcard), action); - } - if (flags & ECS_FLAG_OBSERVED_OBJECT) { - on_delete_action(world, ecs_pair(EcsWildcard, e), action); - } + return ecs_children_w_rel(stage, EcsChildOf, parent); } -void ecs_delete_with( - ecs_world_t *world, - ecs_id_t id) +bool ecs_children_next( + ecs_iter_t *it) { - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_on_delete_action(world, stage, id, EcsDelete)) { - return; + if (it->next == NULL) { + return false; } - on_delete_action(world, id, EcsDelete); - flecs_defer_flush(world, stage); -} + if (it->next == flecs_children_next_ordered) { + if (!it->count) { + return false; + } -void ecs_remove_all( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_on_delete_action(world, stage, id, EcsRemove)) { - return; + it->next = NULL; /* Only return once with ordered children vector */ + + return true; } - on_delete_action(world, id, EcsRemove); - flecs_defer_flush(world, stage); + return ecs_each_next(it); } -void ecs_delete( - ecs_world_t *world, - ecs_entity_t entity) +int32_t ecs_count_id( + const ecs_world_t *world, + ecs_entity_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_delete(world, stage, entity)) { - return; + if (!id) { + return 0; } - ecs_record_t *r = flecs_sparse_get( - ecs_eis(world), ecs_record_t, entity); - if (r) { - ecs_entity_info_t info = {0}; - set_info_from_record(&info, r); + int32_t count = 0; + ecs_iter_t it = ecs_each_id(world, id); + while (ecs_each_next(&it)) { + count += it.count * it.trs[0]->count; + } - ecs_table_t *table = info.table; - uint64_t table_id = 0; - if (table) { - table_id = table->id; - } + return count; +error: + return 0; +} - if (info.row_flags) { - /* Prevent infinite recursion in case of cyclic delete actions */ - r->row &= ECS_ROW_MASK; +/** + * @file entity.c + * @brief Entity API. + * + * This file contains the implementation for the entity API, which includes + * creating/deleting entities, adding/removing/setting components, instantiating + * prefabs, and several other APIs for retrieving entity data. + * + * The file also contains the implementation of the command buffer, which is + * located here so it can call functions private to the compilation unit. + */ - /* Ensure that the store contains no dangling references to the - * deleted entity (as a component, or as part of a relation) */ - on_delete_any_w_entity(world, entity, 0, info.row_flags); - /* Refetch data. In case of circular relations, the entity may have - * moved to a different table. */ - set_info_from_record(&info, r); - - table = info.table; - if (table) { - table_id = table->id; - } else { - table_id = 0; - } +#ifdef FLECS_QUERY_DSL +/** + * @file addons/query_dsl/query_dsl.h + * @brief Query DSL parser addon. + */ - if (r->table) { - ecs_ids_t to_remove = flecs_type_to_ids(r->table->type); - update_component_monitors(world, NULL, &to_remove); - } - } +#ifndef FLECS_QUERY_DSL_H +#define FLECS_QUERY_DSL_H - ecs_assert(!table_id || table, ECS_INTERNAL_ERROR, NULL); - /* If entity has components, remove them. Check if table is still alive, - * as delete actions could have deleted the table already. */ - if (table_id && flecs_sparse_is_alive(&world->store.tables, table_id)) { - ecs_table_diff_t diff = { - .removed = flecs_type_to_ids(table->type), - .un_set = { table->storage_ids, table->storage_count, 0 } - }; +int flecs_terms_parse( + ecs_world_t *world, + const char *name, + const char *code, + char *token_buffer, + ecs_term_t *terms, + int32_t *term_count_out); - delete_entity(world, table, info.data, info.row, &diff); - r->table = NULL; - } +const char* flecs_term_parse( + ecs_world_t *world, + const char *name, + const char *expr, + char *token_buffer, + ecs_term_t *term); - r->row = 0; - r->table = NULL; +const char* flecs_id_parse( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_id_t *id); - /* Remove (and invalidate) entity after executing handlers */ - flecs_sparse_remove(ecs_eis(world), entity); - } +#endif - ecs_assert(!ecs_id_in_use(world, entity), ECS_INTERNAL_ERROR, NULL); - ecs_assert(!ecs_id_in_use(world, ecs_pair(EcsWildcard, entity)), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!ecs_id_in_use(world, ecs_pair(entity, EcsWildcard)), - ECS_INTERNAL_ERROR, NULL); +#endif - flecs_defer_flush(world, stage); -error: - return; +static +flecs_component_ptr_t flecs_table_get_component( + ecs_table_t *table, + int32_t column_index, + int32_t row) +{ + ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); + ecs_column_t *column = &table->data.columns[column_index]; + return (flecs_component_ptr_t){ + .ti = column->ti, + .ptr = ECS_ELEM(column->data, column->ti->size, row) + }; } -void ecs_add_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - add_id(world, entity, id); -error: - return; -} - -void ecs_remove_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id) || ecs_id_is_wildcard(id), - ECS_INVALID_PARAMETER, NULL); - remove_id(world, entity, id); -error: - return; -} - -ecs_entity_t ecs_clone( - ecs_world_t *world, - ecs_entity_t dst, - ecs_entity_t src, - bool copy_value) +flecs_component_ptr_t flecs_get_component_ptr( + const ecs_world_t *world, + ecs_table_t *table, + int32_t row, + ecs_component_record_t *cr) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(src != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, src), ECS_INVALID_PARAMETER, NULL); - ecs_check(!dst || !ecs_get_table(world, dst), ECS_INVALID_PARAMETER, NULL); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (!dst) { - dst = ecs_new_id(world); - } + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (flecs_defer_clone(world, stage, dst, src, copy_value)) { - return dst; + if (!cr) { + return (flecs_component_ptr_t){0}; } - ecs_entity_info_t src_info; - bool found = flecs_get_info(world, src, &src_info); - ecs_table_t *src_table = src_info.table; - - if (!found || !src_table) { - goto done; + if (cr->flags & (EcsIdSparse|EcsIdDontFragment)) { + ecs_entity_t entity = ecs_table_entities(table)[row]; + return (flecs_component_ptr_t){ + .ti = cr->type_info, + .ptr = flecs_component_sparse_get(world, cr, table, entity) + }; } - ecs_type_t src_type = src_table->type; - ecs_table_diff_t diff = {.added = flecs_type_to_ids(src_type)}; - - ecs_entity_info_t dst_info = {0}; - dst_info.row = new_entity(world, dst, &dst_info, src_table, &diff, - true, true); - - if (copy_value) { - flecs_table_move(world, dst, src, src_table, dst_info.data, - dst_info.row, src_table, src_info.data, src_info.row, true); - - flecs_notify_on_set(world, src_table, dst_info.row, 1, NULL, true); + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr || (tr->column == -1)) { + return (flecs_component_ptr_t){0}; } -done: - flecs_defer_flush(world, stage); - return dst; -error: - return 0; + return flecs_table_get_component(table, tr->column, row); } -const void* ecs_get_id( +void* flecs_get_component( const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) + ecs_table_t *table, + int32_t row, + ecs_component_record_t *cr) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(flecs_stage_from_readonly_world(world)->asynchronous == false, - ECS_INVALID_PARAMETER, NULL); + return flecs_get_component_ptr(world, table, row, cr).ptr; +} - world = ecs_get_world(world); +void* flecs_get_base_component( + const ecs_world_t *world, + ecs_table_t *table, + ecs_id_t component, + ecs_component_record_t *cr, + int32_t recur_depth) +{ + ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_OPERATION, + "cycle detected in IsA relationship"); - ecs_record_t *r = ecs_eis_get(world, entity); - if (!r) { + /* Table (and thus entity) does not have component, look for base */ + if (!(table->flags & EcsTableHasIsA)) { return NULL; } - ecs_table_t *table = r->table; - if (!table) { + if (!(cr->flags & EcsIdOnInstantiateInherit)) { return NULL; } - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { + /* Exclude Name */ + if (component == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { return NULL; } - const ecs_table_record_t *tr = NULL; - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - tr = flecs_id_record_table(idr, storage_table); - } else { - /* If the entity does not have a storage table (has no data) but it does - * have the id, the id must be a tag, and getting a tag is illegal. */ - ecs_check(!ecs_owns_id(world, entity, id), ECS_NOT_A_COMPONENT, NULL); - } + /* Table should always be in the table index for (IsA, *), otherwise the + * HasBase flag should not have been set */ + const ecs_table_record_t *tr_isa = flecs_component_get_table( + world->cr_isa_wildcard, table); + ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); - if (!tr) { - return get_base_component(world, table, id, idr, 0); - } + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + int32_t i = tr_isa->index, end = tr_isa->count + tr_isa->index; + void *ptr = NULL; - int32_t row = ECS_RECORD_TO_ROW(r->row); - return get_component_w_index(table, tr->column, row); + do { + ecs_id_t pair = ids[i ++]; + ecs_entity_t base = ecs_pair_second(world, pair); + + ecs_record_t *r = flecs_entities_get(world, base); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + if (cr->flags & EcsIdDontFragment) { + ptr = flecs_component_sparse_get(world, cr, table, base); + } + + if (!ptr) { + ptr = flecs_get_base_component(world, table, component, cr, + recur_depth + 1); + } + } else { + if (cr->flags & EcsIdSparse) { + return flecs_component_sparse_get(world, cr, table, base); + } else { + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_table_get_component(table, tr->column, row).ptr; + } + } + } while (!ptr && (i < end)); + + return ptr; error: return NULL; } -const void* ecs_get_ref_id( - const ecs_world_t *world, - ecs_ref_t *ref, - ecs_entity_t entity, - ecs_id_t id) +ecs_entity_t flecs_new_id( + const ecs_world_t *world) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!entity || !ref->entity || entity == ref->entity, - ECS_INVALID_PARAMETER, NULL); - ecs_check(!id || !ref->component || id == ref->component, - ECS_INVALID_PARAMETER, NULL); - ecs_record_t *record = ref->record; - - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + flecs_poly_assert(world, ecs_world_t); - entity |= ref->entity; + flecs_check_exclusive_world_access_write(world); - if (!record) { - record = ecs_eis_get(world, entity); - } + /* It is possible that the world passed to this function is a stage, so + * make sure we have the actual world. Cast away const since this is one of + * the few functions that may modify the world while it is in readonly mode, + * since it is thread safe (uses atomic inc when in threading mode) */ + ecs_world_t *unsafe_world = ECS_CONST_CAST(ecs_world_t*, world); - if (!record || !record->table) { - goto error; - } + ecs_assert(!(unsafe_world->flags & EcsWorldMultiThreaded), + ECS_INVALID_OPERATION, "cannot create entities in multithreaded mode"); - ecs_table_t *table = record->table; + ecs_entity_t entity = flecs_entities_new_id(unsafe_world); - if (ref->record == record && - ref->table == table && - ref->row == record->row && - ref->alloc_count == table->alloc_count) - { - return ref->ptr; - } + ecs_assert(!unsafe_world->info.max_id || + ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, + ECS_OUT_OF_RANGE, NULL); - id |= ref->component; + return entity; +} - uint32_t row = record->row; - ref->entity = entity; - ref->component = id; - ref->table = table; - ref->row = row; - ref->alloc_count = table->alloc_count; +static +ecs_record_t* flecs_new_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *table, + ecs_table_diff_t *diff, + bool ctor, + ecs_flags32_t evt_flags) +{ + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t row = flecs_table_append(world, table, entity, ctor, true); + record->table = table; + record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); - if (table && id) { - ref->ptr = get_component(world, table, ECS_RECORD_TO_ROW(row), id); - } + ecs_assert(ecs_table_count(table) > row, ECS_INTERNAL_ERROR, NULL); + flecs_actions_new(world, table, row, 1, diff, evt_flags, ctor, true); + ecs_assert(table == record->table, ECS_INTERNAL_ERROR, NULL); - ref->record = record; - return ref->ptr; -error: - return NULL; + return record; } -void* ecs_get_mut_id( +static +void flecs_move_entity( ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id, - bool *is_added) + ecs_record_t *record, + ecs_table_t *dst_table, + ecs_table_diff_t *diff, + bool ctor, + ecs_flags32_t evt_flags) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - void *result; + ecs_table_t *src_table = record->table; + int32_t src_row = ECS_RECORD_TO_ROW(record->row); - if (flecs_defer_set( - world, stage, EcsOpMut, entity, id, 0, NULL, &result, is_added)) - { - return result; - } + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_table->type.count >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_table_count(src_table) > src_row, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record == flecs_entities_get(world, entity), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(record->table == src_table, ECS_INTERNAL_ERROR, NULL); - ecs_entity_info_t info; - result = get_mutable(world, entity, id, &info, is_added); - ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); - - /* Store table so we can quickly check if returned pointer is still valid */ - ecs_table_t *table = info.record->table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + /* Append new row to destination table */ + int32_t dst_row = flecs_table_append(world, dst_table, entity, + false, false); + + /* Invoke remove actions for removed components */ + flecs_actions_move_remove(world, src_table, dst_table, src_row, 1, diff); + + /* Copy entity & components from src_table to dst_table */ + flecs_table_move(world, entity, entity, dst_table, dst_row, + src_table, src_row, ctor); + ecs_assert(record->table == src_table, ECS_INTERNAL_ERROR, NULL); - /* Keep track of alloc count of table, since even if the entity has not - * moved, other entities could have been added to the table which could - * reallocate arrays. Also store the row, as the entity could have - * reallocated. */ - int32_t alloc_count = table->alloc_count; - uint32_t row = info.record->row; + /* Update entity index & delete old data after running remove actions */ + record->table = dst_table; + record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); - flecs_defer_flush(world, stage); + flecs_table_delete(world, src_table, src_row, false); - /* Ensure that after flushing, the pointer is still valid. Flushing may - * trigger callbacks, which could do anything with the entity */ - if (table != info.record->table || - alloc_count != info.record->table->alloc_count || - row != info.record->row) - { - if (flecs_get_info(world, entity, &info) && info.table) { - result = get_component(world, info.table, info.row, id); - } else { - /* A trigger has removed the component we just added. This is not - * allowed, an application should always be able to assume that - * get_mut returns a valid pointer. */ - ecs_throw(ECS_INVALID_OPERATION, NULL); - } - } + flecs_actions_move_add(world, dst_table, src_table, dst_row, 1, diff, + evt_flags, ctor, true); - return result; -error: - return NULL; + ecs_assert(record->table == dst_table, ECS_INTERNAL_ERROR, NULL); } -void* ecs_emplace_id( +void flecs_commit( ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id) + ecs_record_t *record, + ecs_table_t *dst_table, + ecs_table_diff_t *diff, + bool construct, + ecs_flags32_t evt_flags) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - ecs_check(!ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); + flecs_journal_begin(world, EcsJournalMove, entity, + &diff->added, &diff->removed); - ecs_stage_t *stage = flecs_stage_from_world(&world); - void *result; + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *src_table = record->table; + int is_trav = (record->row & EcsEntityIsTraversable) != 0; + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - if (flecs_defer_set( - world, stage, EcsOpMut, entity, id, 0, NULL, &result, NULL)) - { - return result; - } + if (src_table == dst_table) { + /* If source and destination table are the same no action is needed * + * However, if a component was added in the process of traversing a + * table, this suggests that a union relationship could have changed. */ + ecs_flags32_t non_fragment_flags = + src_table->flags & EcsTableHasDontFragment; + if (non_fragment_flags) { + diff->added_flags |= non_fragment_flags; + diff->removed_flags |= non_fragment_flags; - ecs_entity_info_t info = {0}; - flecs_get_info(world, entity, &info); - add_id_w_info(world, entity, &info, id, false /* Add without ctor */); + flecs_actions_move_add(world, src_table, src_table, + ECS_RECORD_TO_ROW(record->row), 1, diff, evt_flags, + construct, true); - void *ptr = get_component(world, info.table, info.row, id); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_actions_move_remove(world, src_table, src_table, + ECS_RECORD_TO_ROW(record->row), 1, diff); + } + flecs_journal_end(); + return; + } - flecs_defer_flush(world, stage); + ecs_os_perf_trace_push("flecs.commit"); - return ptr; -error: - return NULL; -} + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_table_traversable_add(dst_table, is_trav); -void ecs_modified_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + flecs_move_entity(world, entity, record, dst_table, diff, + construct, evt_flags); - ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_table_traversable_add(src_table, -is_trav); - if (flecs_defer_modified(world, stage, entity, id)) { - return; + /* If the entity is being watched, it is being monitored for changes and + * requires rematching systems when components are added or removed. This + * ensures that systems that rely on components from containers or prefabs + * update the matched tables when the application adds or removes a + * component from, for example, a container. */ + if (is_trav) { + flecs_update_component_monitors(world, &diff->added, &diff->removed); } - /* If the entity does not have the component, calling ecs_modified is - * invalid. The assert needs to happen after the defer statement, as the - * entity may not have the component when this function is called while - * operations are being deferred. */ - ecs_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); - - ecs_entity_info_t info = {0}; - if (flecs_get_info(world, entity, &info)) { - ecs_ids_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set(world, info.table, info.row, 1, &ids, true); + if (!src_table->type.count && world->range_check_enabled) { + ecs_check(!world->info.max_id || entity <= world->info.max_id, + ECS_OUT_OF_RANGE, 0); + ecs_check(entity >= world->info.min_id, + ECS_OUT_OF_RANGE, 0); } - flecs_table_mark_dirty(world, info.table, id); - flecs_defer_flush(world, stage); + ecs_os_perf_trace_pop("flecs.commit"); + error: + flecs_journal_end(); return; } -static -ecs_entity_t assign_ptr_w_id( +const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - size_t size, - void *ptr, + ecs_table_t *table, + const ecs_entity_t *entities, + ecs_type_t *component_ids, + int32_t count, + void **component_data, bool is_move, - bool notify) + int32_t *row_out, + ecs_table_diff_t *diff) { - ecs_stage_t *stage = flecs_stage_from_world(&world); + int32_t sparse_count = 0; + if (!entities) { + sparse_count = flecs_entities_count(world); + entities = flecs_entities_new_ids(world, count); + } - if (!entity) { - entity = ecs_new_id(world); - ecs_entity_t scope = stage->scope; - if (scope) { - ecs_add_pair(world, entity, EcsChildOf, scope); - } + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_defer_begin(world, world->stages[0]); + + int32_t row = flecs_table_appendn(world, table, count, entities); + + ecs_type_t type = table->type; + if (!type.count && !component_data) { + flecs_defer_end(world, world->stages[0]); + return entities; } - if (flecs_defer_set(world, stage, EcsOpSet, entity, id, - flecs_utosize(size), ptr, NULL, NULL)) - { - return entity; + ecs_type_t component_array = { 0 }; + if (!component_ids) { + component_ids = &component_array; + component_array.array = type.array; + component_array.count = type.count; } - ecs_entity_info_t info; + flecs_actions_move_add(world, table, NULL, row, count, diff, + (component_data == NULL) ? 0 : EcsEventNoOnSet, true, true); - void *dst = get_mutable(world, entity, id, &info, NULL); - ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); + if (component_data) { + int32_t c_i; + for (c_i = 0; c_i < component_ids->count; c_i ++) { + void *src_ptr = component_data[c_i]; + if (!src_ptr) { + continue; + } - if (ptr) { - ecs_entity_t real_id = ecs_get_typeid(world, id); - const ecs_type_info_t *ti = get_c_info(world, real_id); - if (ti) { - if (is_move) { - ecs_move_t move = ti->lifecycle.move; - if (move) { - move(dst, ptr, 1, ti); - } else { - ecs_os_memcpy(dst, ptr, flecs_utosize(size)); + /* Find component in storage type */ + ecs_entity_t id = component_ids->array[c_i]; + ecs_component_record_t *cr = flecs_components_get(world, id); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = cr->type_info; + if (!ti) { + ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, + "component '%s' passed to to bulk_new() at index %d is a " + "tag/zero sized", + flecs_errstr(ecs_id_str(world, id)), c_i); + } + + int32_t size = ti->size; + void *ptr; + + if (cr->flags & EcsIdSparse) { + int32_t e; + for (e = 0; e < count; e ++) { + ptr = flecs_component_sparse_get( + world, cr, table, entities[e]); + + ecs_copy_t copy; + ecs_move_t move; + if (is_move && (move = ti->hooks.move)) { + move(ptr, src_ptr, 1, ti); + } else if (!is_move && (copy = ti->hooks.copy)) { + copy(ptr, src_ptr, 1, ti); + } else { + ecs_os_memcpy(ptr, src_ptr, size); + } + + flecs_notify_on_set(world, table, row + e, id, true); + + src_ptr = ECS_OFFSET(src_ptr, size); } + } else { - ecs_copy_t copy = ti->lifecycle.copy; - if (copy) { - copy(dst, ptr, 1, ti); + const ecs_table_record_t *tr = + flecs_component_get_table(cr, table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->column != -1, ECS_INVALID_PARAMETER, + "component '%s' passed to bulk_new() at index %d is a " + "tag/zero sized", + flecs_errstr(ecs_id_str(world, id))); + ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, + "component passed to bulk_new() at index %d is " + "invalid/a wildcard", + flecs_errstr(ecs_id_str(world, id))); + + int32_t index = tr->column; + ecs_column_t *column = &table->data.columns[index]; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + ptr = ECS_ELEM(column->data, size, row); + + ecs_copy_t copy; + ecs_move_t move; + if (is_move && (move = ti->hooks.move)) { + move(ptr, src_ptr, count, ti); + } else if (!is_move && (copy = ti->hooks.copy)) { + copy(ptr, src_ptr, count, ti); } else { - ecs_os_memcpy(dst, ptr, flecs_utosize(size)); + ecs_os_memcpy(ptr, src_ptr, size * count); } } - } else { - ecs_os_memcpy(dst, ptr, flecs_utosize(size)); + }; + + int32_t j, storage_count = table->column_count; + for (j = 0; j < storage_count; j ++) { + ecs_id_t component = flecs_column_id(table, j); + ecs_type_t set_type = { + .array = &component, + .count = 1 + }; + + flecs_notify_on_set_ids(world, table, row, count, &set_type); } - } else { - memset(dst, 0, size); } - flecs_table_mark_dirty(world, info.table, id); + flecs_defer_end(world, world->stages[0]); - if (notify) { - ecs_ids_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set(world, info.table, info.row, 1, &ids, true); + if (row_out) { + *row_out = row; } - flecs_defer_flush(world, stage); - - return entity; -error: - return 0; + if (sparse_count) { + entities = flecs_entities_ids(world); + return &entities[sparse_count]; + } else { + return entities; + } } -ecs_entity_t ecs_set_id( +static +void flecs_add_id_w_record( ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id, - size_t size, - const void *ptr) + ecs_record_t *record, + ecs_id_t component, + bool construct) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!entity || ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - /* Safe to cast away const: function won't modify if move arg is false */ - return assign_ptr_w_id( - world, entity, id, size, (void*)ptr, false, true); -error: - return 0; + ecs_table_t *src_table = record->table; + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_table_t *dst_table = flecs_table_traverse_add( + world, src_table, &component, &diff); + flecs_commit(world, entity, record, dst_table, &diff, construct, + EcsEventNoOnSet); /* No OnSet, this function is only called from + * functions that are about to set the component. */ } -ecs_entity_t ecs_get_case( - const ecs_world_t *world, +void flecs_add_id( + ecs_world_t *world, ecs_entity_t entity, - ecs_entity_t sw_id) + ecs_id_t component) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, sw_id), ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_entity_info_t info; - ecs_table_t *table; - if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { - return 0; + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_add(stage, entity, component)) { + return; } - sw_id = sw_id | ECS_SWITCH; + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_table_t *src_table = r->table; - int32_t index = ecs_search(world, table, sw_id, 0); - if (index == -1) { - return 0; - } + ecs_table_t *dst_table = flecs_table_traverse_add( + world, src_table, &component, &diff); - index -= table->sw_column_offset; - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + flecs_commit(world, entity, r, dst_table, &diff, true, 0); - /* Data cannot be NULl, since entity is stored in the table */ - ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_switch_t *sw = info.data->sw_columns[index].data; - return flecs_switch_get(sw, info.row); -error: - return 0; + flecs_defer_end(world, stage); } -void ecs_enable_component_w_id( +void flecs_remove_id( ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id, - bool enable) + ecs_id_t component) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - - if (flecs_defer_enable( - world, stage, entity, id, enable)) - { + if (flecs_defer_remove(stage, entity, component)) { return; - } else { - /* Operations invoked by enable/disable should not be deferred */ - stage->defer --; } - ecs_entity_info_t info; - flecs_get_info(world, entity, &info); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *src_table = r->table; + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_table_t *dst_table = flecs_table_traverse_remove( + world, src_table, &component, &diff); - ecs_entity_t bs_id = (id & ECS_COMPONENT_MASK) | ECS_DISABLED; - - ecs_table_t *table = info.table; - int32_t index = -1; - if (table) { - index = ecs_search(world, table, bs_id, 0); - } + flecs_commit(world, entity, r, dst_table, &diff, true, 0); - if (index == -1) { - ecs_add_id(world, entity, bs_id); - ecs_enable_component_w_id(world, entity, id, enable); - return; - } + flecs_defer_end(world, stage); +} - index -= table->bs_column_offset; - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); +void flecs_add_ids( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t *ids, + int32_t count) +{ + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); - /* Data cannot be NULl, since entity is stored in the table */ - ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &info.data->bs_columns[index].data; - ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = ecs_get_table(world, entity); + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t component = ids[i]; + table = flecs_find_table_add(world, table, component, &diff); + } - flecs_bitset_set(bs, info.row, enable); -error: - return; + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + flecs_commit(world, entity, r, table, &table_diff, true, 0); + flecs_table_diff_builder_fini(world, &diff); } -bool ecs_is_component_enabled_w_id( - const ecs_world_t *world, +flecs_component_ptr_t flecs_ensure( + ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id) + ecs_id_t component, + ecs_record_t *r, + ecs_size_t size) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + flecs_component_ptr_t dst = {0}; - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + flecs_poly_assert(world, ecs_world_t); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_info_t info; - ecs_table_t *table; - if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { - return false; - } + ecs_component_record_t *cr = NULL; + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t bs_id = (id & ECS_COMPONENT_MASK) | ECS_DISABLED; - int32_t index = ecs_search(world, table, bs_id, 0); - if (index == -1) { - /* If table does not have DISABLED column for component, component is - * always enabled, if the entity has it */ - return ecs_has_id(world, entity, id); + if (component < FLECS_HI_COMPONENT_ID) { + int16_t column_index = table->component_map[component]; + if (column_index > 0) { + ecs_column_t *column = &table->data.columns[column_index - 1]; + ecs_assert(column->ti->size == size, ECS_INTERNAL_ERROR, NULL); + dst.ptr = ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); + dst.ti = column->ti; + return dst; + } else if (column_index < 0) { + column_index = flecs_ito(int16_t, -column_index - 1); + const ecs_table_record_t *tr = &table->_->records[column_index]; + cr = tr->hdr.cr; + if (cr->flags & EcsIdSparse) { + dst.ptr = flecs_component_sparse_get( + world, cr, r->table, entity); + dst.ti = cr->type_info; + ecs_assert(dst.ti->size == size, ECS_INTERNAL_ERROR, NULL); + return dst; + } + } + } else { + cr = flecs_components_get(world, component); + dst = flecs_get_component_ptr( + world, table, ECS_RECORD_TO_ROW(r->row), cr); + if (dst.ptr) { + ecs_assert(dst.ti->size == size, ECS_INTERNAL_ERROR, NULL); + return dst; + } } - index -= table->bs_column_offset; - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + /* If entity didn't have component yet, add it */ + flecs_add_id_w_record(world, entity, r, component, true); - /* Data cannot be NULl, since entity is stored in the table */ - ecs_assert(info.data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &info.data->bs_columns[index].data; + /* Flush commands so the pointer we're fetching is stable */ + flecs_defer_end(world, world->stages[0]); + flecs_defer_begin(world, world->stages[0]); - return flecs_bitset_get(bs, info.row); -error: - return false; + if (!cr) { + cr = flecs_components_get(world, component); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + } + + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_get_component_ptr( + world, r->table, ECS_RECORD_TO_ROW(r->row), cr); } -bool ecs_has_id( +flecs_component_ptr_t flecs_get_mut( const ecs_world_t *world, ecs_entity_t entity, - ecs_id_t id) + ecs_entity_t id, + ecs_record_t *r, + ecs_size_t size) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + (void)entity; - /* Make sure we're not working with a stage */ world = ecs_get_world(world); - if (ECS_HAS_ROLE(id, CASE)) { - ecs_entity_info_t info; - ecs_table_t *table; - if (!flecs_get_info(world, entity, &info) || !(table = info.table)) { - return false; - } + flecs_check_exclusive_world_access_write(world); - int32_t index = flecs_table_switch_from_case(world, table, id); - if (index == -1) { - /* If table has IsA relationship, traverse */ - if (table->flags & EcsTableHasIsA) { - int32_t oi = 0; - ecs_entity_t b = 0; - while ((b = ecs_get_object(world, entity, EcsIsA, oi ++))) { - return ecs_has_id(world, b, id); - } + flecs_component_ptr_t result; + + if (id < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_lookup[id]) { + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->component_map != NULL, ECS_INTERNAL_ERROR, NULL); + int16_t column_index = table->component_map[id]; + if (column_index > 0) { + ecs_column_t *column = &table->data.columns[column_index - 1]; + ecs_check(column->ti->size == size, + ECS_INVALID_PARAMETER, "invalid component size"); + result.ptr = ECS_ELEM(column->data, size, + ECS_RECORD_TO_ROW(r->row)); + result.ti = column->ti; + return result; } - return false; + return (flecs_component_ptr_t){0}; } + } - ecs_assert(index < table->sw_column_count, ECS_INTERNAL_ERROR, NULL); - - ecs_data_t *data = info.data; - ecs_switch_t *sw = data->sw_columns[index].data; - ecs_entity_t value = flecs_switch_get(sw, info.row); + ecs_component_record_t *cr = flecs_components_get(world, id); + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_get_component_ptr(world, r->table, row, cr); +error: + return (flecs_component_ptr_t){0}; +} - return value == (id & ECS_COMPONENT_MASK); - } else { - ecs_table_t *table = ecs_get_table(world, entity); - if (!table) { - return false; +void flecs_record_add_flag( + ecs_record_t *record, + uint32_t flag) +{ + if (flag == EcsEntityIsTraversable) { + if (!(record->row & flag)) { + ecs_table_t *table = record->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_table_traversable_add(table, 1); } - - return ecs_search_relation( - world, table, 0, id, EcsIsA, 0, 0, 0, 0, 0, 0) != -1; } -error: - return false; + record->row |= flag; } -ecs_entity_t ecs_get_object( - const ecs_world_t *world, +void flecs_add_flag( + ecs_world_t *world, ecs_entity_t entity, - ecs_entity_t rel, - int32_t index) + uint32_t flag) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); + ecs_record_t *record = flecs_entities_get_any(world, entity); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_record_add_flag(record, flag); +} - world = ecs_get_world(world); +void flecs_add_to_root_table( + ecs_world_t *world, + ecs_entity_t e) +{ + flecs_poly_assert(world, ecs_world_t); - ecs_table_t *table = ecs_get_table(world, entity); - if (!table) { - return 0; - } + ecs_assert(!(world->flags & EcsWorldMultiThreaded), + ECS_INTERNAL_ERROR, NULL); - ecs_id_t wc = ecs_pair(rel, EcsWildcard); - ecs_table_record_t *tr = flecs_get_table_record(world, table, wc); - if (!tr) { - return 0; - } + ecs_record_t *r = flecs_entities_get(world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == NULL, ECS_INTERNAL_ERROR, NULL); - if (index >= tr->count) { - return 0; - } + int32_t row = flecs_table_append(world, &world->store.root, e, false, false); + r->table = &world->store.root; + r->row = ECS_ROW_TO_RECORD(row, r->row & ECS_ROW_FLAGS_MASK); - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - return ecs_pair_second(world, ids[tr->column + index]); -error: - return 0; + flecs_journal(world, EcsJournalNew, e, 0, 0); } -ecs_entity_t ecs_get_object_for_id( +const char* flecs_entity_invalid_reason( const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel, - ecs_id_t id) + ecs_entity_t entity) { - ecs_table_t *table = ecs_get_table(world, entity); - ecs_entity_t subject = 0; - - if (rel) { - int32_t column = ecs_search_relation( - world, table, 0, id, rel, 0, 0, &subject, 0, 0, 0); - if (column == -1) { - return 0; - } - } else { - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - int32_t i, count = ecs_vector_count(table->type); - - for (i = 0; i < count; i ++) { - ecs_id_t ent = ids[i]; - if (ent & ECS_ROLE_MASK) { - /* Skip ids with pairs, roles since 0 was provided for rel */ - break; - } - - if (ecs_has_id(world, ent, id)) { - subject = ent; - break; - } - } + if (!entity) { + return "entity id cannot be 0"; } - if (subject == 0) { - return entity; - } else { - return subject; + if (entity & ECS_PAIR) { + return "cannot use a pair as an entity"; } -} -static -const char* get_identifier( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + if (entity & ECS_ID_FLAGS_MASK) { + return "entity id contains flag bits (TOGGLE or AUTO_OVERRIDE)"; + } - const EcsIdentifier *ptr = ecs_get_pair( - world, entity, EcsIdentifier, tag); + /* Entities should not contain data in dead zone bits */ + if (entity & ~0xFF00FFFFFFFFFFFF) { + return "entity id is not a valid bit pattern for an entity"; + } - if (ptr) { - return ptr->value; - } else { - return NULL; + if (!ecs_is_alive(world, entity)) { + return "entity is not alive"; } -error: + return NULL; } -const char* ecs_get_name( - const ecs_world_t *world, - ecs_entity_t entity) -{ - return get_identifier(world, entity, EcsName); -} +#define flecs_assert_entity_valid(world, entity, function) \ + ecs_check(entity && ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, \ + "invalid entity '%s' passed to %s(): %s", \ + flecs_errstr(ecs_id_str(world, entity)),\ + function,\ + flecs_entity_invalid_reason(world, entity)); -const char* ecs_get_symbol( - const ecs_world_t *world, - ecs_entity_t entity) -{ - return get_identifier(world, entity, EcsSymbol); -} +#define flecs_assert_component_valid(world, entity, component, function)\ + ecs_check(ecs_id_is_valid(world, component), ECS_INVALID_PARAMETER, \ + "invalid component '%s' passed to %s() for entity '%s': %s", \ + flecs_errstr(ecs_id_str(world, component)), \ + function,\ + flecs_errstr_1(ecs_get_path(world, entity)), \ + flecs_id_invalid_reason(world, component)) -static -ecs_entity_t set_identifier( + +/* -- Public functions -- */ + +bool ecs_commit( ecs_world_t *world, ecs_entity_t entity, - ecs_entity_t tag, - const char *name) + ecs_record_t *record, + ecs_table_t *table, + const ecs_type_t *added, + const ecs_type_t *removed) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - if (!entity) { - entity = ecs_new_id(world); - } + flecs_assert_entity_valid(world, entity, "commit"); + ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, + "commit cannot be called on stage or while world is deferred"); - EcsIdentifier *ptr = ecs_get_mut_pair(world, entity, EcsIdentifier, tag, 0); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_strset(&ptr->value, name); - ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); - - return entity; -error: - return 0; -} - - -ecs_entity_t ecs_set_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - if (!entity) { - return ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = name - }); + ecs_table_t *src_table = NULL; + if (!record) { + record = flecs_entities_get(world, entity); + src_table = record->table; } - return set_identifier(world, entity, EcsName, name); -} - -ecs_entity_t ecs_set_symbol( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - return set_identifier(world, entity, EcsSymbol, name); -} - -void ecs_set_alias( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - set_identifier(world, entity, EcsAlias, name); -} - -ecs_id_t ecs_make_pair( - ecs_entity_t relation, - ecs_entity_t object) -{ - return ecs_pair(relation, object); -} - -bool ecs_is_valid( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - /* 0 is not a valid entity id */ - if (!entity) { - return false; - } - - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); - - /* Entity identifiers should not contain flag bits */ - if (entity & ECS_ROLE_MASK) { - return false; - } + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; - /* Entities should not contain data in dead zone bits */ - if (entity & ~0xFF00FFFFFFFFFFFF) { - return false; + if (added) { + diff.added = *added; + diff.added_flags = table->flags & EcsTableAddEdgeFlags; } - - if (entity & ECS_ROLE) { - return ecs_entity_t_lo(entity) != 0; + if (removed) { + diff.removed = *removed; + if (src_table) { + diff.removed_flags = src_table->flags & EcsTableRemoveEdgeFlags; + } } - /* If entity doesn't exist in the world, the id is valid as long as the - * generation is 0. Using a non-existing id with a non-zero generation - * requires calling ecs_ensure first. */ - if (!ecs_exists(world, entity)) { - return ECS_GENERATION(entity) == 0; - } + ecs_defer_begin(world); + flecs_commit(world, entity, record, table, &diff, true, 0); + ecs_defer_end(world); - /* If id exists, it must be alive (the generation count must match) */ - return ecs_is_alive(world, entity); + return src_table != table; error: return false; } -ecs_id_t ecs_strip_generation( - ecs_entity_t e) -{ - /* If this is not a pair, erase the generation bits */ - if (!(e & ECS_ROLE_MASK)) { - e &= ~ECS_GENERATION_MASK; - } - - return e; -} - -bool ecs_is_alive( - const ecs_world_t *world, - ecs_entity_t entity) +ecs_entity_t ecs_new( + ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - return ecs_eis_is_alive(world, entity); + flecs_stage_from_world(&world); + ecs_entity_t e = flecs_new_id(world); + flecs_add_to_root_table(world, e); + return e; error: - return false; + return 0; } -ecs_entity_t ecs_get_alive( - const ecs_world_t *world, - ecs_entity_t entity) +ecs_entity_t ecs_new_low_id( + ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!entity) { - return 0; - } + ecs_check(!(world->flags & EcsWorldMultiThreaded), + ECS_INVALID_OPERATION, "cannot create entities in multithreaded mode"); - if (ecs_is_alive(world, entity)) { - return entity; - } + flecs_stage_from_world(&world); - /* Make sure id does not have generation. This guards against accidentally - * "upcasting" a not alive identifier to a alive one. */ - ecs_assert((uint32_t)entity == entity, ECS_INVALID_PARAMETER, NULL); + flecs_check_exclusive_world_access_write(world); - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + ecs_entity_t e = 0; + if (world->info.last_component_id < FLECS_HI_COMPONENT_ID) { + do { + e = world->info.last_component_id ++; + } while (ecs_exists(world, e) && e <= FLECS_HI_COMPONENT_ID); + } - ecs_entity_t current = ecs_eis_get_current(world, entity); - if (!current || !ecs_is_alive(world, current)) { - return 0; + if (!e || e >= FLECS_HI_COMPONENT_ID) { + /* If the low component ids are depleted, return a regular entity id */ + e = ecs_new(world); + } else { + flecs_entities_ensure(world, e); + flecs_add_to_root_table(world, e); } - return current; -error: + return e; +error: return 0; } -void ecs_ensure( +ecs_entity_t ecs_new_w_table( ecs_world_t *world, - ecs_entity_t entity) + ecs_table_t *table) { - ecs_poly_assert(world, ecs_world_t); /* Cannot be a stage */ - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); - /* Check if a version of the provided id is alive */ - ecs_entity_t any = ecs_get_alive(world, ecs_strip_generation(entity)); - if (any == entity) { - /* If alive and equal to the argument, there's nothing left to do */ - return; + flecs_stage_from_world(&world); + ecs_entity_t entity = flecs_new_id(world); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_flags32_t flags = table->flags & EcsTableAddEdgeFlags; + if (table->flags & EcsTableHasIsA) { + flags |= EcsTableHasOnAdd; } - /* If the id is currently alive but did not match the argument, fail */ - ecs_check(!any, ECS_INVALID_PARAMETER, NULL); + ecs_table_diff_t table_diff = { + .added = table->type, + .added_flags = flags + }; - /* Set generation if not alive. The sparse set checks if the provided - * id matches its own generation which is necessary for alive ids. This - * check would cause ecs_ensure to fail if the generation of the 'entity' - * argument doesn't match with its generation. - * - * While this could've been addressed in the sparse set, this is a rare - * scenario that can only be triggered by ecs_ensure. Implementing it here - * allows the sparse set to not do this check, which is more efficient. */ - ecs_eis_set_generation(world, entity); + flecs_new_entity(world, entity, r, table, &table_diff, true, 0); - /* Ensure id exists. The underlying datastructure will verify that the - * generation count matches the provided one. */ - ecs_eis_ensure(world, entity); + return entity; error: - return; + return 0; } -void ecs_ensure_id( +ecs_entity_t ecs_new_w_id( ecs_world_t *world, - ecs_id_t id) + ecs_id_t component) { - if (ECS_HAS_ROLE(id, PAIR) || ECS_HAS_ROLE(id, CASE)) { - ecs_entity_t r = ECS_PAIR_FIRST(id); - ecs_entity_t o = ECS_PAIR_SECOND(id); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, component), ECS_INVALID_PARAMETER, + "invalid component '%s' passed to new_w(): %s", + flecs_errstr(ecs_id_str(world, component)), + flecs_id_invalid_reason(world, component)) - ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - if (ecs_get_alive(world, r) == 0) { - ecs_ensure(world, r); - } - if (ecs_get_alive(world, o) == 0) { - ecs_ensure(world, o); - } - } else { - ecs_ensure(world, id & ECS_COMPONENT_MASK); + if (flecs_defer_cmd(stage)) { + ecs_entity_t e = ecs_new(world); + ecs_add_id(world, e, component); + return e; } -error: - return; -} -bool ecs_exists( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_table_diff_t table_diff = ECS_TABLE_DIFF_INIT; + ecs_table_t *table = flecs_table_traverse_add( + world, &world->store.root, &component, &table_diff); + + ecs_entity_t entity = flecs_new_id(world); + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_new_entity(world, entity, r, table, &table_diff, true, 0); - world = ecs_get_world(world); + flecs_defer_end(world, stage); - return ecs_eis_exists(world, entity); + return entity; error: - return false; + return 0; } -ecs_table_t* ecs_get_table( - const ecs_world_t *world, - ecs_entity_t entity) +static +void flecs_copy_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *r, + ecs_id_t component, + size_t size, + void *dst_ptr, + const void *src_ptr, + const ecs_type_info_t *ti) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); + ecs_assert(dst_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_record_t *record = ecs_eis_get(world, entity); - ecs_table_t *table; - if (record && (table = record->table)) { - return table; + if (ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, entity, component, dst_ptr, src_ptr, ti); } -error: - return NULL; -} -ecs_table_t* ecs_get_storage_table( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_table_t *table = ecs_get_table(world, entity); - if (table) { - return table->storage_table; + ecs_copy_t copy = ti->hooks.copy; + if (copy) { + copy(dst_ptr, src_ptr, 1, ti); + } else { + ecs_os_memcpy(dst_ptr, src_ptr, flecs_utosize(size)); } - return NULL; -} + flecs_table_mark_dirty(world, r->table, component); -ecs_type_t ecs_get_type( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_table_t *table = ecs_get_table(world, entity); - if (table) { - return table->type; - } + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return NULL; + flecs_notify_on_set( + world, table, ECS_RECORD_TO_ROW(r->row), component, true); } -ecs_entity_t ecs_get_typeid( - const ecs_world_t *world, - ecs_id_t id) +/* Traverse table graph by either adding or removing identifiers parsed from the + * passed in expression. */ +static +int flecs_traverse_from_expr( + ecs_world_t *world, + const char *name, + const char *expr, + ecs_vec_t *ids) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - /* Hardcode components used in bootstrap */ - if (id == ecs_id(EcsComponent)) { - return id; - } else if (id == ecs_id(EcsIdentifier)) { - return id; - } else if (ECS_PAIR_FIRST(id) == ecs_id(EcsIdentifier)) { - return ecs_id(EcsIdentifier); - } else if (ECS_PAIR_FIRST(id) == EcsChildOf) { - return 0; - } else if (ECS_PAIR_FIRST(id) == EcsOnDelete) { - return 0; - } - - if (ECS_HAS_ROLE(id, PAIR)) { - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); - - ecs_entity_t rel = ecs_get_alive(world, ECS_PAIR_FIRST(id)); - - /* If relation is marked as a tag, it never has data. Return relation */ - if (ecs_has_id(world, rel, EcsTag)) { - return 0; - } +#ifdef FLECS_QUERY_DSL + const char *ptr = expr; + if (ptr) { + ecs_id_t component = 0; + while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &component))) { + if (!component) { + break; + } - const EcsComponent *ptr = ecs_get(world, rel, EcsComponent); - if (ptr && ptr->size != 0) { - return rel; - } - - ecs_entity_t obj = ECS_PAIR_SECOND(id); - if (obj) { - obj = ecs_get_alive(world, obj); - ptr = ecs_get(world, obj, EcsComponent); - - if (ptr && ptr->size != 0) { - return obj; + if (!ecs_id_is_valid(world, component)) { + char *idstr = ecs_id_str(world, component); + ecs_parser_error(name, expr, (ptr - expr), + "'%s' is invalid for ecs_entity_desc_t::add_expr", idstr); + ecs_os_free(idstr); + goto error; } + + ecs_vec_append_t(&world->allocator, ids, ecs_id_t)[0] = component; } - /* Neither relation nor object have data */ - return 0; - } else if (id & ECS_ROLE_MASK) { - return 0; - } else { - const EcsComponent *ptr = ecs_get(world, id, EcsComponent); - if (!ptr || !ptr->size) { - return 0; + if (!ptr) { + goto error; } } - - return id; -error: return 0; +#else + (void)world; + (void)name; + (void)expr; + (void)ids; + ecs_err("cannot parse component expression: script addon required"); + goto error; +#endif +error: + return -1; } -ecs_entity_t ecs_id_is_tag( - const ecs_world_t *world, - ecs_id_t id) +/* Add/remove components based on the parsed expression. This operation is + * slower than flecs_traverse_from_expr, but safe to use from a deferred context. */ +static +void flecs_defer_from_expr( + ecs_world_t *world, + ecs_entity_t entity, + const char *name, + const char *expr) { - if (ecs_id_is_wildcard(id)) { - /* If id is a wildcard, we can't tell if it's a tag or not, except - * when the relation part of a pair has the Tag property */ - if (ECS_HAS_ROLE(id, PAIR)) { - if (ECS_PAIR_FIRST(id) != EcsWildcard) { - ecs_entity_t rel = ecs_pair_first(world, id); - if (ecs_is_valid(world, rel)) { - if (ecs_has_id(world, rel, EcsTag)) { - return true; - } - } else { - /* During bootstrap it's possible that not all ids are valid - * yet. Using ecs_get_typeid will ensure correct values are - * returned for only those components initialized during - * bootstrap, while still asserting if another invalid id - * is provided. */ - if (ecs_get_typeid(world, id) == 0) { - return true; - } - } - } else { - /* If relation is * id is not guaranteed to be a tag */ +#ifdef FLECS_QUERY_DSL + const char *ptr = expr; + if (ptr) { + ecs_id_t component = 0; + while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &component))) { + if (!component) { + break; } - } - } else { - if (ecs_get_typeid(world, id) == 0) { - return true; + ecs_add_id(world, entity, component); } } - - return false; +#else + (void)world; + (void)entity; + (void)name; + (void)expr; + ecs_err("cannot parse component expression: script addon required"); +#endif } -int32_t ecs_count_id( - const ecs_world_t *world, - ecs_entity_t id) +/* If operation is not deferred, add components by finding the target + * table and moving the entity towards it. */ +static +int flecs_traverse_add( + ecs_world_t *world, + ecs_entity_t result, + const char *name, + const ecs_entity_desc_t *desc, + ecs_entity_t scope, + ecs_id_t with, + bool new_entity, + bool name_assigned) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const char *sep = desc->sep; + const char *root_sep = desc->root_sep; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + ecs_vec_t ids; + + /* Add components from the 'add_expr' expression. Look up before naming + * entity, so that expression can't resolve to self. */ + ecs_vec_init_t(&world->allocator, &ids, ecs_id_t, 0); + if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { + if (flecs_traverse_from_expr(world, name, desc->add_expr, &ids)) { + goto error; + } + } - if (!id) { - return 0; + /* Set symbol */ + if (desc->symbol && desc->symbol[0]) { + const char *sym = ecs_get_symbol(world, result); + if (sym) { + ecs_assert(!ecs_os_strcmp(desc->symbol, sym), ECS_INCONSISTENT_NAME, + "entity symbol inconsistent: %s (provided) vs. %s (existing)", + desc->symbol, sym); + } else { + ecs_set_symbol(world, result, desc->symbol); + } } - int32_t count = 0; - ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { .id = id }); - while (ecs_term_next(&it)) { - count += it.count; + /* If a name is provided but not yet assigned, add the Name component */ + if (name && !name_assigned) { + if (!ecs_add_path_w_sep(world, result, scope, name, sep, root_sep)) { + if (name[0] == '#') { + /* Numerical ids should always return, unless it's invalid */ + goto error; + } + } + } else if (new_entity && scope) { + ecs_add_pair(world, result, EcsChildOf, scope); } - return count; -error: - return 0; -} + /* Find existing table */ + ecs_table_t *src_table = NULL, *table = NULL; + ecs_record_t *r = flecs_entities_get(world, result); + table = r->table; -void ecs_enable( - ecs_world_t *world, - ecs_entity_t entity, - bool enabled) -{ - const EcsType *type_ptr = ecs_get(world, entity, EcsType); - if (type_ptr) { - /* If entity is a type, disable all entities in the type */ - ecs_vector_each(type_ptr->normalized->type, ecs_entity_t, e, { - ecs_enable(world, *e, enabled); - }); - } else { - if (enabled) { - ecs_remove_id(world, entity, EcsDisabled); - } else { - ecs_add_id(world, entity, EcsDisabled); + /* Add components from the 'add' array */ + if (desc->add) { + int32_t i = 0; + ecs_id_t component; + + while ((component = desc->add[i ++])) { + table = flecs_find_table_add(world, table, component, &diff); } } -} - -bool ecs_defer_begin( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - return flecs_defer_none(world, stage); -error: - return false; -} -bool ecs_defer_end( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - return flecs_defer_flush(world, stage); -error: - return false; -} + /* Add components from the 'set' array */ + if (desc->set) { + int32_t i = 0; + ecs_id_t component; -const char* ecs_role_str( - ecs_entity_t entity) -{ - if (ECS_HAS_ROLE(entity, PAIR)) { - return "PAIR"; - } else - if (ECS_HAS_ROLE(entity, DISABLED)) { - return "DISABLED"; - } else - if (ECS_HAS_ROLE(entity, XOR)) { - return "XOR"; - } else - if (ECS_HAS_ROLE(entity, OR)) { - return "OR"; - } else - if (ECS_HAS_ROLE(entity, AND)) { - return "AND"; - } else - if (ECS_HAS_ROLE(entity, NOT)) { - return "NOT"; - } else - if (ECS_HAS_ROLE(entity, SWITCH)) { - return "SWITCH"; - } else - if (ECS_HAS_ROLE(entity, CASE)) { - return "CASE"; - } else - if (ECS_HAS_ROLE(entity, OVERRIDE)) { - return "OVERRIDE"; - } else { - return "UNKNOWN"; + while ((component = desc->set[i ++].type)) { + table = flecs_find_table_add(world, table, component, &diff); + } } -} -void ecs_id_str_buf( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + /* Add ids from .expr */ + { + int32_t i, count = ecs_vec_count(&ids); + ecs_id_t *expr_ids = ecs_vec_first(&ids); + for (i = 0; i < count; i ++) { + table = flecs_find_table_add(world, table, expr_ids[i], &diff); + } + } - world = ecs_get_world(world); + /* Find destination table */ + /* If this is a new entity without a name, add the scope. If a name is + * provided, the scope will be added by the add_path_w_sep function */ + if (new_entity) { + if (new_entity && scope && !name && !name_assigned) { + table = flecs_find_table_add( + world, table, ecs_pair(EcsChildOf, scope), &diff); + } + if (with) { + table = flecs_find_table_add(world, table, with, &diff); + } + } - if (id & ECS_ROLE_MASK && !ECS_HAS_ROLE(id, PAIR)) { - ecs_strbuf_appendstr(buf, ecs_role_str(id)); - ecs_strbuf_appendch(buf, '|'); + /* Commit entity to destination table */ + if (src_table != table) { + flecs_defer_begin(world, world->stages[0]); + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + flecs_commit(world, result, r, table, &table_diff, true, 0); + flecs_table_diff_builder_fini(world, &diff); + flecs_defer_end(world, world->stages[0]); } - if (ECS_HAS_ROLE(id, PAIR) || ECS_HAS_ROLE(id, CASE)) { - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t obj = ECS_PAIR_SECOND(id); + /* Set component values */ + if (desc->set) { + table = r->table; + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i = 0, row = ECS_RECORD_TO_ROW(r->row); + const ecs_value_t *v; + + flecs_defer_begin(world, world->stages[0]); - ecs_entity_t e; - if ((e = ecs_get_alive(world, rel))) { - rel = e; - } - if ((e = ecs_get_alive(world, obj))) { - obj = e; + while ((void)(v = &desc->set[i ++]), v->type) { + if (!v->ptr) { + continue; + } + ecs_assert(ECS_RECORD_TO_ROW(r->row) == row, ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *cr = flecs_components_get(world, v->type); + flecs_component_ptr_t ptr = flecs_get_component_ptr( + world, table, row, cr); + ecs_check(ptr.ptr != NULL, ECS_INVALID_OPERATION, + "component '%s' added to entity '%s' was removed during the " + "operation, make sure not to remove the component in hooks/observers", + flecs_errstr(ecs_id_str(world, v->type)), + flecs_errstr_2(ecs_get_path(world, result))); + + const ecs_type_info_t *ti = cr->type_info; + flecs_copy_id(world, result, r, v->type, + flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); } - ecs_strbuf_appendch(buf, '('); - ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf); - ecs_strbuf_appendch(buf, ','); - ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); - ecs_strbuf_appendch(buf, ')'); - } else { - ecs_entity_t e = id & ECS_COMPONENT_MASK; - ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); + flecs_defer_end(world, world->stages[0]); } + flecs_table_diff_builder_fini(world, &diff); + ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); + return 0; error: - return; + flecs_table_diff_builder_fini(world, &diff); + ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); + return -1; } -char* ecs_id_str( - const ecs_world_t *world, - ecs_id_t id) +/* When in deferred mode, we need to add/remove components one by one using + * the regular operations. */ +static +void flecs_deferred_add_remove( + ecs_world_t *world, + ecs_entity_t entity, + const char *name, + const ecs_entity_desc_t *desc, + ecs_entity_t scope, + ecs_id_t with, + bool new_entity, + bool name_assigned) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_id_str_buf(world, id, &buf); - return ecs_strbuf_get(&buf); -} + const char *sep = desc->sep; + const char *root_sep = desc->root_sep; + /* If this is a new entity without a name, add the scope. If a name is + * provided, the scope will be added by the add_path_w_sep function */ + if (new_entity) { + if (new_entity && scope && !name && !name_assigned) { + ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); + } -char* ecs_type_str( - const ecs_world_t *world, - ecs_type_t type) -{ - if (!type) { - return ecs_os_strdup(""); + if (with) { + ecs_add_id(world, entity, with); + } } - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_entity_t *ids = ecs_vector_first(type, ecs_entity_t); - int32_t i, count = ecs_vector_count(type); - - for (i = 0; i < count; i ++) { - ecs_entity_t id = ids[i]; + /* Add components from the 'add' id array */ + if (desc->add) { + int32_t i = 0; + ecs_id_t component; - if (i) { - ecs_strbuf_appendch(&buf, ','); - ecs_strbuf_appendch(&buf, ' '); + while ((component = desc->add[i ++])) { + bool defer = true; + if (ECS_HAS_ID_FLAG(component, PAIR) && + ECS_PAIR_FIRST(component) == EcsChildOf) + { + scope = ECS_PAIR_SECOND(component); + if (name && (!desc->id || !name_assigned)) { + /* New named entities are created by temporarily going out of + * readonly mode to ensure no duplicates are created. */ + defer = false; + } + } + if (defer) { + ecs_add_id(world, entity, component); + } } + } - if (id == 1) { - ecs_strbuf_appendstr(&buf, "Component"); - } else { - ecs_id_str_buf(world, id, &buf); + /* Set component values */ + if (desc->set) { + int32_t i = 0; + const ecs_value_t *v; + while ((void)(v = &desc->set[i ++]), v->type) { + if (v->ptr) { + ecs_check(v->type != 0, ECS_INVALID_PARAMETER, + "0 passed for component to ecs_entity_desc_t::set[%d]", i); + const ecs_type_info_t *ti = ecs_get_type_info(world, v->type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "component '%s' passed to ecs_entity_desc_t::set[%d] is a " + "tag/zero sized", + flecs_errstr(ecs_id_str(world, v->type)), i); + ecs_set_id(world, entity, v->type, + flecs_ito(size_t, ti->size), v->ptr); + } else { + ecs_add_id(world, entity, v->type); + } } } - return ecs_strbuf_get(&buf); -} + /* Add components from the 'add_expr' expression */ + if (desc->add_expr) { + flecs_defer_from_expr(world, entity, name, desc->add_expr); + } -static -void flush_bulk_new( - ecs_world_t *world, - ecs_defer_op_t *op) -{ - ecs_entity_t *entities = op->is._n.entities; + int32_t thread_count = ecs_get_stage_count(world); - if (op->id) { - int i, count = op->is._n.count; - for (i = 0; i < count; i ++) { - add_id(world, entities[i], op->id); + /* Set symbol */ + if (desc->symbol) { + const char *sym = ecs_get_symbol(world, entity); + if (!sym || ecs_os_strcmp(sym, desc->symbol)) { + if (thread_count <= 1) { /* See above */ + ecs_suspend_readonly_state_t state; + ecs_world_t *real_world = flecs_suspend_readonly(world, &state); + ecs_set_symbol(world, entity, desc->symbol); + flecs_resume_readonly(real_world, &state); + } else { + ecs_set_symbol(world, entity, desc->symbol); + } } } - ecs_os_free(entities); + /* Set name */ + if (name && !name_assigned) { + ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); + } +error: + return; } -static -void free_value( +ecs_entity_t ecs_entity_init( ecs_world_t *world, - ecs_id_t id, - void *value, - int32_t count) + const ecs_entity_desc_t *desc) { - ecs_entity_t real_id = ecs_get_typeid(world, id); - const ecs_type_info_t *ti = flecs_get_type_info(world, real_id); - ecs_xtor_t dtor = ti->lifecycle.dtor; - - if (dtor) { - ecs_size_t size = ti->size; - void *ptr; - int i; - for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { - dtor(ptr, 1, ti); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_entity_desc_t is uninitialized, initialize to {0} before using"); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t scope = stage->scope; + ecs_id_t with = ecs_get_with(world); + ecs_entity_t result = desc->id; + +#ifdef FLECS_DEBUG + if (desc->add) { + ecs_id_t component; + int32_t i = 0; + while ((component = desc->add[i ++])) { + if (ECS_HAS_ID_FLAG(component, PAIR) && + (ECS_PAIR_FIRST(component) == EcsChildOf)) + { + if (desc->name) { + ecs_check(false, ECS_INVALID_PARAMETER, "%s: cannot set parent in " + "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent", + desc->name); + } else { + ecs_check(false, ECS_INVALID_PARAMETER, "cannot set parent in " + "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent"); + } + } } } -} +#endif -static -void discard_op( - ecs_world_t *world, - ecs_defer_op_t *op) -{ - if (op->kind != EcsOpBulkNew) { - void *value = op->is._1.value; - if (value) { - free_value(world, op->id, op->is._1.value, 1); - ecs_os_free(value); - } - } else { - ecs_os_free(op->is._n.entities); + const char *name = desc->name; + const char *sep = desc->sep; + if (!sep) { + sep = "."; } -} -static -bool is_entity_valid( - ecs_world_t *world, - ecs_entity_t e) -{ - if (ecs_exists(world, e) && !ecs_is_alive(world, e)) { - return false; + if (name) { + if (!name[0]) { + name = NULL; + } else if (flecs_name_is_id(name)){ + ecs_entity_t id = flecs_name_to_id(name); + if (!id) { + return 0; + } + + if (result && (id != result)) { + ecs_err( + "the '#xxx' string provided to ecs_entity_desc_t::name " + "does not match the id provided to ecs_entity_desc_t::id"); + return 0; + } + + name = NULL; + result = id; + } } - return true; -} -static -bool remove_invalid( - ecs_world_t *world, - ecs_id_t *id_out) -{ - ecs_id_t id = *id_out; + const char *root_sep = desc->root_sep; + bool new_entity = false; + bool name_assigned = false; - if (ECS_HAS_ROLE(id, PAIR)) { - ecs_entity_t rel = ecs_pair_first(world, id); - if (!rel || !is_entity_valid(world, rel)) { - /* After relation is deleted we can no longer see what its - * delete action was, so pretend this never happened */ - *id_out = 0; - return true; - } else { - ecs_entity_t obj = ecs_pair_second(world, id); - if (!obj || !is_entity_valid(world, obj)) { - /* Check the relation's policy for deleted objects */ - ecs_id_record_t *idr = flecs_get_id_record(world, rel); - if (idr) { - ecs_entity_t action = ECS_ID_ON_DELETE_OBJECT(idr->flags); - if (action == EcsDelete) { - /* Entity should be deleted, don't bother checking - * other ids */ - return false; - } else if (action == EcsThrow) { - /* If policy is throw this object should not have - * been deleted */ - throw_invalid_delete(world, id); - } else { - *id_out = 0; - return true; - } - } else { - *id_out = 0; - return true; - } + /* Remove optional prefix from name. Entity names can be derived from + * language identifiers, such as components (typenames) and systems + * function names). Because C does not have namespaces, such identifiers + * often encode the namespace as a prefix. + * To ensure interoperability between C and C++ (and potentially other + * languages with namespacing) the entity must be stored without this prefix + * and with the proper namespace, which is what the name_prefix is for */ + const char *prefix = world->info.name_prefix; + if (name && prefix) { + ecs_size_t len = ecs_os_strlen(prefix); + if (!ecs_os_strncmp(name, prefix, len) && + (isupper(name[len]) || name[len] == '_')) + { + if (name[len] == '_') { + name = name + len + 1; + } else { + name = name + len; } } - } else { - id &= ECS_COMPONENT_MASK; - if (!is_entity_valid(world, id)) { - /* After relation is deleted we can no longer see what its - * delete action was, so pretend this never happened */ - *id_out = 0; - return true; - } } - return true; -} + /* Parent field takes precedence over scope */ + if (desc->parent) { + scope = desc->parent; + ecs_check(ecs_is_valid(world, desc->parent), ECS_INVALID_PARAMETER, + "the entity provided in ecs_entity_desc_t::parent is not valid"); + } -/* Leave safe section. Run all deferred commands. */ -bool flecs_defer_flush( - ecs_world_t *world, - ecs_stage_t *stage) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + /* Find or create entity */ + if (!result) { + if (name) { + /* If add array contains a ChildOf pair, use it as scope instead */ + result = ecs_lookup_path_w_sep( + world, scope, name, sep, root_sep, false); + if (result) { + name_assigned = true; + } + } - if (!--stage->defer) { - /* Set to NULL. Processing deferred commands can cause additional - * commands to get enqueued (as result of reactive systems). Make sure - * that the original array is not reallocated, as this would complicate - * processing the queue. */ - ecs_vector_t *defer_queue = stage->defer_queue; - stage->defer_queue = NULL; - - if (defer_queue) { - ecs_defer_op_t *ops = ecs_vector_first(defer_queue, ecs_defer_op_t); - int32_t i, count = ecs_vector_count(defer_queue); - - for (i = 0; i < count; i ++) { - ecs_defer_op_t *op = &ops[i]; - ecs_entity_t e = op->is._1.entity; - if (op->kind == EcsOpBulkNew) { - e = 0; - } + if (!result) { + if (desc->use_low_id) { + result = ecs_new_low_id(world); + } else { + result = ecs_new(world); + } + new_entity = true; + ecs_assert(ecs_get_type(world, result) != NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_get_type(world, result)->count == 0, + ECS_INTERNAL_ERROR, NULL); + } + } else { + /* Make sure provided id is either alive or revivable */ + ecs_make_alive(world, result); - /* If entity is no longer alive, this could be because the queue - * contained both a delete and a subsequent add/remove/set which - * should be ignored. */ - if (e && !ecs_is_alive(world, e) && ecs_eis_exists(world, e)) { - ecs_assert(op->kind != EcsOpNew && op->kind != EcsOpClone, - ECS_INTERNAL_ERROR, NULL); - world->discard_count ++; - discard_op(world, op); - continue; + name_assigned = ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName); + if (name && name_assigned) { + /* If entity has name, verify that name matches. The name provided + * to the function could either have been relative to the current + * scope, or fully qualified. */ + char *path; + ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; + if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { + /* Fully qualified name was provided, so make sure to + * compare with fully qualified name */ + path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); + } else { + /* Relative name was provided, so make sure to compare with + * relative name */ + if (!sep || sep[0]) { + path = ecs_get_path_w_sep(world, scope, result, sep, ""); + } else { + /* Safe, only freed when sep is valid */ + path = ECS_CONST_CAST(char*, ecs_get_name(world, result)); } - - switch(op->kind) { - case EcsOpNew: - case EcsOpAdd: - ecs_assert(op->id != 0, ECS_INTERNAL_ERROR, NULL); - if (remove_invalid(world, &op->id)) { - if (op->id) { - world->add_count ++; - add_id(world, e, op->id); - } - } else { - ecs_delete(world, e); - } - break; - case EcsOpRemove: - remove_id(world, e, op->id); - break; - case EcsOpClone: - ecs_clone(world, e, op->id, op->is._1.clone_value); - break; - case EcsOpSet: - assign_ptr_w_id(world, e, - op->id, flecs_itosize(op->is._1.size), - op->is._1.value, true, true); - break; - case EcsOpMut: - assign_ptr_w_id(world, e, - op->id, flecs_itosize(op->is._1.size), - op->is._1.value, true, false); - break; - case EcsOpModified: - if (ecs_has_id(world, e, op->id)) { - ecs_modified_id(world, e, op->id); + } + if (path) { + if (ecs_os_strcmp(path, name)) { + /* Mismatching name */ + ecs_err("existing entity '%s' is initialized with " + "conflicting name '%s'", path, name); + if (!sep || sep[0]) { + ecs_os_free(path); } - break; - case EcsOpDelete: { - ecs_delete(world, e); - break; + return 0; } - case EcsOpClear: - ecs_clear(world, e); - break; - case EcsOpOnDeleteAction: - on_delete_action(world, op->id, e); - break; - case EcsOpEnable: - ecs_enable_component_w_id(world, e, op->id, true); - break; - case EcsOpDisable: - ecs_enable_component_w_id(world, e, op->id, false); - break; - case EcsOpBulkNew: - flush_bulk_new(world, op); - continue; + if (!sep || sep[0]) { + ecs_os_free(path); } - - if (op->is._1.value) { - ecs_os_free(op->is._1.value); - } } + } + } - if (stage->defer_queue) { - ecs_vector_free(stage->defer_queue); - } + ecs_assert(name_assigned == ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName), + ECS_INTERNAL_ERROR, NULL); - /* Restore defer queue */ - ecs_vector_clear(defer_queue); - stage->defer_queue = defer_queue; + if (ecs_is_deferred(world)) { + flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, + scope, with, new_entity, name_assigned); + } else { + if (flecs_traverse_add(world, result, name, desc, + scope, with, new_entity, name_assigned)) + { + return 0; } - - return true; } + return result; error: - return false; + return 0; } -/* Delete operations from queue without executing them. */ -bool flecs_defer_purge( +const ecs_entity_t* ecs_bulk_init( ecs_world_t *world, - ecs_stage_t *stage) + const ecs_bulk_desc_t *desc) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!--stage->defer) { - ecs_vector_t *defer_queue = stage->defer_queue; - stage->defer_queue = NULL; + flecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_bulk_desc_t is uninitialized, set to {0} before using"); - if (defer_queue) { - ecs_defer_op_t *ops = ecs_vector_first(defer_queue, ecs_defer_op_t); - int32_t i, count = ecs_vector_count(defer_queue); - for (i = 0; i < count; i ++) { - discard_op(world, &ops[i]); - } + flecs_check_exclusive_world_access_write(world); - if (stage->defer_queue) { - ecs_vector_free(stage->defer_queue); - } + const ecs_entity_t *entities = desc->entities; + int32_t count = desc->count; - /* Restore defer queue */ - ecs_vector_clear(defer_queue); - stage->defer_queue = defer_queue; + int32_t sparse_count = 0; + if (!entities) { + sparse_count = flecs_entities_count(world); + entities = flecs_entities_new_ids(world, count); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + int i; + for (i = 0; i < count; i ++) { + ecs_assert(!ecs_is_alive(world, entities[i]), ECS_INVALID_PARAMETER, + "cannot pass alive entities to ecs_bulk_init()"); + flecs_entities_ensure(world, entities[i]); } + } - return true; + ecs_type_t ids; + ecs_table_t *table = desc->table; + if (!table) { + table = &world->store.root; } -error: - return false; -} + if (!table->type.count) { + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + int32_t i = 0; + ecs_id_t component; + while ((component = desc->ids[i])) { + table = flecs_find_table_add(world, table, component, &diff); + i ++; + } -static -ecs_defer_op_t* new_defer_op(ecs_stage_t *stage) { - ecs_defer_op_t *result = ecs_vector_add(&stage->defer_queue, ecs_defer_op_t); - ecs_os_memset(result, 0, ECS_SIZEOF(ecs_defer_op_t)); - return result; -} + ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); + ids.count = i; -static -bool defer_add_remove( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_defer_op_kind_t op_kind, - ecs_entity_t entity, - ecs_id_t id) -{ - if (stage->defer) { - if (!id) { - return true; + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, + &table_diff); + flecs_table_diff_builder_fini(world, &diff); + } else { + ecs_table_diff_t diff = { + .added.array = table->type.array, + .added.count = table->type.count + }; + + int32_t i = 0; + while ((desc->ids[i])) { + i ++; } - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = op_kind; - op->id = id; - op->is._1.entity = entity; + ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); + ids.count = i; - if (op_kind == EcsOpNew) { - world->new_count ++; - } else if (op_kind == EcsOpAdd) { - world->add_count ++; - } else if (op_kind == EcsOpRemove) { - world->remove_count ++; - } + flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, + &diff); + } - return true; + if (!sparse_count) { + return entities; } else { - stage->defer ++; + /* Refetch entity ids, in case the underlying array was reallocated */ + entities = flecs_entities_ids(world); + return &entities[sparse_count]; } - - return false; +error: + return NULL; } -static -void merge_stages( +const ecs_entity_t* ecs_bulk_new_w_id( ecs_world_t *world, - bool force_merge) + ecs_id_t component, + int32_t count) { - bool is_stage = ecs_poly_is(world, ecs_stage_t); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); - bool measure_frame_time = world->measure_frame_time; - - ecs_time_t t_start; - if (measure_frame_time) { - ecs_os_get_time(&t_start); - } - - if (is_stage) { - /* Check for consistency if force_merge is enabled. In practice this - * function will never get called with force_merge disabled for just - * a single stage. */ - if (force_merge || stage->auto_merge) { - ecs_defer_end((ecs_world_t*)stage); - } - } else { - /* Merge stages. Only merge if the stage has auto_merging turned on, or - * if this is a forced merge (like when ecs_merge is called) */ - int32_t i, count = ecs_get_stage_count(world); - for (i = 0; i < count; i ++) { - ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); - ecs_poly_assert(s, ecs_stage_t); - if (force_merge || s->auto_merge) { - ecs_defer_end((ecs_world_t*)s); - } - } + const ecs_entity_t *ids; + if (flecs_defer_bulk_new(world, stage, count, component, &ids)) { + return ids; } - flecs_eval_component_monitors(world); - - if (measure_frame_time) { - world->info.merge_time_total += (float)ecs_time_measure(&t_start); + ecs_table_t *table = &world->store.root; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + + if (component) { + table = flecs_find_table_add(world, table, component, &diff); } - world->info.merge_count_total ++; - - /* If stage is asynchronous, deferring is always enabled */ - if (stage->asynchronous) { - ecs_defer_begin((ecs_world_t*)stage); - } -} + ecs_table_diff_t td; + flecs_table_diff_build_noalloc(&diff, &td); + ids = flecs_bulk_new(world, table, NULL, NULL, count, NULL, false, NULL, &td); + flecs_table_diff_builder_fini(world, &diff); + flecs_defer_end(world, stage); -static -void do_auto_merge( - ecs_world_t *world) -{ - merge_stages(world, false); + return ids; +error: + return NULL; } static -void do_manual_merge( - ecs_world_t *world) -{ - merge_stages(world, true); -} - -bool flecs_defer_none( +void flecs_check_component( ecs_world_t *world, - ecs_stage_t *stage) + ecs_entity_t result, + const EcsComponent *ptr, + ecs_size_t size, + ecs_size_t alignment) { - (void)world; - return (++ stage->defer) == 1; + if (ptr->size != size) { + char *path = ecs_get_path(world, result); + ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); + ecs_os_free(path); + } + if (ptr->alignment != alignment) { + char *path = ecs_get_path(world, result); + ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); + ecs_os_free(path); + } } -bool flecs_defer_modified( +ecs_entity_t ecs_component_init( ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) + const ecs_component_desc_t *desc) { - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpModified; - op->id = id; - op->is._1.entity = entity; - return true; - } else { - stage->defer ++; - } - - return false; -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_component_desc_t is uninitialized, set to {0} before using"); -bool flecs_defer_clone( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t src, - bool clone_value) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpClone; - op->id = src; - op->is._1.entity = entity; - op->is._1.clone_value = clone_value; - return true; - } else { - stage->defer ++; + /* If existing entity is provided, check if it is already registered as a + * component and matches the size/alignment. This can prevent having to + * suspend readonly mode, and increases the number of scenarios in which + * this function can be called in multithreaded mode. */ + ecs_entity_t result = desc->entity; + if (result && ecs_is_alive(world, result)) { + const EcsComponent *const_ptr = ecs_get(world, result, EcsComponent); + if (const_ptr) { + flecs_check_component(world, result, const_ptr, + desc->type.size, desc->type.alignment); + return result; + } } - - return false; -} -bool flecs_defer_delete( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpDelete; - op->is._1.entity = entity; - world->delete_count ++; - return true; + ecs_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); + + bool new_component = true; + if (!result) { + result = ecs_new_low_id(world); + } else { + ecs_make_alive(world, result); + new_component = ecs_has(world, result, EcsComponent); + } + + EcsComponent *ptr = ecs_ensure(world, result, EcsComponent); + if (!ptr->size) { + ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); + ptr->size = desc->type.size; + ptr->alignment = desc->type.alignment; + if (!new_component || ptr->size != desc->type.size) { + if (!ptr->size) { + ecs_trace("#[green]tag#[reset] %s registered", + ecs_get_name(world, result)); + } else { + ecs_trace("#[green]component#[reset] %s registered", + ecs_get_name(world, result)); + } + } } else { - stage->defer ++; + flecs_check_component(world, result, ptr, + desc->type.size, desc->type.alignment); } - return false; -} -bool flecs_defer_clear( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpClear; - op->is._1.entity = entity; - world->clear_count ++; - return true; - } else { - stage->defer ++; + if (desc->type.name && new_component) { + ecs_entity(world, { .id = result, .name = desc->type.name }); } - return false; -} -bool flecs_defer_on_delete_action( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_id_t id, - ecs_entity_t action) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpOnDeleteAction; - op->id = id; - op->is._1.entity = action; - world->clear_count ++; - return true; - } else { - stage->defer ++; + ecs_modified(world, result, EcsComponent); + + if (desc->type.size && + !ecs_id_in_use(world, result) && + !ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) + { + ecs_set_hooks_id(world, result, &desc->type.hooks); } - return false; -} -bool flecs_defer_enable( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id, - bool enable) -{ - (void)world; - if (stage->defer) { - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = enable ? EcsOpEnable : EcsOpDisable; - op->is._1.entity = entity; - op->id = id; - return true; - } else { - stage->defer ++; + if (result >= world->info.last_component_id && + result < FLECS_HI_COMPONENT_ID) + { + world->info.last_component_id = result + 1; } - return false; + + flecs_resume_readonly(world, &readonly_state); + + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); + + return result; +error: + return 0; } -bool flecs_defer_bulk_new( +void ecs_clear( ecs_world_t *world, - ecs_stage_t *stage, - int32_t count, - ecs_id_t id, - const ecs_entity_t **ids_out) + ecs_entity_t entity) { - if (stage->defer) { - ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); - world->bulk_new_count ++; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - /* Use ecs_new_id as this is thread safe */ - int i; - for (i = 0; i < count; i ++) { - ids[i] = ecs_new_id(world); - } + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_clear(stage, entity)) { + return; + } - *ids_out = ids; + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - /* Store data in op */ - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = EcsOpBulkNew; - op->id = id; - op->is._n.entities = ids; - op->is._n.count = count; + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (table->type.count) { + ecs_table_diff_t diff = { + .removed = table->type, + .removed_flags = table->flags & EcsTableRemoveEdgeFlags + }; - return true; - } else { - stage->defer ++; + flecs_commit(world, entity, r, &world->store.root, &diff, false, 0); } - return false; -} - -bool flecs_defer_new( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - return defer_add_remove(world, stage, EcsOpNew, entity, id); -} + flecs_entity_remove_non_fragmenting(world, entity, NULL); -bool flecs_defer_add( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - return defer_add_remove(world, stage, EcsOpAdd, entity, id); + flecs_defer_end(world, stage); +error: + return; } -bool flecs_defer_remove( +void ecs_delete( ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) + ecs_entity_t entity) { - return defer_add_remove(world, stage, EcsOpRemove, entity, id); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); -bool flecs_defer_set( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_defer_op_kind_t op_kind, - ecs_entity_t entity, - ecs_id_t id, - ecs_size_t size, - const void *value, - void **value_out, - bool *is_added) -{ - if (stage->defer) { - world->set_count ++; - if (!size) { - const EcsComponent *cptr = flecs_component_from_id(world, id); - ecs_check(cptr != NULL, ECS_INVALID_PARAMETER, NULL); - size = cptr->size; - } + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_delete(stage, entity)) { + return; + } - ecs_defer_op_t *op = new_defer_op(stage); - op->kind = op_kind; - op->id = id; - op->is._1.entity = entity; - op->is._1.size = size; - op->is._1.value = ecs_os_malloc(size); + ecs_os_perf_trace_push("flecs.delete"); - if (!value) { - value = ecs_get_id(world, entity, id); - if (is_added) { - *is_added = value == NULL; - } - } + ecs_record_t *r = flecs_entities_try(world, entity); + if (r) { + ecs_check(!ecs_has_pair(world, entity, EcsOnDelete, EcsPanic), + ECS_CONSTRAINT_VIOLATED, + "cannot delete entity '%s' with (OnDelete, Panic) trait", + flecs_errstr(ecs_get_path(world, entity))); - const ecs_type_info_t *ti = NULL; - ecs_entity_t real_id = ecs_get_typeid(world, id); - if (real_id) { - ti = flecs_get_type_info(world, real_id); - } + flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); - if (value) { - ecs_copy_t copy; - if (ti && (copy = ti->lifecycle.copy_ctor)) { - copy(op->is._1.value, value, 1, ti); - } else { - ecs_os_memcpy(op->is._1.value, value, size); + ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); + ecs_table_t *table; + if (row_flags) { + if (row_flags & EcsEntityIsTarget) { + flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true, true); + flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true, true); } - } else { - ecs_xtor_t ctor; - if (ti && (ctor = ti->lifecycle.ctor)) { - ctor(op->is._1.value, 1, ti); + + if (row_flags & EcsEntityIsId) { + flecs_on_delete(world, entity, 0, true, true); + flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true, true); } + + /* Merge operations before deleting entity */ + flecs_defer_end(world, stage); + flecs_defer_begin(world, stage); } - if (value_out) { - *value_out = op->is._1.value; + /* Entity is still in use by a query */ + ecs_assert((world->flags & EcsWorldQuit) || + !flecs_component_is_delete_locked(world, entity), + ECS_INVALID_OPERATION, + "cannot delete '%s' as it is still in use by queries", + flecs_errstr(ecs_id_str(world, entity))); + + table = r->table; + + if (table) { /* NULL if entity got cleaned up as result of cycle */ + ecs_table_diff_t diff = { + .removed = table->type, + .removed_flags = table->flags & EcsTableRemoveEdgeFlags + }; + + int32_t row = ECS_RECORD_TO_ROW(r->row); + flecs_actions_move_remove( + world, table, &world->store.root, row, 1, &diff); + flecs_entity_remove_non_fragmenting(world, entity, r); + flecs_table_delete(world, table, row, true); + + if (row_flags & EcsEntityIsTraversable) { + flecs_table_traversable_add(table, -1); + } } + + flecs_entities_remove(world, entity); - return true; - } else { - stage->defer ++; + flecs_journal_end(); } + flecs_defer_end(world, stage); error: - return false; + ecs_os_perf_trace_pop("flecs.delete"); + return; } -void flecs_stage_merge_post_frame( +void ecs_add_id( ecs_world_t *world, - ecs_stage_t *stage) + ecs_entity_t entity, + ecs_id_t component) { - /* Execute post frame actions */ - ecs_vector_each(stage->post_frame_actions, ecs_action_elem_t, action, { - action->action(world, action->ctx); - }); - - ecs_vector_free(stage->post_frame_actions); - stage->post_frame_actions = NULL; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "add"); + flecs_assert_component_valid(world, entity, component, "add"); + flecs_add_id(world, entity, component); +error: + return; } -void flecs_stage_init( +void ecs_remove_id( ecs_world_t *world, - ecs_stage_t *stage) + ecs_entity_t entity, + ecs_id_t component) { - ecs_poly_assert(world, ecs_world_t); - - ecs_poly_init(stage, ecs_stage_t); - - stage->world = world; - stage->thread_ctx = world; - stage->auto_merge = true; - stage->asynchronous = false; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "remove"); + + /* Component validity check is slightly different for remove() because it is + * allowed to remove wildcards, but not allowed to add wildcards. */ + ecs_check(ecs_id_is_valid(world, component) || + ecs_id_is_wildcard(component), ECS_INVALID_PARAMETER, + "invalid component '%s' passed to remove() for entity '%s': %s", + flecs_errstr(ecs_id_str(world, component)), + flecs_errstr_1(ecs_get_path(world, entity)), + flecs_id_invalid_reason(world, component)); + + flecs_remove_id(world, entity, component); +error: + return; } -void flecs_stage_deinit( +void ecs_auto_override_id( ecs_world_t *world, - ecs_stage_t *stage) + ecs_entity_t entity, + ecs_id_t component) { - (void)world; - ecs_poly_assert(world, ecs_world_t); - ecs_poly_assert(stage, ecs_stage_t); - - /* Make sure stage has no unmerged data */ - ecs_assert(ecs_vector_count(stage->defer_queue) == 0, - ECS_INTERNAL_ERROR, NULL); - - ecs_poly_fini(stage, ecs_stage_t); - - ecs_vector_free(stage->defer_queue); + flecs_assert_component_valid(world, entity, component, "auto_override"); + ecs_add_id(world, entity, ECS_AUTO_OVERRIDE | component); +error: + return; } -void ecs_set_stages( +ecs_entity_t ecs_clone( ecs_world_t *world, - int32_t stage_count) + ecs_entity_t dst, + ecs_entity_t src, + bool copy_value) { - ecs_poly_assert(world, ecs_world_t); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, src), ECS_INVALID_PARAMETER, NULL); + ecs_check(!dst || (ecs_get_table(world, dst)->type.count == 0), + ECS_INVALID_PARAMETER, + "target entity for clone() cannot have components"); - ecs_stage_t *stages; - int32_t i, count = ecs_vector_count(world->worker_stages); + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (!dst) { + dst = ecs_new(world); + } - if (count && count != stage_count) { - stages = ecs_vector_first(world->worker_stages, ecs_stage_t); + if (flecs_defer_clone(stage, dst, src, copy_value)) { + return dst; + } - for (i = 0; i < count; i ++) { - /* If stage contains a thread handle, ecs_set_threads was used to - * create the stages. ecs_set_threads and ecs_set_stages should not - * be mixed. */ - ecs_poly_assert(&stages[i], ecs_stage_t); - ecs_check(stages[i].thread == 0, ECS_INVALID_OPERATION, NULL); - flecs_stage_deinit(world, &stages[i]); - } + ecs_record_t *src_r = flecs_entities_get(world, src); + ecs_assert(src_r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *src_table = src_r->table; + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vector_free(world->worker_stages); + ecs_table_t *dst_table = src_table; + if (src_table->flags & EcsTableHasName) { + dst_table = ecs_table_remove_id(world, src_table, + ecs_pair_t(EcsIdentifier, EcsName)); } - - if (stage_count) { - world->worker_stages = ecs_vector_new(ecs_stage_t, stage_count); - for (i = 0; i < stage_count; i ++) { - ecs_stage_t *stage = ecs_vector_add( - &world->worker_stages, ecs_stage_t); - flecs_stage_init(world, stage); - stage->id = 1 + i; /* 0 is reserved for main/temp stage */ + ecs_type_t dst_type = dst_table->type; + ecs_table_diff_t diff = { + .added = dst_type, + .added_flags = dst_table->flags & EcsTableAddEdgeFlags + }; - /* Set thread_ctx to stage, as this stage might be used in a - * multithreaded context */ - stage->thread_ctx = (ecs_world_t*)stage; - } - } else { - /* Set to NULL to prevent double frees */ - world->worker_stages = NULL; - } + ecs_record_t *dst_r = flecs_entities_get(world, dst); - /* Regardless of whether the stage was just initialized or not, when the - * ecs_set_stages function is called, all stages inherit the auto_merge - * property from the world */ - for (i = 0; i < stage_count; i ++) { - ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); - stage->auto_merge = world->stage.auto_merge; + if (dst_table != dst_r->table) { + flecs_move_entity(world, dst, dst_r, dst_table, &diff, true, 0); } -error: - return; -} -int32_t ecs_get_stage_count( - const ecs_world_t *world) -{ - world = ecs_get_world(world); - return ecs_vector_count(world->worker_stages); -} + if (copy_value) { + int32_t row = ECS_RECORD_TO_ROW(dst_r->row); + int32_t i, count = src_table->column_count; + for (i = 0; i < count; i ++) { + int32_t type_id = ecs_table_column_to_type_index(src_table, i); + ecs_id_t component = src_table->type.array[type_id]; -int32_t ecs_get_stage_id( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + void *dst_ptr = ecs_get_mut_id(world, dst, component); + if (!dst_ptr) { + continue; + } + + const void *src_ptr = ecs_get_id(world, src, component); + const ecs_type_info_t *ti = src_table->data.columns[i].ti; + if (ti->hooks.copy) { + ti->hooks.copy(dst_ptr, src_ptr, 1, ti); + } else { + ecs_os_memcpy(dst_ptr, src_ptr, ti->size); + } - if (ecs_poly_is(world, ecs_stage_t)) { - ecs_stage_t *stage = (ecs_stage_t*)world; + flecs_notify_on_set(world, dst_table, row, component, true); + } - /* Index 0 is reserved for main stage */ - return stage->id - 1; - } else if (ecs_poly_is(world, ecs_world_t)) { - return 0; - } else { - ecs_throw(ECS_INTERNAL_ERROR, NULL); + if (dst_table->flags & EcsTableHasSparse) { + count = dst_table->type.count; + for (i = 0; i < count; i ++) { + const ecs_table_record_t *tr = &dst_table->_->records[i]; + ecs_component_record_t *cr = tr->hdr.cr; + if (cr->sparse) { + void *src_ptr = flecs_component_sparse_get( + world, cr, src_table, src); + if (src_ptr) { + ecs_set_id(world, dst, cr->id, + flecs_ito(size_t, cr->type_info->size), src_ptr); + } + } + } + } + } + + if (src_r->row & EcsEntityHasDontFragment) { + ecs_component_record_t *cur = world->cr_non_fragmenting_head; + while (cur) { + if (!ecs_id_is_wildcard(cur->id)) { + ecs_assert(cur->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); + if (cur->sparse) { + if (cur->type_info) { + void *src_ptr = flecs_sparse_get(cur->sparse, 0, src); + if (src_ptr) { + ecs_set_id(world, dst, cur->id, + flecs_ito(size_t, cur->type_info->size), src_ptr); + } + } else { + if (flecs_sparse_has(cur->sparse, src)) { + ecs_add_id(world, dst, cur->id); + } + } + } + } + + cur = cur->non_fragmenting.next; + } } + + flecs_defer_end(world, stage); + return dst; error: return 0; } -ecs_world_t* ecs_get_stage( +const void* ecs_get_id( const ecs_world_t *world, - int32_t stage_id) + ecs_entity_t entity, + ecs_id_t component) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_vector_count(world->worker_stages) > stage_id, + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "get"); + ecs_check(ecs_id_is_valid(world, component) || ecs_id_is_wildcard(component), ECS_INVALID_PARAMETER, NULL); - return (ecs_world_t*)ecs_vector_get( - world->worker_stages, ecs_stage_t, stage_id); -error: - return NULL; -} + world = ecs_get_world(world); -bool ecs_staging_begin( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_process_pending_tables(world); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t i, count = ecs_get_stage_count(world); - for (i = 0; i < count; i ++) { - ecs_world_t *stage = ecs_get_stage(world, i); - ((ecs_stage_t*)stage)->lookup_path = world->stage.lookup_path; - ecs_defer_begin(stage); + if (component < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_lookup[component]) { + ecs_get_low_id(table, r, component); + return NULL; + } } - bool is_readonly = world->is_readonly; + ecs_component_record_t *cr = flecs_components_get(world, component); + if (!cr) { + return NULL; + } - /* From this point on, the world is "locked" for mutations, and it is only - * allowed to enqueue commands from stages */ - world->is_readonly = true; + if (cr->flags & EcsIdDontFragment) { + void *ptr = flecs_component_sparse_get(world, cr, table, entity); + if (ptr) { + return ptr; + } + } - ecs_dbg_3("staging: begin"); + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + return flecs_get_base_component(world, table, component, cr, 0); + } else { + if (cr->flags & EcsIdSparse) { + return flecs_component_sparse_get(world, cr, table, entity); + } + ecs_check(tr->column != -1, ECS_INVALID_PARAMETER, + "component '%s' passed to get() is a tag/zero sized", + flecs_errstr(ecs_id_str(world, component))); + } - return is_readonly; + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_table_get_component(table, tr->column, row).ptr; +error: + return NULL; } -void ecs_staging_end( - ecs_world_t *world) +#ifdef FLECS_DEBUG +static +bool flecs_component_has_on_replace( + const ecs_world_t *world, + ecs_id_t component, + const char *funcname) +{ + const ecs_type_info_t *ti = ecs_get_type_info(world, component); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "invalid component '%s' for %s(): component cannot be a tag/zero sized", + flecs_errstr(ecs_id_str(world, component)), funcname); + return ti->hooks.on_replace != NULL; +error: + return false; +} +#endif + +void* ecs_get_mut_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(world->is_readonly == true, ECS_INVALID_OPERATION, NULL); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "get_mut"); + flecs_assert_component_valid(world, entity, component, "get_mut"); + ecs_dbg_assert(!flecs_component_has_on_replace(world, component, "get_mut"), + ECS_INVALID_PARAMETER, + "cannot call get_mut() for component '%s' which has an on_replace hook " + "(use set()/assign())", + flecs_errstr(ecs_id_str(world, component))); - /* After this it is safe again to mutate the world directly */ - world->is_readonly = false; + world = ecs_get_world(world); + + flecs_check_exclusive_world_access_write(world); - ecs_dbg_3("staging: end"); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); - do_auto_merge(world); + if (component < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_lookup[component]) { + ecs_get_low_id(r->table, r, component); + return NULL; + } + } + + ecs_component_record_t *cr = flecs_components_get(world, component); + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_get_component_ptr(world, r->table, row, cr).ptr; error: - return; + return NULL; } -void ecs_merge( - ecs_world_t *world) +void* ecs_ensure_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component, + size_t size) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_poly_is(world, ecs_world_t) || - ecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); - do_manual_merge(world); + flecs_assert_entity_valid(world, entity, "ensure"); + flecs_assert_component_valid(world, entity, component, "ensure"); + ecs_dbg_assert(!flecs_component_has_on_replace(world, component, "ensure"), + ECS_INVALID_PARAMETER, + "cannot call ensure() for component '%s' which has an on_replace hook " + "(use set()/assign())", + flecs_errstr(ecs_id_str(world, component))); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_cmd(stage)) { + return flecs_defer_ensure( + world, stage, entity, component, flecs_uto(int32_t, size)); + } + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + void *result = flecs_ensure(world, entity, component, r, + flecs_uto(int32_t, size)).ptr; + ecs_check(result != NULL, ECS_INVALID_OPERATION, + "component '%s' ensured on entity '%s' was removed during the " + "operation, make sure not to remove the component in hooks/observers", + flecs_errstr(ecs_id_str(world, component)), + flecs_errstr_2(ecs_get_path(world, entity))); + + flecs_defer_end(world, stage); + return result; error: - return; + return NULL; } -void ecs_set_automerge( +void* ecs_emplace_id( ecs_world_t *world, - bool auto_merge) + ecs_entity_t entity, + ecs_id_t component, + size_t size, + bool *is_new) { - /* If a world is provided, set auto_merge globally for the world. This - * doesn't actually do anything (the main stage never merges) but it serves - * as the default for when stages are created. */ - if (ecs_poly_is(world, ecs_world_t)) { - world->stage.auto_merge = auto_merge; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "emplace"); + flecs_assert_component_valid(world, entity, component, "emplace"); + ecs_dbg_assert(!flecs_component_has_on_replace(world, component, "emplace"), + ECS_INVALID_PARAMETER, + "cannot call emplace() for component '%s' which has an on_replace hook " + "(use set()/entity::replace())", + flecs_errstr(ecs_id_str(world, component))); - /* Propagate change to all stages */ - int i, stage_count = ecs_get_stage_count(world); - for (i = 0; i < stage_count; i ++) { - ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); - stage->auto_merge = auto_merge; - } + ecs_stage_t *stage = flecs_stage_from_world(&world); - /* If a stage is provided, override the auto_merge value for the individual - * stage. This allows an application to control per-stage which stage should - * be automatically merged and which one shouldn't */ - } else { - ecs_poly_assert(world, ecs_stage_t); - ecs_stage_t *stage = (ecs_stage_t*)world; - stage->auto_merge = auto_merge; + if (flecs_defer_cmd(stage)) { + return flecs_defer_emplace( + world, stage, entity, component, flecs_uto(int32_t, size), is_new); } -} -bool ecs_stage_is_readonly( - const ecs_world_t *stage) -{ - const ecs_world_t *world = ecs_get_world(stage); + ecs_check(is_new || !ecs_has_id(world, entity, component), + ECS_INVALID_PARAMETER, + "cannot emplace() existing component '%s' for entity '%s' unless " + "'is_new' argument is provided", + flecs_errstr(ecs_id_str(world, component)), + flecs_errstr_2(ecs_get_path(world, entity))); - if (ecs_poly_is(stage, ecs_stage_t)) { - if (((ecs_stage_t*)stage)->asynchronous) { - return false; - } - } + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; - if (world->is_readonly) { - if (ecs_poly_is(stage, ecs_world_t)) { - return true; + ecs_component_record_t *cr = flecs_components_ensure(world, component); + if (cr->flags & EcsIdDontFragment) { + void *ptr = flecs_component_sparse_get(world, cr, table, entity); + if (ptr) { + if (is_new) { + *is_new = false; + } + flecs_defer_end(world, stage); + return ptr; } - } else { - if (ecs_poly_is(stage, ecs_stage_t)) { - return true; + + if (is_new) { + *is_new = true; } + is_new = NULL; } - return false; + flecs_add_id_w_record(world, entity, r, component, false /* No ctor */); + flecs_defer_end(world, stage); + + void *ptr = flecs_get_component( + world, r->table, ECS_RECORD_TO_ROW(r->row), cr); + ecs_check(ptr != NULL, ECS_INVALID_OPERATION, + "component '%s' emplaced on entity '%s' was removed during the " + "operation, make sure not to remove the component in hooks/observers", + flecs_errstr(ecs_id_str(world, component)), + flecs_errstr_2(ecs_get_path(world, entity))); + + if (is_new) { + *is_new = table != r->table; + } + + return ptr; +error: + return NULL; } -ecs_world_t* ecs_async_stage_new( - ecs_world_t *world) +static +ecs_record_t* flecs_access_begin( + ecs_world_t *stage, + ecs_entity_t entity, + bool write) { - ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); - flecs_stage_init(world, stage); + ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); - stage->id = -1; - stage->auto_merge = false; - stage->asynchronous = true; + const ecs_world_t *world = ecs_get_world(stage); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_defer_begin((ecs_world_t*)stage); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return (ecs_world_t*)stage; -} + int32_t count = ecs_os_ainc(&table->_->lock); + (void)count; + if (write) { + ecs_check(count == 1, ECS_ACCESS_VIOLATION, + "invalid concurrent access to table for entity '%s'", + flecs_errstr(ecs_get_path(world, entity))); + } -void ecs_async_stage_free( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_stage_t); - ecs_stage_t *stage = (ecs_stage_t*)world; - ecs_check(stage->asynchronous == true, ECS_INVALID_PARAMETER, NULL); - flecs_stage_deinit(stage->world, stage); - ecs_os_free(stage); + return r; error: - return; + return NULL; } -bool ecs_stage_is_async( - ecs_world_t *stage) +static +void flecs_access_end( + const ecs_record_t *r, + bool write) { - if (!stage) { - return false; - } - - if (!ecs_poly_is(stage, ecs_stage_t)) { - return false; + ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(r->table != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t count = ecs_os_adec(&r->table->_->lock); + (void)count; + if (write) { + ecs_check(count == 0, ECS_ACCESS_VIOLATION, NULL); } + ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); - return ((ecs_stage_t*)stage)->asynchronous; +error: + return; } -bool ecs_is_deferred( - const ecs_world_t *world) +ecs_record_t* ecs_write_begin( + ecs_world_t *world, + ecs_entity_t entity) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - return stage->defer != 0; -error: - return false; + return flecs_access_begin(world, entity, true); } - -struct ecs_vector_t { - int32_t count; - int32_t size; - -#ifndef FLECS_NDEBUG - int64_t elem_size; /* Used in debug mode to validate size */ -#endif -}; - -/** Resize the vector buffer */ -static -ecs_vector_t* resize( - ecs_vector_t *vector, - int16_t offset, - int32_t size) +void ecs_write_end( + ecs_record_t *r) { - ecs_vector_t *result = ecs_os_realloc(vector, offset + size); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, 0); - return result; + flecs_access_end(r, true); } -/* -- Public functions -- */ - -ecs_vector_t* _ecs_vector_new( - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) +const ecs_record_t* ecs_read_begin( + ecs_world_t *world, + ecs_entity_t entity) { - ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_vector_t *result = - ecs_os_malloc(offset + elem_size * elem_count); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - - result->count = 0; - result->size = elem_count; -#ifndef FLECS_NDEBUG - result->elem_size = elem_size; -#endif - return result; + return flecs_access_begin(world, entity, false); } -ecs_vector_t* _ecs_vector_from_array( - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count, - void *array) +void ecs_read_end( + const ecs_record_t *r) { - ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_vector_t *result = - ecs_os_malloc(offset + elem_size * elem_count); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - - ecs_os_memcpy(ECS_OFFSET(result, offset), array, elem_size * elem_count); - - result->count = elem_count; - result->size = elem_count; -#ifndef FLECS_NDEBUG - result->elem_size = elem_size; -#endif - return result; + flecs_access_end(r, false); } -void ecs_vector_free( - ecs_vector_t *vector) +ecs_entity_t ecs_record_get_entity( + const ecs_record_t *record) { - ecs_os_free(vector); + ecs_check(record != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_table_t *table = record->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return table->data.entities[ECS_RECORD_TO_ROW(record->row)]; +error: + return 0; } -void ecs_vector_clear( - ecs_vector_t *vector) +const void* ecs_record_get_id( + const ecs_world_t *stage, + const ecs_record_t *r, + ecs_id_t component) { - if (vector) { - vector->count = 0; - } + const ecs_world_t *world = ecs_get_world(stage); + ecs_component_record_t *cr = flecs_components_get(world, component); + return flecs_get_component( + world, r->table, ECS_RECORD_TO_ROW(r->row), cr); } -void _ecs_vector_zero( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset) +bool ecs_record_has_id( + ecs_world_t *stage, + const ecs_record_t *r, + ecs_id_t component) { - void *array = ECS_OFFSET(vector, offset); - ecs_os_memset(array, 0, elem_size * vector->count); + const ecs_world_t *world = ecs_get_world(stage); + if (r->table) { + return ecs_table_has_id(world, r->table, component); + } + return false; } -void ecs_vector_assert_size( - ecs_vector_t *vector, - ecs_size_t elem_size) +void* ecs_record_ensure_id( + ecs_world_t *stage, + ecs_record_t *r, + ecs_id_t component) { - (void)elem_size; - - if (vector) { - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - } + const ecs_world_t *world = ecs_get_world(stage); + ecs_component_record_t *cr = flecs_components_get(world, component); + return flecs_get_component( + world, r->table, ECS_RECORD_TO_ROW(r->row), cr); } -void* _ecs_vector_addn( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) +void flecs_modified_id_if( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component, + bool invoke_hook) { - ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); - - if (elem_count == 1) { - return _ecs_vector_add(array_inout, elem_size, offset); - } - - ecs_vector_t *vector = *array_inout; - if (!vector) { - vector = _ecs_vector_new(elem_size, offset, 1); - *array_inout = vector; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "modified"); + flecs_assert_component_valid(world, entity, component, "modified"); - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - int32_t max_count = vector->size; - int32_t old_count = vector->count; - int32_t new_count = old_count + elem_count; + if (flecs_defer_modified(stage, entity, component)) { + return; + } - if ((new_count - 1) >= max_count) { - if (!max_count) { - max_count = elem_count; - } else { - while (max_count < new_count) { - max_count *= 2; - } - } + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - max_count = flecs_next_pow_of_2(max_count); - vector = resize(vector, offset, max_count * elem_size); - vector->size = max_count; - *array_inout = vector; + ecs_component_record_t *cr = flecs_components_get(world, component); + if (!cr || !flecs_component_get_table(cr, table)) { + flecs_defer_end(world, stage); + return; } - vector->count = new_count; + flecs_notify_on_set( + world, table, ECS_RECORD_TO_ROW(r->row), component, invoke_hook); - return ECS_OFFSET(vector, offset + elem_size * old_count); + flecs_table_mark_dirty(world, table, component); + flecs_defer_end(world, stage); +error: + return; } -void* _ecs_vector_add( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset) +void ecs_modified_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component) { - ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vector_t *vector = *array_inout; - int32_t count, size; - - if (vector) { - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - count = vector->count; - size = vector->size; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "modified"); + flecs_assert_component_valid(world, entity, component, "modified"); - if (count >= size) { - size *= 2; - if (!size) { - size = 2; - } + ecs_stage_t *stage = flecs_stage_from_world(&world); - size = flecs_next_pow_of_2(size); - vector = resize(vector, offset, size * elem_size); - *array_inout = vector; - vector->size = size; + if (component < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_set[component]) { + return; } + } - vector->count = count + 1; - return ECS_OFFSET(vector, offset + elem_size * count); + if (flecs_defer_modified(stage, entity, component)) { + return; } - vector = _ecs_vector_new(elem_size, offset, 2); - *array_inout = vector; - vector->count = 1; - vector->size = 2; - return ECS_OFFSET(vector, offset); + /* If the entity does not have the component, calling ecs_modified is + * invalid. The assert needs to happen after the defer statement, as the + * entity may not have the component when this function is called while + * operations are being deferred. */ + ecs_check(ecs_has_id(world, entity, component), ECS_INVALID_PARAMETER, + "invalid call to modified(), entity '%s' does not have component '%s'", + flecs_errstr(ecs_get_path(world, entity)), + flecs_errstr_2(ecs_id_str(world, component))); + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; + flecs_notify_on_set( + world, table, ECS_RECORD_TO_ROW(r->row), component, true); + + flecs_table_mark_dirty(world, table, component); + flecs_defer_end(world, stage); +error: + return; } -void* _ecs_vector_insert_at( - ecs_vector_t **vec, - ecs_size_t elem_size, - int16_t offset, - int32_t index) +void flecs_set_id_move( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t component, + size_t size, + void *ptr, + ecs_cmd_kind_t cmd_kind) { - ecs_assert(vec != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t count = vec[0]->count; - if (index == count) { - return _ecs_vector_add(vec, elem_size, offset); + if (flecs_defer_cmd(stage)) { + ecs_throw(ECS_INVALID_OPERATION, + "cannot flush a command queue to a deferred stage. This happens " + "when a stage is explicitly merged into the world/another stage " + "that is deferred"); } - ecs_assert(index < count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); - _ecs_vector_add(vec, elem_size, offset); - void *start = _ecs_vector_get(*vec, elem_size, offset, index); - if (index < count) { - ecs_os_memmove(ECS_OFFSET(start, elem_size), start, - (count - index) * elem_size); - } + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t dst = flecs_ensure( + world, entity, component, r, flecs_uto(int32_t, size)); + ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); - return start; -} + const ecs_type_info_t *ti = dst.ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); -int32_t _ecs_vector_move_index( - ecs_vector_t **dst, - ecs_vector_t *src, - ecs_size_t elem_size, - int16_t offset, - int32_t index) -{ - if (dst && *dst) { - ecs_dbg_assert((*dst)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + if (ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, entity, component, dst.ptr, ptr, ti); + } + + ecs_move_t move; + if (cmd_kind != EcsCmdEmplace) { + /* ctor will have happened by ensure */ + move = ti->hooks.move_dtor; + } else { + move = ti->hooks.ctor_move_dtor; + } + if (move) { + move(dst.ptr, ptr, 1, ti); + } else { + ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } - ecs_dbg_assert(src->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - void *dst_elem = _ecs_vector_add(dst, elem_size, offset); - void *src_elem = _ecs_vector_get(src, elem_size, offset, index); + flecs_table_mark_dirty(world, r->table, component); - ecs_os_memcpy(dst_elem, src_elem, elem_size); - return _ecs_vector_remove(src, elem_size, offset, index); -} + if (cmd_kind == EcsCmdSet) { + ecs_table_t *table = r->table; + if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { + ecs_type_t ids = { .array = &component, .count = 1 }; + flecs_notify_on_set_ids( + world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids); + } + } -void ecs_vector_remove_last( - ecs_vector_t *vector) -{ - if (vector && vector->count) vector->count --; + flecs_defer_end(world, stage); +error: + return; } -bool _ecs_vector_pop( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - void *value) +void ecs_set_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component, + size_t size, + const void *ptr) { - if (!vector) { - return false; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "set"); + flecs_assert_component_valid(world, entity, component, "set"); + ecs_check(size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, + "invalid call to set() for component '%s' and entity '%s': no " + "component value provided", + flecs_errstr(ecs_id_str(world, component)), + flecs_errstr_1(ecs_id_str(world, entity))); - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - int32_t count = vector->count; - if (!count) { - return false; + if (flecs_defer_cmd(stage)) { + flecs_defer_set(world, stage, entity, component, flecs_utosize(size), + ECS_CONST_CAST(void*, ptr)); + return; } - void *elem = ECS_OFFSET(vector, offset + (count - 1) * elem_size); + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t dst = flecs_ensure(world, entity, component, r, + flecs_uto(int32_t, size)); - if (value) { - ecs_os_memcpy(value, elem, elem_size); + if (component < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_set[component]) { + ecs_os_memcpy(dst.ptr, ptr, size); + goto done; + } } - ecs_vector_remove_last(vector); + flecs_copy_id(world, entity, r, component, size, dst.ptr, ptr, dst.ti); - return true; +done: + flecs_defer_end(world, stage); +error: + return; } -int32_t _ecs_vector_remove( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - int32_t index) +#if defined(FLECS_DEBUG) || defined(FLECS_KEEP_ASSERT) +static +bool flecs_can_toggle( + ecs_world_t *world, + ecs_id_t component) { - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - - int32_t count = vector->count; - void *buffer = ECS_OFFSET(vector, offset); - void *elem = ECS_OFFSET(buffer, index * elem_size); - - ecs_assert(index < count, ECS_INVALID_PARAMETER, NULL); - - count --; - if (index != count) { - void *last_elem = ECS_OFFSET(buffer, elem_size * count); - ecs_os_memcpy(elem, last_elem, elem_size); + ecs_component_record_t *cr = flecs_components_get(world, component); + if (!cr) { + return ecs_has_id(world, component, EcsCanToggle); } - vector->count = count; - - return count; + return (cr->flags & EcsIdCanToggle) != 0; } +#endif -void _ecs_vector_reclaim( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset) +void ecs_enable_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component, + bool enable) { - ecs_vector_t *vector = *array_inout; - if (!vector) { + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + flecs_assert_component_valid(world, entity, component, "enable_component"); + ecs_check(flecs_can_toggle(world, component), ECS_INVALID_OPERATION, + "cannot enable/disable component '%s' as it does not have the CanToggle trait", + flecs_errstr(ecs_id_str(world, component))); + + ecs_entity_t bs_id = component | ECS_TOGGLE; + ecs_add_id((ecs_world_t*)stage, entity, bs_id); + + if (flecs_defer_enable(stage, entity, component, enable)) { return; } - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - - int32_t size = vector->size; - int32_t count = vector->count; + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; + int32_t index = ecs_table_get_type_index(world, table, bs_id); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - if (count < size) { - if (count) { - size = count; - vector = resize(vector, offset, size * elem_size); - vector->size = size; - *array_inout = vector; - } else { - ecs_vector_free(vector); - *array_inout = NULL; - } - } -} + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + index -= table->_->bs_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); -int32_t ecs_vector_count( - const ecs_vector_t *vector) -{ - if (!vector) { - return 0; - } - return vector->count; -} + /* Data cannot be NULL, since entity is stored in the table */ + ecs_bitset_t *bs = &table->_->bs_columns[index]; + ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); -int32_t ecs_vector_size( - const ecs_vector_t *vector) -{ - if (!vector) { - return 0; - } - return vector->size; + flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); + + flecs_defer_end(world, stage); +error: + return; } -int32_t _ecs_vector_set_size( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) +bool ecs_is_enabled_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component) { - ecs_vector_t *vector = *array_inout; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "is_enabled"); + flecs_assert_component_valid(world, entity, component, "is_enabled"); - if (!vector) { - *array_inout = _ecs_vector_new(elem_size, offset, elem_count); - return elem_count; - } else { - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - int32_t result = vector->size; + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (elem_count < vector->count) { - elem_count = vector->count; - } + ecs_entity_t bs_id = component | ECS_TOGGLE; + int32_t index = ecs_table_get_type_index(world, table, bs_id); + if (index == -1) { + /* If table does not have TOGGLE column for component, component is + * always enabled, if the entity has it */ + return ecs_has_id(world, entity, component); + } - if (result < elem_count) { - elem_count = flecs_next_pow_of_2(elem_count); - vector = resize(vector, offset, elem_count * elem_size); - vector->size = elem_count; - *array_inout = vector; - result = elem_count; - } + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + index -= table->_->bs_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &table->_->bs_columns[index]; - return result; - } + return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); +error: + return false; } -int32_t _ecs_vector_grow( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) +void ecs_set_child_order( + ecs_world_t *world, + ecs_entity_t parent, + const ecs_entity_t *children, + int32_t child_count) { - int32_t current = ecs_vector_count(*array_inout); - return _ecs_vector_set_size(array_inout, elem_size, offset, current + elem_count); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, parent), ECS_INVALID_PARAMETER, NULL); + ecs_check(children == NULL || child_count, ECS_INVALID_PARAMETER, + "children array passed to set_child_order() cannot be NULL if " + "child_count is not 0"); + ecs_check(children != NULL || !child_count, ECS_INVALID_PARAMETER, + "children array passed to set_child_order() cannot be not-NULL if " + "child_count is 0"); + ecs_check(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, + "cannot call set_child_oderder() while in multithreaded mode"); -int32_t _ecs_vector_set_count( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) -{ - if (!*array_inout) { - *array_inout = _ecs_vector_new(elem_size, offset, elem_count); - } + flecs_stage_from_world(&world); - ecs_dbg_assert((*array_inout)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + flecs_check_exclusive_world_access_write(world); - (*array_inout)->count = elem_count; - ecs_size_t size = _ecs_vector_set_size(array_inout, elem_size, offset, elem_count); - return size; + flecs_ordered_children_reorder(world, parent, children, child_count); + +error: + return; } -void* _ecs_vector_first( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset) +ecs_entities_t ecs_get_ordered_children( + const ecs_world_t *world, + ecs_entity_t parent) { - (void)elem_size; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, parent), ECS_INVALID_PARAMETER, NULL); - ecs_dbg_assert(!vector || vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - if (vector && vector->size) { - return ECS_OFFSET(vector, offset); - } else { - return NULL; - } -} + world = ecs_get_world(world); -void* _ecs_vector_get( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - int32_t index) -{ - ecs_assert(vector != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(index < vector->count, ECS_INTERNAL_ERROR, NULL); + flecs_check_exclusive_world_access_read(world); - return ECS_OFFSET(vector, offset + elem_size * index); -} + ecs_component_record_t *cr = flecs_components_get( + world, ecs_childof(parent)); -void* _ecs_vector_last( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset) -{ - if (vector) { - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); - int32_t count = vector->count; - if (!count) { - return NULL; - } else { - return ECS_OFFSET(vector, offset + elem_size * (count - 1)); - } - } else { - return NULL; - } -} + ecs_check(cr != NULL && (cr->flags & EcsIdOrderedChildren), + ECS_INVALID_PARAMETER, + "invalid call to get_ordered_children(): parent '%s' does not have " + "the OrderedChildren trait", + flecs_errstr(ecs_get_path(world, parent))); -int32_t _ecs_vector_set_min_size( - ecs_vector_t **vector_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) -{ - if (!*vector_inout || (*vector_inout)->size < elem_count) { - return _ecs_vector_set_size(vector_inout, elem_size, offset, elem_count); - } else { - return (*vector_inout)->size; - } + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + + return (ecs_entities_t){ + .count = ecs_vec_count(&cr->pair->ordered_children), + .alive_count = ecs_vec_count(&cr->pair->ordered_children), + .ids = ecs_vec_first(&cr->pair->ordered_children), + }; +error: + return (ecs_entities_t){0}; } -int32_t _ecs_vector_set_min_count( - ecs_vector_t **vector_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count) +bool ecs_has_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component) { - _ecs_vector_set_min_size(vector_inout, elem_size, offset, elem_count); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "has"); + ecs_check(component != 0, ECS_INVALID_PARAMETER, + "invalid component passed to has(): component cannot be 0"); - ecs_vector_t *v = *vector_inout; - if (v && v->count < elem_count) { - v->count = elem_count; - } + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - return v->count; -} + ecs_record_t *r = flecs_entities_get_any(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -void _ecs_vector_sort( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - ecs_comparator_t compare_action) -{ - if (!vector) { - return; + if (component < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_lookup[component]) { + return table->component_map[component] != 0; + } } - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *cr = flecs_components_get(world, component); + bool can_inherit = false; - int32_t count = vector->count; - void *buffer = ECS_OFFSET(vector, offset); + if (cr) { + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (tr) { + return true; + } - if (count > 1) { - qsort(buffer, (size_t)count, (size_t)elem_size, compare_action); - } -} + if (cr->flags & (EcsIdDontFragment|EcsIdMatchDontFragment)) { + if (flecs_component_sparse_has(cr, entity)) { + return true; + } else { + return flecs_get_base_component( + world, table, component, cr, 0) != NULL; + } + } -void _ecs_vector_memory( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - int32_t *allocd, - int32_t *used) -{ - if (!vector) { - return; + if (ECS_PAIR_FIRST(component) == EcsChildOf) { + if (table->flags & EcsTableHasParent) { + if (ECS_PAIR_SECOND(component) == EcsWildcard) { + return true; + } + + int32_t column = table->component_map[ecs_id(EcsParent)]; + EcsParent *p = flecs_table_get_component( + table, column - 1, ECS_RECORD_TO_ROW(r->row)).ptr; + return (uint32_t)p->value == ECS_PAIR_SECOND(component); + } + } + + can_inherit = cr->flags & EcsIdOnInstantiateInherit; } - ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); + if (!(table->flags & EcsTableHasIsA)) { + return false; + } - if (allocd) { - *allocd += vector->size * elem_size + offset; + if (!can_inherit) { + return false; } - if (used) { - *used += vector->count * elem_size; + + ecs_table_record_t *tr; + int32_t column = ecs_search_relation(world, table, 0, component, + EcsIsA, 0, 0, 0, &tr); + if (column == -1) { + return false; } + + return true; +error: + return false; } -ecs_vector_t* _ecs_vector_copy( - const ecs_vector_t *src, - ecs_size_t elem_size, - int16_t offset) +bool ecs_owns_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component) { - if (!src) { - return NULL; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "owns"); + ecs_check(component != 0, ECS_INVALID_PARAMETER, + "invalid component passed to has(): component cannot be 0"); - ecs_vector_t *dst = _ecs_vector_new(elem_size, offset, src->size); - ecs_os_memcpy(dst, src, offset + elem_size * src->count); - return dst; -} + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); + ecs_record_t *r = flecs_entities_get_any(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -/** The number of elements in a single chunk */ -#define CHUNK_COUNT (4096) + if (component < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_lookup[component]) { + return table->component_map[component] != 0; + } + } -/** Compute the chunk index from an id by stripping the first 12 bits */ -#define CHUNK(index) ((int32_t)((uint32_t)index >> 12)) + ecs_component_record_t *cr = flecs_components_get(world, component); + if (cr) { + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (tr) { + return true; + } -/** This computes the offset of an index inside a chunk */ -#define OFFSET(index) ((int32_t)index & 0xFFF) + if (cr->flags & (EcsIdDontFragment|EcsIdMatchDontFragment)) { + return flecs_component_sparse_has(cr, entity); + } -/* Utility to get a pointer to the payload */ -#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) + if (ECS_PAIR_FIRST(component) == EcsChildOf) { + if (table->flags & EcsTableHasParent) { + if (ECS_PAIR_SECOND(component) == EcsWildcard) { + return true; + } + + int32_t column = table->component_map[ecs_id(EcsParent)]; + EcsParent *p = flecs_table_get_component( + table, column - 1, ECS_RECORD_TO_ROW(r->row)).ptr; + return (uint32_t)p->value == ECS_PAIR_SECOND(component); + } + } + } -typedef struct chunk_t { - int32_t *sparse; /* Sparse array with indices to dense array */ - void *data; /* Store data in sparse array to reduce - * indirection and provide stable pointers. */ -} chunk_t; +error: + return false; +} static -chunk_t* chunk_new( - ecs_sparse_t *sparse, - int32_t chunk_index) +ecs_entity_t flecs_get_prefab_instance_child( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t prefab_child) { - int32_t count = ecs_vector_count(sparse->chunks); - chunk_t *chunks; - - if (count <= chunk_index) { - ecs_vector_set_count(&sparse->chunks, chunk_t, chunk_index + 1); - chunks = ecs_vector_first(sparse->chunks, chunk_t); - ecs_os_memset(&chunks[count], 0, (1 + chunk_index - count) * ECS_SIZEOF(chunk_t)); - } else { - chunks = ecs_vector_first(sparse->chunks, chunk_t); + ecs_map_val_t *index_ptr = ecs_map_get( + &world->prefab_child_indices, prefab_child); + if (!index_ptr) { + return 0; } - ecs_assert(chunks != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_assert_entity_valid(world, prefab_child, "get_target"); + ecs_check(ecs_owns_id(world, prefab_child, EcsPrefab), + ECS_INVALID_OPERATION, + "cannot get target for '%s': entity is not/no longer a prefab", + flecs_errstr(ecs_id_str(world, prefab_child))); - chunk_t *result = &chunks[chunk_index]; - ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); - - /* Initialize sparse array with zero's, as zero is used to indicate that the - * sparse element has not been paired with a dense element. Use zero - * as this means we can take advantage of calloc having a possibly better - * performance than malloc + memset. */ - result->sparse = ecs_os_calloc(ECS_SIZEOF(int32_t) * CHUNK_COUNT); - - /* Initialize the data array with zero's to guarantee that data is - * always initialized. When an entry is removed, data is reset back to - * zero. Initialize now, as this can take advantage of calloc. */ - result->data = ecs_os_calloc(sparse->size * CHUNK_COUNT); - - ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); +#ifdef FLECS_DEBUG + ecs_entity_t prefab = ecs_get_parent(world, prefab_child); + ecs_check(prefab != 0, ECS_INVALID_OPERATION, + "cannot get target for '%s': entity has no parent", + flecs_errstr(ecs_id_str(world, prefab_child))); + + ecs_check(ecs_owns_id(world, prefab, EcsPrefab), ECS_INVALID_OPERATION, + "cannot get target for '%s': parent is not/no longer a prefab", + flecs_errstr(ecs_id_str(world, prefab)), + flecs_errstr_1(ecs_id_str(world, prefab_child))); + + ecs_check(ecs_has_pair(world, entity, EcsIsA, prefab), + ECS_INVALID_OPERATION, + "cannot get target for '%s': entity '%s' is not an instance of prefab '%s'", + flecs_errstr(ecs_id_str(world, prefab_child)), + flecs_errstr_1(ecs_id_str(world, entity)), + flecs_errstr_2(ecs_id_str(world, prefab))); +#endif - return result; + ecs_component_record_t *childof_cr = flecs_components_get( + world, ecs_childof(entity)); + ecs_check(childof_cr != NULL, ECS_INVALID_OPERATION, + "cannot get target for '%s': children of '%s' have changed since " + "prefab instantiation", + flecs_errstr(ecs_id_str(world, prefab_child)), + flecs_errstr_1(ecs_id_str(world, entity))); + + ecs_vec_t *v = &childof_cr->pair->ordered_children; + int32_t index = flecs_uto(int32_t, *index_ptr); + ecs_check(ecs_vec_count(v) >= index, ECS_INVALID_OPERATION, + "cannot get target for '%s': children of '%s' have changed since " + "prefab instantiation", + flecs_errstr(ecs_id_str(world, prefab_child)), + flecs_errstr_1(ecs_id_str(world, entity))); + + ecs_entity_t tgt = ecs_vec_get_t(v, ecs_entity_t, index)[0]; + ecs_check(ecs_has_pair(world, tgt, EcsIsA, prefab_child), ECS_INVALID_OPERATION, + "cannot get target for '%s': children of '%s' have changed since " + "prefab instantiation", + flecs_errstr(ecs_id_str(world, prefab_child)), + flecs_errstr_1(ecs_id_str(world, entity))); + + return tgt; +error: + return 0; } -static -void chunk_free( - chunk_t *chunk) +ecs_entity_t ecs_get_target( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + int32_t index) { - ecs_os_free(chunk->sparse); - ecs_os_free(chunk->data); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "get_target"); + ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); -static -chunk_t* get_chunk( - const ecs_sparse_t *sparse, - int32_t chunk_index) -{ - if (!sparse->chunks) { - return NULL; - } - if (chunk_index >= ecs_vector_count(sparse->chunks)) { - return NULL; + if (rel == EcsChildOf) { + if (index > 0) { + return 0; + } + return ecs_get_parent(world, entity); } - /* If chunk_index is below zero, application used an invalid entity id */ - ecs_assert(chunk_index >= 0, ECS_INVALID_PARAMETER, NULL); - chunk_t *result = ecs_vector_get(sparse->chunks, chunk_t, chunk_index); - if (result && !result->sparse) { - return NULL; - } + world = ecs_get_world(world); - return result; -} + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -static -chunk_t* get_or_create_chunk( - ecs_sparse_t *sparse, - int32_t chunk_index) -{ - chunk_t *chunk = get_chunk(sparse, chunk_index); - if (chunk) { - return chunk; + ecs_id_t wc = ecs_pair(rel, EcsWildcard); + ecs_component_record_t *cr = flecs_components_get(world, wc); + if (!cr) { + if (!index) { + return flecs_get_prefab_instance_child(world, entity, rel); + } + return 0; } - return chunk_new(sparse, chunk_index); -} + const ecs_table_record_t *tr = flecs_component_get_table(cr, table);; + if (!tr) { + if (cr->flags & EcsIdDontFragment) { + if (cr->flags & EcsIdExclusive) { + if (index > 0) { + return 0; + } -static -void grow_dense( - ecs_sparse_t *sparse) -{ - ecs_vector_add(&sparse->dense, uint64_t); -} + ecs_entity_t *tgt = flecs_sparse_get(cr->sparse, 0, entity); + if (tgt) { + return *tgt; + } + } else { + ecs_type_t *type = flecs_sparse_get(cr->sparse, 0, entity); + if (type && (index < type->count)) { + return type->array[index]; + } + } + } -static -uint64_t strip_generation( - uint64_t *index_out) -{ - uint64_t index = *index_out; - uint64_t gen = index & ECS_GENERATION_MASK; - /* Make sure there's no junk in the id */ - ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), - ECS_INVALID_PARAMETER, NULL); - *index_out -= gen; - return gen; -} + if (cr->flags & EcsIdOnInstantiateInherit) { + goto look_in_base; + } -static -void assign_index( - chunk_t * chunk, - uint64_t * dense_array, - uint64_t index, - int32_t dense) -{ - /* Initialize sparse-dense pair. This assigns the dense index to the sparse - * array, and the sparse index to the dense array .*/ - chunk->sparse[OFFSET(index)] = dense; - dense_array[dense] = index; -} + return 0; + } -static -uint64_t inc_gen( - uint64_t index) -{ - /* When an index is deleted, its generation is increased so that we can do - * liveliness checking while recycling ids */ - return ECS_GENERATION_INC(index); -} + if (index >= tr->count) { + index -= tr->count; + goto look_in_base; + } -static -uint64_t inc_id( - ecs_sparse_t *sparse) -{ - /* Generate a new id. The last issued id could be stored in an external - * variable, such as is the case with the last issued entity id, which is - * stored on the world. */ - return ++ (sparse->max_id[0]); -} + ecs_id_t pair_id = table->type.array[tr->index + index]; + if (!ECS_IS_VALUE_PAIR(pair_id)) { + return flecs_entities_get_alive(world, pair_id); + } else { + return ECS_PAIR_SECOND(pair_id); + } -static -uint64_t get_id( - const ecs_sparse_t *sparse) -{ - return sparse->max_id[0]; -} +look_in_base: + if (table->flags & EcsTableHasIsA) { + const ecs_table_record_t *tr_isa = flecs_component_get_table( + world->cr_isa_wildcard, table); + ecs_assert(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_t *ids = table->type.array; + int32_t i = tr_isa->index, end = (i + tr_isa->count); + for (; i < end; i ++) { + ecs_id_t isa_pair = ids[i]; + ecs_entity_t base = ecs_pair_second(world, isa_pair); + ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t t = ecs_get_target(world, base, rel, index); + if (t) { + return t; + } + } + } -static -void set_id( - ecs_sparse_t *sparse, - uint64_t value) -{ - /* Sometimes the max id needs to be assigned directly, which typically - * happens when the API calls get_or_create for an id that hasn't been - * issued before. */ - sparse->max_id[0] = value; +error: + return 0; } -/* Pair dense id with new sparse id */ -static -uint64_t create_id( - ecs_sparse_t *sparse, - int32_t dense) +ecs_entity_t ecs_get_parent( + const ecs_world_t *world, + ecs_entity_t entity) { - uint64_t index = inc_id(sparse); - grow_dense(sparse); - - chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); - ecs_assert(chunk->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); - - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - assign_index(chunk, dense_array, index, dense); - - return index; -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "get_parent"); -/* Create new id */ -static -uint64_t new_index( - ecs_sparse_t *sparse) -{ - ecs_vector_t *dense = sparse->dense; - int32_t dense_count = ecs_vector_count(dense); - int32_t count = sparse->count ++; + world = ecs_get_world(world); - ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (count < dense_count) { - /* If there are unused elements in the dense array, return first */ - uint64_t *dense_array = ecs_vector_first(dense, uint64_t); - return dense_array[count]; - } else { - return create_id(sparse, count); + if (table->flags & EcsTableHasParent) { + int32_t column = table->component_map[ecs_id(EcsParent)]; + ecs_assert(column > 0, ECS_INTERNAL_ERROR, NULL); + EcsParent *p = ecs_table_get_column( + table, column - 1, ECS_RECORD_TO_ROW(r->row)); + ecs_assert(ecs_is_valid(world, p->value), ECS_INVALID_OPERATION, + "Parent component points to invalid parent %u", + p->value, entity); + return p->value; } -} -/* Try obtaining a value from the sparse set, don't care about whether the - * provided index matches the current generation count. */ -static -void* try_sparse_any( - const ecs_sparse_t *sparse, - uint64_t index) -{ - strip_generation(&index); + ecs_component_record_t *cr = world->cr_childof_wildcard; + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - if (!chunk) { - return NULL; - } - - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - bool in_use = dense && (dense < sparse->count); - if (!in_use) { - return NULL; + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + return 0; } - ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); - return DATA(chunk->data, sparse->size, offset); + ecs_entity_t id = table->type.array[tr->index]; + return flecs_entities_get_alive(world, ECS_PAIR_SECOND(id)); +error: + return 0; } -/* Try obtaining a value from the sparse set, make sure it's alive. */ -static -void* try_sparse( - const ecs_sparse_t *sparse, - uint64_t index) +ecs_entity_t ecs_new_w_parent( + ecs_world_t *world, + ecs_entity_t parent, + const char *name) { - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - if (!chunk) { - return NULL; - } + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, + "cannot create new entity while world is multithreaded"); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - bool in_use = dense && (dense < sparse->count); - if (!in_use) { - return NULL; - } + ecs_component_record_t *cr = flecs_components_ensure( + world, ecs_childof(parent)); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); - uint64_t gen = strip_generation(&index); - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + ecs_pair_record_t *pr = cr->pair; + ecs_assert(pr != NULL, ECS_INTERNAL_ERROR, NULL); - if (cur_gen != gen) { - return NULL; + ecs_entity_t entity = 0; + if (name) { + ecs_hashmap_t *name_index = pr->name_index; + if (name_index) { + entity = flecs_name_index_find(name_index, name, 0, 0); + if (entity) { + return entity; + } + } } - ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); - return DATA(chunk->data, sparse->size, offset); -} + ecs_id_t type_ids[] = { + ecs_id(EcsParent), ecs_value_pair(EcsParentDepth, pr->depth)}; -/* Get value from sparse set when it is guaranteed that the value exists. This - * function is used when values are obtained using a dense index */ -static -void* get_sparse( - const ecs_sparse_t *sparse, - int32_t dense, - uint64_t index) -{ - strip_generation(&index); - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - int32_t offset = OFFSET(index); - - ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dense == chunk->sparse[offset], ECS_INTERNAL_ERROR, NULL); - (void)dense; + ecs_type_t type = { .count = 2, .array = type_ids }; + ecs_table_t *table = flecs_table_find_or_create(world, &type); - return DATA(chunk->data, sparse->size, offset); -} + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -/* Swap dense elements. A swap occurs when an element is removed, or when a - * removed element is recycled. */ -static -void swap_dense( - ecs_sparse_t * sparse, - chunk_t * chunk_a, - int32_t a, - int32_t b) -{ - ecs_assert(a != b, ECS_INTERNAL_ERROR, NULL); - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - uint64_t index_a = dense_array[a]; - uint64_t index_b = dense_array[b]; + entity = flecs_new_id(world); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_flags32_t flags = table->flags & EcsTableAddEdgeFlags; - chunk_t *chunk_b = get_or_create_chunk(sparse, CHUNK(index_b)); - assign_index(chunk_a, dense_array, index_a, b); - assign_index(chunk_b, dense_array, index_b, a); -} + ecs_table_diff_t table_diff = { + .added = table->type, + .added_flags = flags + }; -void _flecs_sparse_init( - ecs_sparse_t *result, - ecs_size_t size) -{ - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - result->size = size; - result->max_id_local = UINT64_MAX; - result->max_id = &result->max_id_local; + int32_t row = flecs_table_append(world, table, entity, false, false); + r->table = table; + r->row = (uint32_t)row; - /* Consume first value in dense array as 0 is used in the sparse array to - * indicate that a sparse element hasn't been paired yet. */ - uint64_t *first = ecs_vector_add(&result->dense, uint64_t); - *first = 0; + flecs_actions_new(world, table, row, 1, &table_diff, 0, false, true); - result->count = 1; -} + EcsParent *parent_ptr = table->data.columns[0].data; + parent_ptr = &parent_ptr[row]; + parent_ptr->value = parent; -ecs_sparse_t* _flecs_sparse_new( - ecs_size_t size) -{ - ecs_sparse_t *result = ecs_os_calloc_t(ecs_sparse_t); + if (name) { + bool is_deferred = ecs_is_deferred(world); + if (is_deferred) ecs_defer_suspend(world); + flecs_set_identifier(world, stage, entity, EcsName, name); + if (is_deferred) ecs_defer_resume(world); + } - _flecs_sparse_init(result, size); + flecs_add_non_fragmenting_child_w_records(world, parent, entity, cr, r); - return result; + return entity; } -void flecs_sparse_set_id_source( - ecs_sparse_t * sparse, - uint64_t * id_source) +ecs_entity_t ecs_get_target_for_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + ecs_id_t component) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - sparse->max_id = id_source; -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "get_target_for_id"); -void flecs_sparse_clear( - ecs_sparse_t *sparse) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + if (!component) { + return ecs_get_target(world, entity, rel, 0); + } - ecs_vector_each(sparse->chunks, chunk_t, chunk, { - chunk_free(chunk); - }); + world = ecs_get_world(world); - ecs_vector_free(sparse->chunks); - ecs_vector_set_count(&sparse->dense, uint64_t, 1); + ecs_table_t *table = ecs_get_table(world, entity); + ecs_entity_t src = 0; - sparse->chunks = NULL; - sparse->count = 1; - sparse->max_id_local = 0; -} + if (rel) { + ecs_component_record_t *cr = flecs_components_get(world, component); + if (!cr) { + return 0; + } -void _flecs_sparse_fini( - ecs_sparse_t *sparse) -{ - ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_sparse_clear(sparse); - ecs_vector_free(sparse->dense); -} + int32_t column = ecs_search_relation_for_entity( + world, entity, component, ecs_pair(rel, EcsWildcard), true, cr, &src, 0, 0); + if (column == -1) { + return 0; + } + } else { + if (table) { + ecs_id_t *ids = table->type.array; + int32_t i, count = table->type.count; -void flecs_sparse_free( - ecs_sparse_t *sparse) -{ - if (sparse) { - _flecs_sparse_fini(sparse); - ecs_os_free(sparse); + for (i = 0; i < count; i ++) { + ecs_id_t ent = ids[i]; + if (ent & ECS_ID_FLAGS_MASK) { + /* Skip ids with pairs, roles since 0 was provided for rel */ + break; + } + + if (ecs_has_id(world, ent, component)) { + src = ent; + break; + } + } + } } + + return src; +error: + return 0; } -uint64_t flecs_sparse_new_id( - ecs_sparse_t *sparse) +int32_t ecs_get_depth( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - return new_index(sparse); + ecs_check(ecs_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "get_depth"); + ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, + "cannot determine depth for non-acyclic relationship '%s' " + "(add Acyclic trait to relationship)", + flecs_errstr(ecs_get_path(world, rel))); + + ecs_table_t *table = ecs_get_table(world, entity); + if (table) { + return ecs_table_get_depth(world, table, rel); + } + + return 0; +error: + return -1; } -const uint64_t* flecs_sparse_new_ids( - ecs_sparse_t *sparse, - int32_t new_count) +bool ecs_is_valid( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t dense_count = ecs_vector_count(sparse->dense); - int32_t count = sparse->count; - int32_t remaining = dense_count - count; - int32_t i, to_create = new_count - remaining; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - if (to_create > 0) { - flecs_sparse_set_size(sparse, dense_count + to_create); - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + #ifdef FLECS_DEBUG + world = ecs_get_world(world); + flecs_check_exclusive_world_access_read(world); + #endif - for (i = 0; i < to_create; i ++) { - uint64_t index = create_id(sparse, count + i); - dense_array[dense_count + i] = index; - } + /* 0 is not a valid entity id */ + if (!entity) { + return false; + } + + /* Entity identifiers should not contain flag bits */ + if (entity & ECS_ID_FLAGS_MASK) { + return false; } - sparse->count += new_count; + /* Entities should not contain data in dead zone bits */ + if (entity & ~0xFF00FFFFFFFFFFFF) { + return false; + } - return ecs_vector_get(sparse->dense, uint64_t, count); + /* If id exists, it must be alive (the generation count must match) */ + return ecs_is_alive(world, entity); +error: + return false; } -void* _flecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t size) +ecs_id_t ecs_strip_generation( + ecs_entity_t e) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - uint64_t index = new_index(sparse); - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); - return DATA(chunk->data, size, OFFSET(index)); -} + /* If this is not a pair, erase the generation bits */ + if (!(e & ECS_ID_FLAGS_MASK)) { + e &= ~ECS_GENERATION_MASK; + } -uint64_t flecs_sparse_last_id( - const ecs_sparse_t *sparse) -{ - ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - return dense_array[sparse->count - 1]; + return e; } -void* _flecs_sparse_ensure( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +bool ecs_is_alive( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(ecs_vector_count(sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); - (void)size; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - uint64_t gen = strip_generation(&index); - chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; + world = ecs_get_world(world); - if (dense) { - /* Check if element is alive. If element is not alive, update indices so - * that the first unused dense element points to the sparse element. */ - int32_t count = sparse->count; - if (dense == count) { - /* If dense is the next unused element in the array, simply increase - * the count to make it part of the alive set. */ - sparse->count ++; - } else if (dense > count) { - /* If dense is not alive, swap it with the first unused element. */ - swap_dense(sparse, chunk, dense, count); + flecs_check_exclusive_world_access_read(world); - /* First unused element is now last used element */ - sparse->count ++; - } else { - /* Dense is already alive, nothing to be done */ - } + return flecs_entities_is_alive(world, entity); +error: + return false; +} - /* Ensure provided generation matches current. Only allow mismatching - * generations if the provided generation count is 0. This allows for - * using the ensure function in combination with ids that have their - * generation stripped. */ - ecs_vector_t *dense_vector = sparse->dense; - uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); - ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); - (void)dense_vector; - (void)dense_array; - } else { - /* Element is not paired yet. Must add a new element to dense array */ - grow_dense(sparse); +ecs_entity_t ecs_get_alive( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!entity) { + return 0; + } - ecs_vector_t *dense_vector = sparse->dense; - uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); - int32_t dense_count = ecs_vector_count(dense_vector) - 1; - int32_t count = sparse->count ++; + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - /* If index is larger than max id, update max id */ - if (index >= get_id(sparse)) { - set_id(sparse, index); - } + flecs_check_exclusive_world_access_read(world); - if (count < dense_count) { - /* If there are unused elements in the list, move the first unused - * element to the end of the list */ - uint64_t unused = dense_array[count]; - chunk_t *unused_chunk = get_or_create_chunk(sparse, CHUNK(unused)); - assign_index(unused_chunk, dense_array, unused, dense_count); - } + if (flecs_entities_is_alive(world, entity)) { + return entity; + } - assign_index(chunk, dense_array, index, count); - dense_array[count] |= gen; + /* Make sure id does not have generation. This guards against accidentally + * "upcasting" a not alive identifier to an alive one. */ + if ((uint32_t)entity != entity) { + return 0; } - return DATA(chunk->data, sparse->size, offset); -} + ecs_entity_t current = flecs_entities_get_alive(world, entity); + if (!current || !flecs_entities_is_alive(world, current)) { + return 0; + } -void* _flecs_sparse_set( - ecs_sparse_t * sparse, - ecs_size_t elem_size, - uint64_t index, - void* value) -{ - void *ptr = _flecs_sparse_ensure(sparse, elem_size, index); - ecs_os_memcpy(ptr, value, elem_size); - return ptr; + return current; +error: + return 0; } -void* _flecs_sparse_remove_get( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +void ecs_make_alive( + ecs_world_t *world, + ecs_entity_t entity) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); - uint64_t gen = strip_generation(&index); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; + /* Const cast is safe, function checks for threading */ + world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); - if (dense) { - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; - if (gen != cur_gen) { - /* Generation doesn't match which means that the provided entity is - * already not alive. */ - return NULL; - } + flecs_check_exclusive_world_access_write(world); - /* Increase generation */ - dense_array[dense] = index | inc_gen(cur_gen); - - int32_t count = sparse->count; - - if (dense == (count - 1)) { - /* If dense is the last used element, simply decrease count */ - sparse->count --; - } else if (dense < count) { - /* If element is alive, move it to unused elements */ - swap_dense(sparse, chunk, dense, count - 1); - sparse->count --; - } else { - /* Element is not alive, nothing to be done */ - return NULL; - } + /* The entity index can be mutated while in staged/readonly mode, as long as + * the world is not multithreaded. */ + ecs_assert(!(world->flags & EcsWorldMultiThreaded), + ECS_INVALID_OPERATION, + "cannot call make_alive() while world is in multithreaded mode"); - /* Reset memory to zero on remove */ - return DATA(chunk->data, sparse->size, offset); - } else { - /* Element is not paired and thus not alive, nothing to be done */ - return NULL; + /* Check if a version of the provided id is alive */ + ecs_entity_t current = ecs_get_alive(world, (uint32_t)entity); + if (current == entity) { + /* If alive and equal to the argument, there's nothing left to do */ + return; } -} -void flecs_sparse_remove( - ecs_sparse_t *sparse, - uint64_t index) -{ - void *ptr = _flecs_sparse_remove_get(sparse, 0, index); - if (ptr) { - ecs_os_memset(ptr, 0, sparse->size); - } + /* If the id is currently alive but did not match the argument, fail */ + ecs_check(!current, ECS_INVALID_OPERATION, + "invalid call to make_alive(): entity %u is alive with different " + "generation (%u vs %u)", + (uint32_t)entity, + (uint32_t)(current >> 32), + (uint32_t)(entity >> 32)); + + /* Set generation if not alive. The sparse set checks if the provided + * id matches its own generation which is necessary for alive ids. This + * check would cause ecs_ensure to fail if the generation of the 'entity' + * argument doesn't match with its generation. + * + * While this could've been addressed in the sparse set, this is a rare + * scenario that can only be triggered by ecs_ensure. Implementing it here + * allows the sparse set to not do this check, which is more efficient. */ + flecs_entities_make_alive(world, entity); + + /* Ensure id exists. The underlying data structure will verify that the + * generation count matches the provided one. */ + ecs_record_t *r = flecs_entities_ensure(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t row = flecs_table_append( + world, &world->store.root, entity, false, false); + r->table = &world->store.root; + r->row = ECS_ROW_TO_RECORD(row, r->row & ECS_ROW_FLAGS_MASK); +error: + return; } -void flecs_sparse_set_generation( - ecs_sparse_t *sparse, - uint64_t index) +void ecs_make_alive_id( + ecs_world_t *world, + ecs_id_t component) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - chunk_t *chunk = get_or_create_chunk(sparse, CHUNK(index)); - - uint64_t index_w_gen = index; - strip_generation(&index); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; + flecs_poly_assert(world, ecs_world_t); - if (dense) { - /* Increase generation */ - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - dense_array[dense] = index_w_gen; + if (ECS_HAS_ID_FLAG(component, PAIR)) { + ecs_entity_t r = ECS_PAIR_FIRST(component); + ecs_entity_t t = ECS_PAIR_SECOND(component); + + ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(t != 0, ECS_INVALID_PARAMETER, NULL); + + if (flecs_entities_get_alive(world, r) == 0) { + ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, + "first element of pair is not alive"); + ecs_make_alive(world, r); + } + + if (!ECS_IS_VALUE_PAIR(component)) { + if (flecs_entities_get_alive(world, t) == 0) { + ecs_assert(!ecs_exists(world, t), ECS_INVALID_PARAMETER, + "second element of pair is not alive"); + ecs_make_alive(world, t); + } + } } else { - /* Element is not paired and thus not alive, nothing to be done */ + ecs_make_alive(world, component & ECS_COMPONENT_MASK); } +error: + return; } -bool flecs_sparse_exists( - const ecs_sparse_t *sparse, - uint64_t index) +bool ecs_exists( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - if (!chunk) { - return false; - } - - strip_generation(&index); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - - return dense != 0; -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); -void* _flecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t size, - int32_t dense_index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); - (void)size; + world = ecs_get_world(world); - dense_index ++; + flecs_check_exclusive_world_access_read(world); - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); - return get_sparse(sparse, dense_index, dense_array[dense_index]); + return flecs_entities_exists(world, entity); +error: + return false; } -bool flecs_sparse_is_alive( - const ecs_sparse_t *sparse, - uint64_t index) +void ecs_set_version( + ecs_world_t *world, + ecs_entity_t entity_with_generation) { - return try_sparse(sparse, index) != NULL; -} + flecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, + "cannot change generation for entity %u when world is in readonly mode", + (uint32_t)entity_with_generation); + ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, + "cannot change generation for entity %u while world is deferred", + (uint32_t)entity_with_generation); -uint64_t flecs_sparse_get_alive( - const ecs_sparse_t *sparse, - uint64_t index) -{ - chunk_t *chunk = get_chunk(sparse, CHUNK(index)); - if (!chunk) { - return 0; - } + flecs_check_exclusive_world_access_write(world); - int32_t offset = OFFSET(index); - int32_t dense = chunk->sparse[offset]; - uint64_t *dense_array = ecs_vector_first(sparse->dense, uint64_t); + flecs_entities_make_alive(world, entity_with_generation); - /* If dense is 0 (tombstone) this will return 0 */ - return dense_array[dense]; + if (flecs_entities_is_alive(world, entity_with_generation)) { + ecs_record_t *r = flecs_entities_get(world, entity_with_generation); + if (r && r->table) { + int32_t row = ECS_RECORD_TO_ROW(r->row); + ecs_entity_t *entities = r->table->data.entities; + entities[row] = entity_with_generation; + } + } } -void* _flecs_sparse_get( - const ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; - return try_sparse(sparse, index); -} -void* _flecs_sparse_get_any( - const ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +uint32_t ecs_get_version( + ecs_entity_t entity) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; - return try_sparse_any(sparse, index); + return flecs_uto(uint32_t, ECS_GENERATION(entity)); } -int32_t flecs_sparse_count( - const ecs_sparse_t *sparse) +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity) { - if (!sparse) { - return 0; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "get_table"); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); - return sparse->count - 1; + flecs_check_exclusive_world_access_read(world); + + ecs_record_t *record = flecs_entities_get(world, entity); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + return record->table; +error: + return NULL; } -int32_t flecs_sparse_not_alive_count( - const ecs_sparse_t *sparse) +const ecs_type_t* ecs_get_type( + const ecs_world_t *world, + ecs_entity_t entity) { - if (!sparse) { - return 0; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "get_type"); + ecs_table_t *table = ecs_get_table(world, entity); + if (table) { + return &table->type; } - return ecs_vector_count(sparse->dense) - sparse->count; +error: + return NULL; } -int32_t flecs_sparse_size( - const ecs_sparse_t *sparse) +void ecs_enable( + ecs_world_t *world, + ecs_entity_t entity, + bool enabled) { - if (!sparse) { - return 0; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_assert_entity_valid(world, entity, "enable"); + + if (ecs_has_id(world, entity, EcsPrefab)) { + /* If entity is a type, enable/disable all entities in the type */ + const ecs_type_t *type = ecs_get_type(world, entity); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t *ids = type->array; + int32_t i, count = type->count; + for (i = 0; i < count; i ++) { + ecs_id_t component = ids[i]; + ecs_flags32_t flags = ecs_id_get_flags(world, component); + if (!(flags & EcsIdOnInstantiateDontInherit)){ + ecs_enable(world, component, enabled); + } + } + } else { + if (enabled) { + ecs_remove_id(world, entity, EcsDisabled); + } else { + ecs_add_id(world, entity, EcsDisabled); + } } - - return ecs_vector_count(sparse->dense) - 1; -} - -const uint64_t* flecs_sparse_ids( - const ecs_sparse_t *sparse) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - return &(ecs_vector_first(sparse->dense, uint64_t)[1]); -} - -void flecs_sparse_set_size( - ecs_sparse_t *sparse, - int32_t elem_count) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_vector_set_size(&sparse->dense, uint64_t, elem_count); +error: + return; } static -void sparse_copy( - ecs_sparse_t * dst, - const ecs_sparse_t * src) +void ecs_type_str_buf( + const ecs_world_t *world, + const ecs_type_t *type, + ecs_strbuf_t *buf) { - flecs_sparse_set_size(dst, flecs_sparse_size(src)); - const uint64_t *indices = flecs_sparse_ids(src); - - ecs_size_t size = src->size; - int32_t i, count = src->count; + ecs_entity_t *ids = type->array; + int32_t i, count = type->count; - for (i = 0; i < count - 1; i ++) { - uint64_t index = indices[i]; - void *src_ptr = _flecs_sparse_get(src, size, index); - void *dst_ptr = _flecs_sparse_ensure(dst, size, index); - flecs_sparse_set_generation(dst, index); - ecs_os_memcpy(dst_ptr, src_ptr, size); - } + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; - set_id(dst, get_id(src)); + if (i) { + ecs_strbuf_appendch(buf, ','); + ecs_strbuf_appendch(buf, ' '); + } - ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL); + if (id == 1) { + ecs_strbuf_appendlit(buf, "Component"); + } else { + ecs_id_str_buf(world, id, buf); + } + } } -ecs_sparse_t* flecs_sparse_copy( - const ecs_sparse_t *src) +char* ecs_type_str( + const ecs_world_t *world, + const ecs_type_t *type) { - if (!src) { - return NULL; + if (!type) { + return ecs_os_strdup(""); } - ecs_sparse_t *dst = _flecs_sparse_new(src->size); - sparse_copy(dst, src); - - return dst; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_type_str_buf(world, type, &buf); + return ecs_strbuf_get(&buf); } -void flecs_sparse_restore( - ecs_sparse_t * dst, - const ecs_sparse_t * src) +char* ecs_entity_str( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); - dst->count = 1; - if (src) { - sparse_copy(dst, src); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + flecs_assert_entity_valid(world, entity, "entity_str"); + + ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf, false); + + ecs_strbuf_appendlit(&buf, " ["); + const ecs_type_t *type = ecs_get_type(world, entity); + if (type) { + ecs_type_str_buf(world, type, &buf); } -} + ecs_strbuf_appendch(&buf, ']'); -void flecs_sparse_memory( - ecs_sparse_t *sparse, - int32_t *allocd, - int32_t *used) -{ - (void)sparse; - (void)allocd; - (void)used; + return ecs_strbuf_get(&buf); +error: + return NULL; } -ecs_sparse_t* _ecs_sparse_new( - ecs_size_t elem_size) +ecs_table_range_t flecs_range_from_entity( + const ecs_world_t *world, + ecs_entity_t e) { - return _flecs_sparse_new(elem_size); + ecs_record_t *r = flecs_entities_get(world, e); + if (!r) { + return (ecs_table_range_t){ 0 }; + } + return (ecs_table_range_t){ + .table = r->table, + .offset = ECS_RECORD_TO_ROW(r->row), + .count = 1 + }; } -void* _ecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t elem_size) -{ - return _flecs_sparse_add(sparse, elem_size); -} +/** + * @file entity_name.c + * @brief Functions for working with named entities. + */ -uint64_t ecs_sparse_last_id( - const ecs_sparse_t *sparse) -{ - return flecs_sparse_last_id(sparse); -} -int32_t ecs_sparse_count( - const ecs_sparse_t *sparse) -{ - return flecs_sparse_count(sparse); -} +#define ECS_NAME_BUFFER_LENGTH (64) -void* _ecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - int32_t index) +static +bool flecs_path_append( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf, + bool escape) { - return _flecs_sparse_get_dense(sparse, elem_size, index); -} + flecs_poly_assert(world, ecs_world_t); + ecs_assert(sep[0] != 0, ECS_INVALID_PARAMETER, NULL); -void* _ecs_sparse_get( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id) -{ - return _flecs_sparse_get(sparse, elem_size, id); -} + ecs_entity_t cur = 0; + const char *name = NULL; + ecs_size_t name_len = 0; -ecs_sparse_iter_t _flecs_sparse_iter( - ecs_sparse_t *sparse, - ecs_size_t elem_size) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(elem_size == sparse->size, ECS_INVALID_PARAMETER, NULL); - ecs_sparse_iter_t result; - result.sparse = sparse; - result.ids = flecs_sparse_ids(sparse); - result.size = elem_size; - result.i = 0; - result.count = sparse->count - 1; - return result; -} + if (child && ecs_is_alive(world, child)) { + ecs_record_t *r = flecs_entities_get(world, child); + ecs_assert(r != NULL, ECS_INVALID_OPERATION, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + bool hasName = r->table->flags & EcsTableHasName; + if (hasName) { + cur = ecs_get_target(world, child, EcsChildOf, 0); + if (cur) { + ecs_assert(cur != child, ECS_CYCLE_DETECTED, NULL); + if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) { + flecs_path_append(world, parent, cur, sep, prefix, buf, escape); + if (!sep[1]) { + ecs_strbuf_appendch(buf, sep[0]); + } else { + ecs_strbuf_appendstr(buf, sep); + } + } + } else if (prefix && prefix[0]) { + if (!prefix[1]) { + ecs_strbuf_appendch(buf, prefix[0]); + } else { + ecs_strbuf_appendstr(buf, prefix); + } + } -#ifdef FLECS_SANITIZE -static -void verify_nodes( - flecs_switch_header_t *hdr, - flecs_switch_node_t *nodes) -{ - if (!hdr) { - return; + const EcsIdentifier *id = ecs_get_pair( + world, child, EcsIdentifier, EcsName); + if (id) { + name = id->value; + name_len = id->length; + } + } } - int32_t prev = -1, elem = hdr->element, count = 0; - while (elem != -1) { - ecs_assert(prev == nodes[elem].prev, ECS_INTERNAL_ERROR, NULL); - prev = elem; - elem = nodes[elem].next; - count ++; + if (name) { + /* Check if we need to escape separator character */ + const char *sep_in_name = NULL; + if (!sep[1]) { + sep_in_name = strchr(name, sep[0]); + } + + if (sep_in_name || escape) { + const char *name_ptr; + char ch; + for (name_ptr = name; (ch = name_ptr[0]); name_ptr ++) { + char esc[3]; + if (ch != sep[0]) { + if (escape) { + flecs_chresc(esc, ch, '\"'); + ecs_strbuf_appendch(buf, esc[0]); + if (esc[1]) { + ecs_strbuf_appendch(buf, esc[1]); + } + } else { + ecs_strbuf_appendch(buf, ch); + } + } else { + if (!escape) { + ecs_strbuf_appendch(buf, '\\'); + ecs_strbuf_appendch(buf, sep[0]); + } else { + ecs_strbuf_appendlit(buf, "\\\\"); + flecs_chresc(esc, ch, '\"'); + ecs_strbuf_appendch(buf, esc[0]); + if (esc[1]) { + ecs_strbuf_appendch(buf, esc[1]); + } + } + } + } + } else { + ecs_strbuf_appendstrn(buf, name, name_len); + } + } else { + ecs_strbuf_appendch(buf, '#'); + ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); } - ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); + return cur != 0; } -#else -#define verify_nodes(hdr, nodes) -#endif -static -flecs_switch_header_t *get_header( - const ecs_switch_t *sw, - uint64_t value) +bool flecs_name_is_id( + const char *name) { - if (value == 0) { - return NULL; + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + if (name[0] == '#') { + /* If name is not just digits it's not an id */ + const char *ptr; + char ch; + for (ptr = name + 1; (ch = ptr[0]); ptr ++) { + if (!isdigit(ch)) { + return false; + } + } + return true; } + return false; +} - value = (uint32_t)value; - - ecs_assert(value >= sw->min, ECS_INTERNAL_ERROR, NULL); - ecs_assert(value <= sw->max, ECS_INTERNAL_ERROR, NULL); +ecs_entity_t flecs_name_to_id( + const char *name) +{ + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(name[0] == '#', ECS_INVALID_PARAMETER, NULL); + ecs_entity_t res = flecs_ito(uint64_t, atoll(name + 1)); + if (res >= UINT32_MAX) { + return 0; /* Invalid id */ + } + return res; +} - uint64_t index = value - sw->min; +static +ecs_entity_t flecs_get_builtin( + const char *name) +{ + if (name[0] == '.' && name[1] == '\0') { + return EcsThis; + } else if (name[0] == '*' && name[1] == '\0') { + return EcsWildcard; + } else if (name[0] == '_' && name[1] == '\0') { + return EcsAny; + } else if (name[0] == '$' && name[1] == '\0') { + return EcsVariable; + } - return &sw->headers[index]; + return 0; } static -void remove_node( - flecs_switch_header_t *hdr, - flecs_switch_node_t *nodes, - flecs_switch_node_t *node, - int32_t element) +bool flecs_is_sep( + const char **ptr, + const char *sep) { - ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL); + ecs_size_t len = ecs_os_strlen(sep); - /* Update previous node/header */ - if (hdr->element == element) { - ecs_assert(node->prev == -1, ECS_INVALID_PARAMETER, NULL); - /* If this is the first node, update the header */ - hdr->element = node->next; + if (!ecs_os_strncmp(*ptr, sep, len)) { + *ptr += len; + return true; } else { - /* If this is not the first node, update the previous node to the - * removed node's next ptr */ - ecs_assert(node->prev != -1, ECS_INVALID_PARAMETER, NULL); - flecs_switch_node_t *prev_node = &nodes[node->prev]; - prev_node->next = node->next; + return false; } +} - /* Update next node */ - int32_t next = node->next; - if (next != -1) { - ecs_assert(next >= 0, ECS_INVALID_PARAMETER, NULL); - /* If this is not the last node, update the next node to point to the - * removed node's prev ptr */ - flecs_switch_node_t *next_node = &nodes[next]; - next_node->prev = node->prev; +static +const char* flecs_path_elem( + const char *path, + const char *sep, + char **buffer_out, + ecs_size_t *size_out) +{ + char *buffer = NULL; + if (buffer_out) { + buffer = *buffer_out; } - /* Decrease count of current header */ - hdr->count --; - ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); -} + const char *ptr; + char ch; + int32_t template_nesting = 0; + int32_t pos = 0; + ecs_size_t size = size_out ? *size_out : 0; -ecs_switch_t* flecs_switch_new( - uint64_t min, - uint64_t max, - int32_t elements) -{ - ecs_assert(min <= max, ECS_INVALID_PARAMETER, NULL); + for (ptr = path; (ch = *ptr); ptr ++) { + if (ch == '<') { + template_nesting ++; + } else if (ch == '>') { + template_nesting --; + } else if (ch == '\\') { + ptr ++; + if (buffer) { + buffer[pos] = ptr[0]; + } + pos ++; + continue; + } + + ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); - /* Min must be larger than 0, as 0 is an invalid entity id, and should - * therefore never occur as case id */ - ecs_assert(min > 0, ECS_INVALID_PARAMETER, NULL); + if (!template_nesting && flecs_is_sep(&ptr, sep)) { + break; + } - ecs_switch_t *result = ecs_os_malloc(ECS_SIZEOF(ecs_switch_t)); - result->min = (uint32_t)min; - result->max = (uint32_t)max; + if (buffer) { + if (pos >= (size - 1)) { + if (size == ECS_NAME_BUFFER_LENGTH) { /* stack buffer */ + char *new_buffer = ecs_os_malloc(size * 2 + 1); + ecs_os_memcpy(new_buffer, buffer, size); + buffer = new_buffer; + } else { /* heap buffer */ + buffer = ecs_os_realloc(buffer, size * 2 + 1); + } + size *= 2; + } - int32_t count = (int32_t)(max - min) + 1; - result->headers = ecs_os_calloc(ECS_SIZEOF(flecs_switch_header_t) * count); - result->nodes = ecs_vector_new(flecs_switch_node_t, elements); - result->values = ecs_vector_new(uint64_t, elements); + buffer[pos] = ch; + } - int64_t i; - for (i = 0; i < count; i ++) { - result->headers[i].element = -1; - result->headers[i].count = 0; + pos ++; } - flecs_switch_node_t *nodes = ecs_vector_first( - result->nodes, flecs_switch_node_t); - uint64_t *values = ecs_vector_first( - result->values, uint64_t); - - for (i = 0; i < elements; i ++) { - nodes[i].prev = -1; - nodes[i].next = -1; - values[i] = 0; + if (buffer) { + buffer[pos] = '\0'; + *buffer_out = buffer; + *size_out = size; } - return result; + if (pos || ptr[0]) { + return ptr; + } else { + return NULL; + } +error: + return NULL; } -void flecs_switch_clear( - ecs_switch_t *sw) +static +bool flecs_is_root_path( + const char *path, + const char *prefix) { - int32_t count = (int32_t)(sw->max - sw->min) + 1; - for (int i = 0; i < count; i ++) { - sw->headers[i].element = -1; - sw->headers[i].count = 0; + if (prefix) { + return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); + } else { + return false; } - ecs_vector_free(sw->nodes); - ecs_vector_free(sw->values); - sw->nodes = NULL; - sw->values = NULL; } -void flecs_switch_free( - ecs_switch_t *sw) +static +ecs_entity_t flecs_get_parent_from_path( + const ecs_world_t *world, + ecs_entity_t parent, + const char **path_ptr, + const char *sep, + const char *prefix, + bool new_entity, + bool *error) { - ecs_os_free(sw->headers); - ecs_vector_free(sw->nodes); - ecs_vector_free(sw->values); - ecs_os_free(sw); -} + ecs_assert(error != NULL, ECS_INTERNAL_ERROR, NULL); -void flecs_switch_add( - ecs_switch_t *sw) -{ - flecs_switch_node_t *node = ecs_vector_add(&sw->nodes, flecs_switch_node_t); - uint64_t *value = ecs_vector_add(&sw->values, uint64_t); - node->prev = -1; - node->next = -1; - *value = 0; -} + bool start_from_root = false; + const char *path = *path_ptr; -void flecs_switch_set_count( - ecs_switch_t *sw, - int32_t count) -{ - int32_t old_count = ecs_vector_count(sw->nodes); - if (old_count == count) { - return; + if (flecs_is_root_path(path, prefix)) { + path += ecs_os_strlen(prefix); + parent = 0; + start_from_root = true; } - ecs_vector_set_count(&sw->nodes, flecs_switch_node_t, count); - ecs_vector_set_count(&sw->values, uint64_t, count); + if (path[0] == '#') { + parent = flecs_name_to_id(path); + if (!parent && ecs_os_strncmp(path, "#0", 2)) { + *error = true; + return 0; + } + + path ++; + while (path[0] && isdigit(path[0])) { + path ++; /* Skip id part of path */ + } - flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); - uint64_t *values = ecs_vector_first(sw->values, uint64_t); + /* Skip next separator so that the returned path points to the next + * name element. */ + ecs_size_t sep_len = ecs_os_strlen(sep); + if (!ecs_os_strncmp(path, sep, ecs_os_strlen(sep))) { + path += sep_len; + } - int32_t i; - for (i = old_count; i < count; i ++) { - flecs_switch_node_t *node = &nodes[i]; - node->prev = -1; - node->next = -1; - values[i] = 0; + start_from_root = true; + } + + if (!start_from_root && !parent && new_entity) { + parent = ecs_get_scope(world); } -} -int32_t flecs_switch_count( - ecs_switch_t *sw) -{ - ecs_assert(ecs_vector_count(sw->values) == ecs_vector_count(sw->nodes), - ECS_INTERNAL_ERROR, NULL); - return ecs_vector_count(sw->values); + *path_ptr = path; + + return parent; } -void flecs_switch_ensure( - ecs_switch_t *sw, - int32_t count) +static +void flecs_on_set_symbol( + ecs_iter_t *it) { - int32_t old_count = ecs_vector_count(sw->nodes); - if (old_count >= count) { - return; - } + EcsIdentifier *n = ecs_field(it, EcsIdentifier, 0); + ecs_world_t *world = it->real_world; - flecs_switch_set_count(sw, count); + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + flecs_name_index_ensure( + &world->symbols, e, n[i].value, n[i].length, n[i].hash); + } } -void flecs_switch_addn( - ecs_switch_t *sw, - int32_t count) +void flecs_bootstrap_entity_name( + ecs_world_t *world) { - int32_t old_count = ecs_vector_count(sw->nodes); - flecs_switch_set_count(sw, old_count + count); + ecs_observer(world, { + .query.terms[0] = { + .id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol) + }, + .callback = flecs_on_set_symbol, + .events = {EcsOnSet}, + .yield_existing = true, + .global_observer = true + }); } -void flecs_switch_set( - ecs_switch_t *sw, - int32_t element, - uint64_t value) +void ecs_on_set(EcsIdentifier)( + ecs_iter_t *it) { - ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_world_t *world = it->real_world; + EcsIdentifier *ptr = ecs_field(it, EcsIdentifier, 0); - uint64_t *values = ecs_vector_first(sw->values, uint64_t); - uint64_t cur_value = values[element]; + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); - /* If the node is already assigned to the value, nothing to be done */ - if (cur_value == value) { - return; - } + ecs_entity_t evt = it->event; + ecs_id_t evt_id = it->event_id; + ecs_entity_t kind = ECS_PAIR_SECOND(evt_id); /* Name, Symbol, Alias */ + ecs_id_t pair = ecs_childof(0); + ecs_hashmap_t *index = NULL; + bool has_parent = false; + EcsParent *parents = NULL; - flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); - flecs_switch_node_t *node = &nodes[element]; + if (kind == EcsSymbol) { + index = &world->symbols; + } else if (kind == EcsAlias) { + index = &world->aliases; + } else if (kind == EcsName) { + ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_switch_header_t *cur_hdr = get_header(sw, cur_value); - flecs_switch_header_t *dst_hdr = get_header(sw, value); + if (it->table->flags & EcsTableHasParent) { + has_parent = true; + parents = ecs_table_get(world, it->table, EcsParent, it->offset); + } else { + ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair); + ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL); - verify_nodes(cur_hdr, nodes); - verify_nodes(dst_hdr, nodes); + ecs_component_record_t *cr = flecs_components_get(world, pair); + if (evt == EcsOnSet) { + index = flecs_component_name_index_ensure(world, cr); + } else { + index = flecs_component_name_index_get(world, cr); + } + } + } - /* If value is not 0, and dst_hdr is NULL, then this is not a valid value - * for this switch */ - ecs_assert(dst_hdr != NULL || !value, ECS_INVALID_PARAMETER, NULL); + int i, count = it->count; - if (cur_hdr) { - remove_node(cur_hdr, nodes, node, element); - } + for (i = 0; i < count; i ++) { + EcsIdentifier *cur = &ptr[i]; + uint64_t hash; + ecs_size_t len; + const char *name = cur->value; - /* Now update the node itself by adding it as the first node of dst */ - node->prev = -1; - values[element] = value; + if (kind == EcsName) { + ecs_assert((world->flags & (EcsWorldInit|EcsWorldFini)) || + !(cur->index) || + !(it->table->flags & EcsTableHasBuiltins), + ECS_INVALID_OPERATION, + "cannot rename builtin entity to '%s'", name); + ecs_assert((world->flags & (EcsWorldInit|EcsWorldFini)) || + (it->entities[i] != EcsFlecs), + ECS_INVALID_OPERATION, + "cannot rename flecs root module"); + ecs_assert((world->flags & (EcsWorldInit|EcsWorldFini)) || + (it->entities[i] != EcsFlecsCore), + ECS_INVALID_OPERATION, + "cannot rename flecs.core module"); + + if (has_parent) { + ecs_component_record_t *cr = flecs_components_get( + world, ecs_childof(parents[i].value)); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + index = flecs_component_name_index_ensure(world, cr); + } + } - if (dst_hdr) { - node->next = dst_hdr->element; + if (cur->index && cur->index != index) { + /* If index doesn't match up, the value must have been copied from + * another entity, so reset index & cached index hash */ + cur->index = NULL; + cur->index_hash = 0; + } - /* Also update the dst header */ - int32_t first = dst_hdr->element; - if (first != -1) { - ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL); - flecs_switch_node_t *first_node = &nodes[first]; - first_node->prev = element; + if (cur->value && (evt == EcsOnSet)) { + len = cur->length = ecs_os_strlen(name); + hash = cur->hash = flecs_hash(name, len); + } else { + len = cur->length = 0; + hash = cur->hash = 0; + cur->index = NULL; } - dst_hdr->element = element; - dst_hdr->count ++; + if (index) { + uint64_t index_hash = cur->index_hash; + ecs_entity_t e = it->entities[i]; + + if (hash != index_hash) { + if (index_hash) { + flecs_name_index_remove(index, e, index_hash); + } + if (hash) { + flecs_name_index_ensure(index, e, name, len, hash); + cur->index_hash = hash; + cur->index = index; + } + } else { + /* Name didn't change, but the string could have been + * reallocated. Make sure name index points to correct string */ + flecs_name_index_update_name(index, e, hash, name); + } + } } } -void flecs_switch_remove( - ecs_switch_t *sw, - int32_t element) +static +void flecs_reparent_name_index_intern( + const ecs_entity_t *entities, + ecs_hashmap_t *src_index, + ecs_hashmap_t *dst_index, + EcsIdentifier *names, + int32_t count) { - ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); - - uint64_t *values = ecs_vector_first(sw->values, uint64_t); - uint64_t value = values[element]; - flecs_switch_node_t *nodes = ecs_vector_first(sw->nodes, flecs_switch_node_t); - flecs_switch_node_t *node = &nodes[element]; - - /* If node is currently assigned to a case, remove it from the list */ - if (value != 0) { - flecs_switch_header_t *hdr = get_header(sw, value); - ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i; + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + EcsIdentifier *name = &names[i]; - verify_nodes(hdr, nodes); - remove_node(hdr, nodes, node, element); - } + ecs_assert(e != EcsFlecs, ECS_INVALID_OPERATION, + "cannot reparent flecs root module"); + ecs_assert(e != EcsFlecsCore, ECS_INVALID_OPERATION, + "cannot reparent flecs.core module"); - int32_t last_elem = ecs_vector_count(sw->nodes) - 1; - if (last_elem != element) { - flecs_switch_node_t *last = ecs_vector_last(sw->nodes, flecs_switch_node_t); - int32_t next = last->next, prev = last->prev; - if (next != -1) { - flecs_switch_node_t *n = &nodes[next]; - n->prev = element; + uint64_t index_hash = name->index_hash; + if (index_hash) { + flecs_name_index_remove(src_index, e, index_hash); } + + if (dst_index) { + const char *name_str = name->value; + if (name_str) { + if (name->hash == 0) { + name->length = ecs_os_strlen(name_str); + name->hash = flecs_hash(name_str, name->length); + } - if (prev != -1) { - flecs_switch_node_t *n = &nodes[prev]; - n->next = element; - } else { - flecs_switch_header_t *hdr = get_header(sw, values[last_elem]); - if (hdr && hdr->element != -1) { - ecs_assert(hdr->element == last_elem, - ECS_INTERNAL_ERROR, NULL); - hdr->element = element; + ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); + + flecs_name_index_ensure( + dst_index, e, name_str, name->length, name->hash); + name->index = dst_index; } } } - - /* Remove element from arrays */ - ecs_vector_remove(sw->nodes, flecs_switch_node_t, element); - ecs_vector_remove(sw->values, uint64_t, element); } -uint64_t flecs_switch_get( - const ecs_switch_t *sw, - int32_t element) +void flecs_reparent_name_index( + ecs_world_t *world, + ecs_table_t *dst, + ecs_table_t *src, + int32_t offset, + int32_t count) { - ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->values), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); - - uint64_t *values = ecs_vector_first(sw->values, uint64_t); - return values[element]; -} + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + if (!(dst->flags & EcsTableHasName)) { + /* If destination table doesn't have a name, we don't need to update the + * name index. Even if the src table had a name, the on_remove hook for + * EcsIdentifier will remove the entity from the index. */ + return; + } -ecs_vector_t* flecs_switch_values( - const ecs_switch_t *sw) -{ - return sw->values; -} + if (!src) { + src = &world->store.root; + } -int32_t flecs_switch_case_count( - const ecs_switch_t *sw, - uint64_t value) -{ - flecs_switch_header_t *hdr = get_header(sw, value); - if (!hdr) { - return 0; + ecs_hashmap_t *src_index = flecs_table_get_name_index(world, src); + ecs_hashmap_t *dst_index = flecs_table_get_name_index(world, dst); + ecs_assert(src_index != dst_index, ECS_INTERNAL_ERROR, NULL); + if ((!src_index && !dst_index)) { + return; } - return hdr->count; -} + EcsIdentifier *names = ecs_table_get_pair(world, + dst, EcsIdentifier, EcsName, offset); + ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); -void flecs_switch_swap( - ecs_switch_t *sw, - int32_t elem_1, - int32_t elem_2) -{ - uint64_t v1 = flecs_switch_get(sw, elem_1); - uint64_t v2 = flecs_switch_get(sw, elem_2); + flecs_reparent_name_index_intern(&ecs_table_entities(dst)[offset], + src_index, dst_index, names, count); - flecs_switch_set(sw, elem_2, v1); - flecs_switch_set(sw, elem_1, v2); } -int32_t flecs_switch_first( - const ecs_switch_t *sw, - uint64_t value) +void flecs_unparent_name_index( + ecs_world_t *world, + ecs_table_t *src, + ecs_table_t *dst, + int32_t offset, + int32_t count) { - ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert((uint32_t)value <= sw->max, ECS_INVALID_PARAMETER, NULL); - ecs_assert((uint32_t)value >= sw->min, ECS_INVALID_PARAMETER, NULL); - - flecs_switch_header_t *hdr = get_header(sw, value); - ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); + if (!(src->flags & EcsTableHasName)) { + return; + } - return hdr->element; -} + if (!dst || !(dst->flags & EcsTableHasName)) { + /* If destination table doesn't have a name, we don't need to update the + * name index. Even if the src table had a name, the on_remove hook for + * EcsIdentifier will remove the entity from the index. */ + return; + } -int32_t flecs_switch_next( - const ecs_switch_t *sw, - int32_t element) -{ - ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(element < ecs_vector_count(sw->nodes), ECS_INVALID_PARAMETER, NULL); - ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_hashmap_t *src_index = flecs_table_get_name_index(world, src); + ecs_hashmap_t *dst_index = dst ? flecs_table_get_name_index(world, dst) : NULL; + ecs_assert(src_index != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_switch_node_t *nodes = ecs_vector_first( - sw->nodes, flecs_switch_node_t); + EcsIdentifier *names = ecs_table_get_pair(world, + src, EcsIdentifier, EcsName, offset); + ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); - return nodes[element].next; + flecs_reparent_name_index_intern(&ecs_table_entities(src)[offset], + src_index, dst_index, names, count); } +/* Public functions */ -static -uint64_t name_index_hash( - const void *ptr) +void ecs_get_path_w_sep_buf( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf, + bool escape) { - const ecs_hashed_string_t *str = ptr; - ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); - return str->hash; -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); -static -int name_index_compare( - const void *ptr1, - const void *ptr2) -{ - const ecs_hashed_string_t *str1 = ptr1; - const ecs_hashed_string_t *str2 = ptr2; - ecs_size_t len1 = str1->length; - ecs_size_t len2 = str2->length; - if (len1 != len2) { - return (len1 > len2) - (len1 < len2); + world = ecs_get_world(world); + + if (child == EcsWildcard) { + ecs_strbuf_appendch(buf, '*'); + return; + } + if (child == EcsAny) { + ecs_strbuf_appendch(buf, '_'); + return; } - return ecs_os_memcmp(str1->value, str2->value, len1); -} + if (!sep) { + sep = "."; + } -void flecs_name_index_init( - ecs_hashmap_t *hm) -{ - _flecs_hashmap_init(hm, - ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t), - name_index_hash, - name_index_compare); -} + if (!child || parent != child) { + flecs_path_append(world, parent, child, sep, prefix, buf, escape); + } else { + ecs_strbuf_appendstrn(buf, "", 0); + } -ecs_hashmap_t* flecs_name_index_new(void) -{ - ecs_hashmap_t *result = ecs_os_calloc_t(ecs_hashmap_t); - flecs_name_index_init(result); - return result; +error: + return; } -void flecs_name_index_fini( - ecs_hashmap_t *map) +char* ecs_get_path_w_sep( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix) { - flecs_hashmap_fini(map); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf, false); + return ecs_strbuf_get(&buf); } -void flecs_name_index_free( - ecs_hashmap_t *map) +ecs_entity_t ecs_lookup_child( + const ecs_world_t *world, + ecs_entity_t parent, + const char *name) { - if (map) { - flecs_name_index_fini(map); - ecs_os_free(map); - } -} + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); -ecs_hashed_string_t flecs_get_hashed_string( - const char *name, - ecs_size_t length, - uint64_t hash) -{ - if (!length) { - length = ecs_os_strlen(name); - } else { - ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL); + if (flecs_name_is_id(name)) { + ecs_entity_t result = flecs_name_to_id(name); + if (result && ecs_is_alive(world, result)) { + if (parent && !ecs_has_pair(world, result, EcsChildOf, parent)) { + return 0; + } + return result; + } } - if (!hash) { - hash = flecs_hash(name, length); + ecs_id_t pair = ecs_childof(parent); + ecs_component_record_t *cr = flecs_components_get(world, pair); + ecs_hashmap_t *index = NULL; + if (cr) { + index = flecs_component_name_index_get(world, cr); + } + if (index) { + return flecs_name_index_find(index, name, 0, 0); } else { - ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); + return 0; } +error: + return 0; +} - return (ecs_hashed_string_t) { - .value = (char*)name, - .length = length, - .hash = hash - }; +ecs_entity_t ecs_lookup( + const ecs_world_t *world, + const char *path) +{ + return ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true); } -const uint64_t* flecs_name_index_find_ptr( - const ecs_hashmap_t *map, +ecs_entity_t ecs_lookup_symbol( + const ecs_world_t *world, const char *name, - ecs_size_t length, - uint64_t hash) -{ - ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash); - - ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); - if (!b) { - return NULL; + bool lookup_as_path, + bool recursive) +{ + if (!name) { + return 0; } - ecs_hashed_string_t *keys = ecs_vector_first(b->keys, ecs_hashed_string_t); - int32_t i, count = ecs_vector_count(b->keys); - - for (i = 0; i < count; i ++) { - ecs_hashed_string_t *key = &keys[i]; - ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); - if (hs.length != key->length) { - continue; - } + ecs_entity_t e = 0; + if (lookup_as_path) { + e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, recursive); + } - if (!ecs_os_strcmp(name, key->value)) { - uint64_t *e = ecs_vector_get(b->values, uint64_t, i); - ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); - return e; - } + if (!e) { + e = flecs_name_index_find(&world->symbols, name, 0, 0); } - return NULL; + return e; +error: + return 0; } -uint64_t flecs_name_index_find( - const ecs_hashmap_t *map, - const char *name, - ecs_size_t length, - uint64_t hash) +ecs_entity_t ecs_lookup_path_w_sep( + const ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix, + bool recursive) { - const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash); - if (id) { - return id[0]; + if (!path) { + return 0; } - return 0; -} -void flecs_name_index_remove( - ecs_hashmap_t *map, - uint64_t e, - uint64_t hash) -{ - ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); - if (!b) { - return; + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(!parent || ecs_is_valid(world, parent), + ECS_INVALID_PARAMETER, NULL); + const ecs_world_t *stage = world; + world = ecs_get_world(world); + + ecs_entity_t e = flecs_get_builtin(path); + if (e) { + return e; } - uint64_t *ids = ecs_vector_first(b->values, uint64_t); - int32_t i, count = ecs_vector_count(b->values); + e = flecs_name_index_find(&world->aliases, path, 0, 0); + if (e) { + return e; + } - for (i = 0; i < count; i ++) { - if (ids[i] == e) { - flecs_hm_bucket_remove(map, b, hash, i); - break; - } + char buff[ECS_NAME_BUFFER_LENGTH], *elem = buff; + const char *ptr; + int32_t size = ECS_NAME_BUFFER_LENGTH; + ecs_entity_t cur; + bool lookup_path_search = false; + + const ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); + const ecs_entity_t *lookup_path_cur = lookup_path; + while (lookup_path_cur && *lookup_path_cur) { + lookup_path_cur ++; } -} -void flecs_name_index_update_name( - ecs_hashmap_t *map, - uint64_t e, - uint64_t hash, - const char *name) -{ - ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); - if (!b) { - return; + if (!sep) { + sep = "."; } - uint64_t *ids = ecs_vector_first(b->values, uint64_t); - int32_t i, count = ecs_vector_count(b->values); + bool error = false; + parent = flecs_get_parent_from_path( + stage, parent, &path, sep, prefix, true, &error); + if (error) { + return 0; + } - for (i = 0; i < count; i ++) { - if (ids[i] == e) { - ecs_hashed_string_t *key = ecs_vector_get( - b->keys, ecs_hashed_string_t, i); - key->value = (char*)name; - ecs_assert(ecs_os_strlen(name) == key->length, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_hash(name, key->length) == key->hash, - ECS_INTERNAL_ERROR, NULL); - return; - } + if (parent && !(parent = ecs_get_alive(world, parent))) { + return 0; } - /* Record must already have been in the index */ - ecs_abort(ECS_INTERNAL_ERROR, NULL); -} + if (!path[0]) { + return parent; + } -void flecs_name_index_ensure( - ecs_hashmap_t *map, - uint64_t id, - const char *name, - ecs_size_t length, - uint64_t hash) -{ - ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); + if (!sep[0]) { + return ecs_lookup_child(world, parent, path); + } - ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); - - uint64_t existing = flecs_name_index_find( - map, name, key.length, key.hash); - if (existing) { - if (existing != id) { - ecs_abort(ECS_ALREADY_DEFINED, - "conflicting id registered with name '%s'", name); +retry: + cur = parent; + ptr = path; + + while ((ptr = flecs_path_elem(ptr, sep, &elem, &size))) { + cur = ecs_lookup_child(world, cur, elem); + if (!cur) { + goto tail; } } - flecs_hashmap_result_t hmr = flecs_hashmap_ensure( - map, &key, uint64_t); +tail: + if (!cur && recursive) { + if (!lookup_path_search) { + if (parent) { + parent = ecs_get_target(world, parent, EcsChildOf, 0); + goto retry; + } else { + lookup_path_search = true; + } + } - *((uint64_t*)hmr.value) = id; + if (lookup_path_search) { + if (lookup_path_cur != lookup_path) { + lookup_path_cur --; + parent = lookup_path_cur[0]; + goto retry; + } + } + } + + if (elem != buff) { + ecs_os_free(elem); + } + + return cur; error: - return; + return 0; } +ecs_entity_t ecs_set_scope( + ecs_world_t *world, + ecs_entity_t scope) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); -#ifdef ECS_TARGET_GNU -#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -#endif - -/* See explanation below. The hashing function may read beyond the memory passed - * into the hashing function, but only at word boundaries. This should be safe, - * but trips up address sanitizers and valgrind. - * This ensures clean valgrind logs in debug mode & the best perf in release */ -#if !defined(FLECS_NDEBUG) || defined(ADDRESS_SANITIZER) -#ifndef VALGRIND -#define VALGRIND -#endif -#endif + ecs_entity_t cur = stage->scope; + stage->scope = scope; -/* -------------------------------------------------------------------------------- -lookup3.c, by Bob Jenkins, May 2006, Public Domain. - http://burtleburtle.net/bob/c/lookup3.c -------------------------------------------------------------------------------- -*/ + return cur; +error: + return 0; +} -#ifdef ECS_TARGET_MSVC -//FIXME -#else -#include /* attempt to define endianness */ -#endif -#ifdef ECS_TARGET_LINUX -# include /* attempt to define endianness */ -#endif +ecs_entity_t ecs_get_scope( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->scope; +error: + return 0; +} -/* - * My best guess at if you are big-endian or little-endian. This may - * need adjustment. - */ -#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ - __BYTE_ORDER == __LITTLE_ENDIAN) || \ - (defined(i386) || defined(__i386__) || defined(__i486__) || \ - defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) -# define HASH_LITTLE_ENDIAN 1 -#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ - __BYTE_ORDER == __BIG_ENDIAN) || \ - (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) -# define HASH_LITTLE_ENDIAN 0 -#else -# define HASH_LITTLE_ENDIAN 0 -#endif +ecs_entity_t* ecs_set_lookup_path( + ecs_world_t *world, + const ecs_entity_t *lookup_path) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); -#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + /* Safe: application owns lookup path */ + ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); + stage->lookup_path = lookup_path; -/* -------------------------------------------------------------------------------- -mix -- mix 3 32-bit values reversibly. -This is reversible, so any information in (a,b,c) before mix() is -still in (a,b,c) after mix(). -If four pairs of (a,b,c) inputs are run through mix(), or through -mix() in reverse, there are at least 32 bits of the output that -are sometimes the same for one pair and different for another pair. -This was tested for: -* pairs that differed by one bit, by two bits, in any combination - of top bits of (a,b,c), or in any combination of bottom bits of - (a,b,c). -* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed - the output delta to a Gray code (a^(a>>1)) so a string of 1's (as - is commonly produced by subtraction) look like a single 1-bit - difference. -* the base values were pseudorandom, all zero but one bit set, or - all zero plus a counter that starts at zero. -Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that -satisfy this are - 4 6 8 16 19 4 - 9 15 3 18 27 15 - 14 9 3 7 17 3 -Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing -for "differ" defined as + with a one-bit base and a two-bit delta. I -used http://burtleburtle.net/bob/hash/avalanche.html to choose -the operations, constants, and arrangements of the variables. -This does not achieve avalanche. There are input bits of (a,b,c) -that fail to affect some output bits of (a,b,c), especially of a. The -most thoroughly mixed value is c, but it doesn't really even achieve -avalanche in c. -This allows some parallelism. Read-after-writes are good at doubling -the number of bits affected, so the goal of mixing pulls in the opposite -direction as the goal of parallelism. I did what I could. Rotates -seem to cost as much as shifts on every machine I could lay my hands -on, and rotates are much kinder to the top and bottom bits, so I used -rotates. -------------------------------------------------------------------------------- -*/ -#define mix(a,b,c) \ -{ \ - a -= c; a ^= rot(c, 4); c += b; \ - b -= a; b ^= rot(a, 6); a += c; \ - c -= b; c ^= rot(b, 8); b += a; \ - a -= c; a ^= rot(c,16); c += b; \ - b -= a; b ^= rot(a,19); a += c; \ - c -= b; c ^= rot(b, 4); b += a; \ + return cur; +error: + return NULL; } -/* -------------------------------------------------------------------------------- -final -- final mixing of 3 32-bit values (a,b,c) into c -Pairs of (a,b,c) values differing in only a few bits will usually -produce values of c that look totally different. This was tested for -* pairs that differed by one bit, by two bits, in any combination - of top bits of (a,b,c), or in any combination of bottom bits of - (a,b,c). -* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed - the output delta to a Gray code (a^(a>>1)) so a string of 1's (as - is commonly produced by subtraction) look like a single 1-bit - difference. -* the base values were pseudorandom, all zero but one bit set, or - all zero plus a counter that starts at zero. -These constants passed: - 14 11 25 16 4 14 24 - 12 14 25 16 4 14 24 -and these came close: - 4 8 15 26 3 22 24 - 10 8 15 26 3 22 24 - 11 8 15 26 3 22 24 -------------------------------------------------------------------------------- -*/ -#define final(a,b,c) \ -{ \ - c ^= b; c -= rot(b,14); \ - a ^= c; a -= rot(c,11); \ - b ^= a; b -= rot(a,25); \ - c ^= b; c -= rot(b,16); \ - a ^= c; a -= rot(c,4); \ - b ^= a; b -= rot(a,14); \ - c ^= b; c -= rot(b,24); \ +ecs_entity_t* ecs_get_lookup_path( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + /* Safe: application owns lookup path */ + return ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); +error: + return NULL; } +const char* ecs_set_name_prefix( + ecs_world_t *world, + const char *prefix) +{ + flecs_poly_assert(world, ecs_world_t); + const char *old_prefix = world->info.name_prefix; + world->info.name_prefix = prefix; + return old_prefix; +} -/* - * hashlittle2: return 2 32-bit hash values - * - * This is identical to hashlittle(), except it returns two 32-bit hash - * values instead of just one. This is good enough for hash table - * lookup with 2^^64 buckets, or if you want a second hash if you're not - * happy with the first, or if you want a probably-unique 64-bit ID for - * the key. *pc is better mixed than *pb, so use *pc first. If you want - * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". - */ static -void hashlittle2( - const void *key, /* the key to hash */ - size_t length, /* length of the key */ - uint32_t *pc, /* IN: primary initval, OUT: primary hash */ - uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ +void flecs_add_path( + ecs_world_t *world, + bool defer_suspend, + ecs_entity_t parent, + ecs_entity_t entity, + const char *name) { - uint32_t a,b,c; /* internal state */ - union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ + ecs_suspend_readonly_state_t srs; + ecs_world_t *real_world = NULL; + if (defer_suspend) { + real_world = flecs_suspend_readonly(world, &srs); + ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); + } - /* Set up the internal state */ - a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; - c += *pb; + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, parent); + } - u.ptr = key; - if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { - const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ - const uint8_t *k8; - (void)k8; + ecs_assert(name[0] != '#', ECS_INVALID_PARAMETER, + "path should not contain identifier with #"); - /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ - while (length > 12) - { - a += k[0]; - b += k[1]; - c += k[2]; - mix(a,b,c); - length -= 12; - k += 3; - } - - /*----------------------------- handle the last (probably partial) block */ - /* - * "k[2]&0xffffff" actually reads beyond the end of the string, but - * then masks off the part it's not allowed to read. Because the - * string is aligned, the masked-off tail is in the same word as the - * rest of the string. Every machine with memory protection I've seen - * does it on word boundaries, so is OK with this. But VALGRIND will - * still catch it and complain. The masking trick does make the hash - * noticably faster for short strings (like English words). - */ -#ifndef VALGRIND - - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; - case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; - case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=k[1]&0xffffff; a+=k[0]; break; - case 6 : b+=k[1]&0xffff; a+=k[0]; break; - case 5 : b+=k[1]&0xff; a+=k[0]; break; - case 4 : a+=k[0]; break; - case 3 : a+=k[0]&0xffffff; break; - case 2 : a+=k[0]&0xffff; break; - case 1 : a+=k[0]&0xff; break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ - } - -#else /* make valgrind happy */ - - k8 = (const uint8_t *)k; - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ - case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ - case 9 : c+=k8[8]; /* fall through */ - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ - case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ - case 5 : b+=k8[4]; /* fall through */ - case 4 : a+=k[0]; break; - case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ - case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ - case 1 : a+=k8[0]; break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ - } - -#endif /* !valgrind */ - - } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { - const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ - const uint8_t *k8; - - /*--------------- all but last block: aligned reads and different mixing */ - while (length > 12) - { - a += k[0] + (((uint32_t)k[1])<<16); - b += k[2] + (((uint32_t)k[3])<<16); - c += k[4] + (((uint32_t)k[5])<<16); - mix(a,b,c); - length -= 12; - k += 6; - } + ecs_set_name(world, entity, name); - /*----------------------------- handle the last (probably partial) block */ - k8 = (const uint8_t *)k; - switch(length) - { - case 12: c+=k[4]+(((uint32_t)k[5])<<16); - b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ - case 10: c+=k[4]; - b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 9 : c+=k8[8]; /* fall through */ - case 8 : b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ - case 6 : b+=k[2]; - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 5 : b+=k8[4]; /* fall through */ - case 4 : a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ - case 2 : a+=k[0]; - break; - case 1 : a+=k8[0]; - break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ - } - - } else { /* need to read the key one byte at a time */ - const uint8_t *k = (const uint8_t *)key; - - /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ - while (length > 12) - { - a += k[0]; - a += ((uint32_t)k[1])<<8; - a += ((uint32_t)k[2])<<16; - a += ((uint32_t)k[3])<<24; - b += k[4]; - b += ((uint32_t)k[5])<<8; - b += ((uint32_t)k[6])<<16; - b += ((uint32_t)k[7])<<24; - c += k[8]; - c += ((uint32_t)k[9])<<8; - c += ((uint32_t)k[10])<<16; - c += ((uint32_t)k[11])<<24; - mix(a,b,c); - length -= 12; - k += 12; - } - - /*-------------------------------- last block: affect all 32 bits of (c) */ - switch(length) /* all the case statements fall through */ - { - case 12: c+=((uint32_t)k[11])<<24; - case 11: c+=((uint32_t)k[10])<<16; - case 10: c+=((uint32_t)k[9])<<8; - case 9 : c+=k[8]; - case 8 : b+=((uint32_t)k[7])<<24; - case 7 : b+=((uint32_t)k[6])<<16; - case 6 : b+=((uint32_t)k[5])<<8; - case 5 : b+=k[4]; - case 4 : a+=((uint32_t)k[3])<<24; - case 3 : a+=((uint32_t)k[2])<<16; - case 2 : a+=((uint32_t)k[1])<<8; - case 1 : a+=k[0]; - break; - case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + if (defer_suspend) { + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_resume_readonly(real_world, &srs); + flecs_defer_path(stage, parent, entity, name); } - } - - final(a,b,c); - *pc=c; *pb=b; } -uint64_t flecs_hash( - const void *data, - ecs_size_t length) +ecs_entity_t ecs_add_path_w_sep( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix) { - uint32_t h_1 = 0; - uint32_t h_2 = 0; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_world_t *real_world = world; + if (flecs_poly_is(world, ecs_stage_t)) { + real_world = ecs_get_world(world); + } - hashlittle2( - data, - flecs_ito(size_t, length), - &h_1, - &h_2); + if (!sep) { + sep = "."; + } - return h_1 | ((uint64_t)h_2 << 32); -} + if (!path) { + if (!entity) { + entity = ecs_new(world); + } + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, entity); + } -void ecs_qsort( - void *base, - ecs_size_t nitems, - ecs_size_t size, - int (*compar)(const void *, const void*)) -{ - void *tmp = ecs_os_alloca(size); /* For swap */ + return entity; + } - #define LESS(i, j) \ - compar(ECS_ELEM(base, size, i), ECS_ELEM(base, size, j)) < 0 + bool root_path = flecs_is_root_path(path, prefix); + bool error = false; + parent = flecs_get_parent_from_path( + world, parent, &path, sep, prefix, !entity, &error); + if (error) { + /* Invalid id */ + ecs_err("invalid identifier: '%s'", path); + return 0; + } - #define SWAP(i, j) \ - ecs_os_memcpy(tmp, ECS_ELEM(base, size, i), size),\ - ecs_os_memcpy(ECS_ELEM(base, size, i), ECS_ELEM(base, size, j), size),\ - ecs_os_memcpy(ECS_ELEM(base, size, j), tmp, size) + char buff[ECS_NAME_BUFFER_LENGTH]; + const char *ptr = path; + char *elem = buff; + int32_t size = ECS_NAME_BUFFER_LENGTH; + + /* If we're in deferred/readonly mode suspend it, so that the name index is + * immediately updated. Without this, we could create multiple entities for + * the same name in a single command queue. */ + bool suspend_defer = ecs_is_deferred(world) && + !(real_world->flags & EcsWorldMultiThreaded); + + ecs_entity_t cur = parent; + char *name = NULL; - QSORT(nitems, LESS, SWAP); -} + if (sep[0]) { + while ((ptr = flecs_path_elem(ptr, sep, &elem, &size))) { + ecs_entity_t e = ecs_lookup_child(world, cur, elem); + if (!e) { + if (name) { + ecs_os_free(name); + } + name = ecs_os_strdup(elem); + /* If this is the last entity in the path, use the provided id */ + bool last_elem = false; + if (!flecs_path_elem(ptr, sep, NULL, NULL)) { + e = entity; + last_elem = true; + } -static -void ensure( - ecs_bitset_t *bs, - ecs_size_t size) -{ - if (!bs->size) { - int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); - bs->size = ((size - 1) / 64 + 1) * 64; - bs->data = ecs_os_calloc(new_size); - } else if (size > bs->size) { - int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); - bs->size = ((size - 1) / 64 + 1) * 64; - int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); - bs->data = ecs_os_realloc(bs->data, new_size); - ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); + if (!e) { + if (last_elem) { + ecs_entity_t prev = ecs_set_scope(world, 0); + e = ecs_entity(world, {0}); + ecs_set_scope(world, prev); + } else { + e = ecs_new(world); + } + } + + if (!cur && last_elem && root_path) { + ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); + } + + flecs_add_path(world, suspend_defer, cur, e, name); + } + + cur = e; + } + + if (entity && (cur != entity)) { + ecs_throw(ECS_ALREADY_DEFINED, "cannot assign name '%s' to " + "entity %u, name already used by entity '%s'", path, + (uint32_t)cur, flecs_errstr(ecs_get_path(world, entity))); + } + + if (name) { + ecs_os_free(name); + } + + if (elem != buff) { + ecs_os_free(elem); + } + } else { + flecs_add_path(world, suspend_defer, parent, entity, path); } + + return cur; +error: + return 0; } -void flecs_bitset_init( - ecs_bitset_t* bs) +ecs_entity_t ecs_new_from_path_w_sep( + ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix) { - bs->size = 0; - bs->count = 0; - bs->data = NULL; + return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); } -void flecs_bitset_ensure( - ecs_bitset_t *bs, - int32_t count) +static +const char* flecs_get_identifier( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) { - if (count > bs->count) { - bs->count = count; - ensure(bs, count); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + + const EcsIdentifier *ptr = ecs_get_pair( + world, entity, EcsIdentifier, tag); + + if (ptr) { + return ptr->value; + } else { + return NULL; } +error: + return NULL; } -void flecs_bitset_fini( - ecs_bitset_t *bs) +const char* ecs_get_name( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_os_free(bs->data); - bs->data = NULL; - bs->count = 0; + return flecs_get_identifier(world, entity, EcsName); } -void flecs_bitset_addn( - ecs_bitset_t *bs, - int32_t count) +const char* ecs_get_symbol( + const ecs_world_t *world, + ecs_entity_t entity) { - int32_t elem = bs->count += count; - ensure(bs, elem); + world = ecs_get_world(world); + if (ecs_owns_pair(world, entity, ecs_id(EcsIdentifier), EcsSymbol)) { + return flecs_get_identifier(world, entity, EcsSymbol); + } else { + return NULL; + } } -void flecs_bitset_set( - ecs_bitset_t *bs, - int32_t elem, - bool value) +ecs_entity_t flecs_set_identifier( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t tag, + const char *name) { - ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); - int32_t hi = elem >> 6; - int32_t lo = elem & 0x3F; - uint64_t v = bs->data[hi]; - bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); -error: - return; -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); -bool flecs_bitset_get( - const ecs_bitset_t *bs, - int32_t elem) -{ - ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); - return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); + if (!entity) { + entity = ecs_new(world); + } + + if (!name) { + ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); + return entity; + } + + EcsIdentifier *ptr = ecs_ensure_pair(world, entity, EcsIdentifier, tag); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (tag == EcsName) { + /* Insert command after ensure, but before the name is potentially + * freed. Even though the name is a const char*, it is possible that the + * application passed in the existing name of the entity which could + * still cause it to be freed. */ + flecs_defer_path(stage, 0, entity, name); + } + + char *old = ptr->value; + ptr->value = ecs_os_strdup(name); + + ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); + + /* Free old name after updating name index in on_set handler. */ + ecs_os_free(old); + + return entity; error: - return false; + return 0; } -int32_t flecs_bitset_count( - const ecs_bitset_t *bs) +ecs_entity_t ecs_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) { - return bs->count; + if (!entity) { + return ecs_entity(world, { + .name = name + }); + } + + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_set_identifier(world, stage, entity, EcsName, name); + + return entity; } -void flecs_bitset_remove( - ecs_bitset_t *bs, - int32_t elem) +ecs_entity_t ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) { - ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); - int32_t last = bs->count - 1; - bool last_value = flecs_bitset_get(bs, last); - flecs_bitset_set(bs, elem, last_value); - bs->count --; -error: - return; + return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); } -void flecs_bitset_swap( - ecs_bitset_t *bs, - int32_t elem_a, - int32_t elem_b) +void ecs_set_alias( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) { - ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); - ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); - - bool a = flecs_bitset_get(bs, elem_a); - bool b = flecs_bitset_get(bs, elem_b); - flecs_bitset_set(bs, elem_a, b); - flecs_bitset_set(bs, elem_b, a); -error: - return; + flecs_set_identifier(world, NULL, entity, EcsAlias, name); } -#include -#include - /** - * stm32tpl -- STM32 C++ Template Peripheral Library - * Visit https://github.com/antongus/stm32tpl for new versions - * - * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA + * @file id.c + * @brief Id utilities. */ -#define MAX_PRECISION (10) -#define EXP_THRESHOLD (3) -#define INT64_MAX_F ((double)INT64_MAX) -static const double rounders[MAX_PRECISION + 1] = -{ - 0.5, // 0 - 0.05, // 1 - 0.005, // 2 - 0.0005, // 3 - 0.00005, // 4 - 0.000005, // 5 - 0.0000005, // 6 - 0.00000005, // 7 - 0.000000005, // 8 - 0.0000000005, // 9 - 0.00000000005 // 10 -}; +#ifdef FLECS_QUERY_DSL +#endif -static -char* strbuf_itoa( - char *buf, - int64_t v) +bool ecs_id_match( + ecs_id_t id, + ecs_id_t pattern) { - char *ptr = buf; - char * p1; - char c; + if (id == pattern) { + return true; + } - if (!v) { - *ptr++ = '0'; - } else { - char *p = ptr; - while (v) { - *p++ = (char)('0' + v % 10); - v /= 10; - } + if (ECS_HAS_ID_FLAG(pattern, PAIR)) { + if (!ECS_HAS_ID_FLAG(id, PAIR)) { + return false; + } - p1 = p; + ecs_entity_t id_first = ECS_PAIR_FIRST(id); + ecs_entity_t id_second = ECS_PAIR_SECOND(id); + ecs_entity_t pattern_first = ECS_PAIR_FIRST(pattern); + ecs_entity_t pattern_second = ECS_PAIR_SECOND(pattern); - while (p > ptr) { - c = *--p; - *p = *ptr; - *ptr++ = c; - } - ptr = p1; - } - return ptr; -} + ecs_check(id_first != 0, ECS_INVALID_PARAMETER, + "first element of pair cannot be 0"); + ecs_check(id_second != 0, ECS_INVALID_PARAMETER, + "second element of pair cannot be 0"); -static -int ecs_strbuf_ftoa( - ecs_strbuf_t *out, - double f, - int precision, - char nan_delim) -{ - char buf[64]; - char * ptr = buf; - char c; - int64_t intPart; - int64_t exp = 0; + ecs_check(pattern_first != 0, ECS_INVALID_PARAMETER, + "first element of pair cannot be 0"); + ecs_check(pattern_second != 0, ECS_INVALID_PARAMETER, + "second element of pair cannot be 0"); - if (isnan(f)) { - if (nan_delim) { - ecs_strbuf_appendch(out, nan_delim); - ecs_strbuf_appendstr(out, "NaN"); - return ecs_strbuf_appendch(out, nan_delim); - } else { - return ecs_strbuf_appendstr(out, "NaN"); + bool pattern_first_wildcard = pattern_first == EcsWildcard; + bool pattern_second_wc = pattern_second == EcsWildcard; + if (ECS_IS_VALUE_PAIR(pattern)) { + if (!ECS_IS_VALUE_PAIR(id)) { + return false; + } + + pattern_first_wildcard = false; + pattern_second_wc = false; } - } - if (isinf(f)) { - if (nan_delim) { - ecs_strbuf_appendch(out, nan_delim); - ecs_strbuf_appendstr(out, "Inf"); - return ecs_strbuf_appendch(out, nan_delim); - } else { - return ecs_strbuf_appendstr(out, "Inf"); + + if (pattern_first_wildcard) { + if (pattern_second_wc || pattern_second == id_second) { + return true; + } + } else if (pattern_first == EcsFlag) { + /* Used for internals, helps to keep track of which ids are used in + * pairs that have additional flags (like OVERRIDE and TOGGLE) */ + if (ECS_HAS_ID_FLAG(id, PAIR) && !ECS_IS_PAIR(id)) { + if (ECS_PAIR_FIRST(id) == pattern_second) { + return true; + } + if (ECS_PAIR_SECOND(id) == pattern_second) { + return true; + } + } + } else if (pattern_second == EcsWildcard) { + if (pattern_first == id_first) { + return true; + } + } + } else { + if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { + return false; } - } - if (precision > MAX_PRECISION) { - precision = MAX_PRECISION; + if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { + return true; + } } - if (f < 0) { - f = -f; - *ptr++ = '-'; - } +error: + return false; +} - if (precision < 0) { - if (f < 1.0) precision = 6; - else if (f < 10.0) precision = 5; - else if (f < 100.0) precision = 4; - else if (f < 1000.0) precision = 3; - else if (f < 10000.0) precision = 2; - else if (f < 100000.0) precision = 1; - else precision = 0; - } +bool ecs_id_is_pair( + ecs_id_t id) +{ + return ECS_HAS_ID_FLAG(id, PAIR); +} - if (precision) { - f += rounders[precision]; +bool ecs_id_is_wildcard( + ecs_id_t id) +{ + if ((id == EcsWildcard) || (id == EcsAny)) { + return true; } - /* Make sure that number can be represented as 64bit int, increase exp */ - while (f > INT64_MAX_F) { - f /= 1000 * 1000 * 1000; - exp += 9; + bool is_pair = ECS_IS_PAIR(id); + if (!is_pair) { + return false; } - intPart = (int64_t)f; - f -= (double)intPart; + ecs_entity_t first = ECS_PAIR_FIRST(id); + if (ECS_IS_VALUE_PAIR(id)) { + return (first == EcsWildcard) || (first == EcsAny); + } - ptr = strbuf_itoa(ptr, intPart); + ecs_entity_t second = ECS_PAIR_SECOND(id); - if (precision) { - *ptr++ = '.'; - while (precision--) { - f *= 10.0; - c = (char)f; - *ptr++ = (char)('0' + c); - f -= c; - } - } - *ptr = 0; + return (first == EcsWildcard) || (second == EcsWildcard) || + (first == EcsAny) || (second == EcsAny); +} - /* Remove trailing 0s */ - while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { - ptr[-1] = '\0'; - ptr --; - } - if (ptr != buf && ptr[-1] == '.') { - ptr[-1] = '\0'; - ptr --; +bool ecs_id_is_any( + ecs_id_t id) +{ + if (id == EcsAny) { + return true; } - /* If 0s before . exceed threshold, convert to exponent to save space - * without losing precision. */ - char *cur = ptr; - while ((&cur[-1] != buf) && (cur[-1] == '0')) { - cur --; + bool is_pair = ECS_IS_PAIR(id); + if (!is_pair) { + return false; } - if (exp || ((ptr - cur) > EXP_THRESHOLD)) { - cur[0] = '\0'; - exp += (ptr - cur); - ptr = cur; - } + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_entity_t second = ECS_PAIR_SECOND(id); - if (exp) { - char *p1 = &buf[1]; - if (nan_delim) { - ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); - buf[0] = nan_delim; - p1 ++; - } + return (first == EcsAny) || (second == EcsAny); +} - /* Make sure that exp starts after first character */ - c = p1[0]; +const char* flecs_id_invalid_reason( + const ecs_world_t *world, + ecs_id_t id) +{ + if (!id) { + return "components cannot be 0 (is the component registered?)"; + } + if (ecs_id_is_wildcard(id)) { + return "cannot add wildcards"; + } - if (c) { - p1[0] = '.'; - do { - char t = (++p1)[0]; - p1[0] = c; - c = t; - exp ++; - } while (c); - ptr = p1 + 1; - } else { - ptr = p1; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + if (!ECS_PAIR_FIRST(id) && !ECS_PAIR_SECOND(id)) { + return "invalid pair: both elements are 0"; } - - - ptr[0] = 'e'; - ptr = strbuf_itoa(ptr + 1, exp); - - if (nan_delim) { - ptr[0] = nan_delim; - ptr ++; + if (!ECS_PAIR_FIRST(id)) { + return "invalid pair: first element is 0 (is the relationship registered?)"; + } + if (!ECS_PAIR_SECOND(id)) { + return "invalid pair: second element is 0"; + } + } else if (ECS_HAS_ID_FLAG(id, VALUE_PAIR)) { + if (!ECS_PAIR_FIRST(id)) { + return "invalid value pair: first element is 0 (is the relationship registered?)"; + } + } else if (id & ECS_ID_FLAGS_MASK) { + if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { + ecs_abort(ECS_INTERNAL_ERROR, NULL); } - - ptr[0] = '\0'; } - - return ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); -} -/* Add an extra element to the buffer */ -static -void ecs_strbuf_grow( - ecs_strbuf_t *b) -{ - /* Allocate new element */ - ecs_strbuf_element_embedded *e = ecs_os_malloc_t(ecs_strbuf_element_embedded); - b->size += b->current->pos; - b->current->next = (ecs_strbuf_element*)e; - b->current = (ecs_strbuf_element*)e; - b->elementCount ++; - e->super.buffer_embedded = true; - e->super.buf = e->buf; - e->super.pos = 0; - e->super.next = NULL; + return NULL; } -/* Add an extra dynamic element */ -static -void ecs_strbuf_grow_str( - ecs_strbuf_t *b, - char *str, - char *alloc_str, - int32_t size) +bool ecs_id_is_valid( + const ecs_world_t *world, + ecs_id_t id) { - /* Allocate new element */ - ecs_strbuf_element_str *e = ecs_os_malloc_t(ecs_strbuf_element_str); - b->size += b->current->pos; - b->current->next = (ecs_strbuf_element*)e; - b->current = (ecs_strbuf_element*)e; - b->elementCount ++; - e->super.buffer_embedded = false; - e->super.pos = size ? size : (int32_t)ecs_os_strlen(str); - e->super.next = NULL; - e->super.buf = str; - e->alloc_str = alloc_str; + return flecs_id_invalid_reason(world, id) == NULL; } -static -char* ecs_strbuf_ptr( - ecs_strbuf_t *b) +ecs_flags32_t ecs_id_get_flags( + const ecs_world_t *world, + ecs_id_t id) { - if (b->buf) { - return &b->buf[b->current->pos]; + ecs_component_record_t *cr = flecs_components_get(world, id); + if (cr) { + return cr->flags; } else { - return &b->current->buf[b->current->pos]; + return 0; } } -/* Compute the amount of space left in the current element */ -static -int32_t ecs_strbuf_memLeftInCurrentElement( - ecs_strbuf_t *b) +ecs_id_t ecs_id_from_str( + const ecs_world_t *world, + const char *expr) { - if (b->current->buffer_embedded) { - return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; - } else { +#ifdef FLECS_QUERY_DSL + ecs_id_t result; + + /* Temporarily disable parser logging */ + int prev_level = ecs_log_set_level(-3); + if (!flecs_id_parse(world, NULL, expr, &result)) { + /* Invalid expression */ + ecs_log_set_level(prev_level); return 0; } + ecs_log_set_level(prev_level); + return result; +#else + (void)world; + (void)expr; + ecs_abort(ECS_UNSUPPORTED, + "ecs_id_from_str requires FLECS_QUERY_DSL addon"); +#endif } -/* Compute the amount of space left */ -static -int32_t ecs_strbuf_memLeft( - ecs_strbuf_t *b) +const char* ecs_id_flag_str( + uint64_t entity) { - if (b->max) { - return b->max - b->size - b->current->pos; + if (ECS_IS_VALUE_PAIR(entity)) { + return "VALUE_PAIR"; + } else + if (ECS_HAS_ID_FLAG(entity, PAIR)) { + return "PAIR"; + } else + if (ECS_HAS_ID_FLAG(entity, TOGGLE)) { + return "TOGGLE"; + } else + if (ECS_HAS_ID_FLAG(entity, AUTO_OVERRIDE)) { + return "AUTO_OVERRIDE"; } else { - return INT_MAX; + return "UNKNOWN"; } } -static -void ecs_strbuf_init( - ecs_strbuf_t *b) +void ecs_id_str_buf( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) { - /* Initialize buffer structure only once */ - if (!b->elementCount) { - b->size = 0; - b->firstElement.super.next = NULL; - b->firstElement.super.pos = 0; - b->firstElement.super.buffer_embedded = true; - b->firstElement.super.buf = b->firstElement.buf; - b->elementCount ++; - b->current = (ecs_strbuf_element*)&b->firstElement; - } -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); -/* Append a format string to a buffer */ -static -bool vappend( - ecs_strbuf_t *b, - const char* str, - va_list args) -{ - bool result = true; - va_list arg_cpy; + world = ecs_get_world(world); - if (!str) { - return result; + if (ECS_HAS_ID_FLAG(id, TOGGLE)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); + ecs_strbuf_appendch(buf, '|'); } - ecs_strbuf_init(b); + if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AUTO_OVERRIDE)); + ecs_strbuf_appendch(buf, '|'); + } - int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = ecs_strbuf_memLeft(b); + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ECS_PAIR_SECOND(id); - if (!memLeft) { - return false; - } + ecs_entity_t e; + if ((e = ecs_get_alive(world, rel))) { + rel = e; + } - /* Compute the memory required to add the string to the buffer. If user - * provided buffer, use space left in buffer, otherwise use space left in - * current element. */ - int32_t max_copy = b->buf ? memLeft : memLeftInElement; - int32_t memRequired; + ecs_strbuf_appendch(buf, '('); + ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf, false); + ecs_strbuf_appendch(buf, ','); - va_copy(arg_cpy, args); - memRequired = vsnprintf( - ecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); + if (ECS_IS_VALUE_PAIR(id)) { + ecs_strbuf_appendlit(buf, "@"); + ecs_strbuf_appendint(buf, (uint32_t)tgt); + } else { + if ((e = ecs_get_alive(world, tgt))) { + tgt = e; + } + ecs_get_path_w_sep_buf(world, 0, tgt, NULL, NULL, buf, false); + } - ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); + ecs_strbuf_appendch(buf, ')'); + } else { + ecs_entity_t e = id & ECS_COMPONENT_MASK; + ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf, false); + } - if (memRequired <= memLeftInElement) { - /* Element was large enough to fit string */ - b->current->pos += memRequired; - } else if ((memRequired - memLeftInElement) < memLeft) { - /* If string is a format string, a new buffer of size memRequired is - * needed to re-evaluate the format string and only use the part that - * wasn't already copied to the previous element */ - if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) { - /* Resulting string fits in standard-size buffer. Note that the - * entire string needs to fit, not just the remainder, as the - * format string cannot be partially evaluated */ - ecs_strbuf_grow(b); +error: + return; +} - /* Copy entire string to new buffer */ - ecs_os_vsprintf(ecs_strbuf_ptr(b), str, arg_cpy); +char* ecs_id_str( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_id_str_buf(world, id, &buf); + return ecs_strbuf_get(&buf); +} - /* Ignore the part of the string that was copied into the - * previous buffer. The string copied into the new buffer could - * be memmoved so that only the remainder is left, but that is - * most likely more expensive than just keeping the entire - * string. */ +ecs_id_t ecs_make_pair( + ecs_entity_t relationship, + ecs_entity_t target) +{ + ecs_assert(!ECS_IS_PAIR(relationship) && !ECS_IS_PAIR(target), + ECS_INVALID_PARAMETER, "cannot create nested pairs"); + return ecs_pair(relationship, target); +} - /* Update position in buffer */ - b->current->pos += memRequired; - } else { - /* Resulting string does not fit in standard-size buffer. - * Allocate a new buffer that can hold the entire string. */ - char *dst = ecs_os_malloc(memRequired + 1); - ecs_os_vsprintf(dst, str, arg_cpy); - ecs_strbuf_grow_str(b, dst, dst, memRequired); +bool ecs_id_is_tag( + const ecs_world_t *world, + ecs_id_t id) +{ + if (ecs_id_is_wildcard(id)) { + /* If id is a wildcard, we can't tell if it's a tag or not, except + * when the relationship part of a pair has the PairIsTag property */ + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t first = ECS_PAIR_FIRST(id); + if (first != EcsWildcard && first != EcsAny) { + ecs_entity_t rel = ecs_pair_first(world, id); + if (ecs_is_valid(world, rel)) { + if (ecs_has_id(world, rel, EcsPairIsTag)) { + return true; + } + } else { + /* During bootstrap it's possible that not all ids are valid + * yet. Using ecs_get_typeid will ensure correct values are + * returned for only those components initialized during + * bootstrap, while still asserting if another invalid id + * is provided. */ + if (ecs_get_typeid(world, id) == 0) { + return true; + } + } + } else { + /* If relationship is wildcard id is not guaranteed to be a tag */ + } + } + } else { + if (ecs_get_typeid(world, id) == 0) { + return true; } } - va_end(arg_cpy); - - return ecs_strbuf_memLeft(b) > 0; + return false; } -static -bool appendstr( - ecs_strbuf_t *b, - const char* str, - int n) +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t id) { - ecs_strbuf_init(b); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (ti) { + ecs_assert(ti->component != 0, ECS_INTERNAL_ERROR, NULL); + return ti->component; + } +error: + return 0; +} - int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = ecs_strbuf_memLeft(b); - if (memLeft <= 0) { +bool ecs_id_in_use( + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { return false; } - /* Never write more than what the buffer can store */ - if (n > memLeft) { - n = memLeft; - } + return (flecs_table_cache_count(&cr->cache) != 0); +} + +/** + * @file instantiate.c + * @brief Functions for instantiating prefabs (IsA relationship). + */ - if (n <= memLeftInElement) { - /* Element was large enough to fit string */ - ecs_os_strncpy(ecs_strbuf_ptr(b), str, n); - b->current->pos += n; - } else if ((n - memLeftInElement) < memLeft) { - ecs_os_strncpy(ecs_strbuf_ptr(b), str, memLeftInElement); - /* Element was not large enough, but buffer still has space */ - b->current->pos += memLeftInElement; - n -= memLeftInElement; +static +void flecs_instantiate_slot( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t instance, + ecs_entity_t slot_of, + ecs_entity_t slot, + ecs_entity_t child) +{ + if (base == slot_of) { + /* Instance inherits from slot_of, add slot to instance */ + ecs_component_record_t *cr = flecs_components_ensure( + world, ecs_pair(slot, child)); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *r = flecs_entities_get(world, instance); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_sparse_on_add_cr(world, + r->table, ECS_RECORD_TO_ROW(r->row), cr, true, NULL); + } else { + /* Slot is registered for other prefab, travel hierarchy + * upwards to find instance that inherits from slot_of */ + ecs_entity_t parent = instance; + int32_t depth = 0; + do { + if (ecs_has_pair(world, parent, EcsIsA, slot_of)) { + const char *name = ecs_get_name(world, slot); + if (name == NULL) { + char *slot_of_str = ecs_get_path(world, slot_of); + ecs_throw(ECS_INVALID_OPERATION, "prefab '%s' has unnamed " + "slot (slots must be named)", slot_of_str); + ecs_os_free(slot_of_str); + return; + } + + /* The 'slot' variable is currently pointing to a child (or + * grandchild) of the current base. Find the original slot by + * looking it up under the prefab it was registered. */ + if (depth == 0) { + /* If the current instance is an instance of slot_of, just + * lookup the slot by name, which is faster than having to + * create a relative path. */ + slot = ecs_lookup_child(world, slot_of, name); + } else { + /* If the slot is more than one level away from the slot_of + * parent, use a relative path to find the slot */ + char *path = ecs_get_path_w_sep(world, parent, child, ".", + NULL); + slot = ecs_lookup_path_w_sep(world, slot_of, path, ".", + NULL, false); + ecs_os_free(path); + } - /* Current element was too small, copy remainder into new element */ - if (n < ECS_STRBUF_ELEMENT_SIZE) { - /* A standard-size buffer is large enough for the new string */ - ecs_strbuf_grow(b); + if (slot == 0) { + char *slot_of_str = ecs_get_path(world, slot_of); + char *slot_str = ecs_get_path(world, slot); + ecs_throw(ECS_INVALID_OPERATION, + "'%s' is not in hierarchy for slot '%s'", + slot_of_str, slot_str); + ecs_os_free(slot_of_str); + ecs_os_free(slot_str); + } - /* Copy the remainder to the new buffer */ - if (n) { - /* If a max number of characters to write is set, only a - * subset of the string should be copied to the buffer */ - ecs_os_strncpy( - ecs_strbuf_ptr(b), - str + memLeftInElement, - (size_t)n); - } else { - ecs_os_strcpy(ecs_strbuf_ptr(b), str + memLeftInElement); + ecs_add_pair(world, parent, slot, child); + break; } - /* Update to number of characters copied to new buffer */ - b->current->pos += n; - } else { - /* String doesn't fit in a single element, strdup */ - char *remainder = ecs_os_strdup(str + memLeftInElement); - ecs_strbuf_grow_str(b, remainder, remainder, n); + depth ++; + } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); + + if (parent == 0) { + char *slot_of_str = ecs_get_path(world, slot_of); + char *slot_str = ecs_get_path(world, slot); + ecs_throw(ECS_INVALID_OPERATION, + "'%s' is not in hierarchy for slot '%s'", + slot_of_str, slot_str); + ecs_os_free(slot_of_str); + ecs_os_free(slot_str); } - } else { - /* Buffer max has been reached */ - return false; } - return ecs_strbuf_memLeft(b) > 0; +error: + return; } static -bool appendch( - ecs_strbuf_t *b, - char ch) +int32_t flecs_child_type_insert( + ecs_type_t *type, + void **component_data, + ecs_id_t id) { - ecs_strbuf_init(b); + int32_t i, count = type->count; + for (i = 0; i < count; i ++) { + ecs_id_t cur = type->array[i]; + if (cur == id) { + /* Id is already part of type */ + return -1; + } - int32_t memLeftInElement = ecs_strbuf_memLeftInCurrentElement(b); - int32_t memLeft = ecs_strbuf_memLeft(b); - if (memLeft <= 0) { - return false; + if (cur > id) { + /* A larger id was found so id can't be part of the type. */ + break; + } } - if (memLeftInElement) { - /* Element was large enough to fit string */ - ecs_strbuf_ptr(b)[0] = ch; - b->current->pos ++; - } else { - ecs_strbuf_grow(b); - ecs_strbuf_ptr(b)[0] = ch; - b->current->pos ++; + /* Assumes that the array has enough memory to store the new element. */ + int32_t to_move = type->count - i; + if (to_move) { + ecs_os_memmove(&type->array[i + 1], + &type->array[i], to_move * ECS_SIZEOF(ecs_id_t)); + ecs_os_memmove(&component_data[i + 1], + &component_data[i], to_move * ECS_SIZEOF(void*)); } - return ecs_strbuf_memLeft(b) > 0; -} + component_data[i] = NULL; + type->array[i] = id; + type->count ++; -bool ecs_strbuf_vappend( - ecs_strbuf_t *b, - const char* fmt, - va_list args) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); - return vappend(b, fmt, args); + return i; } -bool ecs_strbuf_append( - ecs_strbuf_t *b, - const char* fmt, - ...) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); +void flecs_instantiate_sparse( + ecs_world_t *world, + const ecs_table_range_t *base_child_range, + const ecs_entity_t *base_children, + ecs_table_t *instance_table, + const ecs_entity_t *instance_children, + int32_t row_offset) +{ + ecs_table_t *base_child_table = base_child_range->table; + if (!(base_child_table->flags & EcsTableHasSparse)) { + return; + } - va_list args; - va_start(args, fmt); - bool result = vappend(b, fmt, args); - va_end(args); + ecs_table_record_t *trs = base_child_table->_->records; + int32_t i, count = base_child_table->type.count; + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &trs[i]; + ecs_component_record_t *cr = tr->hdr.cr; - return result; -} + if (!(cr->flags & EcsIdSparse)) { + continue; + } -bool ecs_strbuf_appendstrn( - ecs_strbuf_t *b, - const char* str, - int32_t len) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - return appendstr(b, str, len); -} + const ecs_type_info_t *ti = cr->type_info; + if (!ti) { + continue; + } -bool ecs_strbuf_appendch( - ecs_strbuf_t *b, - char ch) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return appendch(b, ch); -} + ecs_id_t id = base_child_table->type.array[i]; -bool ecs_strbuf_appendflt( - ecs_strbuf_t *b, - double flt, - char nan_delim) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return ecs_strbuf_ftoa(b, flt, 10, nan_delim); -} + for (int32_t j = 0; j < base_child_range->count; j ++) { + ecs_entity_t child = base_children[j + base_child_range->offset]; + ecs_entity_t instance_child = instance_children[j]; -bool ecs_strbuf_appendstr_zerocpy( - ecs_strbuf_t *b, - char* str) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_strbuf_init(b); - ecs_strbuf_grow_str(b, str, str, 0); - return true; -} + void *src_ptr = flecs_sparse_get(cr->sparse, ti->size, child); + ecs_assert(src_ptr != NULL, ECS_INTERNAL_ERROR, NULL); -bool ecs_strbuf_appendstr_zerocpy_const( - ecs_strbuf_t *b, - const char* str) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - /* Removes const modifier, but logic prevents changing / delete string */ - ecs_strbuf_init(b); - ecs_strbuf_grow_str(b, (char*)str, NULL, 0); - return true; -} + void *dst_ptr = flecs_sparse_get(cr->sparse, ti->size, instance_child); + ecs_assert(dst_ptr != NULL, ECS_INTERNAL_ERROR, NULL); -bool ecs_strbuf_appendstr( - ecs_strbuf_t *b, - const char* str) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - return appendstr(b, str, ecs_os_strlen(str)); + if (ti->hooks.copy) { + ti->hooks.copy(dst_ptr, src_ptr, 1, ti); + } else { + ecs_os_memcpy(dst_ptr, src_ptr, ti->size); + } + + flecs_notify_on_set( + world, instance_table, row_offset + j, id, true); + } + } } -bool ecs_strbuf_mergebuff( - ecs_strbuf_t *dst_buffer, - ecs_strbuf_t *src_buffer) +static +void flecs_instantiate_children( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t instance, + ecs_table_range_t child_range, + const ecs_instantiate_ctx_t *ctx) { - if (src_buffer->elementCount) { - if (src_buffer->buf) { - return ecs_strbuf_appendstr(dst_buffer, src_buffer->buf); - } else { - ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement; + if (!child_range.count) { + return; + } + + ecs_table_t *child_table = child_range.table; + ecs_type_t type = child_table->type; + + ecs_entity_t slot_of = 0; + ecs_entity_t *ids = type.array; + int32_t type_count = type.count; + + ecs_record_t *r = flecs_entities_get(world, instance); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Instantiate child table for each instance */ + + /* Create component array for creating the table */ + ecs_table_diff_t diff = { .added = {0}}; + diff.added.array = ecs_os_alloca_n(ecs_entity_t, type_count + 1); + void **component_data = ecs_os_alloca_n(void*, type_count + 1); - /* Copy first element as it is inlined in the src buffer */ - ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); + /* Copy in component identifiers. Find the base index in the component + * array, since we'll need this to replace the base with the instance id */ + int j, i, childof_base_index = -1; + for (i = 0; i < type_count; i ++) { + ecs_id_t id = ids[i]; - while ((e = e->next)) { - dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); - *dst_buffer->current->next = *e; + /* If id has DontInherit flag don't inherit it, except for the name + * and ChildOf pairs. The name is preserved so applications can lookup + * the instantiated children by name. The ChildOf pair is replaced later + * with the instance parent. */ + if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && + ECS_PAIR_FIRST(id) != EcsChildOf) + { + ecs_table_record_t *tr = &child_table->_->records[i]; + ecs_component_record_t *cr = tr->hdr.cr; + if (cr->flags & EcsIdOnInstantiateDontInherit) { + continue; } } - *src_buffer = ECS_STRBUF_INIT; - } - - return true; -} + /* If child is a slot, keep track of which parent to add it to, but + * don't add slot relationship to child of instance. If this is a child + * of a prefab, keep the SlotOf relationship intact. */ + if (!(table->flags & EcsTableIsPrefab)) { + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsSlotOf) { + ecs_assert(slot_of == 0, ECS_INTERNAL_ERROR, NULL); + slot_of = ecs_pair_second(world, id); + continue; + } + } -char* ecs_strbuf_get( - ecs_strbuf_t *b) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + /* Keep track of the element that creates the ChildOf relationship with + * the prefab parent. We need to replace this element to make sure the + * created children point to the instance and not the prefab */ + if (ECS_HAS_RELATION(id, EcsChildOf) && + (ECS_PAIR_SECOND(id) == (uint32_t)base)) { + childof_base_index = diff.added.count; + } + + /* If this is a pure override, make sure we have a concrete version of the + * component. This relies on the fact that overrides always come after + * concrete components in the table type so we can check the components + * that have already been added to the child table type. */ + if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { + ecs_id_t concreteId = id & ~ECS_AUTO_OVERRIDE; + flecs_child_type_insert(&diff.added, component_data, concreteId); + continue; + } - char* result = NULL; - if (b->elementCount) { - if (b->buf) { - b->buf[b->current->pos] = '\0'; - result = ecs_os_strdup(b->buf); + int32_t column = ecs_table_type_to_column_index(child_table, i); + if (column != -1) { + component_data[diff.added.count] = ecs_table_get_column( + child_table, column, child_range.offset); } else { - void *next = NULL; - int32_t len = b->size + b->current->pos + 1; - - ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; + component_data[diff.added.count] = NULL; + } - result = ecs_os_malloc(len); - char* ptr = result; + diff.added.array[diff.added.count] = id; + diff.added.count ++; + diff.added_flags |= flecs_id_flags_get(world, id); + } - do { - ecs_os_memcpy(ptr, e->buf, e->pos); - ptr += e->pos; - next = e->next; - if (e != &b->firstElement.super) { - if (!e->buffer_embedded) { - ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str); - } - ecs_os_free(e); - } - } while ((e = next)); + /* Table must contain children of base */ + ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); - result[len - 1] = '\0'; - b->length = len; + /* If children are added to a prefab, make sure they are prefabs too */ + if (table->flags & EcsTableIsPrefab) { + if (flecs_child_type_insert( + &diff.added, component_data, EcsPrefab) != -1) + { + childof_base_index ++; } - } else { - result = NULL; } - b->elementCount = 0; + /* Instantiate the prefab child table for each new instance */ + ecs_entity_t *child_ids = flecs_walloc_n( + world, ecs_entity_t, child_range.count); + ecs_table_t *i_table = NULL; - b->content = result; + /* Replace ChildOf element in the component array with instance id */ + diff.added.array[childof_base_index] = ecs_pair(EcsChildOf, instance); - return result; -} + /* Find or create table */ + i_table = flecs_table_find_or_create(world, &diff.added); -char *ecs_strbuf_get_small( - ecs_strbuf_t *b) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(i_table->type.count == diff.added.count, + ECS_INTERNAL_ERROR, NULL); - int32_t written = ecs_strbuf_written(b); - ecs_assert(written <= ECS_STRBUF_ELEMENT_SIZE, ECS_INVALID_OPERATION, NULL); - char *buf = b->firstElement.buf; - buf[written] = '\0'; - return buf; -} + /* The instance is trying to instantiate from a base that is also + * its parent. This would cause the hierarchy to instantiate itself + * which would cause infinite recursion. */ + const ecs_entity_t *children = ecs_table_entities(child_table); -void ecs_strbuf_reset( - ecs_strbuf_t *b) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); +#ifdef FLECS_DEBUG + for (j = 0; j < child_range.count; j ++) { + ecs_entity_t child = children[j + child_range.offset]; + ecs_check(child != instance, ECS_INVALID_PARAMETER, + "cycle detected in IsA relationship"); + } +#else + /* Bit of boilerplate to ensure that we don't get warnings about the + * error label not being used. */ + ecs_check(true, ECS_INVALID_OPERATION, NULL); +#endif - if (b->elementCount && !b->buf) { - void *next = NULL; - ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; - do { - next = e->next; - if (e != (ecs_strbuf_element*)&b->firstElement) { - ecs_os_free(e); - } - } while ((e = next)); + /* Attempt to reserve ids for children that have the same offset from + * the instance as from the base prefab. This ensures stable ids for + * instance children, even across networked applications. */ + ecs_instantiate_ctx_t ctx_cur = {base, instance}; + if (ctx) { + ctx_cur = *ctx; } - *b = ECS_STRBUF_INIT; -} + for (j = 0; j < child_range.count; j ++) { + ecs_entity_t prefab_child = children[j + child_range.offset]; + if ((uint32_t)prefab_child < (uint32_t)ctx_cur.root_prefab) { + /* Child id is smaller than root prefab id, can't use offset */ + child_ids[j] = flecs_new_id(world); + continue; + } -void ecs_strbuf_list_push( - ecs_strbuf_t *b, - const char *list_open, - const char *separator) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); + /* Get prefab offset, ignore lifecycle generation count */ + ecs_entity_t prefab_offset = + (uint32_t)prefab_child - (uint32_t)ctx_cur.root_prefab; + ecs_assert(prefab_offset != 0, ECS_INTERNAL_ERROR, NULL); - b->list_sp ++; - b->list_stack[b->list_sp].count = 0; - b->list_stack[b->list_sp].separator = separator; + /* First check if any entity with the desired id exists */ + ecs_entity_t instance_child = (uint32_t)ctx_cur.root_instance + prefab_offset; + ecs_entity_t alive_id = flecs_entities_get_alive(world, instance_child); + if (alive_id && flecs_entities_is_alive(world, alive_id)) { + /* Alive entity with requested id exists, can't use offset id */ + child_ids[j] = flecs_new_id(world); + continue; + } - if (list_open) { - ecs_strbuf_appendstr(b, list_open); + /* Id is not in use. Make it alive & match the generation of the instance. */ + instance_child = ctx_cur.root_instance + prefab_offset; + flecs_entities_make_alive(world, instance_child); + flecs_entities_ensure(world, instance_child); + ecs_assert(ecs_is_alive(world, instance_child), ECS_INTERNAL_ERROR, NULL); + + child_ids[j] = instance_child; } -} -void ecs_strbuf_list_pop( - ecs_strbuf_t *b, - const char *list_close) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); + /* Create children */ + int32_t child_row; + diff.added_flags |= EcsTableEdgeReparent; + const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, child_ids, + &diff.added, child_range.count, component_data, false, &child_row, &diff); - b->list_sp --; - - if (list_close) { - ecs_strbuf_appendstr(b, list_close); - } -} + flecs_instantiate_sparse( + world, &child_range, children, i_table, i_children, child_row); -void ecs_strbuf_list_next( - ecs_strbuf_t *b) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + /* If children are slots, add slot relationships to parent */ + if (slot_of) { + for (j = 0; j < child_range.count; j ++) { + ecs_entity_t child = children[j + child_range.offset]; + ecs_entity_t i_child = i_children[j]; + flecs_instantiate_slot( + world, base, instance, slot_of, child, i_child); + } + } - int32_t list_sp = b->list_sp; - if (b->list_stack[list_sp].count != 0) { - ecs_strbuf_appendstr(b, b->list_stack[list_sp].separator); + /* If prefab child table has children itself, recursively instantiate */ + for (j = 0; j < child_range.count; j ++) { + ecs_entity_t child = children[j + child_range.offset]; + flecs_instantiate(world, child, i_children[j], &ctx_cur); } - b->list_stack[list_sp].count ++; + + flecs_wfree_n(world, ecs_entity_t, child_range.count, child_ids); +error: + return; } -bool ecs_strbuf_list_append( - ecs_strbuf_t *b, - const char *fmt, - ...) +void flecs_instantiate_dont_fragment( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t instance) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_component_record_t *cur = world->cr_non_fragmenting_head; - ecs_strbuf_list_next(b); + while (cur) { + ecs_assert(cur->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); + if (cur->sparse && !(cur->flags & EcsIdOnInstantiateInherit) && + !ecs_id_is_wildcard(cur->id)) + { + if (flecs_component_sparse_has(cur, base)) { + void *base_ptr = flecs_component_sparse_get( + world, cur, NULL, base); + const ecs_type_info_t *ti = cur->type_info; - va_list args; - va_start(args, fmt); - bool result = vappend(b, fmt, args); - va_end(args); + ecs_record_t *r = flecs_entities_get(world, instance); - return result; -} + void *ptr = NULL; + flecs_sparse_on_add_cr(world, + r->table, ECS_RECORD_TO_ROW(r->row), cur, true, &ptr); -bool ecs_strbuf_list_appendstr( - ecs_strbuf_t *b, - const char *str) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + if (ti) { + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (ti->hooks.copy) { + ti->hooks.copy(ptr, base_ptr, 1, ti); + } else { + ecs_os_memcpy(ptr, base_ptr, ti->size); + } + } - ecs_strbuf_list_next(b); - return ecs_strbuf_appendstr(b, str); + if (ti) { + flecs_notify_on_set( + world, r->table, ECS_RECORD_TO_ROW(r->row), + cur->id, true); + } + } + } + + cur = cur->non_fragmenting.next; + } } -int32_t ecs_strbuf_written( - const ecs_strbuf_t *b) +static +void flecs_instantiate_override_dont_fragment( + ecs_world_t *world, + ecs_table_t *base_table, + ecs_entity_t instance) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return b->size + b->current->pos; -} + int32_t i, type_count = base_table->type.count; + for (i = 0; i < type_count; i ++) { + ecs_id_t id = base_table->type.array[i]; + if (!(id & ECS_AUTO_OVERRIDE)) { + continue; + } -#include + id &= ~ECS_AUTO_OVERRIDE; -/* The ratio used to determine whether the map should rehash. If - * (element_count * LOAD_FACTOR) > bucket_count, bucket count is increased. */ -#define LOAD_FACTOR (1.5f) -#define KEY_SIZE (ECS_SIZEOF(ecs_map_key_t)) -#define GET_ELEM(array, elem_size, index) \ - ECS_OFFSET(array, (elem_size) * (index)) + ecs_flags32_t flags = flecs_component_get_flags(world, id); + if (!(flags & EcsIdDontFragment)) { + continue; + } -static -uint8_t ecs_log2(uint32_t v) { - static const uint8_t log2table[32] = - {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, - 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; - - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; + ecs_add_id(world, instance, id); + } } -/* Get bucket count for number of elements */ -static -int32_t get_bucket_count( - int32_t element_count) +void flecs_instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_entity_t instance, + const ecs_instantiate_ctx_t *ctx) { - return flecs_next_pow_of_2((int32_t)((float)element_count * LOAD_FACTOR)); + ecs_record_t *record = flecs_entities_get_any(world, base); + ecs_table_t *base_table = record->table; + ecs_assert(base_table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (base_table->flags & EcsTableOverrideDontFragment) { + flecs_instantiate_override_dont_fragment( + world, base_table, instance); + } + + /* If base has non-fragmenting components, add to instance */ + if (record->row & EcsEntityHasDontFragment) { + flecs_instantiate_dont_fragment(world, base, instance); + } + + if (!(base_table->flags & EcsTableIsPrefab)) { + /* Don't instantiate children from base entities that aren't prefabs */ + return; + } + + ecs_component_record_t *cr = flecs_components_get(world, ecs_childof(base)); + if (cr) { + ecs_os_perf_trace_push("flecs.instantiate"); + + if (cr->flags & EcsIdOrderedChildren) { + if (flecs_component_has_non_fragmenting_childof(cr)) { + EcsTreeSpawner *ts = flecs_get_mut( + world, base, ecs_id(EcsTreeSpawner), record, + sizeof(EcsTreeSpawner)).ptr; + if (!ts) { + ts = flecs_prefab_spawner_build(world, base); + } + + if (ts) { + flecs_spawner_instantiate(world, ts, instance); + } + + ecs_os_perf_trace_pop("flecs.instantiate"); + return; + } + + ecs_vec_t *children_vec = &cr->pair->ordered_children; + int32_t i, count = ecs_vec_count(children_vec); + ecs_entity_t *children = ecs_vec_first(children_vec); + for (i = 0; i < count; i ++) { + ecs_entity_t child = children[i]; + ecs_table_range_t range = flecs_range_from_entity(world, child); + + if (!(range.table->flags & EcsTableHasChildOf)) { + continue; + } + + flecs_instantiate_children( + world, base, instance, range, ctx); + } + } else { + ecs_table_cache_iter_t it; + if (flecs_table_cache_all_iter((ecs_table_cache_t*)cr, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_range_t range = { + tr->hdr.table, + 0, + ecs_table_count(tr->hdr.table) + }; + + flecs_instantiate_children( + world, base, instance, range, ctx); + } + } + } + + ecs_os_perf_trace_pop("flecs.instantiate"); + } } -/* Get bucket shift amount for a given bucket count */ -static -uint8_t get_bucket_shift ( - int32_t bucket_count) +/** + * @file iter.c + * @brief Iterator API. + * + * The iterator API contains functions that apply to all iterators, such as + * resource management, or fetching resources for a matched table. The API also + * contains functions for generic iterators, which make it possible to iterate + * an iterator without needing to know what created the iterator. + */ + + +/* Utility macros to enforce consistency when initializing iterator fields */ + +/* If term count is smaller than cache size, initialize with inline array, + * otherwise allocate. */ + +void* flecs_iter_calloc( + ecs_iter_t *it, + ecs_size_t size, + ecs_size_t align) { - return (uint8_t)(64u - ecs_log2((uint32_t)bucket_count)); + ecs_world_t *world = it->world; + ecs_stage_t *stage = flecs_stage_from_world((ecs_world_t**)&world); + ecs_stack_t *stack = &stage->allocators.iter_stack; + return flecs_stack_calloc(stack, size, align); } -/* Get bucket index for provided map key */ -static -int32_t get_bucket_index( - const ecs_map_t *map, - uint16_t bucket_shift, - ecs_map_key_t key) +void flecs_iter_free( + void *ptr, + ecs_size_t size) { - ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(map->bucket_shift == bucket_shift, ECS_INTERNAL_ERROR, NULL); - (void)map; - return (int32_t)((11400714819323198485ull * key) >> bucket_shift); + flecs_stack_free(ptr, size); } -/* Get bucket for key */ -static -ecs_bucket_t* get_bucket( - const ecs_map_t *map, - ecs_map_key_t key) +void flecs_iter_init( + const ecs_world_t *world, + ecs_iter_t *it, + bool alloc_resources) { - ecs_assert(map->bucket_shift == get_bucket_shift(map->bucket_count), + ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INTERNAL_ERROR, NULL); - int32_t bucket_id = get_bucket_index(map, map->bucket_shift, key); - ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); - return &map->buckets[bucket_id]; -} -/* Ensure that map has at least new_count buckets */ -static -void ensure_buckets( - ecs_map_t *map, - int32_t new_count) -{ - int32_t bucket_count = map->bucket_count; - new_count = flecs_next_pow_of_2(new_count); - if (new_count < 2) { - new_count = 2; - } + ecs_stage_t *stage = flecs_stage_from_world( + ECS_CONST_CAST(ecs_world_t**, &world)); + ecs_stack_t *stack = &stage->allocators.iter_stack; + + it->priv_.stack_cursor = flecs_stack_get_cursor(stack); - if (new_count && new_count > bucket_count) { - map->buckets = ecs_os_realloc(map->buckets, new_count * ECS_SIZEOF(ecs_bucket_t)); - map->bucket_count = new_count; - map->bucket_shift = get_bucket_shift(new_count); - ecs_os_memset( - ECS_OFFSET(map->buckets, bucket_count * ECS_SIZEOF(ecs_bucket_t)), - 0, (new_count - bucket_count) * ECS_SIZEOF(ecs_bucket_t)); + if (alloc_resources && it->field_count) { + ecs_assert(it->ids == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->sources == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(it->trs == NULL, ECS_INTERNAL_ERROR, NULL); + + it->ids = flecs_stack_calloc_n(stack, ecs_id_t, it->field_count); + it->sources = flecs_stack_calloc_n( + stack, ecs_entity_t, it->field_count); + it->trs = flecs_stack_calloc_n( + stack, ecs_table_record_t*, it->field_count); } } -/* Free contents of bucket */ -static -void clear_bucket( - ecs_bucket_t *bucket) +void ecs_iter_fini( + ecs_iter_t *it) { - ecs_os_free(bucket->keys); - ecs_os_free(bucket->payload); - bucket->keys = NULL; - bucket->payload = NULL; - bucket->count = 0; -} + if (it->fini) { + it->fini(it); + } -/* Clear all buckets */ -static -void clear_buckets( - ecs_map_t *map) -{ - ecs_bucket_t *buckets = map->buckets; - int32_t i, count = map->bucket_count; - for (i = 0; i < count; i ++) { - clear_bucket(&buckets[i]); + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + + ecs_world_t *world = it->world; + if (!world) { + return; } - ecs_os_free(buckets); - map->buckets = NULL; - map->bucket_count = 0; -} -/* Find or create bucket for specified key */ -static -ecs_bucket_t* ensure_bucket( - ecs_map_t *map, - ecs_map_key_t key) -{ - ecs_assert(map->bucket_count >= 2, ECS_INTERNAL_ERROR, NULL); - int32_t bucket_id = get_bucket_index(map, map->bucket_shift, key); - ecs_assert(bucket_id >= 0, ECS_INTERNAL_ERROR, NULL); - return &map->buckets[bucket_id]; + /* Make sure arrays are below stack page size, which means they don't have + * to get freed explicitly. */ + ecs_assert(ECS_SIZEOF(ecs_id_t) * it->field_count < FLECS_STACK_PAGE_SIZE, + ECS_UNSUPPORTED, NULL); + ecs_assert(ECS_SIZEOF(ecs_entity_t) * it->field_count < FLECS_STACK_PAGE_SIZE, + ECS_UNSUPPORTED, NULL); + ecs_assert(ECS_SIZEOF(ecs_table_record_t*) * it->field_count < FLECS_STACK_PAGE_SIZE, + ECS_UNSUPPORTED, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_stack_restore_cursor(&stage->allocators.iter_stack, + it->priv_.stack_cursor); } -/* Add element to bucket */ -static -int32_t add_to_bucket( - ecs_bucket_t *bucket, - ecs_size_t elem_size, - ecs_map_key_t key, - const void *payload) -{ - int32_t index = bucket->count ++; - int32_t bucket_count = index + 1; +/* --- Public API --- */ - bucket->keys = ecs_os_realloc(bucket->keys, KEY_SIZE * bucket_count); - bucket->keys[index] = key; +void* ecs_field_w_size( + const ecs_iter_t *it, + size_t size, + int8_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + ecs_check(size != 0, ECS_INVALID_PARAMETER, + "missing size for field %d", index); + ecs_check(ecs_field_size(it, index) == size || + !ecs_field_size(it, index), + ECS_INVALID_PARAMETER, + "mismatching size for field %d (expected '%s')", + index, + flecs_errstr(ecs_id_str(it->world, it->ids[index]))); + (void)size; - if (elem_size) { - bucket->payload = ecs_os_realloc(bucket->payload, elem_size * bucket_count); - if (payload) { - void *elem = GET_ELEM(bucket->payload, elem_size, index); - ecs_os_memcpy(elem, payload, elem_size); + if (it->ptrs && !it->offset) { + void *ptr = it->ptrs[index]; + if (ptr) { +#ifdef FLECS_DEBUG + if (it->trs[index]) { + /* Make sure that address in ptrs array is the same as what this + * function would have returned if no ptrs array was set. */ + void **temp_ptrs = it->ptrs; + ECS_CONST_CAST(ecs_iter_t*, it)->ptrs = NULL; + ecs_assert(ptr == ecs_field_w_size(it, size, index), + ECS_INTERNAL_ERROR, NULL); + ECS_CONST_CAST(ecs_iter_t*, it)->ptrs = temp_ptrs; + } else { + /* We're just passing in a pointer to a value that may not be + * a component on the entity (such as a pointer to a new value + * in an on_replace hook). */ + } +#endif + return ptr; } - } else { - bucket->payload = NULL; } - return index; -} - -/* Remove element from bucket */ -static -void remove_from_bucket( - ecs_bucket_t *bucket, - ecs_size_t elem_size, - ecs_map_key_t key, - int32_t index) -{ - (void)key; + const ecs_table_record_t *tr = it->trs[index]; + if (!tr) { + ecs_assert(!ecs_field_is_set(it, index), ECS_INTERNAL_ERROR, NULL); + return NULL; + } - ecs_assert(bucket->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(index < bucket->count, ECS_INTERNAL_ERROR, NULL); - - int32_t bucket_count = -- bucket->count; + ecs_assert(!(tr->hdr.cr->flags & EcsIdSparse), ECS_INVALID_OPERATION, + "field %d: use ecs_field_at to access fields for sparse components", + index); - if (index != bucket->count) { - ecs_assert(key == bucket->keys[index], ECS_INTERNAL_ERROR, NULL); - bucket->keys[index] = bucket->keys[bucket_count]; + ecs_entity_t src = it->sources[index]; + ecs_table_t *table; + int32_t row; + if (!src) { + table = it->table; + row = it->offset; + } else { + ecs_record_t *r = flecs_entities_get(it->real_world, src); + table = r->table; + row = ECS_RECORD_TO_ROW(r->row); + } - ecs_map_key_t *elem = GET_ELEM(bucket->payload, elem_size, index); - ecs_map_key_t *last_elem = GET_ELEM(bucket->payload, elem_size, bucket->count); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - ecs_os_memcpy(elem, last_elem, elem_size); - } -} + int32_t column_index = tr->column; + ecs_assert(column_index != -1, ECS_INVALID_PARAMETER, + "field %d: only components can be fetched with fields", index); + ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); -/* Get payload pointer for key from bucket */ -static -void* get_from_bucket( - ecs_bucket_t *bucket, - ecs_map_key_t key, - ecs_size_t elem_size) -{ - ecs_map_key_t *keys = bucket->keys; - int32_t i, count = bucket->count; + ecs_column_t *column = &table->data.columns[column_index]; + ecs_assert((row < table->data.count) || + (it->query && (it->query->flags & EcsQueryMatchEmptyTables)), + ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < count; i ++) { - if (keys[i] == key) { - return GET_ELEM(bucket->payload, elem_size, i); - } - } + return ECS_ELEM(column->data, (ecs_size_t)size, row); +error: return NULL; } -/* Grow number of buckets */ -static -void rehash( - ecs_map_t *map, - int32_t bucket_count) +void* ecs_field_at_w_size( + const ecs_iter_t *it, + size_t size, + int8_t index, + int32_t row) { - ecs_assert(bucket_count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(bucket_count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); - - ensure_buckets(map, bucket_count); + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + ecs_check(!size || ecs_field_size(it, index) == size || + !ecs_field_size(it, index), + ECS_INVALID_PARAMETER, "mismatching size for field %d", index); + + ecs_component_record_t *cr = NULL; + const ecs_table_record_t *tr = it->trs[index]; + if (!tr) { + cr = flecs_components_get(it->real_world, it->ids[index]); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + cr = tr->hdr.cr; + } - ecs_bucket_t *buckets = map->buckets; - ecs_assert(buckets != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_size_t elem_size = map->elem_size; - uint16_t bucket_shift = map->bucket_shift; - int32_t bucket_id; + ecs_assert((cr->flags & EcsIdSparse), ECS_INVALID_OPERATION, + "use ecs_field to access fields for non-sparse components"); + ecs_assert(it->row_fields & (1ull << index), ECS_INTERNAL_ERROR, NULL); - /* Iterate backwards as elements could otherwise be moved to existing - * buckets which could temporarily cause the number of elements in a - * bucket to exceed BUCKET_COUNT. */ - for (bucket_id = bucket_count - 1; bucket_id >= 0; bucket_id --) { - ecs_bucket_t *bucket = &buckets[bucket_id]; + ecs_entity_t src = it->sources[index]; + if (!src) { + src = ecs_table_entities(it->table)[row + it->offset]; + } - int i, count = bucket->count; - ecs_map_key_t *key_array = bucket->keys; - void *payload_array = bucket->payload; + return flecs_sparse_get(cr->sparse, flecs_uto(int32_t, size), src); +error: + return NULL; +} - for (i = 0; i < count; i ++) { - ecs_map_key_t key = key_array[i]; - void *elem = GET_ELEM(payload_array, elem_size, i); - int32_t new_bucket_id = get_bucket_index(map, bucket_shift, key); +bool ecs_field_is_readonly( + const ecs_iter_t *it, + int8_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); - if (new_bucket_id != bucket_id) { - ecs_bucket_t *new_bucket = &buckets[new_bucket_id]; + if (!it->query) { + return false; + } - add_to_bucket(new_bucket, elem_size, key, elem); - remove_from_bucket(bucket, elem_size, key, i); + ecs_check(it->query->terms != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + const ecs_term_t *term = &it->query->terms[index]; - count --; - i --; - } + if (term->inout == EcsIn) { + return true; + } else if (term->inout == EcsInOutDefault) { + if (!ecs_term_match_this(term)) { + return true; } - if (!bucket->count) { - clear_bucket(bucket); + const ecs_term_ref_t *src = &term->src; + if (!(src->id & EcsSelf)) { + return true; } } +error: + return false; } -void _ecs_map_init( - ecs_map_t *result, - ecs_size_t elem_size, - int32_t element_count) -{ - ecs_assert(elem_size < INT16_MAX, ECS_INVALID_PARAMETER, NULL); +bool ecs_field_is_writeonly( + const ecs_iter_t *it, + int8_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, + "operation only valid for query iterators"); + ecs_check(it->query->terms != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + + const ecs_term_t *term = &it->query->terms[index]; + return term->inout == EcsOut; +error: + return false; +} - result->count = 0; - result->elem_size = (int16_t)elem_size; +bool ecs_field_is_set( + const ecs_iter_t *it, + int8_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); - ensure_buckets(result, get_bucket_count(element_count)); + return it->set_fields & (1llu << (index)); +error: + return false; } -ecs_map_t* _ecs_map_new( - ecs_size_t elem_size, - int32_t element_count) +bool ecs_field_is_self( + const ecs_iter_t *it, + int8_t index) { - ecs_map_t *result = ecs_os_calloc_t(ecs_map_t); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - - _ecs_map_init(result, elem_size, element_count); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); - return result; + return it->sources == NULL || it->sources[index] == 0; +error: + return false; } -bool ecs_map_is_initialized( - const ecs_map_t *result) +ecs_id_t ecs_field_id( + const ecs_iter_t *it, + int8_t index) { - return result != NULL && result->bucket_count != 0; + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + + return it->ids[index]; +error: + return 0; } -void ecs_map_fini( - ecs_map_t *map) +int32_t ecs_field_column( + const ecs_iter_t *it, + int8_t index) { - ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); - clear_buckets(map); - ecs_assert(!ecs_map_is_initialized(map), ECS_INTERNAL_ERROR, NULL); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + + const ecs_table_record_t *tr = it->trs[index]; + if (tr) { + return tr->index; + } else { + return -1; + } +error: + return 0; } -void ecs_map_free( - ecs_map_t *map) +ecs_entity_t ecs_field_src( + const ecs_iter_t *it, + int8_t index) { - if (map) { - ecs_map_fini(map); - ecs_os_free(map); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + + if (it->sources) { + return it->sources[index]; + } else { + return 0; } +error: + return 0; } -void* _ecs_map_get( - const ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key) +size_t ecs_field_size( + const ecs_iter_t *it, + int8_t index) { - (void)elem_size; + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + + return (size_t)it->sizes[index]; +error: + return 0; +} - if (!ecs_map_is_initialized(map)) { +char* ecs_iter_str( + const ecs_iter_t *it) +{ + if (!(it->flags & EcsIterIsValid)) { return NULL; } - ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_world_t *world = it->world; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + int8_t i; - ecs_bucket_t * bucket = get_bucket(map, key); - if (!bucket) { - return NULL; + if (it->field_count) { + ecs_strbuf_list_push(&buf, "id: ", ","); + for (i = 0; i < it->field_count; i ++) { + ecs_id_t id = ecs_field_id(it, i); + char *str = ecs_id_str(world, id); + ecs_strbuf_list_appendstr(&buf, str); + ecs_os_free(str); + } + ecs_strbuf_list_pop(&buf, "\n"); + + ecs_strbuf_list_push(&buf, "src: ", ","); + for (i = 0; i < it->field_count; i ++) { + ecs_entity_t subj = ecs_field_src(it, i); + char *str = ecs_get_path(world, subj); + ecs_strbuf_list_appendstr(&buf, str); + ecs_os_free(str); + } + ecs_strbuf_list_pop(&buf, "\n"); + + ecs_strbuf_list_push(&buf, "set: ", ","); + for (i = 0; i < it->field_count; i ++) { + if (ecs_field_is_set(it, i)) { + ecs_strbuf_list_appendlit(&buf, "true"); + } else { + ecs_strbuf_list_appendlit(&buf, "false"); + } + } + ecs_strbuf_list_pop(&buf, "\n"); } - return get_from_bucket(bucket, key, elem_size); -} + int32_t var_count = ecs_iter_get_var_count(it); -void* _ecs_map_get_ptr( - const ecs_map_t *map, - ecs_map_key_t key) -{ - void* ptr_ptr = _ecs_map_get(map, ECS_SIZEOF(void*), key); + if (var_count) { + int32_t actual_count = 0; + for (i = 0; i < var_count; i ++) { + const char *var_name = ecs_iter_get_var_name(it, i); + if (!var_name || var_name[0] == '_' || !strcmp(var_name, "this")) { + /* Skip anonymous variables */ + continue; + } - if (ptr_ptr) { - return *(void**)ptr_ptr; - } else { - return NULL; - } -} + ecs_var_t var = ecs_iter_get_vars(it)[i]; + if (!var.entity) { + /* Skip table variables */ + continue; + } -bool ecs_map_has( - const ecs_map_t *map, - ecs_map_key_t key) -{ - if (!ecs_map_is_initialized(map)) { - return false; - } + if (!actual_count) { + ecs_strbuf_list_push(&buf, "var: ", ","); + } - ecs_bucket_t * bucket = get_bucket(map, key); - if (!bucket) { - return false; - } + char *str = ecs_get_path(world, var.entity); + ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); + ecs_os_free(str); - return get_from_bucket(bucket, key, 0) != NULL; -} + actual_count ++; + } + if (actual_count) { + ecs_strbuf_list_pop(&buf, "\n"); + } + } -void* _ecs_map_ensure( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key) -{ - void *result = _ecs_map_get(map, elem_size, key); - if (!result) { - result = _ecs_map_set(map, elem_size, key, NULL); - if (elem_size) { - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_memset(result, 0, elem_size); + if (it->count) { + ecs_strbuf_appendlit(&buf, "this:\n"); + for (i = 0; i < it->count; i ++) { + ecs_entity_t e = it->entities[i]; + char *str = ecs_get_path(world, e); + ecs_strbuf_appendlit(&buf, " - "); + ecs_strbuf_appendstr(&buf, str); + ecs_strbuf_appendch(&buf, '\n'); + ecs_os_free(str); } } - return result; + return ecs_strbuf_get(&buf); } -void* _ecs_map_set( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key, - const void *payload) +bool ecs_iter_next( + ecs_iter_t *iter) { - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(elem_size == map->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); + return iter->next(iter); +error: + return false; +} - ecs_bucket_t *bucket = ensure_bucket(map, key); - ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); +int32_t ecs_iter_count( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - void *elem = get_from_bucket(bucket, key, elem_size); - if (!elem) { - int32_t index = add_to_bucket(bucket, elem_size, key, payload); - int32_t map_count = ++map->count; - int32_t target_bucket_count = get_bucket_count(map_count); - int32_t map_bucket_count = map->bucket_count; + ECS_BIT_SET(it->flags, EcsIterNoData); - if (target_bucket_count > map_bucket_count) { - rehash(map, target_bucket_count); - bucket = ensure_bucket(map, key); - return get_from_bucket(bucket, key, elem_size); - } else { - return GET_ELEM(bucket->payload, elem_size, index); - } - } else { - if (payload) { - ecs_os_memcpy(elem, payload, elem_size); - } - return elem; + int32_t count = 0; + while (ecs_iter_next(it)) { + count += it->count; } + return count; +error: + return 0; } -int32_t ecs_map_remove( - ecs_map_t *map, - ecs_map_key_t key) +ecs_entity_t ecs_iter_first( + ecs_iter_t *it) { - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_bucket_t * bucket = get_bucket(map, key); - if (!bucket) { - return map->count; - } + ECS_BIT_SET(it->flags, EcsIterNoData); - int32_t i, bucket_count = bucket->count; - for (i = 0; i < bucket_count; i ++) { - if (bucket->keys[i] == key) { - remove_from_bucket(bucket, map->elem_size, key, i); - return --map->count; - } + ecs_entity_t result = 0; + if (ecs_iter_next(it)) { + result = it->entities[0]; + ecs_iter_fini(it); } - return map->count; + return result; +error: + return 0; } -int32_t ecs_map_count( - const ecs_map_t *map) +bool ecs_iter_is_true( + ecs_iter_t *it) { - return map ? map->count : 0; -} + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); -int32_t ecs_map_bucket_count( - const ecs_map_t *map) -{ - return map ? map->bucket_count : 0; -} + ECS_BIT_SET(it->flags, EcsIterNoData); -void ecs_map_clear( - ecs_map_t *map) -{ - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - clear_buckets(map); - map->count = 0; - ensure_buckets(map, 2); + bool result = ecs_iter_next(it); + if (result) { + ecs_iter_fini(it); + } + return result; +error: + return false; } -ecs_map_iter_t ecs_map_iter( - const ecs_map_t *map) +ecs_entity_t ecs_iter_get_var( + ecs_iter_t *it, + int32_t var_id) { - return (ecs_map_iter_t){ - .map = map, - .bucket = NULL, - .bucket_index = 0, - .element_index = 0 - }; + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, + "invalid variable index %d", var_id); + ecs_check(var_id < ecs_iter_get_var_count(it), ECS_INVALID_PARAMETER, + "variable index %d out of bounds", var_id); + ecs_check(ecs_iter_get_vars(it) != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_var_t *var = &ecs_iter_get_vars(it)[var_id]; + ecs_entity_t e = var->entity; + if (!e) { + ecs_table_t *table = var->range.table; + if (!table && !var_id) { + table = it->table; + } + if (table) { + if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { + ecs_assert(ecs_table_count(table) > var->range.offset, + ECS_INTERNAL_ERROR, NULL); + e = ecs_table_entities(table)[var->range.offset]; + } + } + } else { + ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); + } + + return e; +error: + return 0; } -void* _ecs_map_next( - ecs_map_iter_t *iter, - ecs_size_t elem_size, - ecs_map_key_t *key_out) +ecs_table_t* ecs_iter_get_var_as_table( + ecs_iter_t *it, + int32_t var_id) { - const ecs_map_t *map = iter->map; - if (!ecs_map_is_initialized(map)) { - return NULL; + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, + "invalid variable index %d", var_id); + ecs_check(var_id < ecs_iter_get_var_count(it), ECS_INVALID_PARAMETER, + "variable index %d out of bounds", var_id); + ecs_check(ecs_iter_get_vars(it) != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_var_t *var = &ecs_iter_get_vars(it)[var_id]; + ecs_table_t *table = var->range.table; + if (!table && !var_id) { + table = it->table; } - - ecs_assert(!elem_size || elem_size == map->elem_size, - ECS_INVALID_PARAMETER, NULL); - - ecs_bucket_t *bucket = iter->bucket; - int32_t element_index = iter->element_index; - elem_size = map->elem_size; - do { - if (!bucket) { - int32_t bucket_index = iter->bucket_index; - ecs_bucket_t *buckets = map->buckets; - if (bucket_index < map->bucket_count) { - bucket = &buckets[bucket_index]; - iter->bucket = bucket; - - element_index = 0; - iter->element_index = 0; - } else { - return NULL; + if (!table) { + /* If table is not set, try to get table from entity */ + ecs_entity_t e = var->entity; + if (e) { + ecs_record_t *r = flecs_entities_get(it->real_world, e); + if (r) { + table = r->table; + if (ecs_table_count(table) != 1) { + /* If table contains more than the entity, make sure not to + * return a partial table. */ + return NULL; + } } } + } - if (element_index < bucket->count) { - iter->element_index = element_index + 1; - break; - } else { - bucket = NULL; - iter->bucket_index ++; + if (table) { + if (var->range.offset) { + /* Don't return whole table if only partial table is matched */ + return NULL; + } + + if (!var->range.count || ecs_table_count(table) == var->range.count) { + /* Return table if count matches */ + return table; } - } while (true); - - if (key_out) { - *key_out = bucket->keys[element_index]; } - return GET_ELEM(bucket->payload, elem_size, element_index); +error: + return NULL; } -void* _ecs_map_next_ptr( - ecs_map_iter_t *iter, - ecs_map_key_t *key_out) +ecs_table_range_t ecs_iter_get_var_as_range( + ecs_iter_t *it, + int32_t var_id) { - void *result = _ecs_map_next(iter, ECS_SIZEOF(void*), key_out); - if (result) { - return *(void**)result; + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, + "invalid variable index %d", var_id); + ecs_check(var_id < ecs_iter_get_var_count(it), ECS_INVALID_PARAMETER, + "variable index %d out of bounds", var_id); + ecs_check(ecs_iter_get_vars(it) != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_table_range_t result = { 0 }; + + ecs_var_t *var = &ecs_iter_get_vars(it)[var_id]; + ecs_table_t *table = var->range.table; + if (!table && !var_id) { + table = it->table; + } + + if (!table) { + ecs_entity_t e = var->entity; + if (e) { + ecs_record_t *r = flecs_entities_get(it->real_world, e); + if (r) { + result.table = r->table; + result.offset = ECS_RECORD_TO_ROW(r->row); + result.count = 1; + } + } } else { - return NULL; + result.table = table; + result.offset = var->range.offset; + result.count = var->range.count; + if (!result.count) { + result.count = ecs_table_count(table); + } } + + return result; +error: + return (ecs_table_range_t){0}; } -void ecs_map_grow( - ecs_map_t *map, - int32_t element_count) +const char* ecs_iter_get_var_name( + const ecs_iter_t *it, + int32_t var_id) { - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t target_count = map->count + element_count; - int32_t bucket_count = get_bucket_count(target_count); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - if (bucket_count > map->bucket_count) { - rehash(map, bucket_count); + if (var_id == 0) { + return "this"; } -} -void ecs_map_set_size( - ecs_map_t *map, - int32_t element_count) -{ - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t bucket_count = get_bucket_count(element_count); + const ecs_query_t *query = it->query; + ecs_check(query != NULL, ECS_INVALID_PARAMETER, + "can only obtain variable name for iterators that iterate query"); + ecs_check(var_id < query->var_count, ECS_INVALID_PARAMETER, + "variable index out of range for query"); + + return query->vars[var_id]; +error: + return NULL; +} - if (bucket_count) { - rehash(map, bucket_count); +int32_t ecs_iter_get_var_count( + const ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + if (it->query) { + return it->query->var_count; } + + return 1; +error: + return 0; } -ecs_map_t* ecs_map_copy( - ecs_map_t *map) +ecs_var_t* ecs_iter_get_vars( + const ecs_iter_t *it) { - if (!ecs_map_is_initialized(map)) { + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + if (!it->query) { return NULL; } - ecs_size_t elem_size = map->elem_size; - ecs_map_t *result = _ecs_map_new(map->elem_size, ecs_map_count(map)); - - ecs_map_iter_t it = ecs_map_iter(map); - ecs_map_key_t key; - void *ptr; - while ((ptr = _ecs_map_next(&it, elem_size, &key))) { - _ecs_map_set(result, elem_size, key, ptr); + if (it->chain_it) { + return ecs_iter_get_vars(it->chain_it); } - return result; + return it->priv_.iter.query.vars; +error: + return NULL; } -void ecs_map_memory( - ecs_map_t *map, - int32_t *allocd, - int32_t *used) +void ecs_iter_set_var( + ecs_iter_t *it, + int32_t var_id, + ecs_entity_t entity) { - ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - if (used) { - *used = map->count * map->elem_size; + if (it->chain_it) { + ecs_iter_set_var(it->chain_it, var_id, entity); + return; } - if (allocd) { - *allocd += ECS_SIZEOF(ecs_map_t); - - int i, bucket_count = map->bucket_count; - for (i = 0; i < bucket_count; i ++) { - ecs_bucket_t *bucket = &map->buckets[i]; - *allocd += KEY_SIZE * bucket->count; - *allocd += map->elem_size * bucket->count; - } - - *allocd += ECS_SIZEOF(ecs_bucket_t) * bucket_count; - } -} + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, + "invalid variable index %d", var_id); + ecs_check(var_id < FLECS_QUERY_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < ecs_iter_get_var_count(it), ECS_INVALID_PARAMETER, + "variable index %d out of bounds", var_id); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, + "cannot constrain variable while iterating"); + ecs_check(ecs_iter_get_vars(it) != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ecs_iter_get_vars(it)[var_id]; + var->entity = entity; -static -int32_t find_key( - const ecs_hashmap_t *map, - ecs_vector_t *keys, - ecs_size_t key_size, - const void *key) -{ - int32_t i, count = ecs_vector_count(keys); - void *key_array = ecs_vector_first_t(keys, key_size, 8); - for (i = 0; i < count; i ++) { - void *key_ptr = ECS_OFFSET(key_array, key_size * i); - if (map->compare(key_ptr, key) == 0) { - return i; - } + ecs_record_t *r = flecs_entities_get(it->real_world, entity); + if (r) { + var->range.table = r->table; + var->range.offset = ECS_RECORD_TO_ROW(r->row); + var->range.count = 1; + } else { + var->range.table = NULL; + var->range.offset = 0; + var->range.count = 0; } - return -1; + + it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); + + /* Update iterator for constrained iterator */ + flecs_query_iter_constrain(it); + +error: + return; } -void _flecs_hashmap_init( - ecs_hashmap_t *map, - ecs_size_t key_size, - ecs_size_t value_size, - ecs_hash_value_action_t hash, - ecs_compare_action_t compare) +void ecs_iter_set_var_as_table( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_t *table) { - map->key_size = key_size; - map->value_size = value_size; - map->hash = hash; - map->compare = compare; - ecs_map_init(&map->impl, ecs_hm_bucket_t, 0); + ecs_table_range_t range = { .table = ECS_CONST_CAST(ecs_table_t*, table) }; + ecs_iter_set_var_as_range(it, var_id, &range); } -void flecs_hashmap_fini( - ecs_hashmap_t *map) +void ecs_iter_set_var_as_range( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_range_t *range) { - ecs_map_iter_t it = ecs_map_iter(&map->impl); - ecs_hm_bucket_t *bucket; - while ((bucket = ecs_map_next(&it, ecs_hm_bucket_t, NULL))) { - ecs_vector_free(bucket->keys); - ecs_vector_free(bucket->values); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + + if (it->chain_it) { + ecs_iter_set_var_as_range(it->chain_it, var_id, range); + return; } - ecs_map_fini(&map->impl); + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, + "invalid variable index %d", var_id); + ecs_check(var_id < ecs_iter_get_var_count(it), ECS_INVALID_PARAMETER, + "variable index %d out of bounds", var_id); + ecs_check(range != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(range->table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!range->offset || range->offset < ecs_table_count(range->table), + ECS_INVALID_PARAMETER, NULL); + ecs_check((range->offset + range->count) <= ecs_table_count(range->table), + ECS_INVALID_PARAMETER, NULL); + + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, + "cannot set query variables while iterating"); + + ecs_var_t *var = &ecs_iter_get_vars(it)[var_id]; + var->range = *range; + + if (range->count == 1) { + ecs_table_t *table = range->table; + var->entity = ecs_table_entities(table)[range->offset]; + } else { + var->entity = 0; + } + + it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); + + /* Update iterator for constrained iterator */ + flecs_query_iter_constrain(it); + +error: + return; } -void flecs_hashmap_copy( - const ecs_hashmap_t *src, - ecs_hashmap_t *dst) +bool ecs_iter_var_is_constrained( + ecs_iter_t *it, + int32_t var_id) { - if (dst != src) { - *dst = *src; + if (it->chain_it) { + return ecs_iter_var_is_constrained(it->chain_it, var_id); } - - ecs_map_t *impl = ecs_map_copy(&dst->impl); - dst->impl = *impl; - ecs_os_free(impl); - ecs_map_iter_t it = ecs_map_iter(&dst->impl); - ecs_hm_bucket_t *bucket; - while ((bucket = ecs_map_next(&it, ecs_hm_bucket_t, NULL))) { - bucket->keys = ecs_vector_copy_t(bucket->keys, dst->key_size, 8); - bucket->values = ecs_vector_copy_t(bucket->values, dst->value_size, 8); - } + return (it->constrained_vars & (flecs_uto(uint64_t, 1 << var_id))) != 0; } -void* _flecs_hashmap_get( - const ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size) +uint64_t ecs_iter_get_group( + const ecs_iter_t *it) { - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - uint64_t hash = map->hash(key); - ecs_hm_bucket_t *bucket = ecs_map_get(&map->impl, ecs_hm_bucket_t, hash); - if (!bucket) { - return NULL; + if (it->chain_it) { + return ecs_iter_get_group(it->chain_it); } - int32_t index = find_key(map, bucket->keys, key_size, key); - if (index == -1) { - return NULL; - } + ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, + "ecs_iter_get_group must be called on iterator that iterates a query"); + const ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_check(qit->group != NULL, ECS_INVALID_PARAMETER, + "ecs_iter_get_group must be called on iterator that iterates a cached " + "query (query is uncached)"); - return ecs_vector_get_t(bucket->values, value_size, 8, index); + return qit->group->info.id; +error: + return 0; } -flecs_hashmap_result_t _flecs_hashmap_ensure( - ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size) +static +void ecs_chained_iter_fini( + ecs_iter_t *it) { - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - uint64_t hash = map->hash(key); - ecs_hm_bucket_t *bucket = ecs_map_ensure(&map->impl, ecs_hm_bucket_t, hash); - ecs_assert(bucket != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_iter_fini(it->chain_it); - void *value_ptr, *key_ptr; + it->chain_it = NULL; +} - ecs_vector_t *keys = bucket->keys; - if (!keys) { - bucket->keys = ecs_vector_new_t(key_size, 8, 1); - bucket->values = ecs_vector_new_t(value_size, 8, 1); - key_ptr = ecs_vector_add_t(&bucket->keys, key_size, 8); - ecs_os_memcpy(key_ptr, key, key_size); - value_ptr = ecs_vector_add_t(&bucket->values, value_size, 8); - ecs_os_memset(value_ptr, 0, value_size); - } else { - int32_t index = find_key(map, keys, key_size, key); - if (index == -1) { - key_ptr = ecs_vector_add_t(&bucket->keys, key_size, 8); - ecs_os_memcpy(key_ptr, key, key_size); - value_ptr = ecs_vector_add_t(&bucket->values, value_size, 8); - ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_memset(value_ptr, 0, value_size); - } else { - key_ptr = ecs_vector_get_t(bucket->keys, key_size, 8, index); - value_ptr = ecs_vector_get_t(bucket->values, value_size, 8, index); - } - } +ecs_iter_t ecs_page_iter( + const ecs_iter_t *it, + int32_t offset, + int32_t limit) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); - return (flecs_hashmap_result_t){ - .key = key_ptr, - .value = value_ptr, - .hash = hash + ecs_iter_t result = *it; + result.priv_.stack_cursor = NULL; /* Don't copy allocator cursor */ + + result.priv_.iter.page = (ecs_page_iter_t){ + .offset = offset, + .limit = limit, + .remaining = limit }; + result.next = ecs_page_next; + result.fini = ecs_chained_iter_fini; + result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); + + return result; +error: + return (ecs_iter_t){ 0 }; } -void _flecs_hashmap_set( - ecs_hashmap_t *map, - ecs_size_t key_size, - void *key, - ecs_size_t value_size, - const void *value) +bool ecs_page_next( + ecs_iter_t *it) { - void *value_ptr = _flecs_hashmap_ensure(map, key_size, key, value_size).value; - ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_memcpy(value_ptr, value, value_size); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); + + ecs_iter_t *chain_it = it->chain_it; + + do { + if (!ecs_iter_next(chain_it)) { + goto depleted; + } + + ecs_page_iter_t *iter = &it->priv_.iter.page; + + /* Copy everything up to the private iterator data */ + ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv_)); + + if (!chain_it->table) { + goto yield; /* Task query */ + } + + int32_t offset = iter->offset; + int32_t limit = iter->limit; + if (!(offset || limit)) { + if (it->count) { + goto yield; + } else { + goto depleted; + } + } + + int32_t count = it->count; + int32_t remaining = iter->remaining; + + if (offset) { + if (offset > count) { + /* No entities to iterate in current table */ + iter->offset -= count; + it->count = 0; + continue; + } else { + iter->offset = 0; + it->offset = offset; + count = it->count -= offset; + it->entities = + &(ecs_table_entities(it->table)[it->offset]); + } + } + + if (remaining) { + if (remaining > count) { + iter->remaining -= count; + } else { + it->count = remaining; + iter->remaining = 0; + } + } else if (limit) { + /* Limit hit: no more entities left to iterate */ + goto done; + } + } while (it->count == 0); + +yield: + return true; + +done: + /* Cleanup iterator resources if it wasn't yet depleted */ + ecs_iter_fini(chain_it); + +depleted: +error: + return false; } -ecs_hm_bucket_t* flecs_hashmap_get_bucket( - const ecs_hashmap_t *map, - uint64_t hash) +ecs_iter_t ecs_worker_iter( + const ecs_iter_t *it, + int32_t index, + int32_t count) { - ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); - return ecs_map_get(&map->impl, ecs_hm_bucket_t, hash); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(count > 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); + + ecs_iter_t result = *it; + result.priv_.stack_cursor = NULL; /* Don't copy allocator cursor */ + + result.priv_.iter.worker = (ecs_worker_iter_t){ + .index = index, + .count = count + }; + result.next = ecs_worker_next; + result.fini = ecs_chained_iter_fini; + result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); + + return result; +error: + return (ecs_iter_t){ 0 }; } -void flecs_hm_bucket_remove( - ecs_hashmap_t *map, - ecs_hm_bucket_t *bucket, - uint64_t hash, - int32_t index) +bool ecs_worker_next( + ecs_iter_t *it) { - ecs_vector_remove_t(bucket->keys, map->key_size, 8, index); - ecs_vector_remove_t(bucket->values, map->value_size, 8, index); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); - if (!ecs_vector_count(bucket->keys)) { - ecs_vector_free(bucket->keys); - ecs_vector_free(bucket->values); - ecs_map_remove(&map->impl, hash); - } + ecs_iter_t *chain_it = it->chain_it; + ecs_worker_iter_t *iter = &it->priv_.iter.worker; + int32_t res_count = iter->count, res_index = iter->index; + int32_t per_worker, first; + + do { + if (!ecs_iter_next(chain_it)) { + return false; + } + + /* Copy everything up to the private iterator data */ + ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv_)); + + int32_t count = it->count; + per_worker = count / res_count; + first = per_worker * res_index; + count -= per_worker * res_count; + + if (count) { + if (res_index < count) { + per_worker ++; + first += res_index; + } else { + first += count; + } + } + + if (!per_worker && it->table == NULL) { + if (res_index == 0) { + return true; + } else { + // chained iterator was not yet cleaned up + // since it returned true from ecs_iter_next, so clean it up here. + ecs_iter_fini(chain_it); + return false; + } + } + } while (!per_worker); + + it->frame_offset += first; + it->count = per_worker; + it->offset += first; + + it->entities = &(ecs_table_entities(it->table)[it->offset]); + + return true; +error: + return false; } -void _flecs_hashmap_remove_w_hash( - ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size, - uint64_t hash) +/** + * @file misc.c + * @brief Miscellaneous functions. + */ + +#include +#include + + +#ifndef FLECS_NDEBUG +static int64_t flecs_s_min[] = { + [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; +static int64_t flecs_s_max[] = { + [1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; +static uint64_t flecs_u_max[] = { + [1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; + +uint64_t flecs_ito_( + size_t size, + bool is_signed, + bool lt_zero, + uint64_t u, + const char *err) { - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); - (void)value_size; + union { + uint64_t u; + int64_t s; + } v; - ecs_hm_bucket_t *bucket = ecs_map_get(&map->impl, ecs_hm_bucket_t, hash); - if (!bucket) { - return; - } + v.u = u; - int32_t index = find_key(map, bucket->keys, key_size, key); - if (index == -1) { - return; + if (is_signed) { + ecs_assert(v.s >= flecs_s_min[size], ECS_INVALID_CONVERSION, err); + ecs_assert(v.s <= flecs_s_max[size], ECS_INVALID_CONVERSION, err); + } else { + ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); + ecs_assert(u <= flecs_u_max[size], ECS_INVALID_CONVERSION, err); } - flecs_hm_bucket_remove(map, bucket, hash, index); + return u; } +#endif -void _flecs_hashmap_remove( - ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size) +int32_t flecs_next_pow_of_2( + int32_t n) { - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + n --; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n ++; - uint64_t hash = map->hash(key); - _flecs_hashmap_remove_w_hash(map, key_size, key, value_size, hash); + return n; } -flecs_hashmap_iter_t flecs_hashmap_iter( - ecs_hashmap_t *map) +/** Convert time to double */ +double ecs_time_to_double( + ecs_time_t t) { - return (flecs_hashmap_iter_t){ - .it = ecs_map_iter(&map->impl) - }; + double result; + result = t.sec; + return result + (double)t.nanosec / (double)1000000000; } -void* _flecs_hashmap_next( - flecs_hashmap_iter_t *it, - ecs_size_t key_size, - void *key_out, - ecs_size_t value_size) +ecs_time_t ecs_time_sub( + ecs_time_t t1, + ecs_time_t t2) { - int32_t index = ++ it->index; - ecs_hm_bucket_t *bucket = it->bucket; - while (!bucket || it->index >= ecs_vector_count(bucket->keys)) { - bucket = it->bucket = ecs_map_next(&it->it, ecs_hm_bucket_t, NULL); - if (!bucket) { - return NULL; - } - index = it->index = 0; + ecs_time_t result; + + if (t1.nanosec >= t2.nanosec) { + result.nanosec = t1.nanosec - t2.nanosec; + result.sec = t1.sec - t2.sec; + } else { + result.nanosec = t1.nanosec - t2.nanosec + 1000000000; + result.sec = t1.sec - t2.sec - 1; } - if (key_out) { - *(void**)key_out = ecs_vector_get_t(bucket->keys, key_size, 8, index); + return result; +} + +void ecs_sleepf( + double t) +{ + if (t > 0) { + int sec = (int)t; + int nsec = (int)((t - sec) * 1000000000); + ecs_os_sleep(sec, nsec); } - - return ecs_vector_get_t(bucket->values, value_size, 8, index); } +double ecs_time_measure( + ecs_time_t *start) +{ + ecs_time_t stop, temp; + ecs_os_get_time(&stop); + temp = stop; + stop = ecs_time_sub(stop, *start); + *start = temp; + return ecs_time_to_double(stop); +} + +void* ecs_os_memdup( + const void *src, + ecs_size_t size) +{ + if (!src) { + return NULL; + } + + void *dst = ecs_os_malloc(size); + ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_os_memcpy(dst, src, size); + return dst; +} -#ifdef FLECS_LOG +int flecs_entity_compare( + ecs_entity_t e1, + const void *ptr1, + ecs_entity_t e2, + const void *ptr2) +{ + (void)ptr1; + (void)ptr2; + return (e1 > e2) - (e1 < e2); +} -#include -#include +int flecs_id_qsort_cmp(const void *a, const void *b) { + ecs_id_t id_a = *(const ecs_id_t*)a; + ecs_id_t id_b = *(const ecs_id_t*)b; + return (id_a > id_b) - (id_a < id_b); +} -char* ecs_vasprintf( +char* flecs_vasprintf( const char *fmt, va_list args) { @@ -13441,34814 +14488,83314 @@ char* ecs_vasprintf( return NULL; } - ecs_os_vsprintf(result, fmt, args); + ecs_os_vsnprintf(result, size + 1, fmt, args); return result; } -char* ecs_asprintf( +char* flecs_asprintf( const char *fmt, ...) { va_list args; va_start(args, fmt); - char *result = ecs_vasprintf(fmt, args); + char *result = flecs_vasprintf(fmt, args); va_end(args); return result; } -static -void ecs_colorize_buf( - char *msg, - bool enable_colors, - ecs_strbuf_t *buf) -{ - char *ptr, ch, prev = '\0'; - bool isNum = false; - char isStr = '\0'; - bool isVar = false; - bool overrideColor = false; - bool autoColor = true; - bool dontAppend = false; - - for (ptr = msg; (ch = *ptr); ptr++) { - dontAppend = false; - - if (!overrideColor) { - if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - isNum = false; - } - if (isStr && (isStr == ch) && prev != '\\') { - isStr = '\0'; - } else if (((ch == '\'') || (ch == '"')) && !isStr && - !isalpha(prev) && (prev != '\\')) - { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); - isStr = ch; - } - - if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || - (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && - !isalpha(prev) && !isdigit(prev) && (prev != '_') && - (prev != '.')) - { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_GREEN); - isNum = true; - } +char* flecs_to_snake_case(const char *str) { + int32_t upper_count = 0, len = 1; + const char *ptr = str; + char ch, *out, *out_ptr; - if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - isVar = false; - } + for (ptr = &str[1]; (ch = *ptr); ptr ++) { + if (isupper(ch)) { + upper_count ++; + } + len ++; + } - if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); - isVar = true; + out = out_ptr = ecs_os_malloc_n(char, len + upper_count + 1); + for (ptr = str; (ch = *ptr); ptr ++) { + if (isupper(ch)) { + if ((ptr != str) && (out_ptr[-1] != '_')) { + out_ptr[0] = '_'; + out_ptr ++; } + out_ptr[0] = (char)tolower(ch); + out_ptr ++; + } else { + out_ptr[0] = ch; + out_ptr ++; } + } - if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { - bool isColor = true; - overrideColor = true; + out_ptr[0] = '\0'; - /* Custom colors */ - if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { - autoColor = false; - } else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_GREEN); - } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_RED); - } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_BLUE); - } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_MAGENTA); - } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_CYAN); - } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_YELLOW); - } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_GREY); - } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_BOLD); - } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { - overrideColor = false; - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } else { - isColor = false; - overrideColor = false; - } + return out; +} - if (isColor) { - ptr += 2; - while ((ch = *ptr) != ']') ptr ++; - dontAppend = true; - } - if (!autoColor) { - overrideColor = true; - } - } +char* flecs_load_from_file( + const char *filename) +{ + FILE* file; + char* content = NULL; + int32_t bytes; + size_t size; - if (ch == '\n') { - if (isNum || isStr || isVar || overrideColor) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - overrideColor = false; - isNum = false; - isStr = false; - isVar = false; - } - } + /* Open file for reading */ + ecs_os_fopen(&file, filename, "r"); + if (!file) { + ecs_err("%s (%s)", ecs_os_strerror(errno), filename); + goto error; + } - if (!dontAppend) { - ecs_strbuf_appendstrn(buf, ptr, 1); - } - - if (!overrideColor) { - if (((ch == '\'') || (ch == '"')) && !isStr) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } - } - - prev = ch; - } - - if (isNum || isStr || isVar || overrideColor) { - if (enable_colors) ecs_strbuf_appendstr(buf, ECS_NORMAL); - } -} - -void _ecs_logv( - int level, - const char *file, - int32_t line, - const char *fmt, - va_list args) -{ - (void)level; - (void)line; - - ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; - - if (level > ecs_os_api.log_level_) { - return; + /* Determine file size */ + fseek(file, 0, SEEK_END); + bytes = (int32_t)ftell(file); + if (bytes == -1) { + goto error; } + fseek(file, 0, SEEK_SET); - /* Apply color. Even if we don't want color, we still need to call the - * colorize function to get rid of the color tags (e.g. #[green]) */ - char *msg_nocolor = ecs_vasprintf(fmt, args); - ecs_colorize_buf(msg_nocolor, ecs_os_api.log_with_color_, &msg_buf); - ecs_os_free(msg_nocolor); - - char *msg = ecs_strbuf_get(&msg_buf); - if (msg) { - ecs_os_api.log_(level, file, line, msg); - ecs_os_free(msg); + /* Load contents in memory */ + content = ecs_os_malloc(bytes + 1); + size = (size_t)bytes; + if (!(size = fread(content, 1, size, file)) && bytes) { + ecs_err("%s: read zero bytes instead of %d", filename, size); + ecs_os_free(content); + content = NULL; + goto error; } else { - ecs_os_api.log_(level, file, line, ""); + content[size] = '\0'; } -} -void _ecs_log( - int level, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - va_list args; - va_start(args, fmt); - _ecs_logv(level, file, line, fmt, args); - va_end(args); -} + fclose(file); -void _ecs_log_push( - int32_t level) -{ - if (level <= ecs_os_api.log_level_) { - ecs_os_api.log_indent_ ++; + return content; +error: + if (file) { + fclose(file); } + ecs_os_free(content); + return NULL; } -void _ecs_log_pop( - int32_t level) +char* flecs_chresc( + char *out, + char in, + char delimiter) { - if (level <= ecs_os_api.log_level_) { - ecs_os_api.log_indent_ --; + char *bptr = out; + switch(in) { + case '\a': + *bptr++ = '\\'; + *bptr = 'a'; + break; + case '\b': + *bptr++ = '\\'; + *bptr = 'b'; + break; + case '\f': + *bptr++ = '\\'; + *bptr = 'f'; + break; + case '\n': + *bptr++ = '\\'; + *bptr = 'n'; + break; + case '\r': + *bptr++ = '\\'; + *bptr = 'r'; + break; + case '\t': + *bptr++ = '\\'; + *bptr = 't'; + break; + case '\v': + *bptr++ = '\\'; + *bptr = 'v'; + break; + case '\\': + *bptr++ = '\\'; + *bptr = '\\'; + break; + case '\033': + *bptr = '['; /* Used for terminal colors */ + break; + default: + if (in == delimiter) { + *bptr++ = '\\'; + *bptr = delimiter; + } else { + *bptr = in; + } + break; } -} - -void _ecs_parser_errorv( - const char *name, - const char *expr, - int64_t column_arg, - const char *fmt, - va_list args) -{ - int32_t column = flecs_itoi32(column_arg); - if (ecs_os_api.log_level_ >= -2) { - ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; + *(++bptr) = '\0'; - ecs_strbuf_vappend(&msg_buf, fmt, args); + return bptr; +} - if (expr) { - ecs_strbuf_appendstr(&msg_buf, "\n"); +const char* flecs_chrparse( + const char *in, + char *out) +{ + const char *result = in + 1; + char ch; - /* Find start of line by taking column and looking for the - * last occurring newline */ - if (column != -1) { - const char *ptr = &expr[column]; - while (ptr[0] != '\n' && ptr > expr) { - ptr --; - } + if (in[0] == '\\') { + result ++; - if (ptr == expr) { - /* ptr is already at start of line */ - } else { - column -= (int32_t)(ptr - expr + 1); - expr = ptr + 1; - } - } + switch(in[1]) { + case 'a': + ch = '\a'; + break; + case 'b': + ch = '\b'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + case 'v': + ch = '\v'; + break; + case '\\': + ch = '\\'; + break; + case '"': + ch = '"'; + break; + case '0': + ch = '\0'; + break; + case ' ': + ch = ' '; + break; + case '$': + ch = '$'; + break; + default: + goto error; + } + } else { + ch = in[0]; + } - /* Strip newlines from current statement, if any */ - char *newline_ptr = strchr(expr, '\n'); - if (newline_ptr) { - /* Strip newline from expr */ - ecs_strbuf_appendstrn(&msg_buf, expr, - (int32_t)(newline_ptr - expr)); - } else { - ecs_strbuf_appendstr(&msg_buf, expr); - } + if (out) { + *out = ch; + } - ecs_strbuf_appendstr(&msg_buf, "\n"); + return result; +error: + return NULL; +} - if (column != -1) { - ecs_strbuf_append(&msg_buf, "%*s^", column, ""); +ecs_size_t flecs_stresc( + char *out, + ecs_size_t n, + char delimiter, + const char *in) +{ + const char *ptr = in; + char ch, *bptr = out, buff[3]; + ecs_size_t written = 0; + while ((ch = *ptr++)) { + if ((written += (ecs_size_t)(flecs_chresc( + buff, ch, delimiter) - buff)) <= n) + { + /* If size != 0, an out buffer must be provided. */ + ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL); + *bptr++ = buff[0]; + if ((ch = buff[1])) { + *bptr = ch; + bptr++; } } + } - char *msg = ecs_strbuf_get(&msg_buf); - ecs_os_err(name, 0, msg); - ecs_os_free(msg); + if (bptr) { + while (written < n) { + *bptr = '\0'; + bptr++; + written++; + } } + return written; +error: + return 0; } -void _ecs_parser_error( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) +char* flecs_astresc( + char delimiter, + const char *in) { - if (ecs_os_api.log_level_ >= -2) { - va_list args; - va_start(args, fmt); - _ecs_parser_errorv(name, expr, column, fmt, args); - va_end(args); + if (!in) { + return NULL; } + + ecs_size_t len = flecs_stresc(NULL, 0, delimiter, in); + char *out = ecs_os_malloc_n(char, len + 1); + flecs_stresc(out, len, delimiter, in); + out[len] = '\0'; + return out; } -void _ecs_abort( - int32_t err, - const char *file, - int32_t line, - const char *fmt, - ...) +static +bool flecs_parse_is_e( + char e) { - if (fmt) { - va_list args; - va_start(args, fmt); - char *msg = ecs_vasprintf(fmt, args); - va_end(args); - _ecs_fatal(file, line, "%s (%s)", msg, ecs_strerror(err)); - ecs_os_free(msg); - } else { - _ecs_fatal(file, line, "%s", ecs_strerror(err)); - } - ecs_os_api.log_last_error_ = err; + return e == 'e' || e == 'E'; } -bool _ecs_assert( - bool condition, - int32_t err, - const char *cond_str, - const char *file, - int32_t line, - const char *fmt, - ...) +const char* flecs_parse_digit( + const char *ptr, + char *token) { - if (!condition) { - if (fmt) { - va_list args; - va_start(args, fmt); - char *msg = ecs_vasprintf(fmt, args); - va_end(args); - _ecs_fatal(file, line, "assert: %s %s (%s)", - cond_str, msg, ecs_strerror(err)); - ecs_os_free(msg); - } else { - _ecs_fatal(file, line, "assert: %s %s", - cond_str, ecs_strerror(err)); - } - ecs_os_api.log_last_error_ = err; + char *tptr = token; + char ch = ptr[0]; + + if (!isdigit(ch) && ch != '-') { + ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); + return NULL; } - return condition; -} + tptr[0] = ch; + tptr ++; + ptr ++; -void _ecs_deprecated( - const char *file, - int32_t line, - const char *msg) -{ - _ecs_err(file, line, "%s", msg); -} + for (; (ch = *ptr); ptr ++) { + if (!isdigit(ch) && (ch != '.') && !flecs_parse_is_e(ch)) { + if (ch != '-' || !flecs_parse_is_e(ptr[-1])) { + break; + } + } -bool ecs_should_log(int32_t level) { -# if !defined(FLECS_LOG_3) - if (level == 3) { - return false; - } -# endif -# if !defined(FLECS_LOG_2) - if (level == 2) { - return false; - } -# endif -# if !defined(FLECS_LOG_1) - if (level == 1) { - return false; + tptr[0] = ch; + tptr ++; } -# endif - return level <= ecs_os_api.log_level_; + tptr[0] = '\0'; + + return ptr; } -#define ECS_ERR_STR(code) case code: return &(#code[4]) - -const char* ecs_strerror( - int32_t error_code) +const char* flecs_parse_ws_eol( + const char *ptr) { - switch (error_code) { - ECS_ERR_STR(ECS_INVALID_PARAMETER); - ECS_ERR_STR(ECS_NOT_A_COMPONENT); - ECS_ERR_STR(ECS_INTERNAL_ERROR); - ECS_ERR_STR(ECS_ALREADY_DEFINED); - ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); - ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); - ECS_ERR_STR(ECS_NAME_IN_USE); - ECS_ERR_STR(ECS_OUT_OF_MEMORY); - ECS_ERR_STR(ECS_OPERATION_FAILED); - ECS_ERR_STR(ECS_INVALID_CONVERSION); - ECS_ERR_STR(ECS_MODULE_UNDEFINED); - ECS_ERR_STR(ECS_MISSING_SYMBOL); - ECS_ERR_STR(ECS_ALREADY_IN_USE); - ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); - ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); - ECS_ERR_STR(ECS_COLUMN_IS_SHARED); - ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); - ECS_ERR_STR(ECS_INVALID_WHILE_ITERATING); - ECS_ERR_STR(ECS_INVALID_FROM_WORKER); - ECS_ERR_STR(ECS_OUT_OF_RANGE); - ECS_ERR_STR(ECS_MISSING_OS_API); - ECS_ERR_STR(ECS_UNSUPPORTED); - ECS_ERR_STR(ECS_COLUMN_ACCESS_VIOLATION); - ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); - ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); - ECS_ERR_STR(ECS_TYPE_INVALID_CASE); - ECS_ERR_STR(ECS_INCONSISTENT_NAME); - ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); - ECS_ERR_STR(ECS_INVALID_OPERATION); - ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); - ECS_ERR_STR(ECS_LOCKED_STORAGE); - ECS_ERR_STR(ECS_ID_IN_USE); + while (isspace(*ptr)) { + ptr ++; } - return "unknown error code"; + return ptr; } -#else - -/* Empty bodies for when logging is disabled */ +#define FLECS_ERRSTR_MAX (256) +static char flecs_errstr_buf[FLECS_ERRSTR_MAX]; +static char flecs_errstr_buf_1[FLECS_ERRSTR_MAX]; +static char flecs_errstr_buf_2[FLECS_ERRSTR_MAX]; +static char flecs_errstr_buf_3[FLECS_ERRSTR_MAX]; +static char flecs_errstr_buf_4[FLECS_ERRSTR_MAX]; +static char flecs_errstr_buf_5[FLECS_ERRSTR_MAX]; -void _ecs_log( - int32_t level, - const char *file, - int32_t line, - const char *fmt, - ...) +const char* flecs_errstr( + char *str) { - (void)level; - (void)file; - (void)line; - (void)fmt; + ecs_os_strncpy(flecs_errstr_buf, str, FLECS_ERRSTR_MAX - 1); + ecs_os_free(str); + return flecs_errstr_buf; } -void _ecs_parser_error( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) +const char* flecs_errstr_1( + char *str) { - (void)name; - (void)expr; - (void)column; - (void)fmt; + ecs_os_strncpy(flecs_errstr_buf_1, str, FLECS_ERRSTR_MAX - 1); + ecs_os_free(str); + return flecs_errstr_buf_1; } -void _ecs_parser_errorv( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - va_list args) +const char* flecs_errstr_2( + char *str) { - (void)name; - (void)expr; - (void)column; - (void)fmt; - (void)args; + ecs_os_strncpy(flecs_errstr_buf_2, str, FLECS_ERRSTR_MAX - 1); + ecs_os_free(str); + return flecs_errstr_buf_2; } -void _ecs_abort( - int32_t error_code, - const char *file, - int32_t line, - const char *fmt, - ...) +const char* flecs_errstr_3( + char *str) { - (void)error_code; - (void)file; - (void)line; - (void)fmt; + ecs_os_strncpy(flecs_errstr_buf_3, str, FLECS_ERRSTR_MAX - 1); + ecs_os_free(str); + return flecs_errstr_buf_3; } -bool _ecs_assert( - bool condition, - int32_t error_code, - const char *condition_str, - const char *file, - int32_t line, - const char *fmt, - ...) +const char* flecs_errstr_4( + char *str) { - (void)condition; - (void)error_code; - (void)condition_str; - (void)file; - (void)line; - (void)fmt; - return true; + ecs_os_strncpy(flecs_errstr_buf_4, str, FLECS_ERRSTR_MAX - 1); + ecs_os_free(str); + return flecs_errstr_buf_4; } -#endif - -int ecs_log_set_level( - int level) +const char* flecs_errstr_5( + char *str) { - int prev = level; - ecs_os_api.log_level_ = level; - return prev; + ecs_os_strncpy(flecs_errstr_buf_5, str, FLECS_ERRSTR_MAX - 1); + ecs_os_free(str); + return flecs_errstr_buf_5; } -bool ecs_log_enable_colors( - bool enabled) +/** + * @file observable.c + * @brief Observable implementation. + * + * The observable implementation contains functions that find the set of + * observers to invoke for an event. The code also contains the implementation + * of a reachable id cache, which is used to speedup event propagation when + * relationships are added/removed to/from entities. + */ + + +void flecs_observable_init( + ecs_observable_t *observable) { - bool prev = ecs_os_api.log_with_color_; - ecs_os_api.log_with_color_ = enabled; - return prev; + flecs_sparse_init_t(&observable->events, NULL, NULL, ecs_event_record_t); + ecs_vec_init_t(NULL, &observable->global_observers, ecs_observer_t*, 0); + observable->on_add.event = EcsOnAdd; + observable->on_remove.event = EcsOnRemove; + observable->on_set.event = EcsOnSet; } -int ecs_log_last_error(void) +void flecs_observable_fini( + ecs_observable_t *observable) { - int result = ecs_os_api.log_last_error_; - ecs_os_api.log_last_error_ = 0; - return result; -} + int32_t i, count = ecs_vec_count(&observable->global_observers); + ecs_observer_t **observers = ecs_vec_first(&observable->global_observers); + for (i = 0; i < count; i ++) { + flecs_observer_fini(observers[i]); + } -#ifndef FLECS_SYSTEM_PRIVATE_H -#define FLECS_SYSTEM_PRIVATE_H + ecs_assert(!ecs_map_is_init(&observable->on_add.event_ids), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!ecs_map_is_init(&observable->on_remove.event_ids), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!ecs_map_is_init(&observable->on_set.event_ids), + ECS_INTERNAL_ERROR, NULL); -#ifdef FLECS_SYSTEM + ecs_sparse_t *events = &observable->events; + count = flecs_sparse_count(events); + for (i = 0; i < count; i ++) { + ecs_event_record_t *er = + flecs_sparse_get_dense_t(events, ecs_event_record_t, i); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + (void)er; + /* All observers should've unregistered by now */ + ecs_assert(!ecs_map_is_init(&er->event_ids), + ECS_INTERNAL_ERROR, NULL); + } -typedef struct EcsSystem { - ecs_run_action_t run; /* See ecs_system_desc_t */ - ecs_iter_action_t action; /* See ecs_system_desc_t */ + ecs_vec_fini_t(NULL, &observable->global_observers, ecs_observer_t*); + flecs_sparse_fini(&observable->events); +} - ecs_entity_t entity; /* Entity id of system, used for ordering */ - ecs_query_t *query; /* System query */ - ecs_system_status_action_t status_action; /* Status action */ - ecs_entity_t tick_source; /* Tick source associated with system */ +ecs_event_record_t* flecs_event_record_get( + const ecs_observable_t *o, + ecs_entity_t event) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - /* Schedule parameters */ - bool multi_threaded; - bool no_staging; + /* Builtin events*/ + if (event == EcsOnAdd) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_add); + else if (event == EcsOnRemove) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_remove); + else if (event == EcsOnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_set); + else if (event == EcsWildcard) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_wildcard); - int32_t invoke_count; /* Number of times system is invoked */ - float time_spent; /* Time spent on running system */ - FLECS_FLOAT time_passed; /* Time passed since last invocation */ - int32_t last_frame; /* Last frame for which the system was considered */ + /* User events */ + return flecs_sparse_get_t(&o->events, ecs_event_record_t, event); +} - ecs_entity_t self; /* Entity associated with system */ +ecs_event_record_t* flecs_event_record_ensure( + ecs_observable_t *o, + ecs_entity_t event) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - void *ctx; /* Userdata for system */ - void *status_ctx; /* User data for status action */ - void *binding_ctx; /* Optional language binding context */ + ecs_event_record_t *er = flecs_event_record_get(o, event); + if (er) { + return er; + } + er = flecs_sparse_get_t(&o->events, ecs_event_record_t, event); + if (!er) { + er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event, NULL); + } + er->event = event; + return er; +} - ecs_ctx_free_t ctx_free; - ecs_ctx_free_t status_ctx_free; - ecs_ctx_free_t binding_ctx_free; -} EcsSystem; +static +const ecs_event_record_t* flecs_event_record_get_if( + const ecs_observable_t *o, + ecs_entity_t event) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); -/* Invoked when system becomes active / inactive */ -void ecs_system_activate( - ecs_world_t *world, - ecs_entity_t system, - bool activate, - const EcsSystem *system_data); + const ecs_event_record_t *er = flecs_event_record_get(o, event); + if (er) { + if (ecs_map_is_init(&er->event_ids)) { + return er; + } + if (er->any) { + return er; + } + if (er->wildcard) { + return er; + } + if (er->wildcard_pair) { + return er; + } + } -/* Internal function to run a system */ -ecs_entity_t ecs_run_intern( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t system, - EcsSystem *system_data, - int32_t stage_current, - int32_t stage_count, - FLECS_FLOAT delta_time, - int32_t offset, - int32_t limit, - void *param); + return NULL; +} -#endif +ecs_event_id_record_t* flecs_event_id_record_get( + const ecs_event_record_t *er, + ecs_id_t id) +{ + if (!er) { + return NULL; + } -#endif + if (id == EcsAny) return er->any; + else if (id == EcsWildcard) return er->wildcard; + else if (id == ecs_pair(EcsWildcard, EcsWildcard)) return er->wildcard_pair; + else { + if (ecs_map_is_init(&er->event_ids)) { + return ecs_map_get_deref(&er->event_ids, ecs_event_id_record_t, id); + } + return NULL; + } +} +static +ecs_event_id_record_t* flecs_event_id_record_get_if( + const ecs_event_record_t *er, + ecs_id_t id) +{ + ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); + if (!ider) { + return NULL; + } -#ifdef FLECS_PIPELINE -#ifndef FLECS_PIPELINE_PRIVATE_H -#define FLECS_PIPELINE_PRIVATE_H + if (ider->observer_count) { + return ider; + } + return NULL; +} -/** Instruction data for pipeline. - * This type is the element type in the "ops" vector of a pipeline and contains - * information about the set of systems that need to be ran before a merge. */ -typedef struct ecs_pipeline_op_t { - int32_t count; /* Number of systems to run before merge */ - bool multi_threaded; /* Whether systems can be ran multi threaded */ - bool no_staging; /* Whether systems are staged or not */ -} ecs_pipeline_op_t; +ecs_event_id_record_t* flecs_event_id_record_ensure( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_id_t id) +{ + ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); + if (ider) { + return ider; + } -typedef struct EcsPipelineQuery { - ecs_query_t *query; - ecs_query_t *build_query; - ecs_vector_t *ops; - int32_t match_count; - int32_t rebuild_count; - ecs_entity_t last_system; -} EcsPipelineQuery; + ider = ecs_os_calloc_t(ecs_event_id_record_t); -//////////////////////////////////////////////////////////////////////////////// -//// Pipeline API -//////////////////////////////////////////////////////////////////////////////// - -/** Update a pipeline (internal function). - * Before running a pipeline, it must be updated. During this update phase - * all systems in the pipeline are collected, ordered and sync points are - * inserted where necessary. This operation may only be called when staging is - * disabled. - * - * Because multiple threads may run a pipeline, preparing the pipeline must - * happen synchronously, which is why this function is separate from - * ecs_run_pipeline. Not running the prepare step may cause systems to not get - * ran, or ran in the wrong order. - * - * If 0 is provided for the pipeline id, the default pipeline will be ran (this - * is either the builtin pipeline or the pipeline set with set_pipeline()). - * - * @param world The world. - * @param pipeline The pipeline to run. - * @return The number of elements in the pipeline. - */ -bool ecs_pipeline_update( - ecs_world_t *world, - ecs_entity_t pipeline, - bool start_of_frame); - -int32_t ecs_pipeline_reset_iter( - ecs_world_t *world, - const EcsPipelineQuery *pq, - ecs_iter_t *iter_out, - ecs_pipeline_op_t **op_out, - ecs_pipeline_op_t **last_op_out); - -//////////////////////////////////////////////////////////////////////////////// -//// Worker API -//////////////////////////////////////////////////////////////////////////////// - -void ecs_worker_begin( - ecs_world_t *world); - -int32_t ecs_worker_sync( - ecs_world_t *world, - const EcsPipelineQuery *pq, - ecs_iter_t *it, - int32_t i, - ecs_pipeline_op_t **op_out, - ecs_pipeline_op_t **last_op_out); - -void ecs_worker_end( - ecs_world_t *world); - -void ecs_workers_progress( - ecs_world_t *world, - ecs_entity_t pipeline, - FLECS_FLOAT delta_time); - -#endif - - -/* Worker thread */ -static -void* worker(void *arg) { - ecs_stage_t *stage = arg; - ecs_world_t *world = stage->world; - - /* Start worker thread, increase counter so main thread knows how many - * workers are ready */ - ecs_os_mutex_lock(world->sync_mutex); - world->workers_running ++; - - if (!world->quit_workers) { - ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + if (id == EcsAny) { + return er->any = ider; + } else if (id == EcsWildcard) { + return er->wildcard = ider; + } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { + return er->wildcard_pair = ider; } - ecs_os_mutex_unlock(world->sync_mutex); - - while (!world->quit_workers) { - ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); - - ecs_run_pipeline( - (ecs_world_t*)stage, - world->pipeline, - world->info.delta_time); + ecs_map_init_if(&er->event_ids, &world->allocator); + ecs_map_insert_ptr(&er->event_ids, id, ider); + return ider; +} - ecs_set_scope((ecs_world_t*)stage, old_scope); +void flecs_event_id_record_remove( + ecs_event_record_t *er, + ecs_id_t id) +{ + if (id == EcsAny) { + er->any = NULL; + } else if (id == EcsWildcard) { + er->wildcard = NULL; + } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { + er->wildcard_pair = NULL; + } else { + ecs_map_remove(&er->event_ids, id); + if (!ecs_map_count(&er->event_ids)) { + ecs_map_fini(&er->event_ids); + } } - - ecs_os_mutex_lock(world->sync_mutex); - world->workers_running --; - ecs_os_mutex_unlock(world->sync_mutex); - - return NULL; } -/* Start threads */ static -void start_workers( - ecs_world_t *world, - int32_t threads) +int32_t flecs_event_observers_get( + const ecs_event_record_t *er, + ecs_id_t id, + ecs_event_id_record_t **iders) { - ecs_set_stages(world, threads); - - ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); + if (!er) { + return 0; + } - int32_t i; - for (i = 0; i < threads; i ++) { - ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); - ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_poly_assert(stage, ecs_stage_t); + /* Populate array with observer sets matching the id */ + int32_t count = 0; - ecs_vector_get(world->worker_stages, ecs_stage_t, i); - stage->thread = ecs_os_thread_new(worker, stage); - ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); + if (id != EcsAny) { + iders[0] = flecs_event_id_record_get_if(er, EcsAny); + count += iders[count] != 0; } -} -/* Wait until all workers are running */ -static -void wait_for_workers( - ecs_world_t *world) -{ - int32_t stage_count = ecs_get_stage_count(world); - bool wait = true; + iders[count] = flecs_event_id_record_get_if(er, id); + count += iders[count] != 0; - do { - ecs_os_mutex_lock(world->sync_mutex); - if (world->workers_running == stage_count) { - wait = false; + if (id != EcsAny) { + if (ECS_IS_PAIR(id)) { + ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); + ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); + ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); + if (id_fwc != id) { + iders[count] = flecs_event_id_record_get_if(er, id_fwc); + count += iders[count] != 0; + } + if (id_swc != id) { + iders[count] = flecs_event_id_record_get_if(er, id_swc); + count += iders[count] != 0; + } + if (id_pwc != id) { + iders[count] = flecs_event_id_record_get_if(er, id_pwc); + count += iders[count] != 0; + } + } else if (id != EcsWildcard) { + iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); + count += iders[count] != 0; } - ecs_os_mutex_unlock(world->sync_mutex); - } while (wait); -} - -/* Synchronize worker threads */ -static -void sync_worker( - ecs_world_t *world) -{ - int32_t stage_count = ecs_get_stage_count(world); - - /* Signal that thread is waiting */ - ecs_os_mutex_lock(world->sync_mutex); - if (++ world->workers_waiting == stage_count) { - /* Only signal main thread when all threads are waiting */ - ecs_os_cond_signal(world->sync_cond); } - /* Wait until main thread signals that thread can continue */ - ecs_os_cond_wait(world->worker_cond, world->sync_mutex); - ecs_os_mutex_unlock(world->sync_mutex); + return count; } -/* Wait until all threads are waiting on sync point */ -static -void wait_for_sync( - ecs_world_t *world) +bool flecs_observers_exist( + const ecs_observable_t *observable, + ecs_id_t id, + ecs_entity_t event) { - int32_t stage_count = ecs_get_stage_count(world); - - ecs_os_mutex_lock(world->sync_mutex); - if (world->workers_waiting != stage_count) { - ecs_os_cond_wait(world->sync_cond, world->sync_mutex); + const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); + if (!er) { + return false; } - - /* We should have been signalled unless all workers are waiting on sync */ - ecs_assert(world->workers_waiting == stage_count, - ECS_INTERNAL_ERROR, NULL); - ecs_os_mutex_unlock(world->sync_mutex); + return flecs_event_id_record_get_if(er, id) != NULL; } -/* Signal workers that they can start/resume work */ static -void signal_workers( - ecs_world_t *world) -{ - ecs_os_mutex_lock(world->sync_mutex); - ecs_os_cond_broadcast(world->worker_cond); - ecs_os_mutex_unlock(world->sync_mutex); -} +void flecs_emit_propagate( + ecs_world_t *world, + ecs_iter_t *it, + ecs_component_record_t *cr, + ecs_component_record_t *tgt_cr, + ecs_entity_t trav, + ecs_event_id_record_t **iders, + int32_t ider_count); -/** Stop worker threads */ static -bool ecs_stop_threads( - ecs_world_t *world) +void flecs_emit_propagate_id_for_range( + ecs_world_t *world, + ecs_iter_t *it, + ecs_component_record_t *cr, + ecs_entity_t trav, + ecs_event_id_record_t **iders, + int32_t ider_count, + ecs_table_range_t *range) { - bool threads_active = false; + ecs_table_t *table = range->table; + int32_t count = range->count; + int32_t offset = range->offset; - /* Test if threads are created. Cannot use workers_running, since this is - * a potential race if threads haven't spun up yet. */ - ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { - if (stage->thread) { - threads_active = true; - break; - } - stage->thread = 0; - }); + bool owned = flecs_component_get_table(cr, table) != NULL; - /* If no threads are active, just return */ - if (!threads_active) { - return false; + int32_t e; + it->table = table; + it->other_table = NULL; + it->offset = 0; + it->count = count; + it->up_fields = 1; + if (count) { + it->entities = &ecs_table_entities(table)[offset]; } - /* Make sure all threads are running, to ensure they catch the signal */ - wait_for_workers(world); - - /* Signal threads should quit */ - world->quit_workers = true; - signal_workers(world); + int32_t ider_i; + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); - /* Join all threads with main */ - ecs_stage_t *stages = ecs_vector_first(world->worker_stages, ecs_stage_t); - int32_t i, count = ecs_vector_count(world->worker_stages); - for (i = 0; i < count; i ++) { - ecs_os_thread_join(stages[i].thread); - stages[i].thread = 0; + if (!owned) { + /* Owned takes precedence */ + flecs_observers_invoke(world, &ider->self_up, it, table, trav); + } } - world->quit_workers = false; - ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); - - /* Deinitialize stages */ - ecs_set_stages(world, 0); - - return true; -} - -/* -- Private functions -- */ - -void ecs_worker_begin( - ecs_world_t *world) -{ - flecs_stage_from_world(&world); - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); - - if (stage_count == 1) { - ecs_entity_t pipeline = world->pipeline; - const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + if (!table->_->traversable_count) { + return; + } - ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); - if (!op || !op->no_staging) { - ecs_staging_begin(world); + const ecs_entity_t *entities = it->entities; + for (e = 0; e < count; e ++) { + ecs_component_record_t *cr_t = flecs_components_get( + world, ecs_pair(EcsWildcard, entities[e])); + if (cr_t) { + /* Only notify for entities that are used in pairs with + * traversable relationships */ + flecs_emit_propagate(world, it, cr, cr_t, trav, + iders, ider_count); } } } -int32_t ecs_worker_sync( +static +void flecs_emit_propagate_id( ecs_world_t *world, - const EcsPipelineQuery *pq, ecs_iter_t *it, - int32_t i, - ecs_pipeline_op_t **op_out, - ecs_pipeline_op_t **last_op_out) + ecs_component_record_t *cr, + ecs_component_record_t *cur, + ecs_entity_t trav, + ecs_event_id_record_t **iders, + int32_t ider_count) { - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); - int32_t build_count = world->info.pipeline_build_count_total; + ecs_table_cache_iter_t idt; - /* If there are no threads, merge in place */ - if (stage_count == 1) { - if (!op_out[0]->no_staging) { - ecs_staging_end(world); - } + if ((trav == EcsChildOf) && (flecs_component_has_non_fragmenting_childof(cur))) { + ecs_assert(ECS_PAIR_FIRST(cur->id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); - ecs_pipeline_update(world, world->pipeline, false); + if (!(cur->flags & EcsIdMarkedForDelete)) { + int32_t i, count = ecs_vec_count(&cur->pair->ordered_children); + ecs_entity_t *children = ecs_vec_first(&cur->pair->ordered_children); + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_get(world, children[i]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_emit_propagate_id_for_range( + world, it, cr, trav, iders, ider_count, + &(ecs_table_range_t){ + .table = r->table, + .offset = ECS_RECORD_TO_ROW(r->row), + .count = 1 + }); + } + } - /* Synchronize all workers. The last worker to reach the sync point will - * signal the main thread, which will perform the merge. */ - } else { - sync_worker(world); + return; } - if (build_count != world->info.pipeline_build_count_total) { - i = ecs_pipeline_reset_iter(world, pq, it, op_out, last_op_out); - } else { - op_out[0] ++; + if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { + return; } - if (stage_count == 1) { - if (!op_out[0]->no_staging) { - ecs_staging_begin(world); + const ecs_table_record_t *tr; + int32_t event_cur = it->event_cur; + while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!ecs_table_count(table)) { + continue; } + + flecs_emit_propagate_id_for_range( + world, it, cr, trav, iders, ider_count, + &(ecs_table_range_t){ + .table = table, + .count = ecs_table_count(table), + }); } - return i; + it->event_cur = event_cur; + it->up_fields = 0; } -void ecs_worker_end( - ecs_world_t *world) +static +void flecs_emit_propagate( + ecs_world_t *world, + ecs_iter_t *it, + ecs_component_record_t *cr, + ecs_component_record_t *tgt_cr, + ecs_entity_t propagate_trav, + ecs_event_id_record_t **iders, + int32_t ider_count) { - flecs_stage_from_world(&world); + ecs_assert(tgt_cr != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_cr->id); + ecs_dbg_3("propagate events/invalidate cache for %s", idstr); + ecs_os_free(idstr); + } + + ecs_log_push_3(); - /* If there are no threads, merge in place */ - if (stage_count == 1) { - if (ecs_stage_is_readonly(world)) { - ecs_staging_end(world); + /* Propagate to records of traversable relationships */ + ecs_component_record_t *cur = tgt_cr; + while ((cur = flecs_component_trav_next(cur))) { + cur->pair->reachable.generation ++; /* Invalidate cache */ + + /* Get traversed relationship */ + ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); + if (propagate_trav && propagate_trav != trav) { + if (propagate_trav != EcsIsA) { + continue; + } } - /* Synchronize all workers. The last worker to reach the sync point will - * signal the main thread, which will perform the merge. */ - } else { - sync_worker(world); + flecs_emit_propagate_id( + world, it, cr, cur, trav, iders, ider_count); } + + ecs_log_pop_3(); } -void ecs_workers_progress( +static +void flecs_emit_propagate_invalidate_tables( ecs_world_t *world, - ecs_entity_t pipeline, - FLECS_FLOAT delta_time) + ecs_component_record_t *tgt_cr) { - ecs_poly_assert(world, ecs_world_t); - int32_t stage_count = ecs_get_stage_count(world); + ecs_assert(tgt_cr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_time_t start = {0}; - if (world->measure_frame_time) { - ecs_time_measure(&start); + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_cr->id); + ecs_dbg_3("invalidate reachable cache for %s", idstr); + ecs_os_free(idstr); } - if (stage_count == 1) { - ecs_pipeline_update(world, pipeline, true); - ecs_entity_t old_scope = ecs_set_scope(world, 0); - ecs_world_t *stage = ecs_get_stage(world, 0); - ecs_run_pipeline(stage, pipeline, delta_time); - ecs_set_scope(world, old_scope); - } else { - ecs_pipeline_update(world, pipeline, true); + /* Invalidate records of traversable relationships */ + ecs_component_record_t *cur = tgt_cr; + while ((cur = flecs_component_trav_next(cur))) { + ecs_reachable_cache_t *rc = &cur->pair->reachable; + if (rc->current != rc->generation) { + /* Subtree is already marked invalid */ + continue; + } - const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); - ecs_vector_t *ops = pq->ops; - ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); - ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); + rc->generation ++; - /* Make sure workers are running and ready */ - wait_for_workers(world); + if (flecs_component_has_non_fragmenting_childof(cur)) { + int32_t i, count = ecs_vec_count(&cur->pair->ordered_children); + ecs_entity_t *children = ecs_vec_first(&cur->pair->ordered_children); - /* Synchronize n times for each op in the pipeline */ - for (; op <= op_last; op ++) { - if (!op->no_staging) { - ecs_staging_begin(world); + for (i = 0; i < count; i ++) { + ecs_component_record_t *cr_t = flecs_components_get( + world, ecs_pair(EcsWildcard, children[i])); + if (cr_t) { + flecs_emit_propagate_invalidate_tables(world, cr_t); + } } + } - /* Signal workers that they should start running systems */ - world->workers_waiting = 0; - signal_workers(world); - - /* Wait until all workers are waiting on sync point */ - wait_for_sync(world); + ecs_table_cache_iter_t idt; + if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { + continue; + } - /* Merge */ - if (!op->no_staging) { - ecs_staging_end(world); + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; } - if (ecs_pipeline_update(world, pipeline, false)) { - /* Refetch, in case pipeline itself has moved */ - pq = ecs_get(world, pipeline, EcsPipelineQuery); + int32_t e, entity_count = ecs_table_count(table); + const ecs_entity_t *entities = ecs_table_entities(table); - /* Pipeline has changed, reset position in pipeline */ - ecs_iter_t it; - ecs_pipeline_reset_iter(world, pq, &it, &op, &op_last); - op --; + for (e = 0; e < entity_count; e ++) { + ecs_component_record_t *cr_t = flecs_components_get( + world, ecs_pair(EcsWildcard, entities[e])); + if (cr_t) { + flecs_emit_propagate_invalidate_tables(world, cr_t); + } } } } - - if (world->measure_frame_time) { - world->info.system_time_total += (float)ecs_time_measure(&start); - } } -/* -- Public functions -- */ - -void ecs_set_threads( +void flecs_emit_propagate_invalidate( ecs_world_t *world, - int32_t threads) + ecs_table_t *table, + int32_t offset, + int32_t count) { - ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); - - int32_t stage_count = ecs_get_stage_count(world); - - if (stage_count != threads) { - /* Stop existing threads */ - if (stage_count > 1) { - if (ecs_stop_threads(world)) { - ecs_os_cond_free(world->worker_cond); - ecs_os_cond_free(world->sync_cond); - ecs_os_mutex_free(world->sync_mutex); - } - } - - /* Start threads if number of threads > 1 */ - if (threads > 1) { - world->worker_cond = ecs_os_cond_new(); - world->sync_cond = ecs_os_cond_new(); - world->sync_mutex = ecs_os_mutex_new(); - start_workers(world, threads); + const ecs_entity_t *entities = &ecs_table_entities(table)[offset]; + int32_t i; + for (i = 0; i < count; i ++) { + ecs_component_record_t *cr_t = flecs_components_get( + world, ecs_pair(EcsWildcard, entities[i])); + if (cr_t) { + /* Event is used as target in traversable relationship, propagate */ + flecs_emit_propagate_invalidate_tables(world, cr_t); } } } -#endif - +static +void flecs_propagate_entities( + ecs_world_t *world, + ecs_iter_t *it, + ecs_component_record_t *cr, + const ecs_entity_t *entities, + int32_t count, + ecs_entity_t src, + ecs_event_id_record_t **iders, + int32_t ider_count) +{ + if (!count) { + return; + } -#ifdef FLECS_PIPELINE + ecs_entity_t old_src = it->sources[0]; + ecs_table_t *old_table = it->table; + ecs_table_t *old_other_table = it->other_table; + const ecs_entity_t *old_entities = it->entities; + int32_t old_count = it->count; + int32_t old_offset = it->offset; -static ECS_DTOR(EcsPipelineQuery, ptr, { - ecs_vector_free(ptr->ops); -}) + int32_t i; + for (i = 0; i < count; i ++) { + ecs_component_record_t *cr_t = flecs_components_get( + world, ecs_pair(EcsWildcard, entities[i])); + if (cr_t) { + /* Entity is used as target in traversable pairs, propagate */ + ecs_entity_t e = src ? src : entities[i]; + it->sources[0] = e; + flecs_emit_propagate( + world, it, cr, cr_t, 0, iders, ider_count); + } + } + + it->table = old_table; + it->other_table = old_other_table; + it->entities = old_entities; + it->count = old_count; + it->offset = old_offset; + it->sources[0] = old_src; +} static -int compare_entity( - ecs_entity_t e1, - const void *ptr1, - ecs_entity_t e2, - const void *ptr2) -{ - (void)ptr1; - (void)ptr2; - return (e1 > e2) - (e1 < e2); -} +void flecs_emit_forward_up( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_component_record_t *cr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids, + int32_t depth); static -uint64_t group_by_phase( +void flecs_emit_forward_id( ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, ecs_table_t *table, - ecs_entity_t pipeline, - void *ctx) + ecs_component_record_t *cr, + ecs_entity_t tgt, + ecs_table_t *tgt_table, + int32_t column, + ecs_entity_t trav) { - (void)ctx; - - const EcsType *pt = ecs_get(world, pipeline, EcsType); - ecs_assert(pt != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id = cr->id; + ecs_entity_t event = er ? er->event : 0; + bool inherit = trav == EcsIsA; + bool may_override = inherit && (event == EcsOnAdd) && (emit_ids->count > 1); + ecs_event_id_record_t *iders[5]; + ecs_event_id_record_t *iders_onset[5]; - /* Find tag in system that belongs to pipeline */ - ecs_type_t type = ecs_table_get_type(table); - ecs_id_t *sys_comps = ecs_vector_first(type, ecs_id_t); - int32_t c, t, count = ecs_vector_count(type); - - ecs_type_t pipeline_type = NULL; - if (pt->normalized) { - pipeline_type = pt->normalized->type; + /* Skip id if there are no observers for it */ + int32_t ider_i, ider_count = flecs_event_observers_get(er, id, iders); + int32_t ider_onset_i, ider_onset_count = 0; + if (er_onset) { + ider_onset_count = flecs_event_observers_get( + er_onset, id, iders_onset); } - if (!pipeline_type) { - return 0; + if (!may_override && (!ider_count && !ider_onset_count)) { + return; } - ecs_id_t *tags = ecs_vector_first(pipeline_type, ecs_id_t); - int32_t tag_count = ecs_vector_count(pipeline_type); + ecs_entity_t old_src = it->sources[0]; - ecs_id_t result = 0; + it->ids[0] = id; + it->sources[0] = tgt; + it->event_id = id; + ECS_CONST_CAST(int32_t*, it->sizes)[0] = 0; /* safe, owned by observer */ + it->up_fields = 1; - for (c = 0; c < count; c ++) { - ecs_id_t comp = sys_comps[c]; - for (t = 0; t < tag_count; t ++) { - if (comp == tags[t]) { - result = comp; - break; - } - } - if (result) { - break; + int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column); + it->trs[0] = &tgt_table->_->records[column]; + if (storage_i != -1) { + ecs_column_t *c = &tgt_table->data.columns[storage_i]; + ecs_assert(cr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + ECS_CONST_CAST(int32_t*, it->sizes)[0] = c->ti->size; /* safe, see above */ + } + + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + bool owned = tr != NULL; + + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); + + /* Owned takes precedence */ + if (!owned) { + flecs_observers_invoke(world, &ider->self_up, it, table, trav); } } - ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result < INT_MAX, ECS_INTERNAL_ERROR, NULL); + /* Emit OnSet events for newly inherited components */ + if (storage_i != -1) { + if (ider_onset_count) { + it->event = er_onset->event; - return result; -} + for (ider_onset_i = 0; ider_onset_i < ider_onset_count; ider_onset_i ++) { + ecs_event_id_record_t *ider = iders_onset[ider_onset_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); + + /* Owned takes precedence */ + if (!owned) { + flecs_observers_invoke( + world, &ider->self_up, it, table, trav); + } + } -typedef enum ComponentWriteState { - NotWritten = 0, - WriteToMain, - WriteToStage -} ComponentWriteState; + it->event = event; + } + } -typedef struct write_state_t { - ecs_map_t *components; - bool wildcard; -} write_state_t; + it->sources[0] = old_src; + it->up_fields = 0; +} static -int32_t get_write_state( - ecs_map_t *write_state, - ecs_entity_t component) -{ - int32_t *ptr = ecs_map_get(write_state, int32_t, component); - if (ptr) { - return *ptr; - } else { - return 0; - } +void flecs_emit_forward_and_cache_id( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_component_record_t *cr, + ecs_entity_t tgt, + ecs_record_t *tgt_record, + ecs_table_t *tgt_table, + const ecs_table_record_t *tgt_tr, + int32_t column, + ecs_vec_t *reachable_ids, + ecs_entity_t trav) +{ + /* Cache forwarded id for (rel, tgt) pair */ + ecs_reachable_elem_t *elem = ecs_vec_append_t(&world->allocator, + reachable_ids, ecs_reachable_elem_t); + elem->tr = tgt_tr; + elem->record = tgt_record; + elem->src = tgt; + elem->id = cr->id; +#ifndef FLECS_NDEBUG + elem->table = tgt_table; +#endif + ecs_assert(tgt_table == tgt_record->table, ECS_INTERNAL_ERROR, NULL); + + flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, cr, + tgt, tgt_table, column, trav); } static -void set_write_state( - write_state_t *write_state, - ecs_entity_t component, - int32_t value) +int32_t flecs_emit_stack_at( + ecs_vec_t *stack, + ecs_component_record_t *cr) { - if (component == EcsWildcard) { - ecs_assert(value == WriteToStage, ECS_INTERNAL_ERROR, NULL); - write_state->wildcard = true; - } else { - ecs_map_set(write_state->components, component, &value); + int32_t sp = 0, stack_count = ecs_vec_count(stack); + ecs_table_t **stack_elems = ecs_vec_first(stack); + + for (sp = 0; sp < stack_count; sp ++) { + ecs_table_t *elem = stack_elems[sp]; + if (flecs_component_get_table(cr, elem)) { + break; + } } + + return sp; } static -void reset_write_state( - write_state_t *write_state) +bool flecs_emit_stack_has( + ecs_vec_t *stack, + ecs_component_record_t *cr) { - ecs_map_clear(write_state->components); - write_state->wildcard = false; + return flecs_emit_stack_at(stack, cr) != ecs_vec_count(stack); } static -int32_t get_any_write_state( - write_state_t *write_state) -{ - if (write_state->wildcard) { - return WriteToStage; - } +void flecs_emit_forward_cached_ids( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_reachable_cache_t *rc, + ecs_vec_t *reachable_ids, + ecs_vec_t *stack, + ecs_entity_t trav) +{ + ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, + ecs_reachable_elem_t); + int32_t i, count = ecs_vec_count(&rc->ids); + for (i = 0; i < count; i ++) { + ecs_reachable_elem_t *rc_elem = &elems[i]; + const ecs_table_record_t *rc_tr = rc_elem->tr; + ecs_assert(rc_tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *rc_cr = rc_tr->hdr.cr; + ecs_record_t *rc_record = rc_elem->record; + + ecs_assert(rc_cr->id == rc_elem->id, ECS_INTERNAL_ERROR, NULL); + ecs_assert(rc_record != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_entities_get(world, rc_elem->src) == + rc_record, ECS_INTERNAL_ERROR, NULL); + ecs_dbg_assert(rc_record->table == rc_elem->table, + ECS_INTERNAL_ERROR, NULL); - ecs_map_iter_t it = ecs_map_iter(write_state->components); - int32_t *elem; - while ((elem = ecs_map_next(&it, int32_t, NULL))) { - if (*elem == WriteToStage) { - return WriteToStage; + if (flecs_emit_stack_has(stack, rc_cr)) { + continue; } - } - return 0; + flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, + it, table, rc_cr, rc_elem->src, + rc_record, rc_record->table, rc_tr, rc_tr->index, + reachable_ids, trav); + } } static -bool check_term_component( - ecs_term_t *term, - bool is_active, - ecs_entity_t component, - write_state_t *write_state) +void flecs_emit_dump_cache( + ecs_world_t *world, + const ecs_vec_t *vec) { - int32_t state = get_write_state(write_state->components, component); + ecs_reachable_elem_t *elems = ecs_vec_first_t(vec, ecs_reachable_elem_t); + for (int i = 0; i < ecs_vec_count(vec); i ++) { + ecs_reachable_elem_t *elem = &elems[i]; + char *idstr = ecs_id_str(world, elem->id); + char *estr = ecs_id_str(world, elem->src); + #ifndef FLECS_NDEBUG + ecs_table_t *table = elem->table; + #else + ecs_table_t *table = NULL; + #endif + (void)table; + ecs_dbg_3("- id: %s (%u), src: %s (%u), table: %p", + idstr, (uint32_t)elem->id, + estr, (uint32_t)elem->src, + table); + ecs_os_free(idstr); + ecs_os_free(estr); + } + if (!ecs_vec_count(vec)) { + ecs_dbg_3("- no entries"); + } +} - ecs_term_id_t *subj = &term->subj; +static +void flecs_emit_forward_table_up( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t tgt, + ecs_table_t *tgt_table, + ecs_record_t *tgt_record, + ecs_component_record_t *tgt_cr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids, + int32_t depth) +{ + ecs_allocator_t *a = &world->allocator; + int32_t i, id_count = tgt_table->type.count; + ecs_id_t *ids = tgt_table->type.array; + int32_t rc_child_offset = ecs_vec_count(reachable_ids); + int32_t stack_count = ecs_vec_count(stack); + + /* If tgt_cr is out of sync but is not the current component record being updated, + * keep track so that we can update two records for the cost of one. */ + ecs_assert(tgt_cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_reachable_cache_t *rc = &tgt_cr->pair->reachable; + bool parent_revalidate = (reachable_ids != &rc->ids) && + (rc->current != rc->generation); + if (parent_revalidate) { + ecs_vec_reset_t(a, &rc->ids, ecs_reachable_elem_t); + } + + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_cr->id); + ecs_dbg_3("forward events from %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); + + /* Function may have to copy values from overridden components if an IsA + * relationship was added together with other components. */ + ecs_entity_t trav = ECS_PAIR_FIRST(tgt_cr->id); + bool inherit = trav == EcsIsA; + + for (i = 0; i < id_count; i ++) { + ecs_id_t id = ids[i]; + ecs_table_record_t *tgt_tr = &tgt_table->_->records[i]; + ecs_component_record_t *cr = tgt_tr->hdr.cr; + if (inherit && !(cr->flags & EcsIdOnInstantiateInherit)) { + continue; + } - if ((subj->set.mask & EcsSelf) && subj->entity == EcsThis && term->oper != EcsNot) { - switch(term->inout) { - case EcsInOutFilter: - /* Ignore terms that aren't read/written */ - break; - case EcsInOutDefault: - case EcsInOut: - case EcsIn: - if (state == WriteToStage || write_state->wildcard) { - return true; - } - // fall through - case EcsOut: - if (is_active && term->inout != EcsIn) { - set_write_state(write_state, component, WriteToMain); - } - }; + if (cr == tgt_cr) { + char *idstr = ecs_id_str(world, cr->id); + ecs_assert(cr != tgt_cr, ECS_CYCLE_DETECTED, idstr); + ecs_os_free(idstr); + return; + } - } else if (!subj->entity || term->oper == EcsNot) { - bool needs_merge = false; + /* Id has the same relationship, traverse to find ids for forwarding */ + if (ECS_PAIR_FIRST(id) == trav || ECS_PAIR_FIRST(id) == EcsIsA || ( + (trav == EcsChildOf) && id == ecs_id(EcsParent))) + { + ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, + ecs_table_t*); + t[0] = tgt_table; - switch(term->inout) { - case EcsInOutDefault: - case EcsIn: - case EcsInOut: - if (state == WriteToStage) { - needs_merge = true; + if (id == ecs_id(EcsParent)) { + const EcsParent *parent = ecs_get(world, tgt, EcsParent); + ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(parent->value != 0, ECS_INTERNAL_ERROR, NULL); + + cr = flecs_components_get(world, ecs_childof(parent->value)); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); } - if (component == EcsWildcard) { - if (get_any_write_state(write_state) == WriteToStage) { - needs_merge = true; + + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_reachable_cache_t *cr_rc = &cr->pair->reachable; + if (cr_rc->current == cr_rc->generation) { + /* Cache hit, use cached ids to prevent traversing the same + * hierarchy multiple times. This especially speeds up code + * where (deep) hierarchies are created. */ + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, id); + ecs_dbg_3("forward cached for %s", idstr); + ecs_os_free(idstr); } - } - break; - default: - break; - }; + ecs_log_push_3(); + flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it, + table, cr_rc, reachable_ids, stack, trav); + ecs_log_pop_3(); + } else { + /* Cache is dirty, traverse upwards */ + do { + flecs_emit_forward_up(world, er, er_onset, emit_ids, it, + table, cr, stack, reachable_ids, depth); + if (++i >= id_count) { + break; + } - switch(term->inout) { - case EcsInOutDefault: - if ((!(subj->set.mask & EcsSelf) || (subj->entity != EcsThis)) && (subj->set.mask != EcsNothing)) { - /* Default inout behavior is [inout] for This terms, and [in] - * for terms that match other entities */ - break; - } - // fall through - case EcsInOut: - case EcsOut: - if (is_active) { - set_write_state(write_state, component, WriteToStage); + id = ids[i]; + if (ECS_PAIR_FIRST(id) != trav) { + break; + } + } while (true); } - break; - default: - break; - }; - if (needs_merge) { - return true; + ecs_vec_remove_last(stack); + continue; } - } - return false; -} + int32_t stack_at = flecs_emit_stack_at(stack, cr); + if (parent_revalidate && (stack_at == (stack_count - 1))) { + /* If parent component record needs to be revalidated, add id */ + ecs_reachable_elem_t *elem = ecs_vec_append_t(a, &rc->ids, + ecs_reachable_elem_t); + elem->tr = tgt_tr; + elem->record = tgt_record; + elem->src = tgt; + elem->id = cr->id; +#ifndef FLECS_NDEBUG + elem->table = tgt_table; +#endif + } -static -bool check_term( - ecs_term_t *term, - bool is_active, - write_state_t *write_state) -{ - if (term->oper != EcsOr) { - return check_term_component( - term, is_active, term->id, write_state); - } + /* Skip id if it's masked by a lower table in the tree */ + if (stack_at != stack_count) { + continue; + } - return false; -} + flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, + table, cr, tgt, tgt_record, tgt_table, tgt_tr, i, + reachable_ids, trav); + } -static -bool check_terms( - ecs_filter_t *filter, - bool is_active, - write_state_t *ws) -{ - bool needs_merge = false; - ecs_term_t *terms = filter->terms; - int32_t t, term_count = filter->term_count; + if (parent_revalidate) { + /* If this is not the current cache being updated, but it's marked + * as out of date, use intermediate results to populate cache. */ + int32_t rc_parent_offset = ecs_vec_count(&rc->ids); - /* Check This terms first. This way if a term indicating writing to a stage - * was added before the term, it won't cause merging. */ - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &terms[t]; - if (term->subj.entity == EcsThis) { - needs_merge |= check_term(term, is_active, ws); + /* Only add ids that were added for this table */ + int32_t count = ecs_vec_count(reachable_ids); + count -= rc_child_offset; + + /* Append ids to any ids that already were added /*/ + if (count) { + ecs_vec_grow_t(a, &rc->ids, ecs_reachable_elem_t, count); + ecs_reachable_elem_t *dst = ecs_vec_get_t(&rc->ids, + ecs_reachable_elem_t, rc_parent_offset); + ecs_reachable_elem_t *src = ecs_vec_get_t(reachable_ids, + ecs_reachable_elem_t, rc_child_offset); + ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count); } - } - /* Now check staged terms */ - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &terms[t]; - if (term->subj.entity != EcsThis) { - needs_merge |= check_term(term, is_active, ws); + rc->current = rc->generation; + + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_cr->id); + ecs_dbg_3("cache revalidated for %s:", idstr); + ecs_os_free(idstr); + flecs_emit_dump_cache(world, &rc->ids); } } - return needs_merge; + ecs_log_pop_3(); } static -bool build_pipeline( +void flecs_emit_forward_up( ecs_world_t *world, - ecs_entity_t pipeline, - EcsPipelineQuery *pq) -{ - (void)pipeline; + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_component_record_t *cr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids, + int32_t depth) +{ + if (depth >= FLECS_DAG_DEPTH_MAX) { + char *idstr = ecs_id_str(world, cr->id); + ecs_assert(depth < FLECS_DAG_DEPTH_MAX, ECS_CYCLE_DETECTED, idstr); + ecs_os_free(idstr); + return; + } - ecs_query_iter(world, pq->query); + ecs_id_t id = cr->id; + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + tgt = flecs_entities_get_alive(world, tgt); + if (!tgt) { + return; + } - if (pq->match_count == pq->query->match_count) { - /* No need to rebuild the pipeline */ - return false; + ecs_record_t *tgt_record = flecs_entities_try(world, tgt); + ecs_table_t *tgt_table; + if (!tgt_record || !(tgt_table = tgt_record->table)) { + return; } - world->info.pipeline_build_count_total ++; - pq->rebuild_count ++; + flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, + tgt, tgt_table, tgt_record, cr, stack, reachable_ids, depth + 1); +} - write_state_t ws = { - .components = ecs_map_new(int32_t, ECS_HI_COMPONENT_ID), - .wildcard = false - }; +static +void flecs_emit_forward( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_component_record_t *cr) +{ + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_reachable_cache_t *rc = &cr->pair->reachable; - ecs_pipeline_op_t *op = NULL; - ecs_vector_t *ops = NULL; - ecs_query_t *query = pq->build_query; + if (rc->current != rc->generation) { + /* Cache miss, iterate the tree to find ids to forward */ + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, cr->id); + ecs_dbg_3("reachable cache miss for %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); - if (pq->ops) { - ecs_vector_free(pq->ops); - } + ecs_vec_t stack; + ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0); + ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t); + flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, + cr, &stack, &rc->ids, 0); + it->sources[0] = 0; + ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); - bool multi_threaded = false; - bool no_staging = false; - bool first = true; + if (it->event == EcsOnAdd || it->event == EcsOnRemove) { + /* Only OnAdd/OnRemove events can validate top-level cache, which + * is for the id for which the event is emitted. + * The reason for this is that we don't want to validate the cache + * while the administration for the mutated entity isn't up to + * date yet. */ + rc->current = rc->generation; + } - /* Iterate systems in pipeline, add ops for running / merging */ - ecs_iter_t it = ecs_query_iter(world, query); - while (ecs_query_next(&it)) { - EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + if (ecs_should_log_3()) { + ecs_dbg_3("cache after rebuild:"); + flecs_emit_dump_cache(world, &rc->ids); + } - int i; - for (i = 0; i < it.count; i ++) { - ecs_query_t *q = sys[i].query; - if (!q) { - continue; - } + ecs_log_pop_3(); + } else { + /* Cache hit, use cached values instead of walking the tree */ + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, cr->id); + ecs_dbg_3("reachable cache hit for %s", idstr); + ecs_os_free(idstr); + flecs_emit_dump_cache(world, &rc->ids); + } + + ecs_entity_t trav = ECS_PAIR_FIRST(cr->id); + ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, + ecs_reachable_elem_t); + int32_t i, count = ecs_vec_count(&rc->ids); + for (i = 0; i < count; i ++) { + ecs_reachable_elem_t *elem = &elems[i]; + const ecs_table_record_t *tr = elem->tr; + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *rc_cr = tr->hdr.cr; + ecs_record_t *r = elem->record; - bool needs_merge = false; - bool is_active = !ecs_has_id( - world, it.entities[i], EcsInactive); - needs_merge = check_terms(&q->filter, is_active, &ws); + ecs_assert(rc_cr->id == elem->id, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_entities_get(world, elem->src) == r, + ECS_INTERNAL_ERROR, NULL); + ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); - if (is_active) { - if (first) { - multi_threaded = sys[i].multi_threaded; - no_staging = sys[i].no_staging; - first = false; - } + flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, + rc_cr, elem->src, r->table, tr->index, trav); + } + } - if (sys[i].multi_threaded != multi_threaded) { - needs_merge = true; - multi_threaded = sys[i].multi_threaded; - } - if (sys[i].no_staging != no_staging) { - needs_merge = true; - no_staging = sys[i].no_staging; - } + /* Propagate events for new reachable ids downwards */ + if (table->_->traversable_count) { + int32_t i; + const ecs_entity_t *entities = ecs_table_entities(table); + entities = ECS_ELEM_T(entities, ecs_entity_t, it->offset); + for (i = 0; i < it->count; i ++) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + if ((r->row & EcsEntityIsTraversable)) { + break; } + } - if (needs_merge) { - /* After merge all components will be merged, so reset state */ - reset_write_state(&ws); - op = NULL; + if (i != it->count) { + ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, + ecs_reachable_elem_t); + int32_t count = ecs_vec_count(&rc->ids); + for (i = 0; i < count; i ++) { + ecs_reachable_elem_t *elem = &elems[i]; + const ecs_table_record_t *tr = elem->tr; + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *rc_cr = tr->hdr.cr; + ecs_record_t *r = elem->record; + + ecs_assert(rc_cr->id == elem->id, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_entities_get(world, elem->src) == r, + ECS_INTERNAL_ERROR, NULL); + ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); + (void)r; - /* Re-evaluate columns to set write flags if system is active. - * If system is inactive, it can't write anything and so it - * should not insert unnecessary merges. */ - needs_merge = false; - if (is_active) { - needs_merge = check_terms(&q->filter, true, &ws); + /* If entities already have the component, don't propagate */ + if (flecs_component_get_table(rc_cr, it->table)) { + continue; } - /* The component states were just reset, so if we conclude that - * another merge is needed something is wrong. */ - ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); - } - - if (!op) { - op = ecs_vector_add(&ops, ecs_pipeline_op_t); - op->count = 0; - op->multi_threaded = false; - op->no_staging = false; - } + ecs_event_id_record_t *iders[5] = {0}; + int32_t ider_count = flecs_event_observers_get( + er, rc_cr->id, iders); - /* Don't increase count for inactive systems, as they are ignored by - * the query used to run the pipeline. */ - if (is_active) { - if (!op->count) { - op->multi_threaded = multi_threaded; - op->no_staging = no_staging; - } - op->count ++; + flecs_propagate_entities(world, it, rc_cr, it->entities, + it->count, elem->src, iders, ider_count); } } } +} - ecs_map_free(ws.components); +static +void flecs_emit_on_set_for_override_on_add( + ecs_world_t *world, + const ecs_event_record_t *er_onset, + int32_t evtx, + ecs_iter_t *it, + ecs_id_t id, + ecs_component_record_t *cr, + ecs_table_t *table) +{ + (void)evtx; - /* Find the system ran last this frame (helps workers reset iter) */ - ecs_entity_t last_system = 0; - op = ecs_vector_first(ops, ecs_pipeline_op_t); - int32_t i, ran_since_merge = 0, op_index = 0; + ecs_ref_t storage; + const ecs_ref_t *o = flecs_table_get_override( + world, table, id, cr, &storage); + if (!o) { + return; + } - ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); + /* Table has override for component. If this overrides a + * component that was already reachable for the table we + * don't need to emit since the value didn't change. */ + ecs_entity_t base = o->entity; - /* Add schedule to debug tracing */ - ecs_dbg("#[green]pipeline#[reset] rebuild:"); - ecs_log_push_1(); + ecs_table_t *other = it->other_table; + if (other) { + if (ecs_table_has_id(world, other, ecs_pair(EcsIsA, base))) { + /* If previous table already had (IsA, base), entity already + * inherited the component, so no new value needs to be emitted. */ + return; + } + } - ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", - op->multi_threaded, !op->no_staging); - ecs_log_push_1(); - - it = ecs_query_iter(world, pq->query); - while (ecs_query_next(&it)) { - EcsSystem *sys = ecs_term(&it, EcsSystem, 1); - for (i = 0; i < it.count; i ++) { - if (ecs_should_log_1()) { - char *path = ecs_get_fullpath(world, it.entities[i]); - ecs_dbg("#[green]system#[reset] %s", path); - ecs_os_free(path); - } - - ran_since_merge ++; - if (ran_since_merge == op[op_index].count) { - ecs_dbg("#[magenta]merge#[reset]"); - ecs_log_pop_1(); - ran_since_merge = 0; - op_index ++; - if (op_index < ecs_vector_count(ops)) { - ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", - op[op_index].multi_threaded, !op[op_index].no_staging); - } - ecs_log_push_1(); - } - - if (sys[i].last_frame == (world->info.frame_count_total + 1)) { - last_system = it.entities[i]; - - /* Can't break from loop yet. It's possible that previously - * inactive systems that ran before the last ran system are now - * active. */ - } - } + ecs_event_id_record_t *iders_set[5] = {0}; + int32_t ider_set_i, ider_set_count = + flecs_event_observers_get(er_onset, id, iders_set); + if (!ider_set_count) { + /* No OnSet observers for component */ + return; } - ecs_log_pop_1(); - ecs_log_pop_1(); + it->ids[0] = id; + it->event_id = id; + it->trs[0] = flecs_component_get_table(cr, table); + it->sources[0] = 0; - /* Force sort of query as this could increase the match_count */ - pq->match_count = pq->query->match_count; - pq->ops = ops; - pq->last_system = last_system; + /* Only valid for components, so type info must exist */ + ecs_assert(cr && cr->type_info, ECS_INTERNAL_ERROR, NULL); + ECS_CONST_CAST(ecs_size_t*, it->sizes)[0] = cr->type_info->size; - return true; + /* Invoke OnSet observers for new inherited component value. */ + for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { + ecs_event_id_record_t *ider = iders_set[ider_set_i]; + flecs_observers_invoke(world, &ider->self, it, table, 0); + ecs_assert(it->event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + flecs_observers_invoke(world, &ider->self_up, it, table, 0); + ecs_assert(it->event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + } } -int32_t ecs_pipeline_reset_iter( +static +void flecs_emit_on_set_for_override_on_remove( ecs_world_t *world, - const EcsPipelineQuery *pq, - ecs_iter_t *iter_out, - ecs_pipeline_op_t **op_out, - ecs_pipeline_op_t **last_op_out) + const ecs_event_record_t *er_onset, + int32_t evtx, + ecs_iter_t *it, + ecs_id_t id, + ecs_component_record_t *cr, + ecs_table_t *table) { - ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); - int32_t i, ran_since_merge = 0, op_index = 0; + (void)evtx; - if (!pq->last_system) { - /* It's possible that all systems that were ran were removed entirely - * from the pipeline (they could have been deleted or disabled). In that - * case (which should be very rare) the pipeline can't make assumptions - * about where to continue, so end frame. */ - return -1; + ecs_ref_t storage; + const ecs_ref_t *o = flecs_table_get_override(world, table, id, cr, &storage); + if (!o) { + return; } - /* Move iterator to last ran system */ - *iter_out = ecs_query_iter(world, pq->query); - while (ecs_query_next(iter_out)) { - for (i = 0; i < iter_out->count; i ++) { - ran_since_merge ++; - if (ran_since_merge == op[op_index].count) { - ran_since_merge = 0; - op_index ++; - } - - if (iter_out->entities[i] == pq->last_system) { - *op_out = &op[op_index]; - *last_op_out = ecs_vector_last(pq->ops, ecs_pipeline_op_t); - return i; - } - } + ecs_event_id_record_t *iders_set[5] = {0}; + int32_t ider_set_i, ider_set_count = + flecs_event_observers_get(er_onset, id, iders_set); + if (!ider_set_count) { + /* No OnSet observers for component */ + return; } - ecs_abort(ECS_INTERNAL_ERROR, NULL); + /* We're removing, so emit an OnSet for the base component. */ + ecs_entity_t base = o->entity; + ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *base_r = flecs_entities_get(world, base); + const ecs_table_record_t *base_tr = + flecs_component_get_table(cr, base_r->table); - return -1; -} + it->ids[0] = id; + it->event_id = id; + it->sources[0] = base; + it->trs[0] = base_tr; + it->up_fields = 1; -bool ecs_pipeline_update( - ecs_world_t *world, - ecs_entity_t pipeline, - bool start_of_frame) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); - ecs_assert(pipeline != 0, ECS_INTERNAL_ERROR, NULL); + /* Only valid for components, so type info must exist */ + ecs_assert(cr && cr->type_info, ECS_INTERNAL_ERROR, NULL); + ECS_CONST_CAST(ecs_size_t*, it->sizes)[0] = cr->type_info->size; - /* If any entity mutations happened that could have affected query matching - * notify appropriate queries so caches are up to date. This includes the - * pipeline query. */ - if (start_of_frame) { - ecs_force_aperiodic(world); + /* Invoke OnSet observers for previous inherited component value. */ + for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { + ecs_event_id_record_t *ider = iders_set[ider_set_i]; + flecs_observers_invoke(world, &ider->self_up, it, table, EcsIsA); + ecs_assert(it->event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + flecs_observers_invoke(world, &ider->up, it, table, EcsIsA); + ecs_assert(it->event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } - - bool added = false; - EcsPipelineQuery *pq = ecs_get_mut(world, pipeline, EcsPipelineQuery, &added); - ecs_assert(added == false, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); - - return build_pipeline(world, pipeline, pq); } -void ecs_run_pipeline( +/* The emit function is responsible for finding and invoking the observers + * matching the emitted event. The function is also capable of forwarding events + * for newly reachable ids (after adding a relationship) and propagating events + * downwards. Both capabilities are not just useful in application logic, but + * are also an important building block for keeping query caches in sync. */ +void flecs_emit( ecs_world_t *world, - ecs_entity_t pipeline, - FLECS_FLOAT delta_time) + ecs_world_t *stage, + ecs_event_desc_t *desc) { - ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->event != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->event != EcsWildcard, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->ids != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->ids->count != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL); - if (!pipeline) { - pipeline = world->pipeline; - } + ecs_os_perf_trace_push("flecs.emit"); - ecs_assert(pipeline != 0, ECS_INVALID_PARAMETER, NULL); + ecs_time_t t = {0}; + bool measure_time = world->flags & EcsWorldMeasureSystemTime; + if (measure_time) { + ecs_time_measure(&t); + } - /* If the world is passed to ecs_run_pipeline, the function will take care - * of staging, so the world should not be in staged mode when called. */ - if (ecs_poly_is(world, ecs_world_t)) { - ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); + const ecs_type_t *ids = desc->ids; + ecs_entity_t event = desc->event; + ecs_table_t *table = desc->table, *other_table = desc->other_table; + int32_t offset = desc->offset; + int32_t i, count = desc->count; + ecs_flags32_t table_flags = table->flags; - /* Forward to worker_progress. This function handles staging, threading - * and synchronization across workers. */ - ecs_workers_progress(world, pipeline, delta_time); - return; + /* Deferring cannot be suspended for observers */ + int32_t defer = world->stages[0]->defer; + if (defer < 0) { + world->stages[0]->defer *= -1; + } - /* If a stage is passed, the function could be ran from a worker thread. In - * that case the main thread should manage staging, and staging should be - * enabled. */ - } else { - ecs_poly_assert(world, ecs_stage_t); + /* Table events are emitted for internal table operations only, and do not + * provide component data and/or entity ids. */ + bool table_event = desc->flags & EcsEventTableOnly; + if (!count && !table_event) { + /* If no count is provided, forward event for all entities in table */ + count = ecs_table_count(table) - offset; } - ecs_stage_t *stage = flecs_stage_from_world(&world); - - const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + /* The world event id is used to determine if an observer has already been + * triggered for an event. Observers for multiple components are split up + * into multiple observers for a single component, and this counter is used + * to make sure a multi observer only triggers once, even if multiple of its + * single-component observers trigger. */ + int32_t evtx = ++world->event_id; - ecs_vector_t *ops = pq->ops; - ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); - ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); - int32_t ran_since_merge = 0; + ecs_id_t ids_cache = 0; + ecs_size_t sizes_cache = 0; + const ecs_table_record_t* trs_cache = 0; + ecs_entity_t sources_cache = 0; - int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); - int32_t stage_count = ecs_get_stage_count(world); + ecs_iter_t it = { + .world = stage, + .real_world = world, + .event = event, + .event_cur = evtx, + .table = table, + .field_count = 1, + .ids = &ids_cache, + .sizes = &sizes_cache, + .trs = (const ecs_table_record_t**)&trs_cache, + .sources = &sources_cache, + .other_table = other_table, + .offset = offset, + .count = count, + .param = ECS_CONST_CAST(void*, desc->param), + .flags = desc->flags | EcsIterIsValid + }; - ecs_worker_begin(stage->thread_ctx); + ecs_observable_t *observable = flecs_get_observable(desc->observable); + ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_iter_t it = ecs_query_iter(world, pq->query); - while (ecs_query_next(&it)) { - EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + /* Event records contain all observers for a specific event. In addition to + * the emitted event, also request data for the Wildcard event (for + * observers subscribing to the wildcard event), OnSet events. The + * latter to are used for automatically emitting OnSet events for + * inherited components, for example when an IsA relationship is added to an + * entity. This doesn't add much overhead, as fetching records is cheap for + * builtin event types. */ + const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); + const ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard); + const ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet); - int32_t i; - for(i = 0; i < it.count; i ++) { - ecs_entity_t e = it.entities[i]; + if (count) { + it.entities = &ecs_table_entities(table)[offset]; + } - if (!stage_index) { - ecs_dbg_3("pipeline: run system %s", ecs_get_name(world, e)); + int32_t id_count = ids->count; + ecs_id_t *id_array = ids->array; + bool do_on_set = !(desc->flags & EcsEventNoOnSet); + + /* When we add an (IsA, b) pair we need to emit OnSet events for any new + * component values that are reachable through the instance, either + * inherited or overridden. OnSet events for inherited components are + * emitted by the event forwarding logic. For overriding, we only need to + * emit an OnSet if both the IsA pair and the component were added in the + * same event. If a new override is added for an existing base component, + * it changes the ownership of the component, but not the value, so no OnSet + * is needed. */ + bool can_override_on_add = count && + do_on_set && + (event == EcsOnAdd) && + (table_flags & EcsTableHasIsA); + + /* If we remove an override, this reexposes the component from the base. + * Since the override could have a different value from the base, this + * effectively changes the value of the component for the entity, so an + * OnSet event must be emitted. */ + bool can_override_on_remove = count && + do_on_set && + (event == EcsOnRemove) && + (it.other_table) && + (it.other_table->flags & EcsTableHasIsA); + + /* When a new (traversable) relationship is added (emitting an OnAdd/OnRemove + * event) this will cause the components of the target entity to be + * propagated to the source entity. This makes it possible for observers to + * get notified of any new reachable components though the relationship. */ + bool can_forward = true; + + /* Does table has observed entities */ + bool has_observed = table_flags & EcsTableHasTraversable; + + ecs_event_id_record_t *iders[5] = {0}; + ecs_table_record_t dummy_tr; + +repeat_event: + /* This is the core event logic, which is executed for each event. By + * default this is just the event kind from the ecs_event_desc_t struct, but + * can also include the Wildcard and UnSet events. The latter is emitted as + * counterpart to OnSet, for any removed ids associated with data. */ + for (i = 0; i < id_count; i ++) { + /* Emit event for each id passed to the function. In most cases this + * will just be one id, like a component that was added, removed or set. + * In some cases events are emitted for multiple ids. + * + * One example is when an id was added with a "With" property, or + * inheriting from a prefab with overrides. In these cases an entity is + * moved directly to the archetype with the additional components. */ + ecs_id_t id = id_array[i]; + + /* If id is wildcard this could be a remove(Rel, *) call for a + * DontFragment component (for regular components this gets handled by + * the table graph which returns a vector with removed ids). + * This will be handled at a higher level than flecs_emit, so we can + * ignore the wildcard */ + if (id != EcsAny && ecs_id_is_wildcard(id)) { + continue; + } + + /* Forward events for Parent component as ChildOf pairs. */ + if (id == ecs_id(EcsParent) && !table_event && + (table->flags & EcsTableHasParent)) + { + ecs_event_desc_t pdesc = *desc; + + pdesc.event = event; + if (event == EcsOnSet) { + /* If the Parent component is set, forward it as an OnAdd event + * for a ChildOf pair. */ + pdesc.event = EcsOnAdd; } - if (!stage_index || op->multi_threaded) { - ecs_stage_t *s = NULL; - if (!op->no_staging) { - s = stage; + /* Iterate over entities, forward for their specific parent. */ + const EcsParent *parents = ecs_table_get_column(table, + table->component_map[ecs_id(EcsParent)] - 1, it.offset); + int32_t p, parent_count = pdesc.count; + + for (p = 0; p < parent_count; p ++) { + ecs_entity_t parent = parents[p].value; + if (parent && flecs_entities_is_alive(world, parent)) { + ecs_id_t pair = ecs_childof(parent); + ecs_type_t type = { .count = 1, .array = &pair }; + pdesc.ids = &type; + flecs_emit(world, stage, &pdesc); } + } + } - ecs_run_intern(world, s, e, &sys[i], stage_index, - stage_count, delta_time, 0, 0, NULL); + int32_t ider_i, ider_count = 0; + ecs_component_record_t *cr = flecs_components_get(world, id); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_flags32_t cr_flags = cr->flags; + + /* Check if this id is a pair of an traversable relationship. If so, we + * may have to forward ids from the pair's target. */ + if (can_forward && ECS_IS_PAIR(id) && (cr_flags & EcsIdTraversable)) { + const ecs_event_record_t *er_fwd = NULL; + if (ECS_PAIR_FIRST(id) == EcsIsA) { + if (event == EcsOnAdd) { + if (!world->stages[0]->base) { + /* Adding an IsA relationship can trigger prefab + * instantiation, which can instantiate prefab + * hierarchies for the entity to which the + * relationship was added. */ + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + + /* Setting this value prevents flecs_instantiate + * from being called recursively, in case prefab + * children also have IsA relationships. */ + world->stages[0]->base = tgt; + const ecs_entity_t *instances = + ecs_table_entities(table); + int32_t e; + + for (e = 0; e < count; e ++) { + flecs_instantiate( + world, tgt, instances[offset + e], NULL); + } + + world->stages[0]->base = 0; + } + + /* Adding an IsA relationship will emit OnSet events for + * any new reachable components. */ + er_fwd = er_onset; + } } - sys[i].last_frame = world->info.frame_count_total + 1; + /* Forward events for components from pair target */ + flecs_emit_forward(world, er, er_fwd, ids, &it, table, cr); - ran_since_merge ++; - world->info.systems_ran_frame ++; + ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + } - if (op != op_last && ran_since_merge == op->count) { - ran_since_merge = 0; + if (er) { + /* Get observer sets for id. There can be multiple sets of matching + * observers, in case an observer matches for wildcard ids. For + * example, both observers for (ChildOf, p) and (ChildOf, *) would + * match an event for (ChildOf, p). */ + ider_count = flecs_event_observers_get(er, id, iders); + } - if (!stage_index) { - ecs_dbg_3("merge"); - } + if (!ider_count && !(can_override_on_add || can_override_on_remove)) { + /* If nothing more to do for this id, early out */ + continue; + } - /* If the set of matched systems changed as a result of the - * merge, we have to reset the iterator and move it to our - * current position (system). If there are a lot of systems - * in the pipeline this can be an expensive operation, but - * should happen infrequently. */ - i = ecs_worker_sync(world, pq, &it, i, &op, &op_last); - sys = ecs_term(&it, EcsSystem, 1); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + dummy_tr = (ecs_table_record_t){ + .hdr.cr = cr, + .hdr.table = table, + .index = -1, + .column = -1, + .count = 0 + }; + + bool dont_fragment = cr_flags & EcsIdDontFragment; + if (!dont_fragment && id != EcsAny && (ECS_PAIR_FIRST(id) != EcsChildOf)) { + if (tr == NULL) { + /* When a single batch contains multiple add's for an exclusive + * relationship, it's possible that an id was in the added list + * that is no longer available for the entity. */ + continue; } + } else { + /* When matching Any the table may not have a record for it */ + tr = &dummy_tr; } - } - ecs_worker_end(stage->thread_ctx); -} + it.trs[0] = tr; + it.event_id = id; + it.ids[0] = id; -static -void add_pipeline_tags_to_sig( - ecs_world_t *world, - ecs_term_t *terms, - ecs_type_t type) -{ - (void)world; - - int32_t i, count = ecs_vector_count(type); - ecs_entity_t *entities = ecs_vector_first(type, ecs_entity_t); + const ecs_type_info_t *ti = cr->type_info; + if (ti) { + /* safe, owned by observer */ + ECS_CONST_CAST(int32_t*, it.sizes)[0] = ti->size; + } else { + ECS_CONST_CAST(int32_t*, it.sizes)[0] = 0; + } - for (i = 0; i < count; i ++) { - terms[i] = (ecs_term_t){ - .inout = EcsIn, - .oper = EcsOr, - .pred.entity = entities[i], - .subj = { - .entity = EcsThis, - .set.mask = EcsSelf | EcsSuperSet - } - }; - } -} + /* Actually invoke observers for this event/id */ + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->self, &it, table, 0); + ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + flecs_observers_invoke(world, &ider->self_up, &it, table, 0); + ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + } -static -ecs_query_t* build_pipeline_query( - ecs_world_t *world, - ecs_entity_t pipeline, - const char *name, - bool not_inactive) -{ - const EcsType *type_ptr = ecs_get(world, pipeline, EcsType); - ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_type_t type = NULL; - if (type_ptr->normalized) { - type = type_ptr->normalized->type; + if (!ider_count || !count || !has_observed) { + continue; + } + + /* The table->traversable_count value indicates if the table contains any + * entities that are used as targets of traversable relationships. If the + * entity/entities for which the event was generated is used as such a + * target, events must be propagated downwards. */ + flecs_propagate_entities( + world, &it, cr, it.entities, count, 0, iders, ider_count); } - int32_t type_count = ecs_vector_count(type); - int32_t term_count = 1; + can_forward = false; /* Don't forward twice */ - if (not_inactive) { - term_count ++; + if (wcer && er != wcer) { + /* Repeat event loop for Wildcard event */ + er = wcer; + it.event = event; + goto repeat_event; } - ecs_term_t *terms = ecs_os_malloc( - (type_count + term_count) * ECS_SIZEOF(ecs_term_t)); + /* Invoke OnSet observers for component overrides if necessary */ + if (count && (can_override_on_add|can_override_on_remove)) { + for (i = 0; i < id_count; i ++) { + ecs_id_t id = id_array[i]; - terms[0] = (ecs_term_t){ - .inout = EcsIn, - .oper = EcsAnd, - .pred.entity = ecs_id(EcsSystem), - .subj = { - .entity = EcsThis, - .set.mask = EcsSelf | EcsSuperSet - } - }; + bool non_trivial_set = true; + if (id < FLECS_HI_COMPONENT_ID) { + non_trivial_set = world->non_trivial_set[id]; + } - if (not_inactive) { - terms[1] = (ecs_term_t){ - .inout = EcsIn, - .oper = EcsNot, - .pred.entity = EcsInactive, - .subj = { - .entity = EcsThis, - .set.mask = EcsSelf | EcsSuperSet + if (non_trivial_set) { + ecs_component_record_t *cr = flecs_components_get(world, id); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = cr->type_info;; + ecs_flags32_t cr_flags = cr->flags; + + /* Can only override components that don't have DontInherit trait. */ + bool id_can_override_on_add = can_override_on_add; + bool id_can_override_on_remove = can_override_on_remove; + id_can_override_on_add &= !(cr_flags & EcsIdOnInstantiateDontInherit); + id_can_override_on_remove &= !(cr_flags & EcsIdOnInstantiateDontInherit); + id_can_override_on_add &= ti != NULL; + id_can_override_on_remove &= ti != NULL; + + if (id_can_override_on_add) { + flecs_emit_on_set_for_override_on_add( + world, er_onset, evtx, &it, id, cr, table); + } else if (id_can_override_on_remove) { + flecs_emit_on_set_for_override_on_remove( + world, er_onset, evtx, &it, id, cr, table); + } } - }; + } } - add_pipeline_tags_to_sig(world, &terms[term_count], type); - - ecs_query_t *result = ecs_query_init(world, &(ecs_query_desc_t){ - .filter = { - .name = name, - .terms_buffer = terms, - .terms_buffer_count = term_count + type_count - }, - .order_by = compare_entity, - .group_by = group_by_phase, - .group_by_id = pipeline - }); - - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); +error: + world->stages[0]->defer = defer; - ecs_os_free(terms); + ecs_os_perf_trace_pop("flecs.emit"); - return result; + if (measure_time) { + world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); + } + return; } -static -void OnUpdatePipeline( - ecs_iter_t *it) +void ecs_emit( + ecs_world_t *stage, + ecs_event_desc_t *desc) { - ecs_world_t *world = it->world; - ecs_entity_t *entities = it->entities; + ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage)); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(desc->param && desc->const_param), ECS_INVALID_PARAMETER, + "cannot set param and const_param at the same time"); - int32_t i; - for (i = it->count - 1; i >= 0; i --) { - ecs_entity_t pipeline = entities[i]; - - ecs_trace("#[green]pipeline#[reset] %s created", - ecs_get_name(world, pipeline)); - ecs_log_push(); + if (desc->entity) { + ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL); + ecs_record_t *r = flecs_entities_get(world, desc->entity); + desc->table = r->table; + desc->offset = ECS_RECORD_TO_ROW(r->row); + desc->count = 1; + } + + if (!desc->observable) { + desc->observable = world; + } - /* Build signature for pipeline query that matches EcsSystems, has the - * pipeline phases as OR columns, and ignores systems with EcsInactive. - * Note that EcsDisabled is automatically ignored - * by the regular query matching */ - ecs_query_t *query = build_pipeline_query( - world, pipeline, "BuiltinPipelineQuery", true); - ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Build signature for pipeline build query. The build query includes - * systems that are inactive, as an inactive system may become active as - * a result of another system, and as a result the correct merge - * operations need to be put in place. */ - ecs_query_t *build_query = build_pipeline_query( - world, pipeline, "BuiltinPipelineBuildQuery", false); - ecs_assert(build_query != NULL, ECS_INTERNAL_ERROR, NULL); - - bool added = false; - EcsPipelineQuery *pq = ecs_get_mut( - world, pipeline, EcsPipelineQuery, &added); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_t default_ids = (ecs_type_t){ + .count = 1, + .array = (ecs_id_t[]){ EcsAny } + }; - if (added) { - /* Should not modify pipeline after it has been used */ - ecs_assert(pq->ops == NULL, ECS_INVALID_OPERATION, NULL); + if (!desc->ids || !desc->ids->count) { + desc->ids = &default_ids; + } - if (pq->query) { - ecs_query_fini(pq->query); - } - if (pq->build_query) { - ecs_query_fini(pq->build_query); - } - } + if (desc->const_param) { + desc->param = ECS_CONST_CAST(void*, desc->const_param); + desc->const_param = NULL; + } - pq->query = query; - pq->build_query = build_query; - pq->match_count = -1; - pq->ops = NULL; - pq->last_system = 0; + ecs_defer_begin(world); + flecs_emit(world, stage, desc); + ecs_defer_end(world); - ecs_log_pop(); + if (desc->ids == &default_ids) { + desc->ids = NULL; } +error: + return; } -/* -- Public API -- */ - -bool ecs_progress( +void ecs_enqueue( ecs_world_t *world, - FLECS_FLOAT user_delta_time) + ecs_event_desc_t *desc) { - float delta_time = ecs_frame_begin(world, user_delta_time); + if (!ecs_is_deferred(world)) { + ecs_emit(world, desc); + return; + } - ecs_dbg_3("#[normal]begin progress(dt = %.2f)", (double)delta_time); + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_enqueue(world, stage, desc); +} - ecs_run_pipeline(world, 0, delta_time); +/** + * @file observer.c + * @brief Observer implementation. + * + * The observer implementation contains functions for creating, deleting and + * invoking observers. The code is split up into single-term observers and + * multi-term observers. Multi-term observers are created from multiple single- + * term observers. + */ - ecs_dbg_3("#[normal]end progress"); - ecs_frame_end(world); +static +ecs_entity_t flecs_get_observer_event( + ecs_term_t *term, + ecs_entity_t event) +{ + /* If operator is Not, reverse the event */ + if (term && term->oper == EcsNot) { + if (event == EcsOnAdd || event == EcsOnSet) { + event = EcsOnRemove; + } else if (event == EcsOnRemove) { + event = EcsOnAdd; + } + } - return !world->should_quit; + return event; } -void ecs_set_time_scale( - ecs_world_t *world, - FLECS_FLOAT scale) +static +ecs_flags32_t flecs_id_flag_for_event( + ecs_entity_t e) { - world->info.time_scale = scale; + if (e == EcsOnAdd) { + return EcsIdHasOnAdd; + } + if (e == EcsOnRemove) { + return EcsIdHasOnRemove; + } + if (e == EcsOnSet) { + return EcsIdHasOnSet; + } + if (e == EcsOnTableCreate) { + return EcsIdHasOnTableCreate; + } + if (e == EcsOnTableDelete) { + return EcsIdHasOnTableDelete; + } + if (e == EcsWildcard) { + return EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet| + EcsIdHasOnTableCreate|EcsIdHasOnTableDelete; + } + return 0; } -void ecs_reset_clock( - ecs_world_t *world) +static +void flecs_inc_observer_count( + ecs_world_t *world, + ecs_entity_t event, + ecs_event_record_t *evt, + ecs_id_t id, + int32_t value) { - world->info.world_time_total = 0; - world->info.world_time_total_raw = 0; -} + ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t result = idt->observer_count += value; + if (result == 1) { + /* Notify framework that there are observers for the event/id. This + * allows parts of the code to skip event evaluation early */ + flecs_notify_tables(world, id, &(ecs_table_event_t){ + .kind = EcsTableTriggersForId, + .event = event + }); -void ecs_deactivate_systems( - ecs_world_t *world) -{ - ecs_assert(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); + ecs_flags32_t flags = flecs_id_flag_for_event(event); + if (flags) { + ecs_component_record_t *cr = flecs_components_get(world, id); + if (cr) { + cr->flags |= flags; + } - ecs_entity_t pipeline = world->pipeline; - const EcsPipelineQuery *pq = ecs_get( world, pipeline, EcsPipelineQuery); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + /* Track that we've created an OnSet observer so we know not to take + * fast code path when doing a set operation. */ + if (event == EcsOnSet || event == EcsWildcard) { + if (id < FLECS_HI_COMPONENT_ID) { + world->non_trivial_set[id] = true; + } + + if (id == EcsWildcard || id == EcsAny) { + ecs_os_memset_n(world->non_trivial_set, true, bool, + FLECS_HI_COMPONENT_ID); + } + } + } + } else if (result == 0) { + /* Ditto, but the reverse */ + flecs_notify_tables(world, id, &(ecs_table_event_t){ + .kind = EcsTableNoTriggersForId, + .event = event + }); - /* Iterate over all systems, add EcsInvalid tag if queries aren't matched - * with any tables */ - ecs_iter_t it = ecs_query_iter(world, pq->build_query); + ecs_flags32_t flags = flecs_id_flag_for_event(event); + if (flags) { + ecs_component_record_t *cr = flecs_components_get(world, id); + if (cr) { + cr->flags &= ~flags; + } + } - /* Make sure that we defer adding the inactive tags until after iterating - * the query */ - flecs_defer_none(world, &world->stage); + flecs_event_id_record_remove(evt, id); + ecs_os_free(idt); + } - while( ecs_query_next(&it)) { - EcsSystem *sys = ecs_term(&it, EcsSystem, 1); + if (ECS_PAIR_FIRST(id) == EcsChildOf) { + ecs_observable_t *observable = &world->observable; + if (event == EcsOnAdd) { + ecs_event_record_t *er_onset = + flecs_event_record_ensure(observable, EcsOnSet); + flecs_inc_observer_count( + world, EcsOnSet, er_onset, ecs_id(EcsParent), value); + } else { + flecs_inc_observer_count( + world, event, evt, ecs_id(EcsParent), value); + } + } +} - int32_t i; - for (i = 0; i < it.count; i ++) { - ecs_query_t *query = sys[i].query; - if (query) { - if (!ecs_query_table_count(query)) { - ecs_add_id(world, it.entities[i], EcsInactive); - } - } +static +ecs_id_t flecs_observer_id( + ecs_id_t id) +{ + if (ECS_IS_PAIR(id)) { + if (ECS_PAIR_FIRST(id) == EcsAny) { + id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); + } + if (ECS_PAIR_SECOND(id) == EcsAny) { + id = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); } } - flecs_defer_flush(world, &world->stage); + return id; } -void ecs_set_pipeline( +static +void flecs_register_observer_for_event_and_id( ecs_world_t *world, - ecs_entity_t pipeline) + ecs_observable_t *observable, + ecs_observer_t *o, + size_t offset, + const ecs_term_t *term, + ecs_entity_t event, + ecs_id_t term_id) { - ecs_poly_assert(world, ecs_world_t); - ecs_check( ecs_get(world, pipeline, EcsPipelineQuery) != NULL, - ECS_INVALID_PARAMETER, "not a pipeline"); + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_entity_t trav = term ? term->trav : 0; - world->pipeline = pipeline; -error: - return; -} + /* Get observers for event */ + ecs_event_record_t *er = flecs_event_record_ensure(observable, event); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); -ecs_entity_t ecs_get_pipeline( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return world->pipeline; -error: - return 0; -} + /* Get observers for (component) id for event */ + ecs_event_id_record_t *idt = flecs_event_id_record_ensure( + world, er, term_id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); -/* -- Module implementation -- */ + ecs_map_t *observers = ECS_OFFSET(idt, offset); + ecs_map_init_if(observers, &world->allocator); + ecs_map_insert_ptr(observers, impl->id, o); + + flecs_inc_observer_count(world, event, er, term_id, 1); + if (trav && (term_id != ecs_id(EcsParent))) { + flecs_inc_observer_count(world, event, er, + ecs_pair(trav, EcsWildcard), 1); + } +} static -void FlecsPipelineFini( +void flecs_register_observer_for_id( ecs_world_t *world, - void *ctx) + ecs_observable_t *observable, + ecs_observer_t *o, + size_t offset, + ecs_id_t term_id) { - (void)ctx; - if (ecs_get_stage_count(world)) { - ecs_set_threads(world, 0); + ecs_term_t *term = o->query ? &o->query->terms[0] : NULL; + + int i, j; + for (i = 0; i < o->event_count; i ++) { + ecs_entity_t event = flecs_get_observer_event( + term, o->events[i]); + + for (j = 0; j < i; j ++) { + if (event == flecs_get_observer_event(term, o->events[j])) { + break; + } + } + if (i != j) { + /* Duplicate event, ignore */ + continue; + } + + flecs_register_observer_for_event_and_id( + world, observable, o, offset, term, event, term_id); } } -void FlecsPipelineImport( - ecs_world_t *world) +static +void flecs_uni_observer_register( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *o) { - ECS_MODULE(world, FlecsPipeline); + ecs_term_t *term = o->query ? &o->query->terms[0] : NULL; + ecs_flags64_t flags = term ? ECS_TERM_REF_FLAGS(&term->src) : EcsSelf; - ECS_IMPORT(world, FlecsSystem); + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_id_t term_id = flecs_observer_id(impl->register_id); - ecs_set_name_prefix(world, "Ecs"); + if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { + flecs_register_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, self_up), term_id); + } else if (flags & EcsSelf) { + flecs_register_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, self), term_id); + } else if (flags & EcsUp) { + ecs_assert(term->trav != 0, ECS_INTERNAL_ERROR, NULL); + flecs_register_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, up), term_id); + } - flecs_bootstrap_tag(world, EcsPipeline); - flecs_bootstrap_component(world, EcsPipelineQuery); - - /* Phases of the builtin pipeline are regular entities. Names are set so - * they can be resolved by type expressions. */ - flecs_bootstrap_tag(world, EcsPreFrame); - flecs_bootstrap_tag(world, EcsOnLoad); - flecs_bootstrap_tag(world, EcsPostLoad); - flecs_bootstrap_tag(world, EcsPreUpdate); - flecs_bootstrap_tag(world, EcsOnUpdate); - flecs_bootstrap_tag(world, EcsOnValidate); - flecs_bootstrap_tag(world, EcsPostUpdate); - flecs_bootstrap_tag(world, EcsPreStore); - flecs_bootstrap_tag(world, EcsOnStore); - flecs_bootstrap_tag(world, EcsPostFrame); - - /* Set ctor and dtor for PipelineQuery */ - ecs_set(world, ecs_id(EcsPipelineQuery), EcsComponentLifecycle, { - .ctor = ecs_default_ctor, - .dtor = ecs_dtor(EcsPipelineQuery) - }); + if (term && (term->trav == EcsChildOf)) { + ecs_assert(o->query != NULL, ECS_INTERNAL_ERROR, NULL); + if (o->query->flags & EcsQueryTableOnly) { + flecs_register_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent)); + } + } +} - /* When the Pipeline tag is added a pipeline will be created */ - ecs_observer_init(world, &(ecs_observer_desc_t) { - .entity.name = "OnUpdatePipeline", - .filter.terms = { - { .id = EcsPipeline }, - { .id = ecs_id(EcsType) } - }, - .events = { EcsOnSet }, - .callback = OnUpdatePipeline - }); +static +void flecs_unregister_observer_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *o, + size_t offset, + ecs_id_t term_id) +{ + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_term_t *term = o->query ? &o->query->terms[0] : NULL; + ecs_entity_t trav = term ? term->trav : 0; + + int i, j; + for (i = 0; i < o->event_count; i ++) { + ecs_entity_t event = flecs_get_observer_event( + term, o->events[i]); + for (j = 0; j < i; j ++) { + if (event == flecs_get_observer_event(term, o->events[j])) { + break; + } + } + if (i != j) { + /* Duplicate event, ignore */ + continue; + } - /* Create the builtin pipeline */ - world->pipeline = ecs_type_init(world, &(ecs_type_desc_t){ - .entity = { - .name = "BuiltinPipeline", - .add = {EcsPipeline} - }, - .ids = { - EcsPreFrame, EcsOnLoad, EcsPostLoad, EcsPreUpdate, EcsOnUpdate, - EcsOnValidate, EcsPostUpdate, EcsPreStore, EcsOnStore, EcsPostFrame - } - }); + /* Get observers for event */ + ecs_event_record_t *er = flecs_event_record_get(observable, event); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); - /* Cleanup thread administration when world is destroyed */ - ecs_atfini(world, FlecsPipelineFini, NULL); + /* Get observers for (component) id */ + ecs_event_id_record_t *idt = flecs_event_id_record_get(er, term_id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *id_observers = ECS_OFFSET(idt, offset); + ecs_map_remove(id_observers, impl->id); + if (!ecs_map_count(id_observers)) { + ecs_map_fini(id_observers); + } + + flecs_inc_observer_count(world, event, er, term_id, -1); + if (trav && (term_id != ecs_id(EcsParent))) { + flecs_inc_observer_count(world, event, er, + ecs_pair(trav, EcsWildcard), -1); + } + } } -#endif +static +void flecs_unregister_observer( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *o) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_t *q = o->query; + if (q && q->term_count == 0) { + return; + } + ecs_term_t *term = q ? &q->terms[0] : NULL; + ecs_flags64_t flags = term ? ECS_TERM_REF_FLAGS(&term->src) : EcsSelf; -#ifdef FLECS_TIMER + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_id_t term_id = flecs_observer_id(impl->register_id); -static -void AddTickSource(ecs_iter_t *it) { - int32_t i; - for (i = 0; i < it->count; i ++) { - ecs_set(it->world, it->entities[i], EcsTickSource, {0}); + if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { + flecs_unregister_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, self_up), term_id); + } else if (flags & EcsSelf) { + flecs_unregister_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, self), term_id); + } else if (flags & EcsUp) { + flecs_unregister_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, up), term_id); + } + + if (term && (term->trav == EcsChildOf)) { + if (o->query->flags & EcsQueryTableOnly) { + flecs_unregister_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent)); + } } } static -void ProgressTimers(ecs_iter_t *it) { - EcsTimer *timer = ecs_term(it, EcsTimer, 1); - EcsTickSource *tick_source = ecs_term(it, EcsTickSource, 2); +bool flecs_ignore_observer( + ecs_observer_t *o, + ecs_table_t *table, + ecs_iter_t *it) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_observer_impl_t *impl = flecs_observer_impl(o); + int32_t *last_event_id = impl->last_event_id; + if (last_event_id && last_event_id[0] == it->event_cur) { + return true; + } - int i; - for (i = 0; i < it->count; i ++) { - tick_source[i].tick = false; + if (impl->flags & (EcsObserverIsDisabled|EcsObserverIsParentDisabled)) { + return true; + } - if (!timer[i].active) { - continue; - } + ecs_flags32_t table_flags = table->flags, query_flags = impl->flags; - const ecs_world_info_t *info = ecs_get_world_info(it->world); - FLECS_FLOAT time_elapsed = timer[i].time + info->delta_time_raw; - FLECS_FLOAT timeout = timer[i].timeout; - - if (time_elapsed >= timeout) { - FLECS_FLOAT t = time_elapsed - timeout; - if (t > timeout) { - t = 0; - } + bool result = (table_flags & EcsTableIsPrefab) && + !(query_flags & EcsQueryMatchPrefab); + result = result || ((table_flags & EcsTableIsDisabled) && + !(query_flags & EcsQueryMatchDisabled)); - timer[i].time = t; /* Initialize with remainder */ - tick_source[i].tick = true; - tick_source[i].time_elapsed = time_elapsed; + return result; +} - if (timer[i].single_shot) { - timer[i].active = false; - } +static +void flecs_default_uni_observer_run_callback(ecs_iter_t *it) { + ecs_observer_t *o = it->ctx; + it->ctx = o->ctx; + it->callback = o->callback; + o->callback(it); +} + +static +void flecs_observer_invoke( + ecs_observer_t *o, + ecs_iter_t *it) +{ + if (o->run) { + it->next = flecs_default_next_callback; + it->callback = o->callback; + it->interrupted_by = 0; + if (flecs_observer_impl(o)->flags & EcsObserverBypassQuery) { + it->ctx = o; } else { - timer[i].time = time_elapsed; - } + it->ctx = o->ctx; + } + o->run(it); + } else { + ecs_iter_action_t callback = o->callback; + it->callback = callback; + callback(it); } } static -void ProgressRateFilters(ecs_iter_t *it) { - EcsRateFilter *filter = ecs_term(it, EcsRateFilter, 1); - EcsTickSource *tick_dst = ecs_term(it, EcsTickSource, 2); +void flecs_uni_observer_invoke( + ecs_world_t *world, + ecs_observer_t *o, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav) +{ + if (flecs_ignore_observer(o, table, it)) { + return; + } - int i; - for (i = 0; i < it->count; i ++) { - ecs_entity_t src = filter[i].src; - bool inc = false; + if (ecs_should_log_3()) { + char *path = ecs_get_path(world, it->system); + ecs_dbg_3("observer: invoke %s", path); + ecs_os_free(path); + } - filter[i].time_elapsed += it->delta_time; + ecs_log_push_3(); - if (src) { - const EcsTickSource *tick_src = ecs_get(it->world, src, EcsTickSource); - if (tick_src) { - inc = tick_src->tick; - } else { - inc = true; - } - } else { - inc = true; + ecs_observer_impl_t *impl = flecs_observer_impl(o); + it->system = o->entity; + it->ctx = o->ctx; + it->callback_ctx = o->callback_ctx; + it->run_ctx = o->run_ctx; + it->term_index = impl->term_index; + + ecs_entity_t event = it->event; + int32_t event_cur = it->event_cur; + ecs_entity_t old_system = flecs_stage_set_system( + world->stages[0], o->entity); + ecs_flags32_t set_fields_cur = it->set_fields; + it->set_fields = 1; + + ecs_query_t *query = o->query; + it->query = query; + + if (!query) { + /* Invoke trivial observer */ + it->event = event; + flecs_observer_invoke(o, it); + } else { + ecs_term_t *term = &query->terms[0]; + ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); + if (trav && term->trav != trav) { + return; } - if (inc) { - filter[i].tick_count ++; - bool triggered = !(filter[i].tick_count % filter[i].rate); - tick_dst[i].tick = triggered; - tick_dst[i].time_elapsed = filter[i].time_elapsed; + bool is_filter = term->inout == EcsInOutNone; + ECS_BIT_COND(it->flags, EcsIterNoData, is_filter); + it->ref_fields = query->fixed_fields | query->row_fields; + ecs_termset_t row_fields = it->row_fields; + it->row_fields = query->row_fields; + it->event = flecs_get_observer_event(term, event); - if (triggered) { - filter[i].time_elapsed = 0; - } + bool match_this = query->flags & EcsQueryMatchThis; + + if (match_this) { + /* Invoke observer for $this field */ + flecs_observer_invoke(o, it); + ecs_os_inc(&query->eval_count); } else { - tick_dst[i].tick = false; - } - } -} + /* Not a $this field, translate the iterator data from a $this field to + * a field with it->sources set. */ + ecs_entity_t observer_src = ECS_TERM_REF_ID(&term->src); + ecs_assert(observer_src != 0, ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = it->entities; + int32_t i, count = it->count; + ecs_entity_t src = it->sources[0]; + ecs_table_t *old_table = it->table; -static -void ProgressTickSource(ecs_iter_t *it) { - EcsTickSource *tick_src = ecs_term(it, EcsTickSource, 1); + it->entities = NULL; + it->count = 0; + it->table = NULL; - /* If tick source has no filters, tick unconditionally */ - int i; - for (i = 0; i < it->count; i ++) { - tick_src[i].tick = true; - tick_src[i].time_elapsed = it->delta_time; - } -} + /* Loop all entities for which the event was emitted. Usually this is + * just one, but it is possible to emit events for a table range. */ + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; -ecs_entity_t ecs_set_timeout( - ecs_world_t *world, - ecs_entity_t timer, - FLECS_FLOAT timeout) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + /* Filter on the source of the observer field */ + if (observer_src == e) { + if (!src) { + /* Only overwrite source if event wasn't forwarded or + * propagated from another entity. */ + it->sources[0] = e; + } - timer = ecs_set(world, timer, EcsTimer, { - .timeout = timeout, - .single_shot = true, - .active = true - }); + flecs_observer_invoke(o, it); + ecs_os_inc(&query->eval_count); - EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); - if (system_data) { - system_data->tick_source = timer; - } + /* Restore source */ + it->sources[0] = src; -error: - return timer; -} + /* Observer can only match one source explicitly, so we don't + * have to check any other entities. */ + break; + } + } -FLECS_FLOAT ecs_get_timeout( - const ecs_world_t *world, - ecs_entity_t timer) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); + it->entities = entities; + it->count = count; + it->table = old_table; + } - const EcsTimer *value = ecs_get(world, timer, EcsTimer); - if (value) { - return value->timeout; + it->row_fields = row_fields; } -error: - return 0; + + flecs_stage_set_system(world->stages[0], old_system); + + it->event = event; + it->event_cur = event_cur; + it->set_fields = set_fields_cur; + + ecs_log_pop_3(); + + world->info.observers_ran_total ++; } -ecs_entity_t ecs_set_interval( +void flecs_observers_invoke( ecs_world_t *world, - ecs_entity_t timer, - FLECS_FLOAT interval) + ecs_map_t *observers, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + if (ecs_map_is_init(observers)) { + ecs_table_lock(it->world, table); - timer = ecs_set(world, timer, EcsTimer, { - .timeout = interval, - .active = true - }); + ecs_map_iter_t oit = ecs_map_iter(observers); + while (ecs_map_next(&oit)) { + ecs_observer_t *o = ecs_map_ptr(&oit); + ecs_assert(it->table == table, ECS_INTERNAL_ERROR, NULL); + flecs_uni_observer_invoke(world, o, it, table, trav); - EcsSystem *system_data = ecs_get_mut(world, timer, EcsSystem, NULL); - if (system_data) { - system_data->tick_source = timer; + ecs_assert(ecs_map_iter_valid(&oit), ECS_INVALID_OPERATION, + "observer list modified while notifying: " + "cannot create observer from observer"); + } + + ecs_table_unlock(it->world, table); } -error: - return timer; } -FLECS_FLOAT ecs_get_interval( - const ecs_world_t *world, - ecs_entity_t timer) +static +void flecs_multi_observer_invoke( + ecs_iter_t *it) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_observer_t *o = it->ctx; + flecs_poly_assert(o, ecs_observer_t); - if (!timer) { - return 0; + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_world_t *world = it->real_world; + + if (impl->last_event_id[0] == it->event_cur) { + /* Already handled this event */ + return; } - const EcsTimer *value = ecs_get(world, timer, EcsTimer); - if (value) { - return value->timeout; + ecs_table_t *table = it->table; + ecs_table_t *prev_table = it->other_table; + int8_t pivot_term = it->term_index; + ecs_term_t *term = &o->query->terms[pivot_term]; + + bool is_not = term->oper == EcsNot; + if (is_not) { + table = it->other_table; + prev_table = it->table; } -error: - return 0; -} -void ecs_start_timer( - ecs_world_t *world, - ecs_entity_t timer) -{ - EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ptr->active = true; - ptr->time = 0; -error: - return; -} + table = table ? table : &world->store.root; + prev_table = prev_table ? prev_table : &world->store.root; -void ecs_stop_timer( - ecs_world_t *world, - ecs_entity_t timer) -{ - EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer, NULL); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ptr->active = false; -error: - return; -} + ecs_iter_t user_it; -ecs_entity_t ecs_set_rate( - ecs_world_t *world, - ecs_entity_t filter, - int32_t rate, - ecs_entity_t source) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + bool match; + if (is_not) { + match = ecs_query_has_table(o->query, table, &user_it); + if (match) { + /* The target table matches but the entity hasn't moved to it yet. + * Now match the not_query, which will populate the iterator with + * data from the table the entity is still stored in. */ + user_it.flags |= EcsIterSkip; /* Prevent change detection on fini */ + ecs_iter_fini(&user_it); + match = ecs_query_has_table(impl->not_query, prev_table, &user_it); + + /* A not query replaces Not terms with Optional terms, so if the + * regular query matches, the not_query should also match. */ + ecs_assert(match, ECS_INTERNAL_ERROR, NULL); + } + } else { + ecs_table_range_t range = { + .table = table, + .offset = it->offset, + .count = it->count + }; - filter = ecs_set(world, filter, EcsRateFilter, { - .rate = rate, - .src = source - }); + match = ecs_query_has_range(o->query, &range, &user_it); + } - EcsSystem *system_data = ecs_get_mut(world, filter, EcsSystem, NULL); - if (system_data) { - system_data->tick_source = filter; - } + if (match) { + /* Monitor observers only invoke when the query matches for the first + * time with an entity */ + if (impl->flags & EcsObserverIsMonitor) { + ecs_iter_t table_it; + if (ecs_query_has_table(o->query, prev_table, &table_it)) { + /* Prevent change detection on fini */ + user_it.flags |= EcsIterSkip; + table_it.flags |= EcsIterSkip; + ecs_iter_fini(&table_it); + ecs_iter_fini(&user_it); + goto done; + } + } -error: - return filter; -} + impl->last_event_id[0] = it->event_cur; -void ecs_set_tick_source( - ecs_world_t *world, - ecs_entity_t system, - ecs_entity_t tick_source) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL); + /* Patch data from original iterator. If the observer query has + * wildcards which triggered the original event, the component id that + * got matched by ecs_query_has_range may not be the same as the one + * that caused the event. We need to make sure to communicate the + * component id that actually triggered the observer. */ + int8_t pivot_field = term->field_index; + ecs_assert(pivot_field >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pivot_field < user_it.field_count, ECS_INTERNAL_ERROR, NULL); + user_it.ids[pivot_field] = it->event_id; + user_it.trs[pivot_field] = it->trs[0]; + user_it.term_index = pivot_term; - EcsSystem *system_data = ecs_get_mut(world, system, EcsSystem, NULL); - ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + user_it.ctx = o->ctx; + user_it.callback_ctx = o->callback_ctx; + user_it.run_ctx = o->run_ctx; + user_it.param = it->param; + user_it.callback = o->callback; + user_it.system = o->entity; + user_it.event = it->event; + user_it.event_id = it->event_id; + user_it.other_table = it->other_table; - system_data->tick_source = tick_source; -error: - return; -} + ecs_entity_t old_system = flecs_stage_set_system( + world->stages[0], o->entity); + ecs_table_lock(it->world, table); -void FlecsTimerImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsTimer); + if (o->run) { + user_it.next = flecs_default_next_callback; + o->run(&user_it); + } else { + user_it.callback(&user_it); + } - ECS_IMPORT(world, FlecsPipeline); + user_it.flags |= EcsIterSkip; /* Prevent change detection on fini */ + ecs_iter_fini(&user_it); - ecs_set_name_prefix(world, "Ecs"); + ecs_table_unlock(it->world, table); + flecs_stage_set_system(world->stages[0], old_system); + } else { + /* While the observer query was strictly speaking evaluated, it's more + * useful to measure how often the observer was actually invoked. */ + o->query->eval_count --; + } - flecs_bootstrap_component(world, EcsTimer); - flecs_bootstrap_component(world, EcsRateFilter); +done: + return; +} - /* Add EcsTickSource to timers and rate filters */ - ecs_system_init(world, &(ecs_system_desc_t) { - .entity = { .name = "AddTickSource", .add = { EcsPreFrame } }, - .query.filter.terms = { - { .id = ecs_id(EcsTimer), .oper = EcsOr, .inout = EcsIn }, - { .id = ecs_id(EcsRateFilter), .oper = EcsOr, .inout = EcsIn }, - { .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut} - }, - .callback = AddTickSource - }); +static +void flecs_multi_observer_invoke_no_query( + ecs_iter_t *it) +{ + ecs_observer_t *o = it->ctx; + flecs_poly_assert(o, ecs_observer_t); - /* Timer handling */ - ecs_system_init(world, &(ecs_system_desc_t) { - .entity = { .name = "ProgressTimers", .add = { EcsPreFrame } }, - .query.filter.terms = { - { .id = ecs_id(EcsTimer) }, - { .id = ecs_id(EcsTickSource) } - }, - .callback = ProgressTimers - }); + ecs_world_t *world = it->real_world; + ecs_table_t *table = it->table; + ecs_iter_t user_it = *it; - /* Rate filter handling */ - ecs_system_init(world, &(ecs_system_desc_t) { - .entity = { .name = "ProgressRateFilters", .add = { EcsPreFrame } }, - .query.filter.terms = { - { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, - { .id = ecs_id(EcsTickSource), .inout = EcsOut } - }, - .callback = ProgressRateFilters - }); + user_it.ctx = o->ctx; + user_it.callback_ctx = o->callback_ctx; + user_it.run_ctx = o->run_ctx; + user_it.param = it->param; + user_it.callback = o->callback; + user_it.system = o->entity; + user_it.event = it->event; + + ecs_entity_t old_system = flecs_stage_set_system( + world->stages[0], o->entity); + ecs_table_lock(it->world, table); + + if (o->run) { + user_it.next = flecs_default_next_callback; + o->run(&user_it); + } else { + user_it.callback(&user_it); + } - /* TickSource without a timer or rate filter just increases each frame */ - ecs_system_init(world, &(ecs_system_desc_t) { - .entity = { .name = "ProgressTickSource", .add = { EcsPreFrame } }, - .query.filter.terms = { - { .id = ecs_id(EcsTickSource), .inout = EcsOut }, - { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, - { .id = ecs_id(EcsTimer), .oper = EcsNot } - }, - .callback = ProgressTickSource - }); + ecs_table_unlock(it->world, table); + flecs_stage_set_system(world->stages[0], old_system); } -#endif - -#include - -/* Utilities for C++ API */ - -#ifdef FLECS_CPP +/* For convenience, so applications can use a single run callback that uses + * ecs_iter_next to iterate results for systems/queries and observers. */ +bool flecs_default_next_callback(ecs_iter_t *it) { + if (it->interrupted_by) { + return false; + } else { + /* Use interrupted_by to signal the next iteration must return false */ + ecs_assert(it->system != 0, ECS_INTERNAL_ERROR, NULL); + it->interrupted_by = it->system; + return true; + } +} -/* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to - * a uniform identifier */ +/* Run action for children of multi observer */ +static +void flecs_multi_observer_builtin_run(ecs_iter_t *it) { + ecs_observer_t *o = it->ctx; + ecs_run_action_t run = o->run; -#define ECS_CONST_PREFIX "const " -#define ECS_STRUCT_PREFIX "struct " -#define ECS_CLASS_PREFIX "class " -#define ECS_ENUM_PREFIX "enum " + if (run) { + if (flecs_observer_impl(o)->flags & EcsObserverBypassQuery) { + it->next = flecs_default_next_callback; + it->callback = flecs_multi_observer_invoke; + it->interrupted_by = 0; + it->run_ctx = o->run_ctx; + run(it); + return; + } + } -#define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX)) -#define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX)) -#define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX)) -#define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX)) + flecs_multi_observer_invoke(it); +} static -ecs_size_t ecs_cpp_strip_prefix( - char *typeName, - ecs_size_t len, - const char *prefix, - ecs_size_t prefix_len) +void flecs_observer_yield_existing( + ecs_world_t *world, + ecs_observer_t *o, + bool yield_on_remove) { - if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) { - ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len); - typeName[len - prefix_len] = '\0'; - len -= prefix_len; + ecs_run_action_t run = o->run; + if (!run) { + run = flecs_multi_observer_invoke_no_query; } - return len; -} -static -void ecs_cpp_trim_type_name( - char *typeName) -{ - ecs_size_t len = ecs_os_strlen(typeName); + ecs_defer_begin(world); - len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN); - len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN); - len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN); - len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN); + /* If yield existing is enabled, invoke for each thing that matches + * the event, if the event is iterable. */ + int i, count = o->event_count; + for (i = 0; i < count; i ++) { + ecs_entity_t event = o->events[i]; - while (typeName[len - 1] == ' ' || - typeName[len - 1] == '&' || - typeName[len - 1] == '*') - { - len --; - typeName[len] = '\0'; - } + /* We only yield for OnRemove events if the observer is deleted. */ + if (event == EcsOnRemove) { + if (!yield_on_remove) { + continue; + } + } else { + if (yield_on_remove) { + continue; + } + } - /* Remove const at end of string */ - if (len > ECS_CONST_LEN) { - if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) { - typeName[len - ECS_CONST_LEN] = '\0'; + ecs_iter_t it; + if (o->query) { + it = ecs_query_iter(world, o->query); + } else { + it = ecs_each_id(world, flecs_observer_impl(o)->register_id); } - len -= ECS_CONST_LEN; - } - /* Check if there are any remaining "struct " strings, which can happen - * if this is a template type on msvc. */ - if (len > ECS_STRUCT_LEN) { - char *ptr = typeName; - while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) { - /* Make sure we're not matched with part of a longer identifier - * that contains 'struct' */ - if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) { - ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN, - ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1); - len -= ECS_STRUCT_LEN; - } + it.system = o->entity; + it.ctx = o; + it.callback = flecs_default_uni_observer_run_callback; + it.callback_ctx = o->callback_ctx; + it.run_ctx = o->run_ctx; + it.event = o->events[i]; + while (o->query ? ecs_query_next(&it) : ecs_each_next(&it)) { + it.event_id = it.ids[0]; + it.event_cur = ++ world->event_id; + + ecs_iter_next_action_t next = it.next; + it.next = flecs_default_next_callback; + run(&it); + it.next = next; + it.interrupted_by = 0; } } -} -char* ecs_cpp_get_type_name( - char *type_name, - const char *func_name, - size_t len) -{ - memcpy(type_name, func_name + ECS_FUNC_NAME_FRONT(const char*, type_name), len); - type_name[len] = '\0'; - ecs_cpp_trim_type_name(type_name); - return type_name; + ecs_defer_end(world); } -char* ecs_cpp_get_symbol_name( - char *symbol_name, - const char *type_name, - size_t len) +static +int flecs_uni_observer_init( + ecs_world_t *world, + ecs_observer_t *o, + ecs_id_t component_id, + const ecs_observer_desc_t *desc) { - // Symbol is same as name, but with '::' replaced with '.' - ecs_os_strcpy(symbol_name, type_name); + ecs_observer_impl_t *impl = flecs_observer_impl(o); + impl->last_event_id = desc->last_event_id; + if (!impl->last_event_id) { + impl->last_event_id = &impl->last_event_id_storage; + } - char *ptr; - size_t i; - for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) { - if (*ptr == ':') { - symbol_name[i] = '.'; - ptr ++; - } else { - symbol_name[i] = *ptr; + impl->register_id = component_id; + + if (ecs_id_is_tag(world, component_id)) { + /* If id is a tag, downgrade OnSet to OnAdd. */ + int32_t e, count = o->event_count; + bool has_on_add = false; + for (e = 0; e < count; e ++) { + if (o->events[e] == EcsOnAdd) { + has_on_add = true; + } + } + + for (e = 0; e < count; e ++) { + if (o->events[e] == EcsOnSet) { + if (has_on_add) { + /* Already registered */ + o->events[e] = 0; + } else { + o->events[e] = EcsOnAdd; + } + } } } - symbol_name[i] = '\0'; + flecs_uni_observer_register(world, o->observable, o); - return symbol_name; + return 0; } static -const char* cpp_func_rchr( - const char *func_name, - ecs_size_t func_name_len, - char ch) +int flecs_observer_add_child( + ecs_world_t *world, + ecs_observer_t *o, + const ecs_observer_desc_t *child_desc) { - const char *r = strrchr(func_name, ch); - if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))) { - return NULL; + ecs_assert(child_desc->query.flags & EcsQueryNested, + ECS_INTERNAL_ERROR, NULL); + + ecs_observer_t *child_observer = flecs_observer_init( + world, 0, child_desc); + if (!child_observer) { + return -1; } - return r; + + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_vec_append_t(&world->allocator, &impl->children, + ecs_observer_t*)[0] = child_observer; + child_observer->entity = o->entity; + return 0; } static -const char* cpp_func_max( - const char *a, - const char *b) +int flecs_multi_observer_init( + ecs_world_t *world, + ecs_observer_t *o, + const ecs_observer_desc_t *desc) { - if (a > b) return a; - return b; -} + ecs_observer_impl_t *impl = flecs_observer_impl(o); -char* ecs_cpp_get_constant_name( - char *constant_name, - const char *func_name, - size_t func_name_len) -{ - ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); - const char *start = cpp_func_rchr(func_name, f_len, ' '); - start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ')')); - start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ':')); - start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ',')); - ecs_assert(start != NULL, ECS_INVALID_PARAMETER, func_name); - start ++; + /* Create last event id for filtering out the same event that arrives from + * more than one term */ + impl->last_event_id = ecs_os_calloc_t(int32_t); - ecs_size_t len = flecs_uto(ecs_size_t, - (f_len - (start - func_name) - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))); - ecs_os_memcpy_n(constant_name, start, char, len); - constant_name[len] = '\0'; - return constant_name; -} + /* Mark observer as multi observer */ + impl->flags |= EcsObserverIsMulti; + + /* Vector that stores a single-component observer for each query term */ + ecs_vec_init_t(&world->allocator, &impl->children, ecs_observer_t*, 2); + + /* Create a child observer for each term in the query */ + ecs_query_t *query = o->query; + ecs_observer_desc_t child_desc = *desc; + child_desc.last_event_id = impl->last_event_id; + child_desc.run = NULL; + child_desc.callback = flecs_multi_observer_builtin_run; + child_desc.ctx = o; + child_desc.ctx_free = NULL; + child_desc.query.expr = NULL; + child_desc.callback_ctx = NULL; + child_desc.callback_ctx_free = NULL; + child_desc.run_ctx = NULL; + child_desc.run_ctx_free = NULL; + child_desc.yield_existing = false; + child_desc.flags_ &= ~(EcsObserverYieldOnCreate|EcsObserverYieldOnDelete); + ecs_os_zeromem(&child_desc.entity); + ecs_os_zeromem(&child_desc.query.terms); + ecs_os_zeromem(&child_desc.query); + ecs_os_memcpy_n(child_desc.events, o->events, ecs_entity_t, o->event_count); + + child_desc.query.flags |= EcsQueryNested; + + int i, term_count = query->term_count; + bool optional_only = query->flags & EcsQueryMatchThis; + bool has_not = false; + for (i = 0; i < term_count; i ++) { + if (query->terms[i].oper != EcsOptional) { + if (ecs_term_match_this(&query->terms[i])) { + optional_only = false; + } + } -// Names returned from the name_helper class do not start with :: -// but are relative to the root. If the namespace of the type -// overlaps with the namespace of the current module, strip it from -// the implicit identifier. -// This allows for registration of component types that are not in the -// module namespace to still be registered under the module scope. -const char* ecs_cpp_trim_module( - ecs_world_t *world, - const char *type_name) -{ - ecs_entity_t scope = ecs_get_scope(world); - if (!scope) { - return type_name; + if ((query->terms[i].oper == EcsNot) && + (query->terms[i].inout != EcsInOutFilter)) + { + has_not = true; + } } - char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); - if (path) { - const char *ptr = strrchr(type_name, ':'); - ecs_assert(ptr != type_name, ECS_INTERNAL_ERROR, NULL); - if (ptr) { - ptr --; - ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL); - ecs_size_t name_path_len = (ecs_size_t)(ptr - type_name); - if (name_path_len <= ecs_os_strlen(path)) { - if (!ecs_os_strncmp(type_name, path, name_path_len)) { - type_name = &type_name[name_path_len + 2]; + /* If an observer is only interested in table events, we only need to + * observe a single component, as each table event will be emitted for all + * components of the source table. */ + bool only_table_events = true; + for (i = 0; i < o->event_count; i ++) { + ecs_entity_t e = o->events[i]; + if (e != EcsOnTableCreate && e != EcsOnTableDelete) { + only_table_events = false; + break; + } + } + + if (query->flags & EcsQueryMatchPrefab) { + child_desc.query.flags |= EcsQueryMatchPrefab; + } + + if (query->flags & EcsQueryMatchDisabled) { + child_desc.query.flags |= EcsQueryMatchDisabled; + } + + bool self_term_handled = false; + for (i = 0; i < term_count; i ++) { + if (query->terms[i].inout == EcsInOutFilter) { + continue; + } + + ecs_term_t *term = &child_desc.query.terms[0]; + child_desc.term_index_ = query->terms[i].field_index; + *term = query->terms[i]; + + /* Don't create observers for non-$this terms */ + if (!ecs_term_match_this(term) && term->src.id & EcsIsVariable) { + continue; + } + + int16_t oper = term->oper; + ecs_id_t id = term->id; + + if (only_table_events) { + /* For table event observers, only observe a single $this|self + * term. Make sure to create observers for non-self terms, as those + * require event propagation. */ + if (ecs_term_match_this(term) && + (term->src.id & EcsTraverseFlags) == EcsSelf) + { + if (oper == EcsAnd) { + if (!self_term_handled) { + self_term_handled = true; + } else { + continue; + } } } } - } - ecs_os_free(path); - return type_name; -} + /* AndFrom & OrFrom terms insert multiple observers */ + if (oper == EcsAndFrom || oper == EcsOrFrom) { + const ecs_type_t *type = ecs_get_type(world, id); + if (!type) { + continue; + } -// Validate registered component -void ecs_cpp_component_validate( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - size_t size, - size_t alignment, - bool implicit_name) -{ - /* If entity has a name check if it matches */ - if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { - if (!implicit_name && id >= EcsFirstUserComponentId) { -# ifndef FLECS_NDEBUG - char *path = ecs_get_path_w_sep( - world, 0, id, "::", NULL); - if (ecs_os_strcmp(path, name)) { - ecs_err( - "component '%s' already registered with name '%s'", - name, path); - ecs_abort(ECS_INCONSISTENT_NAME, NULL); + int32_t ti, ti_count = type->count; + ecs_id_t *ti_ids = type->array; + + /* Correct operator will be applied when an event occurs, and + * the observer is evaluated on the observer source */ + term->oper = EcsAnd; + for (ti = 0; ti < ti_count; ti ++) { + ecs_id_t ti_id = ti_ids[ti]; + ecs_component_record_t *cr = flecs_components_get(world, ti_id); + if (cr->flags & EcsIdOnInstantiateDontInherit) { + continue; + } + + term->first.name = NULL; + term->first.id = ti_ids[ti]; + term->id = ti_ids[ti]; + + if (flecs_observer_add_child(world, o, &child_desc)) { + goto error; + } } - ecs_os_free(path); -# endif + continue; } - } else { - /* Ensure that the entity id valid */ - if (!ecs_is_alive(world, id)) { - ecs_ensure(world, id); + + /* Single component observers never use OR */ + if (oper == EcsOr) { + term->oper = EcsAnd; + } + + /* If observer only contains optional terms, match everything */ + if (optional_only) { + term->id = EcsAny; + term->first.id = EcsAny; + term->src.id = EcsThis | EcsIsVariable | EcsSelf; + term->second.id = 0; + } else if (term->oper == EcsOptional) { + if (only_table_events || desc->events[0] == EcsMonitor) { + /* For table events & monitors optional terms aren't necessary */ + continue; + } } - /* Register name with entity, so that when the entity is created the - * correct id will be resolved from the name. Only do this when the - * entity is empty. */ - ecs_add_path_w_sep(world, id, 0, name, "::", "::"); + if (flecs_observer_add_child(world, o, &child_desc)) { + goto error; + } + + if (optional_only) { + break; + } } - /* If a component was already registered with this id but with a - * different size, the ecs_component_init function will fail. */ + /* If observer has Not terms, we need to create a query that replaces Not + * with Optional which we can use to populate the observer data for the + * table that the entity moved away from (or to, if it's an OnRemove + * observer). */ + if (has_not) { + ecs_query_desc_t not_desc = desc->query; + not_desc.expr = NULL; - /* We need to explicitly call ecs_component_init here again. Even though - * the component was already registered, it may have been registered - * with a different world. This ensures that the component is registered - * with the same id for the current world. - * If the component was registered already, nothing will change. */ - ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t) { - .entity.entity = id, - .size = size, - .alignment = alignment - }); - (void)ent; - ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy_n(not_desc.terms, o->query->terms, + ecs_term_t, term_count); /* cast suppresses warning */ + + for (i = 0; i < term_count; i ++) { + if (not_desc.terms[i].oper == EcsNot) { + not_desc.terms[i].oper = EcsOptional; + } + } + + flecs_observer_impl(o)->not_query = + ecs_query_init(world, ¬_desc); + } + + return 0; +error: + return -1; } -ecs_entity_t ecs_cpp_component_register( +static +void flecs_observer_poly_fini(void *ptr) { + flecs_observer_fini(ptr); +} + +ecs_observer_t* flecs_observer_init( ecs_world_t *world, - ecs_entity_t id, - const char *name, - const char *symbol, - ecs_size_t size, - ecs_size_t alignment) + ecs_entity_t entity, + const ecs_observer_desc_t *desc) { - (void)size; - (void)alignment; - - /* If the component is not yet registered, ensure no other component - * or entity has been registered with this name. Ensure component is - * looked up from root. */ - ecs_entity_t prev_scope = ecs_set_scope(world, 0); - ecs_entity_t ent; - if (id) { - ent = id; - } else { - ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); - } - ecs_set_scope(world, prev_scope); + ecs_assert(flecs_poly_is(world, ecs_world_t), + ECS_INTERNAL_ERROR, NULL); + ecs_check(desc->callback != NULL || desc->run != NULL, + ECS_INVALID_OPERATION, + "cannot create observer: must at least specify callback or run"); + + ecs_observer_impl_t *impl = flecs_calloc_t( + &world->allocator, ecs_observer_impl_t); + ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); + impl->id = ++ world->observable.last_observer_id; + + flecs_poly_init(impl, ecs_observer_t); + ecs_observer_t *o = &impl->pub; + o->world = world; + impl->dtor = flecs_observer_poly_fini; + + /* Make writeable copy of query desc so that we can set name. This will + * make debugging easier, as any error messages related to creating the + * query will have the name of the observer. */ + ecs_query_desc_t query_desc = desc->query; + query_desc.entity = 0; + query_desc.cache_kind = EcsQueryCacheNone; + + ecs_query_t *query = NULL; + + /* Only do optimization when not in sanitized mode. This ensures that the + * behavior is consistent between observers with and without queries, as + * both paths will be exercised in unit tests. */ +#ifndef FLECS_SANITIZE + /* Temporary arrays for dummy query */ + ecs_term_t terms[FLECS_TERM_COUNT_MAX] = {0}; + ecs_size_t sizes[FLECS_TERM_COUNT_MAX] = {0}; + ecs_id_t ids[FLECS_TERM_COUNT_MAX] = {0}; + + ecs_query_t dummy_query = { + .terms = terms, + .sizes = sizes, + .ids = ids + }; - /* If entity exists, compare symbol name to ensure that the component - * we are trying to register under this name is the same */ - if (ent) { - if (!id && ecs_has(world, ent, EcsComponent)) { - const char *sym = ecs_get_symbol(world, ent); - ecs_assert(sym != NULL, ECS_MISSING_SYMBOL, - ecs_get_name(world, ent)); - (void)sym; - -# ifndef FLECS_NDEBUG - if (ecs_os_strcmp(sym, symbol)) { - ecs_err( - "component with name '%s' is already registered for"\ - " type '%s' (trying to register for type '%s')", - name, sym, symbol); - ecs_abort(ECS_NAME_IN_USE, NULL); - } -# endif - - /* If an existing id was provided, it's possible that this id was - * registered with another type. Make sure that in this case at - * least the component size/alignment matches. - * This allows applications to alias two different types to the same - * id, which enables things like redefining a C type in C++ by - * inheriting from it & adding utility functions etc. */ - } else { - const EcsComponent *comp = ecs_get(world, ent, EcsComponent); - if (comp) { - ecs_assert(comp->size == size, - ECS_INVALID_COMPONENT_SIZE, NULL); - ecs_assert(comp->alignment == alignment, - ECS_INVALID_COMPONENT_ALIGNMENT, NULL); + if (desc->events[0] != EcsMonitor) { + if (flecs_query_finalize_simple(world, &dummy_query, &query_desc)) { + /* Flag is set if query increased the keep_alive count of the + * queried for component, which prevents deleting the component + * while queries are still alive. */ + bool trivial_observer = (dummy_query.term_count == 1) && + (dummy_query.flags & EcsQueryIsTrivial) && + (dummy_query.flags & EcsQueryMatchOnlySelf) && + !dummy_query.row_fields; + if (trivial_observer) { + if (ECS_PAIR_FIRST(dummy_query.terms[0].id) != EcsChildOf) { + dummy_query.flags |= desc->query.flags; + query = &dummy_query; + } } else { - /* If the existing id is not a component, no checking is - * needed. */ + /* We're going to create an actual query, so undo the keep_alive + * increment of the dummy_query. */ + int32_t i, count = dummy_query.term_count; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + flecs_component_unlock(world, term->id); + } } } + } +#endif - /* If no entity is found, lookup symbol to check if the component was - * registered under a different name. */ - } else { - ent = ecs_lookup_symbol(world, symbol, false); - ecs_assert(ent == 0, ECS_INCONSISTENT_COMPONENT_ID, symbol); + /* Create query */ + if (!query) { + query = o->query = ecs_query_init( + world, &query_desc); + if (query == NULL) { + flecs_observer_fini(o); + return 0; + } + + flecs_poly_assert(query, ecs_query_t); } - return id; -} + ecs_check(query->term_count > 0, ECS_INVALID_PARAMETER, + "observer must have at least one term"); -ecs_entity_t ecs_cpp_component_register_explicit( - ecs_world_t *world, - ecs_entity_t s_id, - ecs_entity_t id, - const char *name, - const char *type_name, - const char *symbol, - size_t size, - size_t alignment, - bool is_component) -{ - // If an explicit id is provided, it is possible that the symbol and - // name differ from the actual type, as the application may alias - // one type to another. - if (!id) { - if (!name) { - // If no name was provided, retrieve the name implicitly from - // the name_helper class. - name = ecs_cpp_trim_module(world, type_name); + int i, var_count = 0; + for (i = 0; i < query->term_count; i ++) { + ecs_term_t *term = &query->terms[i]; + if (!ecs_term_match_this(term)) { + if (term->src.id & EcsIsVariable) { + /* Term has a non-$this variable source */ + var_count ++; + } } - } else { - // If an explicit id is provided but it has no name, inherit - // the name from the type. - if (!ecs_is_valid(world, id) || !ecs_get_name(world, id)) { - name = ecs_cpp_trim_module(world, type_name); + } + + ecs_check(query->term_count > var_count, ECS_UNSUPPORTED, + "observers with only non-$this variable sources are not yet supported"); + (void)var_count; + + o->run = desc->run; + o->callback = desc->callback; + o->ctx = desc->ctx; + o->callback_ctx = desc->callback_ctx; + o->run_ctx = desc->run_ctx; + o->ctx_free = desc->ctx_free; + o->callback_ctx_free = desc->callback_ctx_free; + o->run_ctx_free = desc->run_ctx_free; + o->observable = flecs_get_observable(world); + o->entity = entity; + o->world = world; + impl->term_index = desc->term_index_; + impl->flags |= desc->flags_ | + (query->flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)); + + ecs_check(!(desc->yield_existing && + (desc->flags_ & (EcsObserverYieldOnCreate|EcsObserverYieldOnDelete))), + ECS_INVALID_PARAMETER, + "cannot set yield_existing and YieldOn* flags at the same time"); + + /* Check if observer is monitor. Monitors are created as multi observers + * since they require pre/post checking of the filter to test if the + * entity is entering/leaving the monitor. */ + for (i = 0; i < FLECS_EVENT_DESC_MAX; i ++) { + ecs_entity_t event = desc->events[i]; + if (!event) { + break; + } + + if (event == EcsMonitor) { + ecs_check(i == 0, ECS_INVALID_PARAMETER, + "monitor observers can only have a single Monitor event"); + + o->events[0] = EcsOnAdd; + o->events[1] = EcsOnRemove; + o->event_count ++; + impl->flags |= EcsObserverIsMonitor; + if (desc->yield_existing) { + impl->flags |= EcsObserverYieldOnCreate; + impl->flags |= EcsObserverYieldOnDelete; + } + } else { + o->events[i] = event; + if (desc->yield_existing) { + if (event == EcsOnRemove) { + impl->flags |= EcsObserverYieldOnDelete; + } else { + impl->flags |= EcsObserverYieldOnCreate; + } + } } + + o->event_count ++; } - ecs_entity_t entity; - if (is_component || size != 0) { - entity = ecs_component_init(world, &(ecs_component_desc_t){ - .entity.entity = s_id, - .entity.name = name, - .entity.sep = "::", - .entity.root_sep = "::", - .entity.symbol = symbol, - .size = size, - .alignment = alignment - }); + /* Observer must have at least one event */ + ecs_check(o->event_count != 0, ECS_INVALID_PARAMETER, + "observer must have at least one event"); + + bool multi = false; + + if (query->term_count == 1 && !desc->last_event_id) { + ecs_term_t *term = &query->terms[0]; + /* If the query has a single term but it is a *From operator, we + * need to create a multi observer */ + multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom); + + /* An observer with only optional terms is a special case that is + * only handled by multi observers */ + multi |= term->oper == EcsOptional; + } + + bool is_monitor = impl->flags & EcsObserverIsMonitor; + if (query->term_count == 1 && !is_monitor && !multi) { + ecs_term_t *term = &query->terms[0]; + term->field_index = flecs_ito(int8_t, desc->term_index_); + if (flecs_uni_observer_init(world, o, term->id, desc)) { + goto error; + } } else { - entity = ecs_entity_init(world, &(ecs_entity_desc_t){ - .entity = s_id, - .name = name, - .sep = "::", - .root_sep = "::", - .symbol = symbol - }); + if (flecs_multi_observer_init(world, o, desc)) { + goto error; + } } - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); + if (impl->flags & EcsObserverYieldOnCreate) { + flecs_observer_yield_existing(world, o, false); + } - return entity; + return o; +error: + return NULL; } -ecs_entity_t ecs_cpp_enum_constant_register( +ecs_entity_t ecs_observer_init( ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t id, - const char *name, - int value) + const ecs_observer_desc_t *desc) { - ecs_suspend_readonly_state_t readonly_state; - world = flecs_suspend_readonly(world, &readonly_state); + ecs_entity_t entity = 0; + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_observer_desc_t was not initialized to zero"); + ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, + "cannot create observer while world is being deleted"); - const char *parent_name = ecs_get_name(world, parent); - ecs_size_t parent_name_len = ecs_os_strlen(parent_name); - if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { - name += parent_name_len; - if (name[0] == '_') { - name ++; - } + entity = desc->entity; + if (!entity && !desc->global_observer) { + entity = ecs_entity(world, {0}); } - ecs_entity_t prev = ecs_set_scope(world, parent); - id = ecs_entity_init(world, &(ecs_entity_desc_t) { - .entity = id, - .name = name - }); - ecs_assert(id != 0, ECS_INVALID_OPERATION, name); - ecs_set_scope(world, prev); + EcsPoly *poly = NULL; + if (!entity) { + ecs_observer_t *o = flecs_observer_init(world, entity, desc);\ + if (!o) { + goto error; + } - #ifdef FLECS_DEBUG - const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); - ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); - ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED, - "enum component must have 32bit size"); - #endif + ecs_vec_append_t(NULL, &world->observable.global_observers, + ecs_observer_t*)[0] = o; + } else { + poly = flecs_poly_bind(world, entity, ecs_observer_t); - ecs_set_id(world, id, parent, sizeof(int), &value); + if (!poly->poly) { + ecs_observer_t *o = flecs_observer_init(world, entity, desc);\ + if (!o) { + goto error; + } - flecs_resume_readonly(world, &readonly_state); + ecs_assert(o->entity == entity, ECS_INTERNAL_ERROR, NULL); + poly->poly = o; - ecs_trace("#[green]constant#[reset] %s.%s created with value %d", - ecs_get_name(world, parent), name, value); + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]observer#[reset] %s created", + ecs_get_name(world, entity)); + } + } else { + flecs_poly_assert(poly->poly, ecs_observer_t); + ecs_observer_t *o = (ecs_observer_t*)poly->poly; - return id; -} + if (o->ctx_free) { + if (o->ctx && o->ctx != desc->ctx) { + o->ctx_free(o->ctx); + } + } -static int32_t flecs_reset_count = 0; + if (o->callback_ctx_free) { + if (o->callback_ctx && o->callback_ctx != desc->callback_ctx) { + o->callback_ctx_free(o->callback_ctx); + o->callback_ctx_free = NULL; + o->callback_ctx = NULL; + } + } -int32_t ecs_cpp_reset_count_get(void) { - return flecs_reset_count; -} + if (o->run_ctx_free) { + if (o->run_ctx && o->run_ctx != desc->run_ctx) { + o->run_ctx_free(o->run_ctx); + o->run_ctx_free = NULL; + o->run_ctx = NULL; + } + } -int32_t ecs_cpp_reset_count_inc(void) { - return ++flecs_reset_count; -} + if (desc->run) { + o->run = desc->run; + if (!desc->callback) { + o->callback = NULL; + } + } -#endif + if (desc->callback) { + o->callback = desc->callback; + if (!desc->run) { + o->run = NULL; + } + } + if (desc->ctx) { + o->ctx = desc->ctx; + } -#ifdef FLECS_DEPRECATED + if (desc->callback_ctx) { + o->callback_ctx = desc->callback_ctx; + } + if (desc->run_ctx) { + o->run_ctx = desc->run_ctx; + } -#endif + if (desc->ctx_free) { + o->ctx_free = desc->ctx_free; + } + if (desc->callback_ctx_free) { + o->callback_ctx_free = desc->callback_ctx_free; + } -#ifdef FLECS_OS_API_IMPL -#ifdef ECS_TARGET_MSVC -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -#include + if (desc->run_ctx_free) { + o->run_ctx_free = desc->run_ctx_free; + } + } -static -ecs_os_thread_t win_thread_new( - ecs_os_thread_callback_t callback, - void *arg) + flecs_poly_modified(world, entity, ecs_observer_t); + } + + return entity; +error: + if (entity) { + ecs_delete(world, entity); + } + return 0; +} + +const ecs_observer_t* ecs_observer_get( + const ecs_world_t *world, + ecs_entity_t observer) { - HANDLE *thread = ecs_os_malloc_t(HANDLE); - *thread = CreateThread( - NULL, 0, (LPTHREAD_START_ROUTINE)callback, arg, 0, NULL); - return (ecs_os_thread_t)(uintptr_t)thread; + return flecs_poly_get(world, observer, ecs_observer_t); } -static -void* win_thread_join( - ecs_os_thread_t thr) +void flecs_observer_fini( + ecs_observer_t *o) { - HANDLE *thread = (HANDLE*)(uintptr_t)thr; - DWORD r = WaitForSingleObject(*thread, INFINITE); - if (r == WAIT_FAILED) { - ecs_err("win_thread_join: WaitForSingleObject failed"); + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = o->world; + + flecs_poly_assert(world, ecs_world_t); + ecs_observer_impl_t *impl = flecs_observer_impl(o); + + if (impl->flags & EcsObserverYieldOnDelete) { + flecs_observer_yield_existing(world, o, true); } - ecs_os_free(thread); - return NULL; + + if (impl->flags & EcsObserverIsMulti) { + ecs_observer_t **children = ecs_vec_first(&impl->children); + int32_t i, children_count = ecs_vec_count(&impl->children); + + for (i = 0; i < children_count; i ++) { + flecs_observer_fini(children[i]); + } + + ecs_os_free(impl->last_event_id); + } else { + flecs_unregister_observer(world, o->observable, o); + } + + ecs_vec_fini_t(&world->allocator, &impl->children, ecs_observer_t*); + + /* Cleanup queries */ + if (o->query) { + ecs_query_fini(o->query); + } else if (impl->register_id) { + flecs_component_unlock(world, impl->register_id); + } + + if (impl->not_query) { + ecs_query_fini(impl->not_query); + } + + /* Cleanup context */ + if (o->ctx_free) { + o->ctx_free(o->ctx); + } + + if (o->callback_ctx_free) { + o->callback_ctx_free(o->callback_ctx); + } + + if (o->run_ctx_free) { + o->run_ctx_free(o->run_ctx); + } + + flecs_poly_fini(o, ecs_observer_t); + flecs_free_t(&world->allocator, ecs_observer_impl_t, o); } -static -int32_t win_ainc( - int32_t *count) +void flecs_observer_set_disable_bit( + ecs_world_t *world, + ecs_entity_t e, + ecs_flags32_t bit, + bool cond) { - return InterlockedIncrement(count); + const EcsPoly *poly = ecs_get_pair(world, e, EcsPoly, EcsObserver); + if (!poly || !poly->poly) { + return; + } + + ecs_observer_t *o = poly->poly; + ecs_observer_impl_t *impl = flecs_observer_impl(o); + if (impl->flags & EcsObserverIsMulti) { + ecs_observer_t **children = ecs_vec_first(&impl->children); + int32_t i, children_count = ecs_vec_count(&impl->children); + if (children_count) { + for (i = 0; i < children_count; i ++) { + ECS_BIT_COND(flecs_observer_impl(children[i])->flags, bit, cond); + } + } + } else { + flecs_poly_assert(o, ecs_observer_t); + ECS_BIT_COND(impl->flags, bit, cond); + } } +/** + * @file on_delete.c + * @brief Implementation of OnDelete/OnDeleteTarget traits. + */ + + static -int32_t win_adec( - int32_t *count) +void flecs_marked_id_push( + ecs_world_t *world, + ecs_component_record_t* cr, + ecs_entity_t action, + bool delete_id) { - return InterlockedDecrement(count); + ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, + &world->store.marked_ids, ecs_marked_id_t); + + m->cr = cr; + m->id = cr->id; + m->action = action; + m->delete_id = delete_id; + + cr->flags |= EcsIdMarkedForDelete; + + flecs_component_claim(world, cr); } static -ecs_os_mutex_t win_mutex_new(void) { - CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); - InitializeCriticalSection(mutex); - return (ecs_os_mutex_t)(uintptr_t)mutex; -} +void flecs_component_mark_for_delete( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_entity_t action, + bool delete_id); static -void win_mutex_free( - ecs_os_mutex_t m) +void flecs_target_mark_for_delete( + ecs_world_t *world, + ecs_entity_t e) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - DeleteCriticalSection(mutex); - ecs_os_free(mutex); + ecs_component_record_t *cr; + ecs_record_t *r = flecs_entities_get(world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If entity is not used as id or as relationship target, there won't + * be any tables with a reference to it. */ + ecs_flags32_t flags = r->row & ECS_ROW_FLAGS_MASK; + if (!(flags & (EcsEntityIsId|EcsEntityIsTarget))) { + return; + } + + if (flags & EcsEntityIsId) { + if ((cr = flecs_components_get(world, e))) { + flecs_component_mark_for_delete(world, cr, + ECS_ID_ON_DELETE(cr->flags), true); + } + if ((cr = flecs_components_get(world, ecs_pair(e, EcsWildcard)))) { + flecs_component_mark_for_delete(world, cr, + ECS_ID_ON_DELETE(cr->flags), true); + } + } + if (flags & EcsEntityIsTarget) { + if ((cr = flecs_components_get(world, ecs_pair(EcsWildcard, e)))) { + flecs_component_mark_for_delete(world, cr, + ECS_ID_ON_DELETE_TARGET(cr->flags), true); + } + if ((cr = flecs_components_get(world, ecs_pair(EcsFlag, e)))) { + flecs_component_mark_for_delete(world, cr, + ECS_ID_ON_DELETE_TARGET(cr->flags), true); + } + } } static -void win_mutex_lock( - ecs_os_mutex_t m) +void flecs_targets_mark_for_delete( + ecs_world_t *world, + ecs_table_t *table) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - EnterCriticalSection(mutex); + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + flecs_target_mark_for_delete(world, entities[i]); + } } static -void win_mutex_unlock( - ecs_os_mutex_t m) +bool flecs_id_is_delete_target( + ecs_id_t id, + ecs_entity_t action) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - LeaveCriticalSection(mutex); + if (!action && ecs_id_is_pair(id) && ECS_PAIR_FIRST(id) == EcsWildcard) { + /* If no explicit delete action is provided, and the id we're deleting + * has the form (*, Target), use OnDeleteTarget action */ + return true; + } + return false; } static -ecs_os_cond_t win_cond_new(void) { - CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); - InitializeConditionVariable(cond); - return (ecs_os_cond_t)(uintptr_t)cond; -} - -static -void win_cond_free( - ecs_os_cond_t c) +ecs_entity_t flecs_get_delete_action( + ecs_table_t *table, + const ecs_table_record_t *tr, + ecs_entity_t action, + bool delete_target) { - (void)c; + ecs_entity_t result = action; + if (!result && delete_target) { + ecs_component_record_t *cr = tr->hdr.cr; + ecs_id_t id = cr->id; + + /* If action is not specified and we're deleting a relationship target, + * derive the action from the current record */ + int32_t i = tr->index, count = tr->count; + do { + ecs_type_t *type = &table->type; + ecs_table_record_t *trr = &table->_->records[i]; + ecs_component_record_t *crr = trr->hdr.cr; + result = ECS_ID_ON_DELETE_TARGET(crr->flags); + if (result == EcsDelete) { + /* Delete takes precedence over Remove */ + break; + } + + if (count > 1) { + /* If table contains multiple pairs for target they are not + * guaranteed to occupy consecutive elements in the table's type + * vector, so a linear search is needed to find matches. */ + for (++ i; i < type->count; i ++) { + if (ecs_id_match(type->array[i], id)) { + break; + } + } + + /* We should always have as many matching ids as tr->count */ + ecs_assert(i < type->count, ECS_INTERNAL_ERROR, NULL); + } + } while (--count); + } + + return result; } -static -void win_cond_signal( - ecs_os_cond_t c) +static +void flecs_simple_delete( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *r) { - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - WakeConditionVariable(cond); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_check(!ecs_has_pair(world, entity, EcsOnDelete, EcsPanic), + ECS_CONSTRAINT_VIOLATED, + "cannot delete entity '%s' with (OnDelete, Panic) trait", + flecs_errstr(ecs_get_path(world, entity))); + + flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); + + /* Entity is still in use by a query */ + ecs_assert((world->flags & EcsWorldQuit) || + !flecs_component_is_delete_locked(world, entity), + ECS_INVALID_OPERATION, + "cannot delete '%s' as it is still in use by queries", + flecs_errstr(ecs_id_str(world, entity))); + + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_diff_t diff = { + .removed = table->type, + .removed_flags = table->flags & EcsTableRemoveEdgeFlags + }; + + int32_t row = ECS_RECORD_TO_ROW(r->row); + flecs_actions_delete_tree(world, table, row, 1, &diff); + flecs_entity_remove_non_fragmenting(world, entity, r); + flecs_table_delete(world, table, row, true); + + flecs_entities_remove(world, entity); + + flecs_journal_end(); +error: + return; } -static -void win_cond_broadcast( - ecs_os_cond_t c) +static +bool flecs_is_childof_tgt_only( + const ecs_component_record_t *cr) { - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - WakeAllConditionVariable(cond); + ecs_pair_record_t *pr = cr->pair; + if (pr->second.next) { + return false; + } + + if (ECS_PAIR_FIRST(pr->second.prev->id) != EcsWildcard) { + return false; + } + + return true; } -static -void win_cond_wait( - ecs_os_cond_t c, - ecs_os_mutex_t m) +static +void flecs_component_delete_non_fragmenting_childof( + ecs_world_t *world, + ecs_component_record_t *cr) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - SleepConditionVariableCS(cond, mutex, INFINITE); -} + cr->flags |= EcsIdMarkedForDelete; -static bool win_time_initialized; -static double win_time_freq; -static LARGE_INTEGER win_time_start; + ecs_pair_record_t *pr = cr->pair; + int32_t i, count = ecs_vec_count(&pr->ordered_children); + ecs_entity_t *children = ecs_vec_first(&pr->ordered_children); -static -void win_time_setup(void) { - if ( win_time_initialized) { - return; + for (i = 0; i < count; i ++) { + ecs_entity_t e = children[i]; + ecs_record_t *r = flecs_entities_get_any(world, e); + if ((r->row & EcsEntityIsTarget)) { + ecs_component_record_t *child_cr = flecs_components_get( + world, ecs_childof(e)); + if (child_cr && flecs_component_has_non_fragmenting_childof(child_cr)) { + if (!flecs_is_childof_tgt_only(child_cr)) { + /* Entity is used as target with other relationships, go + * through regular cleanup path. */ + flecs_target_mark_for_delete(world, e); + } else { + flecs_component_delete_non_fragmenting_childof(world, child_cr); + } + } else { + /* Entity is a target but is not a (non-fragmenting) ChildOf + * target. Go through regular cleanup path. */ + flecs_target_mark_for_delete(world, e); + } + } + + flecs_simple_delete(world, e, r); } - - win_time_initialized = true; - LARGE_INTEGER freq; - QueryPerformanceFrequency(&freq); - QueryPerformanceCounter(&win_time_start); - win_time_freq = (double)freq.QuadPart / 1000000000.0; + ecs_component_record_t *tgt_wc = pr->second.prev; + ecs_assert(ECS_PAIR_FIRST(tgt_wc->id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); + + flecs_component_release(world, tgt_wc); } static -void win_sleep( - int32_t sec, - int32_t nanosec) +bool flecs_component_mark_non_fragmenting_childof( + ecs_world_t *world, + ecs_component_record_t *cr) { - HANDLE timer; - LARGE_INTEGER ft; + ecs_entity_t tgt = ECS_PAIR_SECOND(cr->id); - ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); + ecs_component_record_t *childof_cr = flecs_components_get( + world, ecs_childof(tgt)); + if (!childof_cr) { + return false; + } - timer = CreateWaitableTimer(NULL, TRUE, NULL); - SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); - WaitForSingleObject(timer, INFINITE); - CloseHandle(timer); -} + ecs_flags32_t flags = childof_cr->flags; -static double win_time_freq; -static ULONG win_current_resolution; + if (flags & EcsIdMarkedForDelete) { + return false; + } -static -void win_enable_high_timer_resolution(bool enable) -{ - HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); - if (!hntdll) { - return; + childof_cr->flags |= EcsIdMarkedForDelete; + + if (!flecs_component_has_non_fragmenting_childof(childof_cr)) { + return false; } - LONG (__stdcall *pNtSetTimerResolution)( - ULONG desired, BOOLEAN set, ULONG * current); + ecs_pair_record_t *pr = childof_cr->pair; - pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) - GetProcAddress(hntdll, "NtSetTimerResolution"); + if (!pr->second.next) { + if (ECS_PAIR_FIRST(pr->second.prev->id) == EcsWildcard) { + /* Entity is only used as ChildOf target */ + flecs_component_delete_non_fragmenting_childof(world, childof_cr); + return true; + } + } - if(!pNtSetTimerResolution) { - return; + flecs_marked_id_push(world, childof_cr, EcsDelete, true); + + int32_t i, count = ecs_vec_count(&pr->ordered_children); + ecs_entity_t *children = ecs_vec_first(&pr->ordered_children); + for (i = 0; i < count; i ++) { + ecs_entity_t e = children[i]; + + ecs_component_record_t *tgt_cr = flecs_components_get( + world, ecs_pair(EcsWildcard, e)); + if (!tgt_cr) { + continue; + } + + flecs_component_mark_for_delete(world, tgt_cr, 0, true); } - ULONG current, resolution = 10000; /* 1 ms */ + return false; +} - if (!enable && win_current_resolution) { - pNtSetTimerResolution(win_current_resolution, 0, ¤t); - win_current_resolution = 0; - return; - } else if (!enable) { +static +void flecs_component_mark_for_delete( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_entity_t action, + bool delete_id) +{ + if (cr->flags & EcsIdMarkedForDelete) { return; } - if (resolution == win_current_resolution) { - return; + flecs_marked_id_push(world, cr, action, delete_id); + + ecs_id_t id = cr->id; + + bool delete_target = flecs_id_is_delete_target(id, action); + if (delete_target) { + if (flecs_component_mark_non_fragmenting_childof(world, cr)) { + return; + } } - if (win_current_resolution) { - pNtSetTimerResolution(win_current_resolution, 0, ¤t); + /* Mark all tables with the id for delete */ + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&cr->cache, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (table->flags & EcsTableMarkedForDelete) { + continue; + } + + ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, + delete_target); + + /* If this is a Delete action, recursively mark ids & tables */ + if (cur_action == EcsDelete) { + table->flags |= EcsTableMarkedForDelete; + ecs_log_push_2(); + flecs_targets_mark_for_delete(world, table); + ecs_log_pop_2(); + } else if (cur_action == EcsPanic) { + flecs_throw_invalid_delete(world, id); + } + } } - if (pNtSetTimerResolution(resolution, 1, ¤t)) { - /* Try setting a lower resolution */ - resolution *= 2; - if(pNtSetTimerResolution(resolution, 1, ¤t)) return; + /* Same for empty tables */ + if (flecs_table_cache_empty_iter(&cr->cache, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + tr->hdr.table->flags |= EcsTableMarkedForDelete; + } } - win_current_resolution = resolution; + /* Flag component records for deletion */ + if (ecs_id_is_wildcard(id)) { + ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *cur = cr; + if (ECS_PAIR_SECOND(id) == EcsWildcard) { + while ((cur = flecs_component_first_next(cur))) { + cur->flags |= EcsIdMarkedForDelete; + } + } else { + /* Iterating all pairs for relationship target */ + ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + while ((cur = flecs_component_second_next(cur))) { + cur->flags |= EcsIdMarkedForDelete; + + /* If relationship is traversable and is removed upon deletion + * of a target, we may have to rematch queries. If a query + * matched for example (IsA, A) -> (IsA, B) -> Position, and + * B is deleted, Position would no longer be reachable from + * tables that have (IsA, B). */ + if (cur->flags & EcsIdTraversable) { + /* If tables with (R, target) are deleted anyway we don't + * need to rematch. Since this will happen recursively it is + * guaranteed that queries cannot have tables that reached a + * component through the deleted entity. */ + if (!(cur->flags & EcsIdOnDeleteTargetDelete)) { + /* Only bother if tables have relationship. */ + if (ecs_map_count(&cur->cache.index)) { + flecs_update_component_monitors(world, NULL, + &(ecs_type_t){ + .array = (ecs_id_t[]){cur->id}, + .count = 1 + }); + } + } + } + } + } + } } static -uint64_t win_time_now(void) { - uint64_t now; +bool flecs_on_delete_mark( + ecs_world_t *world, + ecs_id_t id, + ecs_entity_t action, + bool delete_id) +{ + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { + /* If there's no component record, there's nothing to delete */ + return false; + } - LARGE_INTEGER qpc_t; - QueryPerformanceCounter(&qpc_t); - now = (uint64_t)(qpc_t.QuadPart / win_time_freq); + if (!action) { + /* If no explicit action is provided, derive it */ + if (!ecs_id_is_pair(id) || ECS_PAIR_SECOND(id) == EcsWildcard) { + /* Delete actions are determined by the component, or in the case + * of a pair by the relationship. */ + action = ECS_ID_ON_DELETE(cr->flags); + } + } - return now; + if (action == EcsPanic) { + /* This id is protected from deletion */ + flecs_throw_invalid_delete(world, id); + return false; + } + + flecs_component_mark_for_delete(world, cr, action, delete_id); + + return true; } -void ecs_set_os_api_impl(void) { - ecs_os_set_api_defaults(); +static +void flecs_remove_from_table( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_table_diff_t temp_diff = { .added = {0} }; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + ecs_table_t *dst_table = table; - ecs_os_api_t api = ecs_os_api; + /* To find the dst table, remove all ids that are marked for deletion */ + int32_t i, t, count = ecs_vec_count(&world->store.marked_ids); + ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); + const ecs_table_record_t *tr; + for (i = 0; i < count; i ++) { + const ecs_component_record_t *cr = ids[i].cr; - api.thread_new_ = win_thread_new; - api.thread_join_ = win_thread_join; - api.ainc_ = win_ainc; - api.adec_ = win_adec; - api.mutex_new_ = win_mutex_new; - api.mutex_free_ = win_mutex_free; - api.mutex_lock_ = win_mutex_lock; - api.mutex_unlock_ = win_mutex_unlock; - api.cond_new_ = win_cond_new; - api.cond_free_ = win_cond_free; - api.cond_signal_ = win_cond_signal; - api.cond_broadcast_ = win_cond_broadcast; - api.cond_wait_ = win_cond_wait; - api.sleep_ = win_sleep; - api.now_ = win_time_now; - api.enable_high_timer_resolution_ = win_enable_high_timer_resolution; + if (!(tr = flecs_component_get_table(cr, dst_table))) { + continue; + } - win_time_setup(); + t = tr->index; - ecs_os_set_api(&api); -} + do { + ecs_id_t id = dst_table->type.array[t]; + ecs_table_t *tgt_table = flecs_table_traverse_remove( + world, dst_table, &id, &temp_diff); + ecs_assert(tgt_table != dst_table, ECS_INTERNAL_ERROR, NULL); + dst_table = tgt_table; + flecs_table_diff_build_append_table(world, &diff, &temp_diff); + } while (dst_table->type.count && (t = ecs_search_offset( + world, dst_table, t, cr->id, NULL)) != -1); + } -#else -#include "pthread.h" + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); -#if defined(__APPLE__) && defined(__MACH__) -#include -#elif defined(__EMSCRIPTEN__) -#include -#else -#include -#endif + if (dst_table != table) { + int32_t table_count = ecs_table_count(table); + if (diff.removed.count && table_count) { + ecs_log_push_3(); + ecs_table_diff_t td; + flecs_table_diff_build_noalloc(&diff, &td); + + if (table->flags & EcsTableHasTraversable) { + for (i = 0; i < diff.removed.count; i ++) { + flecs_update_component_monitors(world, NULL, &(ecs_type_t){ + .array = (ecs_id_t[]){td.removed.array[i]}, + .count = 1 + }); + } + } -static -ecs_os_thread_t posix_thread_new( - ecs_os_thread_callback_t callback, - void *arg) -{ - pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); + flecs_actions_move_remove(world, table, dst_table, 0, table_count, &td); + ecs_log_pop_3(); + } - if (pthread_create (thread, NULL, callback, arg) != 0) { - ecs_os_abort(); + flecs_table_merge(world, dst_table, table); } - return (ecs_os_thread_t)(uintptr_t)thread; + flecs_table_diff_builder_fini(world, &diff); } static -void* posix_thread_join( - ecs_os_thread_t thread) +bool flecs_on_delete_clear_entities( + ecs_world_t *world, + bool force_delete) { - void *arg; - pthread_t *thr = (pthread_t*)(uintptr_t)thread; - pthread_join(*thr, &arg); - ecs_os_free(thr); - return arg; -} + /* Iterate in reverse order so that DAGs get deleted bottom to top */ + int32_t i, last = ecs_vec_count(&world->store.marked_ids), first = 0; + ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); + + do { + for (i = last - 1; i >= first; i --) { + ecs_component_record_t *cr = ids[i].cr; + ecs_entity_t action = ids[i].action; + + /* Empty all tables for id */ + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&cr->cache, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; -static -int32_t posix_ainc( - int32_t *count) -{ - int value; -#ifdef __GNUC__ - value = __sync_add_and_fetch (count, 1); - return value; -#else - /* Unsupported */ - abort(); -#endif + /* If table contains prefabs and we're not deleting the + * prefab entity itself (!force_delete), don't delete table. + * This means that delete_with/remove_all can be used safely + * for game entities without risking modifying prefabs. + * If force_delete is true, it means that one of the + * components, relationships or relationship targets is + * being deleted in which case the table must go too. */ + if ((table->flags & EcsTableIsPrefab) && !force_delete) { + table->flags &= ~EcsTableMarkedForDelete; + continue; + } + + if ((action == EcsRemove) || + !(table->flags & EcsTableMarkedForDelete)) + { + flecs_remove_from_table(world, table); + } else { + ecs_dbg_3( + "#[red]delete#[reset] entities from table %u", + (uint32_t)table->id); + ecs_table_clear_entities(world, table); + } + } + } + + /* If component record contains children with Parent components, + * delete them. */ + if (flecs_component_has_non_fragmenting_childof(cr)) { + int32_t c, count = ecs_vec_count(&cr->pair->ordered_children); + ecs_entity_t *children = ecs_vec_first(&cr->pair->ordered_children); + + ecs_defer_suspend(world); + for (c = count - 1; c >= 0; c --) { + ecs_delete(world, children[c]); + } + ecs_defer_resume(world); + } + + /* User code (from triggers) could have enqueued more ids to delete, + * reobtain the array in case it got reallocated */ + ids = ecs_vec_first(&world->store.marked_ids); + } + + /* Check if new ids were marked since we started */ + int32_t new_last = ecs_vec_count(&world->store.marked_ids); + if (new_last != last) { + /* Iterate remaining ids */ + ecs_assert(new_last > last, ECS_INTERNAL_ERROR, NULL); + first = last; + last = new_last; + } else { + break; + } + } while (true); + + return true; } static -int32_t posix_adec( - int32_t *count) +void flecs_on_delete_clear_sparse( + ecs_world_t *world, + ecs_component_record_t *cr) { - int value; -#ifdef __GNUC__ - value = __sync_sub_and_fetch (count, 1); - return value; -#else - /* Unsupported */ - abort(); -#endif -} + ecs_component_record_t *cur = cr; + while ((cur = flecs_component_second_next(cur))) { + if (!cur->sparse || (!(cur->flags & EcsIdDontFragment))) { + continue; + } -static -ecs_os_mutex_t posix_mutex_new(void) { - pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); - if (pthread_mutex_init(mutex, NULL)) { - abort(); + if (cur->flags & EcsIdOnDeleteTargetDelete) { + flecs_component_delete_sparse(world, cur); + } else if (cur->flags & EcsIdOnDeleteTargetPanic) { + flecs_throw_invalid_delete(world, cr->id); + } } - return (ecs_os_mutex_t)(uintptr_t)mutex; } static -void posix_mutex_free( - ecs_os_mutex_t m) +bool flecs_on_delete_clear_ids( + ecs_world_t *world, + bool force_delete) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - pthread_mutex_destroy(mutex); - ecs_os_free(mutex); + int32_t i, count = ecs_vec_count(&world->store.marked_ids); + ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); + int twice = 2; + + (void)force_delete; + + do { + for (i = 0; i < count; i ++) { + /* Release normal ids before wildcard ids */ + if (ecs_id_is_wildcard(ids[i].id)) { + if (twice == 2) { + continue; + } + } else { + if (twice == 1) { + continue; + } + } + + ecs_component_record_t *cr = ids[i].cr; + bool delete_id = ids[i].delete_id; + + /* Run OnDeleteTarget traits for non-fragmenting relationships */ + ecs_id_t component_id = cr->id; + if (ECS_IS_PAIR(component_id) && + (ECS_PAIR_FIRST(component_id) == EcsWildcard) && + (cr->flags & EcsIdMatchDontFragment)) + { + flecs_on_delete_clear_sparse(world, cr); + } + + /* Run OnDelete traits for non-fragmenting components */ + if (ids[i].action == EcsDelete) { + if (cr->flags & EcsIdDontFragment) { + flecs_component_delete_sparse(world, cr); + } + } + + if (flecs_component_release_tables(world, cr)) { + ecs_assert(!force_delete, ECS_INVALID_OPERATION, + "cannot delete component '%s': tables are keeping it alive", + flecs_errstr(ecs_id_str(world, cr->id))); + + /* There are still tables remaining. This can happen when + * flecs_table_keep has been called for a table, which is used + * whenever code doesn't want a table to get deleted. */ + cr->flags &= ~EcsIdMarkedForDelete; + flecs_component_release(world, cr); + } else { + /* Release the claim taken by flecs_marked_id_push. This may delete the + * component record as all other claims may have been released. */ + int32_t rc = flecs_component_release(world, cr); + ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); + (void)rc; + + /* If rc is 0, the id was likely deleted by a nested delete_with call + * made by an on_remove handler/OnRemove observer */ + if (rc) { + if (delete_id) { + /* If id should be deleted, release initial claim. This happens when + * a component, tag, or part of a pair is deleted. */ + flecs_component_release(world, cr); + } else { + /* If id should not be deleted, unmark component record for deletion. This + * happens when all instances *of* an id are deleted, for example + * when calling ecs_remove_all or ecs_delete_with. */ + cr->flags &= ~EcsIdMarkedForDelete; + } + } + } + } + } while (-- twice); + + return true; } -static -void posix_mutex_lock( - ecs_os_mutex_t m) +void flecs_throw_invalid_delete( + ecs_world_t *world, + ecs_id_t id) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_mutex_lock(mutex)) { - abort(); + (void)id; + + if (!(world->flags & EcsWorldQuit)) { + ecs_throw(ECS_CONSTRAINT_VIOLATED, + "(OnDelete, Panic) constraint violated while deleting entities with %s", + flecs_errstr(ecs_id_str(world, id))); } +error: + return; } -static -void posix_mutex_unlock( - ecs_os_mutex_t m) +void flecs_on_delete( + ecs_world_t *world, + ecs_id_t id, + ecs_entity_t action, + bool delete_id, + bool force_delete) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_mutex_unlock(mutex)) { - abort(); - } -} + /* Cleanup can happen recursively. If a cleanup action is already in + * progress, only append ids to the marked_ids. The topmost cleanup + * frame will handle the actual cleanup. */ + int32_t i, count = ecs_vec_count(&world->store.marked_ids); -static -ecs_os_cond_t posix_cond_new(void) { - pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); - if (pthread_cond_init(cond, NULL)) { - abort(); + /* Collect all ids that need to be deleted */ + flecs_on_delete_mark(world, id, action, delete_id); + + /* Only perform cleanup if we're the first stack frame doing it */ + if (!count && ecs_vec_count(&world->store.marked_ids)) { + ecs_dbg_2("#[red]delete#[reset]"); + ecs_log_push_2(); + + /* Delete all the entities from the to be deleted tables/components */ + flecs_on_delete_clear_entities(world, force_delete); + + /* Release remaining references to the ids */ + flecs_on_delete_clear_ids(world, force_delete); + + /* Ids are deleted, clear stack */ + ecs_vec_clear(&world->store.marked_ids); + + /* If any components got deleted, cleanup type info. Delaying this + * ensures that type info remains available during cleanup. */ + count = ecs_vec_count(&world->store.deleted_components); + ecs_entity_t *comps = ecs_vec_first(&world->store.deleted_components); + for (i = 0; i < count; i ++) { + flecs_type_info_free(world, comps[i]); + } + + ecs_vec_clear(&world->store.deleted_components); + + ecs_log_pop_2(); } - return (ecs_os_cond_t)(uintptr_t)cond; } -static -void posix_cond_free( - ecs_os_cond_t c) +void ecs_delete_with( + ecs_world_t *world, + ecs_id_t id) { - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_destroy(cond)) { - abort(); + flecs_journal_begin(world, EcsJournalDeleteWith, id, NULL, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_on_delete_action(stage, id, EcsDelete)) { + return; } - ecs_os_free(cond); + + flecs_on_delete(world, id, EcsDelete, false, false); + flecs_defer_end(world, stage); + + flecs_journal_end(); } -static -void posix_cond_signal( - ecs_os_cond_t c) +void ecs_remove_all( + ecs_world_t *world, + ecs_id_t id) { - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_signal(cond)) { - abort(); - } -} + flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); -static -void posix_cond_broadcast( - ecs_os_cond_t c) -{ - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_broadcast(cond)) { - abort(); + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { + return; } -} -static -void posix_cond_wait( - ecs_os_cond_t c, - ecs_os_mutex_t m) -{ - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_cond_wait(cond, mutex)) { - abort(); - } + flecs_on_delete(world, id, EcsRemove, false, false); + flecs_defer_end(world, stage); + + flecs_journal_end(); } -static bool posix_time_initialized; +/** + * @file os_api.c + * @brief Operating system abstraction API. + * + * The OS API implements an overridable interface for implementing functions + * that are operating system specific, in addition to a number of hooks which + * allow for customization by the user, like logging. + */ -#if defined(__APPLE__) && defined(__MACH__) -static mach_timebase_info_data_t posix_osx_timebase; -static uint64_t posix_time_start; -#else -static uint64_t posix_time_start; -#endif +#include -static -void posix_time_setup(void) { - if (posix_time_initialized) { - return; - } - - posix_time_initialized = true; +void ecs_os_api_impl(ecs_os_api_t *api); - #if defined(__APPLE__) && defined(__MACH__) - mach_timebase_info(&posix_osx_timebase); - posix_time_start = mach_absolute_time(); - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; - #endif -} +static bool ecs_os_api_initialized = false; +static bool ecs_os_api_initializing = false; +static int ecs_os_api_init_count = 0; -static -void posix_sleep( - int32_t sec, - int32_t nanosec) -{ - struct timespec sleepTime; - ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); +ecs_os_api_t ecs_os_api = { + .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, + .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ +}; - sleepTime.tv_sec = sec; - sleepTime.tv_nsec = nanosec; - if (nanosleep(&sleepTime, NULL)) { - ecs_err("nanosleep failed"); +int64_t ecs_os_api_malloc_count = 0; +int64_t ecs_os_api_realloc_count = 0; +int64_t ecs_os_api_calloc_count = 0; +int64_t ecs_os_api_free_count = 0; + +void ecs_os_set_api( + ecs_os_api_t *os_api) +{ + if (!ecs_os_api_initialized) { + ecs_os_api = *os_api; + ecs_os_api_initialized = true; } } -static -void posix_enable_high_timer_resolution(bool enable) { - (void)enable; +ecs_os_api_t ecs_os_get_api(void) { + return ecs_os_api; } -/* prevent 64-bit overflow when computing relative timestamp - see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 -*/ -#if defined(ECS_TARGET_DARWIN) -static -int64_t posix_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { - int64_t q = value / denom; - int64_t r = value % denom; - return q * numer + r * numer / denom; +void ecs_os_init(void) +{ + if (!ecs_os_api_initialized) { + ecs_os_set_api_defaults(); + } + + if (!(ecs_os_api_init_count ++)) { + if (ecs_os_api.init_) { + ecs_os_api.init_(); + } + } } -#endif - -static -uint64_t posix_time_now(void) { - ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); - uint64_t now; - - #if defined(ECS_TARGET_DARWIN) - now = (uint64_t) posix_int64_muldiv( - (int64_t)mach_absolute_time(), - (int64_t)posix_osx_timebase.numer, - (int64_t)posix_osx_timebase.denom); - #elif defined(__EMSCRIPTEN__) - now = (long long)(emscripten_get_now() * 1000.0 * 1000); - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); - #endif - - return now; +void ecs_os_fini(void) { + if (!--ecs_os_api_init_count) { + if (ecs_os_api.fini_) { + ecs_os_api.fini_(); + } + } } -void ecs_set_os_api_impl(void) { - ecs_os_set_api_defaults(); - - ecs_os_api_t api = ecs_os_api; - - api.thread_new_ = posix_thread_new; - api.thread_join_ = posix_thread_join; - api.ainc_ = posix_ainc; - api.adec_ = posix_adec; - api.mutex_new_ = posix_mutex_new; - api.mutex_free_ = posix_mutex_free; - api.mutex_lock_ = posix_mutex_lock; - api.mutex_unlock_ = posix_mutex_unlock; - api.cond_new_ = posix_cond_new; - api.cond_free_ = posix_cond_free; - api.cond_signal_ = posix_cond_signal; - api.cond_broadcast_ = posix_cond_broadcast; - api.cond_wait_ = posix_cond_wait; - api.sleep_ = posix_sleep; - api.now_ = posix_time_now; - api.enable_high_timer_resolution_ = posix_enable_high_timer_resolution; - - posix_time_setup(); +/* Assume every non-glibc Linux target has no execinfo. + This mainly fixes musl support, as musl doesn't define any preprocessor macro specifying its presence. */ +#if (defined(ECS_TARGET_LINUX) && !defined(__GLIBC__)) || defined(__COSMOCC__) +#define HAVE_EXECINFO 0 +#elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) +#define HAVE_EXECINFO 1 +#else +#define HAVE_EXECINFO 0 +#endif - ecs_os_set_api(&api); -} +#define ECS_BT_BUF_SIZE 100 +#ifdef ECS_TARGET_WINDOWS +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN #endif +#ifndef NOMINMAX +#define NOMINMAX #endif +#endif +#include +#include +#ifdef ECS_TARGET_MSVC +#pragma comment(lib, "DbgHelp.lib") +#endif -#ifdef FLECS_PLECS - -#include -#include - -#define TOK_NEWLINE '\n' -#define TOK_WITH "with" -#define TOK_USING "using" - -#define STACK_MAX_SIZE (64) - -typedef struct { - const char *name; - const char *code; - - ecs_entity_t last_predicate; - ecs_entity_t last_subject; - ecs_entity_t last_object; +void flecs_dump_backtrace( + void *stream) +{ + void* stack[ECS_BT_BUF_SIZE]; + unsigned short frames; + SYMBOL_INFO *symbol; + HANDLE hProcess = GetCurrentProcess(); - ecs_id_t last_assign_id; - ecs_entity_t assign_to; + SymInitialize(hProcess, NULL, TRUE); - ecs_entity_t scope[STACK_MAX_SIZE]; - ecs_entity_t default_scope_type[STACK_MAX_SIZE]; - ecs_entity_t with[STACK_MAX_SIZE]; - ecs_entity_t using[STACK_MAX_SIZE]; - int32_t with_frames[STACK_MAX_SIZE]; - int32_t using_frames[STACK_MAX_SIZE]; - int32_t sp; - int32_t with_frame; - int32_t using_frame; + frames = CaptureStackBackTrace(0, ECS_BT_BUF_SIZE, stack, NULL); + symbol = (SYMBOL_INFO*)ecs_os_malloc( + sizeof(SYMBOL_INFO) + 256 * sizeof(char)); + symbol->MaxNameLen = 255; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - char *comment; + for (int i = 0; i < frames; i++) { + SymFromAddr(hProcess, (DWORD)(uintptr_t)stack[i], NULL, symbol); + fprintf(stream, "%s\n", symbol->Name); + } - bool with_stmt; - bool scope_assign_stmt; - bool using_stmt; - bool assign_stmt; - bool isa_stmt; + ecs_os_free(symbol); +} - int32_t errors; -} plecs_state_t; +#elif HAVE_EXECINFO +#include -static -ecs_entity_t plecs_lookup( - const ecs_world_t *world, - const char *path, - plecs_state_t *state, - ecs_entity_t rel, - bool is_subject) +void flecs_dump_backtrace( + void *stream) { - ecs_entity_t e = 0; + int nptrs; + void *buffer[ECS_BT_BUF_SIZE]; + char **strings; - if (!is_subject) { - ecs_entity_t oneof = 0; - if (rel) { - if (ecs_has_id(world, rel, EcsOneOf)) { - oneof = rel; - } else { - oneof = ecs_get_object(world, rel, EcsOneOf, 0); - } - if (oneof) { - return ecs_lookup_path_w_sep( - world, oneof, path, NULL, NULL, false); - } - } - int using_scope = state->using_frame - 1; - for (; using_scope >= 0; using_scope--) { - e = ecs_lookup_path_w_sep( - world, state->using[using_scope], path, NULL, NULL, false); - if (e) { - break; - } - } + nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); + + strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { + return; } - if (!e) { - e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); + for (int j = 1; j < nptrs; j++) { + fprintf(stream, "%s\n", strings[j]); } - return e; + free(strings); } - -/* Lookup action used for deserializing entity refs in component values */ -#ifdef FLECS_EXPR -static -ecs_entity_t plecs_lookup_action( - const ecs_world_t *world, - const char *path, - void *ctx) -{ - return plecs_lookup(world, path, ctx, 0, false); +#else +void flecs_dump_backtrace( + void *stream) +{ + (void)stream; } #endif +#undef HAVE_EXECINFO_H static -void clear_comment( - const char *expr, - const char *ptr, - plecs_state_t *state) +void flecs_log_msg( + int32_t level, + const char *file, + int32_t line, + const char *msg) { - if (state->comment) { - ecs_parser_error(state->name, expr, ptr - expr, "unused doc comment"); - ecs_os_free(state->comment); - state->comment = NULL; - - state->errors ++; /* Non-fatal error */ + FILE *stream = ecs_os_api.log_out_; + if (!stream) { + stream = stdout; } -} -static -const char* parse_fluff( - const char *expr, - const char *ptr, - plecs_state_t *state) -{ - char *comment; - const char *next = ecs_parse_fluff(ptr, &comment); - - if (comment && comment[0] == '/') { - comment = (char*)ecs_parse_fluff(comment + 1, NULL); - int32_t len = (ecs_size_t)(next - comment); - int32_t newline_count = 0; - - /* Trim trailing whitespaces */ - while (len >= 0 && (isspace(comment[len - 1]))) { - if (comment[len - 1] == '\n') { - newline_count ++; - if (newline_count > 1) { - /* If newline separates comment from statement, discard */ - len = -1; - break; - } - } - len --; + bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; + bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; + bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; + + time_t now = 0; + + if (deltatime) { + now = time(NULL); + int64_t delta = 0; + if (ecs_os_api.log_last_timestamp_) { + delta = now - ecs_os_api.log_last_timestamp_; } + ecs_os_api.log_last_timestamp_ = (int64_t)now; - if (len > 0) { - clear_comment(expr, ptr, state); - state->comment = ecs_os_calloc_n(char, len + 1); - ecs_os_strncpy(state->comment, comment, len); + if (delta) { + if (delta < 10) { + fputs(" ", stream); + } + if (delta < 100) { + fputs(" ", stream); + } + char time_buf[20]; + ecs_os_snprintf(time_buf, 20, "%u", (uint32_t)delta); + fputs("+", stream); + fputs(time_buf, stream); + fputs(" ", stream); } else { - ecs_parser_error(state->name, expr, ptr - expr, - "unused doc comment"); - state->errors ++; - } - } else { - if (ptr != next && state->comment) { - clear_comment(expr, ptr, state); + fputs(" ", stream); } } - return next; -} - -static -ecs_entity_t ensure_entity( - ecs_world_t *world, - plecs_state_t *state, - const char *path, - ecs_entity_t rel, - bool is_subject) -{ - if (!path) { - return 0; + if (timestamp) { + if (!now) { + now = time(NULL); + } + char time_buf[20]; + ecs_os_snprintf(time_buf, 20, "%u", (uint32_t)now); + fputs(time_buf, stream); + fputs(" ", stream); } - ecs_entity_t e = plecs_lookup(world, path, state, rel, is_subject); - if (!e) { - if (rel && flecs_get_oneof(world, rel)) { - /* If relationship has oneof and entity was not found, don't proceed - * with creating an entity as this can cause asserts later on */ - char *relstr = ecs_get_fullpath(world, rel); - ecs_parser_error(state->name, 0, 0, - "invalid identifier '%s' for relation '%s'", path, relstr); - ecs_os_free(relstr); - return 0; + if (level >= 4) { + if (use_colors) fputs(ECS_NORMAL, stream); + fputs("jrnl", stream); + } else if (level >= 0) { + if (level == 0) { + if (use_colors) fputs(ECS_MAGENTA, stream); + } else { + if (use_colors) fputs(ECS_GREY, stream); } + fputs("info", stream); + } else if (level == -2) { + if (use_colors) fputs(ECS_YELLOW, stream); + fputs("warning", stream); + } else if (level == -3) { + if (use_colors) fputs(ECS_RED, stream); + fputs("error", stream); + } else if (level == -4) { + if (use_colors) fputs(ECS_RED, stream); + fputs("fatal", stream); + } + + if (use_colors) fputs(ECS_NORMAL, stream); + fputs(": ", stream); + + if (level >= 0) { + if (ecs_os_api.log_indent_) { + char indent[32]; + int i, indent_count = ecs_os_api.log_indent_; + if (indent_count > 15) indent_count = 15; + + for (i = 0; i < indent_count; i ++) { + indent[i * 2] = '|'; + indent[i * 2 + 1] = ' '; + } + + if (ecs_os_api.log_indent_ != indent_count) { + indent[i * 2 - 2] = '+'; + } + + indent[i * 2] = '\0'; - if (!is_subject) { - /* If this is not a subject create an existing empty id, which - * ensures that scope & with are not applied */ - e = ecs_new_id(world); + fputs(indent, stream); } + } - e = ecs_add_path(world, e, 0, path); - ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); - } else { - /* If entity exists, make sure it gets the right scope and with */ - if (is_subject) { - ecs_entity_t scope = ecs_get_scope(world); - if (scope) { - ecs_add_pair(world, e, EcsChildOf, scope); + if (level < 0) { + if (file) { + const char *file_ptr = strrchr(file, '/'); + if (!file_ptr) { + file_ptr = strrchr(file, '\\'); } - ecs_entity_t with = ecs_get_with(world); - if (with) { - ecs_add_id(world, e, with); + if (file_ptr) { + file = file_ptr + 1; } + + fputs(file, stream); + fputs(": ", stream); + } + + if (line) { + fprintf(stream, "%d: ", line); } } - return e; + fputs(msg, stream); + + fputs("\n", stream); + + if (level == -4) { + flecs_dump_backtrace(stream); + } } -static -bool pred_is_subj( - ecs_term_t *term, - plecs_state_t *state) +void ecs_os_dbg( + const char *file, + int32_t line, + const char *msg) { - if (term->subj.name != NULL) { - return false; - } - if (term->obj.name != NULL) { - return false; - } - if (term->subj.set.mask == EcsNothing) { - return false; - } - if (state->with_stmt) { - return false; - } - if (state->assign_stmt) { - return false; - } - if (state->isa_stmt) { - return false; - } - if (state->using_stmt) { - return false; + if (ecs_os_api.log_) { + ecs_os_api.log_(1, file, line, msg); } - - return true; } -/* Set masks aren't useful in plecs, so translate them back to entity names */ -static -const char* set_mask_to_name( - ecs_flags32_t flags) +void ecs_os_trace( + const char *file, + int32_t line, + const char *msg) { - if (flags == EcsSelf) { - return "self"; - } else if (flags == EcsAll) { - return "all"; - } else if (flags == EcsSuperSet) { - return "super"; - } else if (flags == EcsSubSet) { - return "sub"; - } else if (flags == EcsCascade || flags == (EcsSuperSet|EcsCascade)) { - return "cascade"; - } else if (flags == EcsParent) { - return "parent"; + if (ecs_os_api.log_) { + ecs_os_api.log_(0, file, line, msg); } - return NULL; } -static -int create_term( - ecs_world_t *world, - ecs_term_t *term, - const char *name, - const char *expr, - int64_t column, - plecs_state_t *state) +void ecs_os_warn( + const char *file, + int32_t line, + const char *msg) { - state->last_subject = 0; - state->last_predicate = 0; - state->last_object = 0; - state->last_assign_id = 0; - - const char *pred_name = term->pred.name; - const char *subj_name = term->subj.name; - const char *obj_name = term->obj.name; - - if (!subj_name) { - subj_name = set_mask_to_name(term->subj.set.mask); - } - if (!obj_name) { - obj_name = set_mask_to_name(term->obj.set.mask); + if (ecs_os_api.log_) { + ecs_os_api.log_(-2, file, line, msg); } +} - if (!ecs_term_id_is_set(&term->pred)) { - ecs_parser_error(name, expr, column, "missing predicate in expression"); - return -1; +void ecs_os_err( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(-3, file, line, msg); } +} - if (state->assign_stmt && term->subj.entity != EcsThis) { - ecs_parser_error(name, expr, column, - "invalid statement in assign statement"); - return -1; +void ecs_os_fatal( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(-4, file, line, msg); } +} - bool pred_as_subj = pred_is_subj(term, state); - - ecs_entity_t pred = ensure_entity(world, state, pred_name, 0, pred_as_subj); - ecs_entity_t subj = ensure_entity(world, state, subj_name, pred, true); - ecs_entity_t obj = 0; - - if (ecs_term_id_is_set(&term->obj)) { - obj = ensure_entity(world, state, obj_name, pred, - state->assign_stmt == false); - if (!obj) { - return -1; - } - } +static +void ecs_os_gettime(ecs_time_t *time) { + ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL); + + uint64_t now = ecs_os_now(); + uint64_t sec = now / 1000000000; - if (state->assign_stmt || state->isa_stmt) { - subj = state->assign_to; - } + assert(sec < UINT32_MAX); + assert((now - sec * 1000000000) < UINT32_MAX); - if (state->isa_stmt && obj) { - ecs_parser_error(name, expr, column, - "invalid object in inheritance statement"); - return -1; - } + time->sec = (uint32_t)sec; + time->nanosec = (uint32_t)(now - sec * 1000000000); +} - if (state->using_stmt && (obj || subj)) { - ecs_parser_error(name, expr, column, - "invalid predicate/object in using statement"); - return -1; - } +#ifdef FLECS_TRACK_OS_ALLOC +ecs_size_t ecs_os_allocated_bytes = 0; +static +void* ecs_os_api_malloc(ecs_size_t size) { + ecs_os_linc(&ecs_os_api_malloc_count); + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + void *result = malloc((size_t)size + 16); + *(ecs_size_t*)result = size; + ecs_os_allocated_bytes += size; + return ECS_OFFSET(result, 16); +} - if (state->isa_stmt) { - pred = ecs_pair(EcsIsA, pred); - } +static +void* ecs_os_api_calloc(ecs_size_t size) { + ecs_os_linc(&ecs_os_api_calloc_count); + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + void *result = calloc(1, (size_t)size + 16); + *(ecs_size_t*)result = size; + ecs_os_allocated_bytes += size; + return ECS_OFFSET(result, 16); +} - if (subj) { - if (!obj) { - ecs_add_id(world, subj, pred); - state->last_assign_id = pred; - } else { - ecs_add_pair(world, subj, pred, obj); - state->last_object = obj; - state->last_assign_id = ecs_pair(pred, obj); - } - state->last_predicate = pred; - state->last_subject = subj; +static +void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - pred_as_subj = false; + if (ptr) { + ecs_os_linc(&ecs_os_api_realloc_count); + ptr = ECS_OFFSET(ptr, -16); + ecs_os_allocated_bytes -= *(ecs_size_t*)ptr; } else { - if (!obj) { - /* If no subject or object were provided, use predicate as subj - * unless the expression explictly excluded the subject */ - if (pred_as_subj) { - state->last_subject = pred; - subj = pred; - } else { - state->last_predicate = pred; - pred_as_subj = false; - } - } else { - state->last_predicate = pred; - state->last_object = obj; - pred_as_subj = false; - } + /* If not actually reallocing, treat as malloc */ + ecs_os_linc(&ecs_os_api_malloc_count); } - - /* If this is a with clause (the list of entities between 'with' and scope - * open), add subject to the array of with frames */ - if (state->with_stmt) { - ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); - ecs_id_t id; - - if (obj) { - id = ecs_pair(pred, obj); - } else { - id = pred; - } - - state->with[state->with_frame ++] = id; - } else if (state->using_stmt) { - ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(obj == 0, ECS_INTERNAL_ERROR, NULL); - - state->using[state->using_frame ++] = pred; - state->using_frames[state->sp] = state->using_frame; - - /* If this is not a with/using clause, add with frames to subject */ - } else { - if (subj) { - int32_t i, frame_count = state->with_frames[state->sp]; - for (i = 0; i < frame_count; i ++) { - ecs_add_id(world, subj, state->with[i]); - } - } + if (!size) { + return NULL; } - /* If an id was provided by itself, add default scope type to it */ - ecs_entity_t default_scope_type = state->default_scope_type[state->sp]; - if (pred_as_subj && default_scope_type) { - ecs_add_id(world, subj, default_scope_type); - } + ptr = realloc(ptr, (size_t)size + 16); + *(ecs_size_t*)ptr = size; + ecs_os_allocated_bytes += size; + return ECS_OFFSET(ptr, 16); +} - /* If a comment preceded the statement, add it as a brief description */ -#ifdef FLECS_DOC - if (subj && state->comment) { - ecs_doc_set_brief(world, subj, state->comment); - ecs_os_free(state->comment); - state->comment = NULL; +static +void ecs_os_api_free(void *ptr) { + if (ptr) { + ptr = ECS_OFFSET(ptr, -16); + ecs_size_t size = *(ecs_size_t*)ptr; + ecs_os_allocated_bytes -= size; + ecs_os_linc(&ecs_os_api_free_count); } -#endif + free(ptr); +} +#else +static +void* ecs_os_api_malloc(ecs_size_t size) { + ecs_os_linc(&ecs_os_api_malloc_count); + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + return malloc((size_t)size); +} - return 0; +static +void* ecs_os_api_calloc(ecs_size_t size) { + ecs_os_linc(&ecs_os_api_calloc_count); + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); + return calloc(1, (size_t)size); } static -const char* parse_inherit_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) -{ - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "cannot nest inheritance"); - return NULL; - } +void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - if (!state->last_subject) { - ecs_parser_error(name, expr, ptr - expr, - "missing entity to assign inheritance to"); - return NULL; + if (ptr) { + ecs_os_linc(&ecs_os_api_realloc_count); + } else { + /* If not actually reallocing, treat as malloc */ + ecs_os_linc(&ecs_os_api_malloc_count); } - state->isa_stmt = true; - state->assign_to = state->last_subject; - - return ptr; + return realloc(ptr, (size_t)size); } static -const char* parse_assign_expr( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) -{ - (void)world; - - if (!state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "unexpected value outside of assignment statement"); - return NULL; +void ecs_os_api_free(void *ptr) { + if (ptr) { + ecs_os_linc(&ecs_os_api_free_count); } + free(ptr); +} +#endif - ecs_id_t assign_id = state->last_assign_id; - if (!assign_id) { - ecs_parser_error(name, expr, ptr - expr, - "missing type for assignment statement"); +static +char* ecs_os_api_strdup(const char *str) { + if (str) { + int len = ecs_os_strlen(str); + char *result = ecs_os_malloc(len + 1); + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_os_strcpy(result, str); + return result; + } else { return NULL; } +} -#ifndef FLECS_EXPR - ecs_parser_error(name, expr, ptr - expr, - "cannot parse value, missing FLECS_EXPR addon"); - return NULL; -#else - ecs_entity_t assign_to = state->assign_to; - if (!assign_to) { - assign_to = state->last_subject; - } - - if (!assign_to) { - ecs_parser_error(name, expr, ptr - expr, - "missing entity to assign to"); - return NULL; - } +void ecs_os_strset(char **str, const char *value) { + char *old = str[0]; + str[0] = ecs_os_strdup(value); + ecs_os_free(old); +} - ecs_entity_t type = ecs_get_typeid(world, assign_id); - if (!type) { - char *id_str = ecs_id_str(world, assign_id); - ecs_parser_error(name, expr, ptr - expr, - "invalid assignment, '%s' is not a type", id_str); - ecs_os_free(id_str); - return NULL; +void ecs_os_perf_trace_push_( + const char *file, + size_t line, + const char *name) +{ + if (ecs_os_api.perf_trace_push_) { + ecs_os_api.perf_trace_push_(file, line, name); } +} - void *value_ptr = ecs_get_mut_id( - world, assign_to, assign_id, NULL); - - ptr = ecs_parse_expr(world, ptr, type, value_ptr, - &(ecs_parse_expr_desc_t) { - .name = name, - .expr = expr, - .lookup_action = plecs_lookup_action, - .lookup_ctx = state - }); - if (!ptr) { - return NULL; +void ecs_os_perf_trace_pop_( + const char *file, + size_t line, + const char *name) +{ + if (ecs_os_api.perf_trace_pop_) { + ecs_os_api.perf_trace_pop_(file, line, name); } - - ecs_modified_id(world, assign_to, assign_id); -#endif - - return ptr; } +/* Replace dots with underscores */ static -const char* parse_assign_stmt( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) -{ - (void)world; +char *module_file_base(const char *module, char sep) { + char *base = ecs_os_strdup(module); + ecs_size_t i, len = ecs_os_strlen(base); + for (i = 0; i < len; i ++) { + if (base[i] == '.') { + base[i] = sep; + } + } - state->isa_stmt = false; + return base; +} - /* Component scope (add components to entity) */ - if (!state->last_subject) { - ecs_parser_error(name, expr, ptr - expr, - "missing entity to assign to"); - return NULL; - } +static +char* ecs_os_api_module_to_dl(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid assign statement in assign statement"); - return NULL; + /* Best guess, use module name with underscores + OS library extension */ + char *file_base = module_file_base(module, '_'); + +# if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) + ecs_strbuf_appendlit(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendlit(&lib, ".so"); +# elif defined(ECS_TARGET_DARWIN) + ecs_strbuf_appendlit(&lib, "lib"); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendlit(&lib, ".dylib"); +# elif defined(ECS_TARGET_WINDOWS) + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendlit(&lib, ".dll"); +# endif + + ecs_os_free(file_base); + + return ecs_strbuf_get(&lib); +} + +static +char* ecs_os_api_module_to_etc(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; + + /* Best guess, use module name with dashes + /etc */ + char *file_base = module_file_base(module, '-'); + + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendlit(&lib, "/etc"); + + ecs_os_free(file_base); + + return ecs_strbuf_get(&lib); +} + +void ecs_os_set_api_defaults(void) +{ + /* Don't overwrite if already initialized */ + if (ecs_os_api_initialized != 0) { + return; } - if (!state->scope_assign_stmt) { - state->assign_to = state->last_subject; + if (ecs_os_api_initializing != 0) { + return; } - state->assign_stmt = true; + ecs_os_api_initializing = true; - /* Assignment without a preceding component */ - if (ptr[0] == '{') { - ecs_entity_t type = 0; - - if (state->scope_assign_stmt) { - ecs_assert(state->assign_to == ecs_get_scope(world), - ECS_INTERNAL_ERROR, NULL); - } + /* Memory management */ + ecs_os_api.malloc_ = ecs_os_api_malloc; + ecs_os_api.free_ = ecs_os_api_free; + ecs_os_api.realloc_ = ecs_os_api_realloc; + ecs_os_api.calloc_ = ecs_os_api_calloc; - /* If we're in a scope & last_subject is a type, assign to scope */ - if (ecs_get_scope(world) != 0) { - type = ecs_get_typeid(world, state->last_subject); - if (type != 0) { - type = state->last_subject; - } - } + /* Strings */ + ecs_os_api.strdup_ = ecs_os_api_strdup; - /* If type hasn't been set yet, check if scope has default type */ - if (!type && !state->scope_assign_stmt) { - type = state->default_scope_type[state->sp]; - } + /* Time */ + ecs_os_api.get_time_ = ecs_os_gettime; - /* If no type has been found still, check if last with id is a type */ - if (!type && !state->scope_assign_stmt) { - int32_t with_frame_count = state->with_frames[state->sp]; - if (with_frame_count) { - type = state->with[with_frame_count - 1]; - } - } + /* Logging */ + ecs_os_api.log_ = flecs_log_msg; - if (!type) { - ecs_parser_error(name, expr, ptr - expr, - "missing type for assignment"); - return NULL; - } + /* Modules */ + if (!ecs_os_api.module_to_dl_) { + ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; + } - state->last_assign_id = type; + if (!ecs_os_api.module_to_etc_) { + ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; } - return ptr; + ecs_os_api.abort_ = abort; + +# ifdef FLECS_OS_API_IMPL + /* Initialize defaults to OS API IMPL addon, but still allow for overriding + * by the application */ + ecs_set_os_api_impl(); + ecs_os_api_initialized = false; +# endif + + ecs_os_api_initializing = false; } -static -const char* parse_using_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) -{ - if (state->isa_stmt || state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid usage of using keyword"); - return NULL; - } +bool ecs_os_has_heap(void) { + return + (ecs_os_api.malloc_ != NULL) && + (ecs_os_api.calloc_ != NULL) && + (ecs_os_api.realloc_ != NULL) && + (ecs_os_api.free_ != NULL); +} + +bool ecs_os_has_threading(void) { + return + (ecs_os_api.mutex_new_ != NULL) && + (ecs_os_api.mutex_free_ != NULL) && + (ecs_os_api.mutex_lock_ != NULL) && + (ecs_os_api.mutex_unlock_ != NULL) && + (ecs_os_api.cond_new_ != NULL) && + (ecs_os_api.cond_free_ != NULL) && + (ecs_os_api.cond_wait_ != NULL) && + (ecs_os_api.cond_signal_ != NULL) && + (ecs_os_api.cond_broadcast_ != NULL) && + (ecs_os_api.thread_new_ != NULL) && + (ecs_os_api.thread_join_ != NULL) && + (ecs_os_api.thread_self_ != NULL); +} + +bool ecs_os_has_task_support(void) { + return + (ecs_os_api.mutex_new_ != NULL) && + (ecs_os_api.mutex_free_ != NULL) && + (ecs_os_api.mutex_lock_ != NULL) && + (ecs_os_api.mutex_unlock_ != NULL) && + (ecs_os_api.cond_new_ != NULL) && + (ecs_os_api.cond_free_ != NULL) && + (ecs_os_api.cond_wait_ != NULL) && + (ecs_os_api.cond_signal_ != NULL) && + (ecs_os_api.cond_broadcast_ != NULL) && + (ecs_os_api.task_new_ != NULL) && + (ecs_os_api.task_join_ != NULL); +} - /* Add following expressions to using list */ - state->using_stmt = true; +bool ecs_os_has_time(void) { + return + (ecs_os_api.get_time_ != NULL) && + (ecs_os_api.sleep_ != NULL) && + (ecs_os_api.now_ != NULL); +} - return ptr + 5; +bool ecs_os_has_logging(void) { + return (ecs_os_api.log_ != NULL); } -static -const char* parse_with_stmt( - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) -{ - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid with after inheritance"); - return NULL; +bool ecs_os_has_dl(void) { + return + (ecs_os_api.dlopen_ != NULL) && + (ecs_os_api.dlproc_ != NULL) && + (ecs_os_api.dlclose_ != NULL); +} + +bool ecs_os_has_modules(void) { + return + (ecs_os_api.module_to_dl_ != NULL) && + (ecs_os_api.module_to_etc_ != NULL); +} + +#if defined(ECS_TARGET_WINDOWS) +static char error_str[255]; +#endif + +const char* ecs_os_strerror(int err) { +# if defined(ECS_TARGET_WINDOWS) + strerror_s(error_str, 255, err); + return error_str; +# else + return strerror(err); +# endif +} + +/** + * @file poly.c + * @brief Functions for managing poly objects. + * + * The poly framework makes it possible to generalize common functionality for + * different kinds of API objects, as well as improved type safety checks. Poly + * objects have a header that identifiers what kind of object it is. This can + * then be used to discover a set of "mixins" implemented by the type. + * + * Mixins are like a vtable, but for members. Each type populates the table with + * offsets to the members that correspond with the mixin. If an entry in the + * mixin table is not set, the type does not support the mixin. + */ + + +static const char* mixin_kind_str[] = { + [EcsMixinWorld] = "world", + [EcsMixinEntity] = "entity", + [EcsMixinObservable] = "observable", + [EcsMixinDtor] = "dtor", + [EcsMixinMax] = "max (should never be requested by application)" +}; + +ecs_mixins_t ecs_world_t_mixins = { + .type_name = "ecs_world_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_world_t, self), + [EcsMixinObservable] = offsetof(ecs_world_t, observable), } +}; - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid with in assign_stmt"); - return NULL; +ecs_mixins_t ecs_stage_t_mixins = { + .type_name = "ecs_stage_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_stage_t, world) } +}; - /* Add following expressions to with list */ - state->with_stmt = true; - return ptr + 5; -} +ecs_mixins_t ecs_observer_t_mixins = { + .type_name = "ecs_observer_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_observer_t, world), + [EcsMixinEntity] = offsetof(ecs_observer_t, entity), + [EcsMixinDtor] = offsetof(ecs_observer_impl_t, dtor) + } +}; static -const char* parse_scope_open( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +void* assert_mixin( + const ecs_poly_t *poly, + ecs_mixin_kind_t kind) { - state->isa_stmt = false; + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); + + const ecs_header_t *hdr = poly; + ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(hdr->type != 0, ECS_INVALID_PARAMETER, + "invalid/freed pointer to flecs object detected"); - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid scope in assign_stmt"); - return NULL; - } + const ecs_mixins_t *mixins = hdr->mixins; + ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); - state->sp ++; + ecs_size_t offset = mixins->elems[kind]; + ecs_assert(offset != 0, ECS_INVALID_PARAMETER, + "mixin %s not available for type %s", + mixin_kind_str[kind], mixins ? mixins->type_name : "unknown"); + (void)mixin_kind_str; - ecs_entity_t scope = 0; - ecs_entity_t default_scope_type = 0; + /* Object has mixin, return its address */ + return ECS_OFFSET(hdr, offset); +} + +void* flecs_poly_init_( + ecs_poly_t *poly, + int32_t type, + ecs_size_t size, + ecs_mixins_t *mixins) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - if (!state->with_stmt) { - if (state->last_subject) { - scope = state->last_subject; - ecs_set_scope(world, state->last_subject); + ecs_header_t *hdr = poly; + ecs_os_memset(poly, 0, size); - /* Check if scope has a default child component */ - ecs_entity_t def_type_src = ecs_get_object_for_id(world, scope, - 0, ecs_pair(EcsDefaultChildComponent, EcsWildcard)); + hdr->type = type; + hdr->refcount = 1; + hdr->mixins = mixins; - if (def_type_src) { - default_scope_type = ecs_get_object( - world, def_type_src, EcsDefaultChildComponent, 0); - } - } else { - if (state->last_object) { - scope = ecs_pair( - state->last_predicate, state->last_object); - ecs_set_with(world, scope); - } else { - if (state->last_predicate) { - scope = ecs_pair(EcsChildOf, state->last_predicate); - } - ecs_set_scope(world, state->last_predicate); - } - } + return poly; +} - state->scope[state->sp] = scope; - state->default_scope_type[state->sp] = default_scope_type; - } else { - state->scope[state->sp] = state->scope[state->sp - 1]; - state->default_scope_type[state->sp] = - state->default_scope_type[state->sp - 1]; - } +void flecs_poly_fini_( + ecs_poly_t *poly, + int32_t type) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + (void)type; - state->using_frames[state->sp] = state->using_frame; - state->with_frames[state->sp] = state->with_frame; - state->with_stmt = false; + ecs_header_t *hdr = poly; - return ptr; + /* Don't deinit poly that wasn't initialized */ + ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, + "incorrect function called to free flecs object"); + hdr->type = 0; } -static -const char* parse_scope_close( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +int32_t flecs_poly_claim_( + ecs_poly_t *poly) { - if (state->isa_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "invalid '}' after inheritance statement"); - return NULL; + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_header_t *hdr = poly; + ecs_assert(hdr->type != 0, ECS_INVALID_PARAMETER, + "invalid/freed pointer to flecs object detected"); + if (ecs_os_has_threading()) { + return ecs_os_ainc(&hdr->refcount); + } else { + return ++hdr->refcount; } +} - if (state->assign_stmt) { - ecs_parser_error(name, expr, ptr - expr, - "unfinished assignment before }"); - return NULL; +int32_t flecs_poly_release_( + ecs_poly_t *poly) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_header_t *hdr = poly; + ecs_assert(hdr->type != 0, ECS_INVALID_PARAMETER, + "invalid/freed pointer to flecs object detected"); + if (ecs_os_has_threading()) { + return ecs_os_adec(&hdr->refcount); + } else { + return --hdr->refcount; } +} - state->scope[state->sp] = 0; - state->default_scope_type[state->sp] = 0; - state->sp --; +int32_t flecs_poly_refcount( + ecs_poly_t *poly) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_header_t *hdr = poly; + ecs_assert(hdr->type != 0, ECS_INVALID_PARAMETER, + "invalid/freed pointer to flecs object detected"); + return hdr->refcount; +} - if (state->sp < 0) { - ecs_parser_error(name, expr, ptr - expr, "invalid } without a {"); - return NULL; +EcsPoly* flecs_poly_bind_( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) +{ + /* Add tag to the entity for easy querying. This will make it possible to + * query for `Query` instead of `(Poly, Query) */ + if (!ecs_has_id(world, entity, tag)) { + ecs_add_id(world, entity, tag); } - ecs_id_t id = state->scope[state->sp]; - - if (!id || ECS_HAS_ROLE(id, PAIR)) { - ecs_set_with(world, id); + /* Never defer creation of a poly object */ + bool deferred = false; + if (ecs_is_deferred(world)) { + deferred = true; + ecs_defer_suspend(world); } - if (!id || !ECS_HAS_ROLE(id, PAIR)) { - ecs_set_scope(world, id); - } + /* If this is a new poly, leave the actual creation up to the caller so they + * call tell the difference between a create or an update */ + EcsPoly *result = ecs_ensure_pair(world, entity, EcsPoly, tag); - state->with_frame = state->with_frames[state->sp]; - state->using_frame = state->using_frames[state->sp]; - state->last_subject = 0; - state->assign_stmt = false; + if (deferred) { + ecs_defer_resume(world); + } - return ptr; + return result; } -static -const char *parse_plecs_term( +void flecs_poly_modified_( ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) + ecs_entity_t entity, + ecs_entity_t tag) { - ecs_term_t term = {0}; - ecs_entity_t scope = ecs_get_scope(world); + ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); +} - /* If first character is a (, this should be interpreted as an id assigned - * to the current scope if: - * - this is not already an assignment: "Foo = (Hello, World)" - * - this is in a scope - */ - bool scope_assignment = (ptr[0] == '(') && !state->assign_stmt && scope != 0; +const EcsPoly* flecs_poly_bind_get_( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) +{ + return ecs_get_pair(world, entity, EcsPoly, tag); +} - ptr = ecs_parse_term(world, name, expr, ptr, &term); - if (!ptr) { - return NULL; +ecs_poly_t* flecs_poly_get_( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) +{ + const EcsPoly *p = flecs_poly_bind_get_(world, entity, tag); + if (p) { + return p->poly; } + return NULL; +} - if (!ecs_term_is_initialized(&term)) { - ecs_parser_error(name, expr, ptr - expr, "expected identifier"); - return NULL; /* No term found */ - } +bool flecs_poly_is_( + const ecs_poly_t *poly, + int32_t type) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - /* Lookahead to check if this is an implicit scope assignment (no parens) */ - if (ptr[0] == '=') { - const char *tptr = ecs_parse_fluff(ptr + 1, NULL); - if (tptr[0] == '{') { - ecs_entity_t pred = plecs_lookup( - world, term.pred.name, state, 0, false); - ecs_entity_t obj = plecs_lookup( - world, term.obj.name, state, pred, false); - ecs_id_t id = 0; - if (pred && obj) { - id = ecs_pair(pred, obj); - } else if (pred) { - id = pred; - } + const ecs_header_t *hdr = poly; + ecs_assert(hdr->type != 0, ECS_INVALID_PARAMETER, + "invalid/freed pointer to flecs object detected"); + return hdr->type == type; +} - if (id && (ecs_get_typeid(world, id) != 0)) { - scope_assignment = true; - } - } - } +ecs_observable_t* flecs_get_observable( + const ecs_poly_t *poly) +{ + return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); +} - bool prev = state->assign_stmt; - if (scope_assignment) { - state->assign_stmt = true; - state->assign_to = scope; - } - if (create_term(world, &term, name, expr, (ptr - expr), state)) { - ecs_term_fini(&term); - return NULL; /* Failed to create term */ - } - if (scope_assignment) { - state->last_subject = state->last_assign_id; - state->scope_assign_stmt = true; +const ecs_world_t* ecs_get_world( + const ecs_poly_t *poly) +{ + if (((const ecs_header_t*)poly)->type == ecs_world_t_magic) { + return poly; } - state->assign_stmt = prev; - - ecs_term_fini(&term); - - return ptr; + return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); } -static -const char* parse_stmt( - ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - plecs_state_t *state) +ecs_entity_t ecs_get_entity( + const ecs_poly_t *poly) { - state->assign_stmt = false; - state->scope_assign_stmt = false; - state->isa_stmt = false; - state->with_stmt = false; - state->using_stmt = false; - state->last_subject = 0; - state->last_predicate = 0; - state->last_object = 0; + return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); +} - ptr = parse_fluff(expr, ptr, state); +flecs_poly_dtor_t* flecs_get_dtor( + const ecs_poly_t *poly) +{ + return (flecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); +} - char ch = ptr[0]; +/** + * @file ref.c + * @brief Ref API. + * + * Refs provide faster access to components than get. + */ - if (!ch) { - goto done; - } else if (ch == '{') { - ptr = parse_fluff(expr, ptr + 1, state); - goto scope_open; - } else if (ch == '}') { - ptr = parse_fluff(expr, ptr + 1, state); - goto scope_close; - } else if (ch == '(') { - goto term_expr; - } else if (!ecs_os_strncmp(ptr, TOK_USING " ", 5)) { - ptr = parse_using_stmt(name, expr, ptr, state); - if (!ptr) goto error; - goto term_expr; - } else if (!ecs_os_strncmp(ptr, TOK_WITH " ", 5)) { - ptr = parse_with_stmt(name, expr, ptr, state); - if (!ptr) goto error; - goto term_expr; - } else { - goto term_expr; - } - -term_expr: - if (!ptr[0]) { - goto done; - } - if (!(ptr = parse_plecs_term(world, name, ptr, ptr, state))) { - goto error; - } +ecs_ref_t ecs_ref_init_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); - ptr = parse_fluff(expr, ptr, state); + flecs_check_exclusive_world_access_read(world); - if (ptr[0] == '{' && !isspace(ptr[-1])) { - /* A '{' directly after an identifier (no whitespace) is a literal */ - goto assign_expr; - } + ecs_record_t *record = flecs_entities_get(world, entity); + ecs_check(record != NULL, ECS_INVALID_PARAMETER, + "cannot create ref for empty entity"); - if (!state->using_stmt) { - if (ptr[0] == ':') { - ptr = parse_fluff(expr, ptr + 1, state); - goto inherit_stmt; - } else if (ptr[0] == '=') { - ptr = parse_fluff(expr, ptr + 1, state); - goto assign_stmt; - } else if (ptr[0] == ',') { - ptr = parse_fluff(expr, ptr + 1, state); - goto term_expr; - } else if (ptr[0] == '{') { - state->assign_stmt = false; - ptr = parse_fluff(expr, ptr + 1, state); - goto scope_open; - } - } + ecs_ref_t result = { + .entity = entity, + .id = id, + .record = record + }; - state->assign_stmt = false; - goto done; + ecs_table_t *table = record->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -inherit_stmt: - ptr = parse_inherit_stmt(name, expr, ptr, state); - if (!ptr) goto error; + result.table_id = table->id; + result.table_version_fast = flecs_get_table_version_fast(world, result.table_id); + result.table_version = table->version; + result.ptr = flecs_get_component( + world, table, ECS_RECORD_TO_ROW(record->row), + flecs_components_get(world, id)); - /* Expect base identifier */ - goto term_expr; + return result; +error: + return (ecs_ref_t){0}; +} -assign_stmt: - ptr = parse_assign_stmt(world, name, expr, ptr, state); - if (!ptr) goto error; +void ecs_ref_update( + const ecs_world_t *world, + ecs_ref_t *ref) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->record == flecs_entities_get_any(world, ref->entity), + ECS_INVALID_OPERATION, "a corrupt ref was passed to ecs_ref_update"); - ptr = parse_fluff(expr, ptr, state); + flecs_check_exclusive_world_access_read(world); - /* Assignment without a preceding component */ - if (ptr[0] == '{') { - goto assign_expr; + if (ref->table_version_fast == flecs_get_table_version_fast( + world, ref->table_id)) + { + return; } - /* Expect component identifiers */ - goto term_expr; + ecs_record_t *r = ref->record; + ecs_table_t *table = r->table; + if (!table) { /* Table can be NULL, entity could have been deleted */ + ref->table_id = 0; + ref->table_version_fast = 0; + ref->table_version = 0; + ref->ptr = NULL; + return; + } -assign_expr: - ptr = parse_assign_expr(world, name, expr, ptr, state); - if (!ptr) goto error; + ecs_check(ecs_is_alive(world, ref->entity), ECS_INVALID_PARAMETER, NULL); - ptr = parse_fluff(expr, ptr, state); - if (ptr[0] == ',') { - ptr ++; - goto term_expr; - } else if (ptr[0] == '{') { - state->assign_stmt = false; - ptr ++; - goto scope_open; - } else { - state->assign_stmt = false; - goto done; + if (ref->table_id == table->id && ref->table_version == table->version) { + ref->table_version_fast = flecs_get_table_version_fast(world, ref->table_id); + return; } -scope_open: - ptr = parse_scope_open(world, name, expr, ptr, state); - if (!ptr) goto error; - goto done; - -scope_close: - ptr = parse_scope_close(world, name, expr, ptr, state); - if (!ptr) goto error; - goto done; + ref->table_id = table->id; + ref->table_version_fast = flecs_get_table_version_fast(world, ref->table_id); + ref->table_version = table->version; + ref->ptr = flecs_get_component(world, table, ECS_RECORD_TO_ROW(r->row), + flecs_components_get(world, ref->id)); -done: - return ptr; error: - return NULL; + return; } -int ecs_plecs_from_str( - ecs_world_t *world, - const char *name, - const char *expr) +void* ecs_ref_get_id( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_id_t id) { - const char *ptr = expr; - ecs_term_t term = {0}; - plecs_state_t state = {0}; - - if (!expr) { - return 0; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, "ref not initialized"); + ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, "ref not initialized"); + ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, "ref not initialized"); + ecs_check(id == ref->id, ECS_INVALID_PARAMETER, "id does not match ref"); - state.scope[0] = 0; - ecs_entity_t prev_scope = ecs_set_scope(world, 0); - ecs_entity_t prev_with = ecs_set_with(world, 0); + (void)id; - do { - expr = ptr = parse_stmt(world, name, expr, ptr, &state); - if (!ptr) { - goto error; - } + ecs_ref_update(world, ref); - if (!ptr[0]) { - break; /* End of expression */ - } - } while (true); + return ref->ptr; +error: + return NULL; +} - ecs_set_scope(world, prev_scope); - ecs_set_with(world, prev_with); - - clear_comment(expr, ptr, &state); +/** + * @file search.c + * @brief Search functions to find (component) ids in table types. + * + * Search functions are used to find the column index of a (component) id in a + * table. Additionally, search functions implement the logic for finding a + * component id by following a relationship upwards. + */ - if (state.sp != 0) { - ecs_parser_error(name, expr, 0, "missing end of scope"); - goto error; - } - if (state.assign_stmt) { - ecs_parser_error(name, expr, 0, "unfinished assignment"); - goto error; - } +static +int32_t flecs_table_search_relation( + const ecs_world_t *world, + const ecs_record_t *record, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_component_record_t *cr, + ecs_id_t rel, + ecs_component_record_t *cr_r, + bool self, + ecs_entity_t *tgt_out, + ecs_id_t *id_out, + ecs_table_record_t **tr_out); - if (state.errors) { - goto error; +static +int32_t flecs_table_search( + const ecs_table_t *table, + ecs_component_record_t *cr, + ecs_id_t *id_out, + ecs_table_record_t **tr_out) +{ + ecs_table_record_t *tr = ecs_table_cache_get(&cr->cache, table); + if (tr) { + int32_t r = tr->index; + if (tr_out) tr_out[0] = tr; + if (id_out) { + id_out[0] = table->type.array[r]; + } + return r; } - return 0; -error: - ecs_set_scope(world, state.scope[0]); - ecs_set_with(world, prev_with); - ecs_term_fini(&term); return -1; } -int ecs_plecs_from_file( - ecs_world_t *world, - const char *filename) +static +int32_t flecs_table_offset_search( + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_id_t *id_out) { - FILE* file; - char* content = NULL; - int32_t bytes; - size_t size; + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - /* Open file for reading */ - ecs_os_fopen(&file, filename, "r"); - if (!file) { - ecs_err("%s (%s)", ecs_os_strerror(errno), filename); - goto error; + ecs_id_t *ids = table->type.array; + int32_t count = table->type.count; + while (offset < count) { + ecs_id_t type_id = ids[offset ++]; + if (ecs_id_match(type_id, id)) { + if (id_out) { + id_out[0] = type_id; + } + return offset - 1; + } } - /* Determine file size */ - fseek(file, 0 , SEEK_END); - bytes = (int32_t)ftell(file); - if (bytes == -1) { - goto error; - } - rewind(file); + return -1; +} - /* Load contents in memory */ - content = ecs_os_malloc(bytes + 1); - size = (size_t)bytes; - if (!(size = fread(content, 1, size, file)) && bytes) { - ecs_err("%s: read zero bytes instead of %d", filename, size); - ecs_os_free(content); - content = NULL; - goto error; - } else { - content[size] = '\0'; +bool flecs_type_can_inherit_id( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_component_record_t *cr, + ecs_id_t id) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + if (cr->flags & EcsIdOnInstantiateDontInherit) { + return false; } - fclose(file); + if (cr->flags & EcsIdExclusive) { + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t er = ECS_PAIR_FIRST(id); + ecs_component_record_t *cr_wc = flecs_components_get( + world, ecs_pair(er, EcsWildcard)); + if (cr_wc && flecs_component_get_table(cr_wc, table)) { + return false; + } + } + } - int result = ecs_plecs_from_str(world, filename, content); - ecs_os_free(content); - return result; -error: - ecs_os_free(content); - return -1; + return true; } -#endif +static +int32_t flecs_table_search_relation_for_tgt( + const ecs_world_t *world, + ecs_entity_t tgt, + ecs_id_t id, + ecs_component_record_t *cr, + ecs_id_t rel, + ecs_component_record_t *cr_r, + ecs_entity_t *tgt_out, + ecs_id_t *id_out, + ecs_table_record_t **tr_out) +{ + ecs_record_t *r = flecs_entities_get_any(world, tgt); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *tgt_table = r->table; + ecs_assert(tgt_table != NULL, ECS_INTERNAL_ERROR, NULL); -#ifdef FLECS_RULES + int32_t result = -1; -#include + if (cr->flags & EcsIdDontFragment) { + if (flecs_sparse_get(cr->sparse, 0, tgt) != NULL) { + result = -2; + goto found; + } + } else { + result = flecs_table_search_relation(world, r, tgt_table, 0, id, cr, + rel, cr_r, true, tgt_out, id_out, tr_out); + if (result != -1) { + goto found; + } + } -/** Implementation of the rule query engine. - * - * A rule (terminology borrowed from prolog) is a list of constraints that - * specify which conditions must be met for an entity to match the rule. While - * this description matches any kind of ECS query, the rule engine has features - * that go beyond regular (flecs) ECS queries: - * - * - query for all components of an entity (vs. all entities for a component) - * - query for all relationship pairs of an entity - * - support for query variables that are resolved at evaluation time - * - automatic traversal of transitive relationships - * - * Query terms can have the following forms: - * - * - Component(Subject) - * - Relation(Subject, Object) - * - * Additionally the query parser supports the following shorthand notations: - * - * - Component // short for Component(This) - * - (Relation, Object) // short for Relation(This, Object) - * - * The subject, or first arugment of a term represents the entity on which the - * component or relation is matched. By default the subject is set to a builtin - * This variable, which causes the behavior to match a regular ECS query: - * - * - Position, Velocity - * - * Is equivalent to - * - * - Position(This), Velocity(This) - * - * The function of the variable is to ensure that all components are matched on - * the same entity. Conceptually the query first populates the This variable - * with all entities that have Position. When the query evaluates the Velocity - * term, the variable is populated and the entity it contains will be checked - * for whether it has Velocity. - * - * The actual implementation is more efficient and does not check per-entity. - * - * Custom variables can be used to join parts of different terms. For example, - * the following query can be used to find entities with a parent that has a - * Position component (note that variable names start with a _): - * - * - ChildOf(This, _Parent), Component(_Parent) - * - * The rule engine uses a backtracking algorithm to find the set of entities - * and variables that match all terms. As soon as the engine finds a term that - * does not match with the currently evaluated entity, the entity is discarded. - * When an entity is found for which all terms match, the entity is yielded to - * the iterator. - * - * While a rule is being evaluated, a variable can either contain a single - * entity or a table. The engine will attempt to work with tables as much as - * possible so entities can be eliminated/yielded in bulk. A rule may store - * both the table and entity version of a variable and only switch from table to - * entity when necessary. - * - * The rule engine has an algorithm for computing which variables should be - * resolved first. This algorithm works by finding a "root" variable, which is - * the subject variable that occurs in the term with the least dependencies. The - * remaining variables are then resolved based on their "distance" from the root - * with the closest variables being resolved first. - * - * This generally results in an ordering that resolves the variables with the - * least dependencies first and the most dependencies last, which is beneficial - * for two reasons: - * - * - it improves the average performance of all queries - * - it makes performance less dependent on how an application orders the terms - * - * A possible improvement would be for the query engine to also consider - * the number of tables that need to be evaluated for each term, as starting - * with the smallest term reduces the amount of work. Other than static variable - * analysis however, this can only be determined when the query is executed. - * - * Rules are "compiled" into a set of instructions that encode the operations - * the query needs to perform in order to find the right set of entities. - * Operations can either yield data, which progresses the program, or signal - * that there is no (more) matching data, which discards the current variables. - * - * An operation can yield multiple times, if there are multiple matches for its - * inputs. Operations are called with a redo flag, which can be either true or - * false. When redo is true the operation will yield the next result. When redo - * is false, the operation will reset its state and start from the first result. - * - * Operations can have an input, output and a filter. Most commonly an operation - * either matches the filter against an input and yields if it matches, or uses - * the filter to find all matching results and store the result in the output. - * - * Variables are resolved by matching a filter against the output of an - * operation. When a term contains variables, they are encoded as register ids - * in the filter. When the filter is evaluated, the most recent values of the - * register are used to match/lookup the output. - * - * For example, a filter could be (ChildOf, _Parent). When the program starts, - * the _Parent register is initialized with *, so that when this filter is first - * evaluated, the operation will find all tables with (ChildOf, *). The _Parent - * register is then populated by taking the actual value of the table. If the - * table has type [(ChildOf, Sun)], _Parent will be initialized with Sun. - * - * It is possible that a filter matches multiple times. Consider the filter - * (Likes, _Food), and a table [(Likes, Apples), (Likes, Pears)]. In this case - * an operation will yield the table twice, once with _Food=Apples, and once - * with _Food=Pears. - * - * If a rule contains a term with a transitive relation, it will automatically - * substitute the parts of the term to find a fact that matches. The following - * examples illustrate how transitivity is resolved: - * - * Query: - * LocatedIn(Bob, SanFrancisco) - * - * Expands to: - * LocatedIn(Bob, SanFrancisco:self|subset) - * - * Explanation: - * "Is Bob located in San Francisco" - This term is true if Bob is either - * located in San Francisco, or is located in anything that is itself located - * in (a subset of) San Francisco. - * - * - * Query: - * LocatedIn(Bob, X) - * - * Expands to: - * LocatedIn(Bob, X:self|superset) - * - * Explanation: - * "Where is Bob located?" - This term recursively returns all places that - * Bob is located in, which includes his location and the supersets of his - * location. When Bob is located in San Francisco, he is also located in - * the United States, North America etc. - * - * - * Query: - * LocatedIn(X, NorthAmerica) - * - * Expands to: - * LocatedIn(X, NorthAmerica:self|subset) - * - * Explanation: - * "What is located in North America?" - This term returns everything located - * in North America and its subsets, as something located in San Francisco is - * located in UnitedStates, which is located in NorthAmerica. - * - * - * Query: - * LocatedIn(X, Y) - * - * Expands to: - * LocatedIn(X, Y) - * - * Explanation: - * "Where is everything located" - This term returns everything that is - * located somewhere. No substitution is performed as this would explode the - * results while not yielding new information. - * - * - * In the above terms, the variable indicates the part of the term that is - * unknown at evaluation time. In an actual rule the picked strategy depends on - * whether the variable is known when the term is evaluated. For example, if - * variable X has been resolved by the time Located(X, Y) is evaluated, the - * strategy from the LocatedIn(Bob, X) example will be used. - */ + return -1; +found: + if (tgt_out && !tgt_out[0]) { + tgt_out[0] = ecs_get_alive(world, tgt); + } -#define ECS_RULE_MAX_VAR_COUNT (32) + return result; +} -#define RULE_PAIR_PREDICATE (1) -#define RULE_PAIR_OBJECT (2) +static +int32_t flecs_table_search_relation_pairs( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_component_record_t *cr, + ecs_id_t rel, + ecs_component_record_t *cr_r, + ecs_entity_t *tgt_out, + ecs_id_t *id_out, + ecs_table_record_t **tr_out) +{ + bool exclusive = cr_r->flags & EcsIdExclusive; -/* A rule pair contains a predicate and object that can be stored in a register. */ -typedef struct ecs_rule_pair_t { - union { - int32_t reg; - ecs_entity_t ent; - } pred; - union { - int32_t reg; - ecs_entity_t ent; - } obj; - int32_t reg_mask; /* bit 1 = predicate, bit 2 = object */ - - bool transitive; /* Is predicate transitive */ - bool final; /* Is predicate final */ - bool reflexive; /* Is predicate reflexive */ - bool acyclic; /* Is predicate acyclic */ - bool obj_0; -} ecs_rule_pair_t; - -/* Filter for evaluating & reifing types and variables. Filters are created ad- - * hoc from pairs, and take into account all variables that had been resolved - * up to that point. */ -typedef struct ecs_rule_filter_t { - ecs_id_t mask; /* Mask with wildcard in place of variables */ - - bool wildcard; /* Does the filter contain wildcards */ - bool pred_wildcard; /* Is predicate a wildcard */ - bool obj_wildcard; /* Is object a wildcard */ - bool same_var; /* True if pred & obj are both the same variable */ - - int32_t hi_var; /* If hi part should be stored in var, this is the var id */ - int32_t lo_var; /* If lo part should be stored in var, this is the var id */ -} ecs_rule_filter_t; - -/* A rule register stores temporary values for rule variables */ -typedef enum ecs_rule_var_kind_t { - EcsRuleVarKindTable, /* Used for sorting, must be smallest */ - EcsRuleVarKindEntity, - EcsRuleVarKindUnknown -} ecs_rule_var_kind_t; - -/* Operations describe how the rule should be evaluated */ -typedef enum ecs_rule_op_kind_t { - EcsRuleInput, /* Input placeholder, first instruction in every rule */ - EcsRuleSelect, /* Selects all ables for a given predicate */ - EcsRuleWith, /* Applies a filter to a table or entity */ - EcsRuleSubSet, /* Finds all subsets for transitive relationship */ - EcsRuleSuperSet, /* Finds all supersets for a transitive relationship */ - EcsRuleStore, /* Store entity in table or entity variable */ - EcsRuleEach, /* Forwards each entity in a table */ - EcsRuleSetJmp, /* Set label for jump operation to one of two values */ - EcsRuleJump, /* Jump to an operation label */ - EcsRuleNot, /* Invert result of an operation */ - EcsRuleInTable, /* Test if entity (subject) is in table (r_in) */ - EcsRuleEq, /* Test if entity in (subject) and (r_in) are equal */ - EcsRuleYield /* Yield result */ -} ecs_rule_op_kind_t; - -/* Single operation */ -typedef struct ecs_rule_op_t { - ecs_rule_op_kind_t kind; /* What kind of operation is it */ - ecs_rule_pair_t filter; /* Parameter that contains optional filter */ - ecs_entity_t subject; /* If set, operation has a constant subject */ - - int32_t on_pass; /* Jump location when match succeeds */ - int32_t on_fail; /* Jump location when match fails */ - int32_t frame; /* Register frame */ - - int32_t term; /* Corresponding term index in signature */ - int32_t r_in; /* Optional In/Out registers */ - int32_t r_out; - - bool has_in, has_out; /* Keep track of whether operation uses input - * and/or output registers. This helps with - * debugging rule programs. */ -} ecs_rule_op_t; - -/* With context. Shared with select. */ -typedef struct ecs_rule_with_ctx_t { - ecs_id_record_t *idr; /* Currently evaluated table set */ - ecs_table_cache_iter_t it; int32_t column; -} ecs_rule_with_ctx_t; + ecs_id_t id_r; + if (offset) { + column = flecs_table_offset_search(table, offset, rel, &id_r); + } else { + column = flecs_table_search(table, cr_r, &id_r, 0); + } -/* Subset context */ -typedef struct ecs_rule_subset_frame_t { - ecs_rule_with_ctx_t with_ctx; - ecs_table_t *table; - int32_t row; - int32_t column; -} ecs_rule_subset_frame_t; + while (column != -1) { + ecs_entity_t tgt = ECS_PAIR_SECOND(id_r); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); -typedef struct ecs_rule_subset_ctx_t { - ecs_rule_subset_frame_t storage[16]; /* Alloc-free array for small trees */ - ecs_rule_subset_frame_t *stack; - int32_t sp; -} ecs_rule_subset_ctx_t; + if (flecs_table_search_relation_for_tgt( + world, tgt, id, cr, rel, cr_r, tgt_out, id_out, tr_out) != -1) + { + return column; + } -/* Superset context */ -typedef struct ecs_rule_superset_frame_t { - ecs_table_t *table; - int32_t column; -} ecs_rule_superset_frame_t; + if (exclusive) { + break; + } -typedef struct ecs_rule_superset_ctx_t { - ecs_rule_superset_frame_t storage[16]; /* Alloc-free array for small trees */ - ecs_rule_superset_frame_t *stack; - ecs_id_record_t *idr; - int32_t sp; -} ecs_rule_superset_ctx_t; + column = flecs_table_offset_search(table, column + 1, rel, &id_r); + } -/* Each context */ -typedef struct ecs_rule_each_ctx_t { - int32_t row; /* Currently evaluated row in evaluated table */ -} ecs_rule_each_ctx_t; + return -1; +} -/* Jump context */ -typedef struct ecs_rule_setjmp_ctx_t { - int32_t label; /* Operation label to jump to */ -} ecs_rule_setjmp_ctx_t; +static +int32_t flecs_table_search_relation( + const ecs_world_t *world, + const ecs_record_t *record, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_component_record_t *cr, + ecs_id_t rel, + ecs_component_record_t *cr_r, + bool self, + ecs_entity_t *tgt_out, + ecs_id_t *id_out, + ecs_table_record_t **tr_out) +{ + bool dont_fragment = cr->flags & EcsIdDontFragment; -/* Operation context. This is a per-operation, per-iterator structure that - * stores information for stateful operations. */ -typedef struct ecs_rule_op_ctx_t { - union { - ecs_rule_subset_ctx_t subset; - ecs_rule_superset_ctx_t superset; - ecs_rule_with_ctx_t with; - ecs_rule_each_ctx_t each; - ecs_rule_setjmp_ctx_t setjmp; - } is; -} ecs_rule_op_ctx_t; - -/* Rule variables allow for the rule to be parameterized */ -typedef struct ecs_rule_var_t { - ecs_rule_var_kind_t kind; - char *name; /* Variable name */ - int32_t id; /* Unique variable id */ - int32_t other; /* Id to table variable (-1 if none exists) */ - int32_t occurs; /* Number of occurrences (used for operation ordering) */ - int32_t depth; /* Depth in dependency tree (used for operation ordering) */ - bool marked; /* Used for cycle detection */ -} ecs_rule_var_t; - -/* Variable ids per term */ -typedef struct ecs_rule_term_vars_t { - int32_t pred; - int32_t subj; - int32_t obj; -} ecs_rule_term_vars_t; - -/* Top-level rule datastructure */ -struct ecs_rule_t { - ecs_header_t hdr; - - ecs_world_t *world; /* Ref to world so rule can be used by itself */ - ecs_rule_op_t *operations; /* Operations array */ - ecs_filter_t filter; /* Filter of rule */ + if (self && !dont_fragment) { + if (offset) { + int32_t r = flecs_table_offset_search(table, offset, id, id_out); + if (r != -1) { + return r; + } + } else { + int32_t r = flecs_table_search(table, cr, id_out, tr_out); + if (r != -1) { + return r; + } + } + } + + ecs_flags32_t flags = table->flags; - /* Variable array */ - ecs_rule_var_t vars[ECS_RULE_MAX_VAR_COUNT]; + if (!(flags & EcsTableHasPairs) || !rel) { + return -1; + } - /* Passed to iterator */ - char *var_names[ECS_RULE_MAX_VAR_COUNT]; + if (flags & EcsTableHasIsA) { + if (flecs_type_can_inherit_id(world, table, cr, id)) { + int32_t column = flecs_table_search_relation_pairs( + world, table, offset, id, cr, rel, world->cr_isa_wildcard, + tgt_out, id_out, tr_out); + if (column != -1) { + return column; + } + } + if (rel == ecs_isa(EcsWildcard)) { + return -1; + } + } - /* Variable ids used in terms */ - ecs_rule_term_vars_t term_vars[ECS_RULE_MAX_VAR_COUNT]; + if (rel == ecs_childof(EcsWildcard)) { + cr_r = world->cr_childof_wildcard; - /* Variable evaluation order */ - int32_t var_eval_order[ECS_RULE_MAX_VAR_COUNT]; + if (table->flags & EcsTableHasParent) { + /* Can't resolve parent on */ + ecs_assert(record != NULL, ECS_INVALID_PARAMETER, + "cannot traverse ChildOf on table with Parent component, " + "search on entity instead"); - int32_t var_count; /* Number of variables in signature */ - int32_t subj_var_count; - int32_t frame_count; /* Number of register frames */ - int32_t operation_count; /* Number of operations in rule */ + int32_t column = table->component_map[ecs_id(EcsParent)]; + ecs_assert(column > 0, ECS_INTERNAL_ERROR, NULL); - ecs_iterable_t iterable; /* Iterable mixin */ -}; + const EcsParent *parents = table->data.columns[column - 1].data; + ecs_entity_t parent = + parents[ECS_RECORD_TO_ROW(record->row)].value; -/* ecs_rule_t mixins */ -ecs_mixins_t ecs_rule_t_mixins = { - .type_name = "ecs_rule_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_rule_t, world), - [EcsMixinIterable] = offsetof(ecs_rule_t, iterable) + return flecs_table_search_relation_for_tgt(world, parent, id, cr, + rel, cr_r, tgt_out, id_out, tr_out); + } } -}; -static -void rule_error( - const ecs_rule_t *rule, - const char *fmt, - ...) -{ - char *fstr = ecs_filter_str(rule->world, &rule->filter); - va_list valist; - va_start(valist, fmt); - ecs_parser_errorv(rule->filter.name, fstr, -1, fmt, valist); - va_end(valist); - ecs_os_free(fstr); -} + if (!cr_r) { + cr_r = flecs_components_get(world, rel); + if (!cr_r) { + return -1; + } + } -static -bool subj_is_set( - ecs_term_t *term) -{ - return ecs_term_id_is_set(&term->subj); + return flecs_table_search_relation_pairs(world, table, offset, id, cr, rel, + cr_r, tgt_out, id_out, tr_out); } -static -bool obj_is_set( - ecs_term_t *term) +int32_t ecs_search_relation_for_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + ecs_entity_t rel, + bool self, + ecs_component_record_t *cr, + ecs_entity_t *tgt_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out) { - return ecs_term_id_is_set(&term->obj) || term->role == ECS_PAIR; -} + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); -static -ecs_rule_op_t* create_operation( - ecs_rule_t *rule) -{ - int32_t cur = rule->operation_count ++; - rule->operations = ecs_os_realloc( - rule->operations, (cur + 1) * ECS_SIZEOF(ecs_rule_op_t)); + if (tgt_out) tgt_out[0] = 0; + if (!cr) { + cr = flecs_components_get(world, id); + } - ecs_rule_op_t *result = &rule->operations[cur]; - ecs_os_memset_t(result, 0, ecs_rule_op_t); + int32_t result = flecs_table_search_relation(world, r, r->table, 0, id, cr, + rel, NULL, self, tgt_out, id_out, tr_out); + if (result != -1) { + if (tgt_out && !tgt_out[0]) { + tgt_out[0] = entity; + } + } return result; } -static -const char* get_var_name(const char *name) { - if (name && !ecs_os_strcmp(name, "This")) { - /* Make sure that both This and . resolve to the same variable */ - name = "."; - } +int32_t ecs_search_relation( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + ecs_flags64_t flags, + ecs_entity_t *tgt_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - return name; -} + flags = flags ? flags : (EcsSelf|EcsUp); -static -ecs_rule_var_t* create_variable( - ecs_rule_t *rule, - ecs_rule_var_kind_t kind, - const char *name) -{ - int32_t cur = ++ rule->var_count; - - name = get_var_name(name); - if (name && !ecs_os_strcmp(name, "*")) { - /* Wildcards are treated as anonymous variables */ - name = NULL; + if (tgt_out) tgt_out[0] = 0; + if (!(flags & EcsUp)) { + return ecs_search_offset(world, table, offset, id, id_out); } - ecs_rule_var_t *var = &rule->vars[cur - 1]; - if (name) { - var->name = ecs_os_strdup(name); - } else { - /* Anonymous register */ - char name_buff[32]; - ecs_os_sprintf(name_buff, "_%u", cur - 1); - var->name = ecs_os_strdup(name_buff); + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { + return -1; } - var->kind = kind; - - /* The variable id is the location in the variable array and also points to - * the register element that corresponds with the variable. */ - var->id = cur - 1; - - /* Depth is used to calculate how far the variable is from the root, where - * the root is the variable with 0 dependencies. */ - var->depth = UINT8_MAX; - var->marked = false; - var->occurs = 0; + int32_t result = flecs_table_search_relation(world, NULL, table, offset, id, + cr, ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, tgt_out, + id_out, tr_out); - return var; -} - -static -ecs_rule_var_t* create_anonymous_variable( - ecs_rule_t *rule, - ecs_rule_var_kind_t kind) -{ - return create_variable(rule, kind, NULL); + return result; } -/* Find variable with specified name and type. If Unknown is provided as type, - * the function will return any variable with the provided name. The root - * variable can occur both as a table and entity variable, as some rules - * require that each entity in a table is iterated. In this case, there are two - * variables, one for the table and one for the entities in the table, that both - * have the same name. */ -static -ecs_rule_var_t* find_variable( - const ecs_rule_t *rule, - ecs_rule_var_kind_t kind, - const char *name) +int32_t ecs_search( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + ecs_id_t *id_out) { - ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); - - name = get_var_name(name); + flecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - const ecs_rule_var_t *variables = rule->vars; - int32_t i, count = rule->var_count; - - for (i = 0; i < count; i ++) { - const ecs_rule_var_t *variable = &variables[i]; - if (!ecs_os_strcmp(name, variable->name)) { - if (kind == EcsRuleVarKindUnknown || kind == variable->kind) { - return (ecs_rule_var_t*)variable; - } - } + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { + return -1; } - return NULL; + return flecs_table_search(table, cr, id_out, 0); } -/* Ensure variable with specified name and type exists. If an existing variable - * is found with an unknown type, its type will be overwritten with the - * specified type. During the variable ordering phase it is not yet clear which - * variable is the root. Which variable is the root determines its type, which - * is why during this phase variables are still untyped. */ -static -ecs_rule_var_t* ensure_variable( - ecs_rule_t *rule, - ecs_rule_var_kind_t kind, - const char *name) +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) { - ecs_rule_var_t *var = find_variable(rule, kind, name); - if (!var) { - var = create_variable(rule, kind, name); - } else { - if (var->kind == EcsRuleVarKindUnknown) { - var->kind = kind; - } + if (!offset) { + flecs_poly_assert(world, ecs_world_t); + return ecs_search(world, table, id, id_out); } - return var; + return flecs_table_offset_search(table, offset, id, id_out); } static -const char *term_id_var_name( - ecs_term_id_t *term_id) +int32_t flecs_relation_depth_walk( + const ecs_world_t *world, + const ecs_component_record_t *cr, + const ecs_table_t *first, + const ecs_table_t *table) { - if (term_id->var == EcsVarIsVariable) { - if (term_id->name) { - return term_id->name; - } else if (term_id->entity == EcsThis) { - return "."; - } else if (term_id->entity == EcsWildcard) { - return "*"; - } else if (term_id->entity == EcsAny) { - return "_"; - } else if (term_id->entity == EcsVariable) { - return "$"; - } else { - ecs_check(term_id->name != NULL, ECS_INVALID_PARAMETER, NULL); - } - } + int32_t result = 0; -error: - return NULL; -} + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + return 0; + } -static -int ensure_term_id_variable( - ecs_rule_t *rule, - ecs_term_t *term, - ecs_term_id_t *term_id) -{ - if (term_id->var == EcsVarIsVariable) { - if (term_id->entity == EcsAny) { - /* Any variables aren't translated to rule variables since their - * result isn't stored. */ + int32_t i = tr->index, end = i + tr->count; + for (; i != end; i ++) { + ecs_entity_t o = ecs_pair_second(world, table->type.array[i]); + if (!o) { + /* Rare, but can happen during cleanup when an intermediate table is + * created that contains a pair that is about to be removed but + * hasn't yet, where the target is not alive. + * Would be better if this intermediate table wouldn't get created, + * but that requires a refactor of the cleanup logic. */ return 0; } - const char *name = term_id_var_name(term_id); + ecs_table_t *ot = ecs_get_table(world, o); + if (!ot) { + continue; + } + + ecs_assert(ot != first, ECS_CYCLE_DETECTED, NULL); + int32_t cur = flecs_relation_depth_walk(world, cr, first, ot); + if (cur > result) { + result = cur; + } + } + + return result + 1; +} + +int32_t flecs_relation_depth( + const ecs_world_t *world, + ecs_entity_t r, + const ecs_table_t *table) +{ + if (r == EcsChildOf) { + if (table->flags & EcsTableHasChildOf) { + ecs_component_record_t *cr_wc = world->cr_childof_wildcard; + const ecs_table_record_t *tr_wc = + flecs_component_get_table(cr_wc, table); + ecs_assert(tr_wc != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = &table->_->records[tr_wc->index]; + ecs_component_record_t *cr = tr->hdr.cr; + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + return cr->pair->depth; + } else if (table->flags & EcsTableHasParent) { + ecs_component_record_t *cr_wc = flecs_components_get( + world, ecs_pair(EcsParentDepth, EcsWildcard)); + if (!cr_wc) { + return 0; + } - /* If this is a Not term, it should not introduce new variables. It may - * however create entity variables if there already was an existing - * table variable */ - if (term->oper == EcsNot) { - if (!find_variable(rule, EcsRuleVarKindUnknown, name)) { - rule_error(rule, "variable in '%s' only appears in Not term", - name); - return -1; + const ecs_table_record_t *tr_wc = flecs_component_get_table( + cr_wc, table); + if (!tr_wc) { + return 0; } + + ecs_id_t depth_pair = table->type.array[tr_wc->index]; + ecs_assert(ECS_PAIR_FIRST(depth_pair) == EcsParentDepth, + ECS_INTERNAL_ERROR, NULL); + return flecs_uto(int32_t, ECS_PAIR_SECOND(depth_pair)); + } else { + return 0; } + } - ecs_rule_var_t *var = ensure_variable(rule, EcsRuleVarKindEntity, name); - ecs_os_strset(&term_id->name, var->name); + ecs_component_record_t *cr = flecs_components_get( + world, ecs_pair(r, EcsWildcard)); + if (!cr) { return 0; } - return 0; + return flecs_relation_depth_walk(world, cr, table, table); } -static -bool term_id_is_variable( - ecs_term_id_t *term_id) -{ - return term_id->var == EcsVarIsVariable; -} +/** + * @file stage.c + * @brief Staging implementation. + * + * A stage is an object that can be used to temporarily store mutations to a + * world while a world is in readonly mode. ECS operations that are invoked on + * a stage are stored in a command buffer, which is flushed during sync points, + * or manually by the user. + * + * Stages contain additional state to enable other API functionality without + * having to mutate the world, such as setting the current scope, and allocators + * that are local to a stage. + * + * In a multi threaded application, each thread has its own stage which allows + * threads to insert mutations without having to lock administration. + */ -/* Get variable from a term identifier */ -static -ecs_rule_var_t* term_id_to_var( - ecs_rule_t *rule, - ecs_term_id_t *id) -{ - if (id->var == EcsVarIsVariable) {; - return find_variable(rule, EcsRuleVarKindUnknown, term_id_var_name(id)); - } - return NULL; -} -/* Get variable from a term predicate */ static -ecs_rule_var_t* term_pred( - ecs_rule_t *rule, - ecs_term_t *term) +void flecs_stage_merge( + ecs_world_t *world) { - return term_id_to_var(rule, &term->pred); -} + bool is_stage = flecs_poly_is(world, ecs_stage_t); + ecs_stage_t *stage = flecs_stage_from_world(&world); -/* Get variable from a term subject */ -static -ecs_rule_var_t* term_subj( - ecs_rule_t *rule, - ecs_term_t *term) -{ - return term_id_to_var(rule, &term->subj); -} + bool measure_frame_time = ECS_BIT_IS_SET(ecs_world_get_flags(world), + EcsWorldMeasureFrameTime); -/* Get variable from a term object */ -static -ecs_rule_var_t* term_obj( - ecs_rule_t *rule, - ecs_term_t *term) -{ - if (obj_is_set(term)) { - return term_id_to_var(rule, &term->obj); - } else { - return NULL; + ecs_time_t t_start = {0}; + if (measure_frame_time) { + ecs_os_get_time(&t_start); } -} -/* Return predicate variable from pair */ -static -ecs_rule_var_t* pair_pred( - ecs_rule_t *rule, - const ecs_rule_pair_t *pair) -{ - if (pair->reg_mask & RULE_PAIR_PREDICATE) { - return &rule->vars[pair->pred.reg]; + ecs_dbg_3("#[magenta]merge"); + ecs_log_push_3(); + + if (is_stage) { + /* Check for consistency if force_merge is enabled. In practice this + * function will never get called with force_merge disabled for just + * a single stage. */ + ecs_assert(stage->defer == 1, ECS_INVALID_OPERATION, + "mismatching defer_begin/defer_end detected"); + flecs_defer_end(world, stage); } else { - return NULL; + /* Merge stages. Only merge if the stage has auto_merging turned on, or + * if this is a forced merge (like when ecs_merge is called) */ + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); + flecs_poly_assert(s, ecs_stage_t); + flecs_defer_end(world, s); + } } -} -/* Return object variable from pair */ -static -ecs_rule_var_t* pair_obj( - ecs_rule_t *rule, - const ecs_rule_pair_t *pair) -{ - if (pair->reg_mask & RULE_PAIR_OBJECT) { - return &rule->vars[pair->obj.reg]; - } else { - return NULL; + flecs_eval_component_monitors(world); + + if (measure_frame_time) { + world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); } -} -/* Create new frame for storing register values. Each operation that yields data - * gets its own register frame, which contains all variables reified up to that - * point. The preceding frame always contains the reified variables from the - * previous operation. Operations that do not yield data (such as control flow) - * do not have their own frames. */ -static -int32_t push_frame( - ecs_rule_t *rule) -{ - return rule->frame_count ++; -} + world->info.merge_count_total ++; -/* Get register array for current stack frame. The stack frame is determined by - * the current operation that is evaluated. The register array contains the - * values for the reified variables. If a variable hasn't been reified yet, its - * register will store a wildcard. */ -static -ecs_var_t* get_register_frame( - const ecs_rule_iter_t *it, - int32_t frame) -{ - if (it->registers) { - return &it->registers[frame * it->rule->var_count]; - } else { - return NULL; + /* If stage is unmanaged, deferring is always enabled */ + if (stage->id == -1) { + flecs_defer_begin(world, stage); } + + ecs_log_pop_3(); } -/* Get register array for current stack frame. The stack frame is determined by - * the current operation that is evaluated. The register array contains the - * values for the reified variables. If a variable hasn't been reified yet, its - * register will store a wildcard. */ -static -ecs_var_t* get_registers( - const ecs_rule_iter_t *it, - ecs_rule_op_t *op) +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage) { - return get_register_frame(it, op->frame); -} + /* Execute post frame actions */ + int32_t i, count = ecs_vec_count(&stage->post_frame_actions); + ecs_action_elem_t *elems = ecs_vec_first(&stage->post_frame_actions); + for (i = 0; i < count; i ++) { + elems[i].action(world, elems[i].ctx); + } -/* Get columns array. Columns store, for each matched column in a table, the - * index at which it occurs. This reduces the amount of searching that - * operations need to do in a type, since select/with already provide it. */ -static -int32_t* rule_get_columns_frame( - ecs_rule_iter_t *it, - int32_t frame) -{ - return &it->columns[frame * it->rule->filter.term_count]; + ecs_vec_clear(&stage->post_frame_actions); } -static -int32_t* rule_get_columns( - ecs_rule_iter_t *it, - ecs_rule_op_t *op) +ecs_entity_t flecs_stage_set_system( + ecs_stage_t *stage, + ecs_entity_t system) { - return rule_get_columns_frame(it, op->frame); + ecs_entity_t old = stage->system; + stage->system = system; + return old; } static -void entity_reg_set( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r, - ecs_entity_t entity) +ecs_stage_t* flecs_stage_new( + ecs_world_t *world) { - (void)rule; - ecs_assert(rule->vars[r].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); - ecs_check(ecs_is_valid(rule->world, entity), ECS_INVALID_PARAMETER, NULL); - regs[r].entity = entity; -error: - return; -} + flecs_poly_assert(world, ecs_world_t); + ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); + flecs_poly_init(stage, ecs_stage_t); -static -ecs_entity_t entity_reg_get( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r) -{ - (void)rule; - ecs_entity_t e = regs[r].entity; - if (!e) { - return EcsWildcard; - } - - ecs_check(ecs_is_valid(rule->world, e), ECS_INVALID_PARAMETER, NULL); - return e; -error: - return 0; -} + stage->world = world; + stage->thread_ctx = world; -static -void table_reg_set( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r, - ecs_table_t *table) -{ - (void)rule; - ecs_assert(rule->vars[r].kind == EcsRuleVarKindTable, - ECS_INTERNAL_ERROR, NULL); + flecs_stack_init(&stage->allocators.iter_stack); + flecs_allocator_init(&stage->allocator); + flecs_ballocator_init_n(&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t, + FLECS_SPARSE_PAGE_SIZE); + flecs_ballocator_init_t(&stage->allocators.query_impl, ecs_query_impl_t); + flecs_ballocator_init_t(&stage->allocators.query_cache, ecs_query_cache_t); - regs[r].range.table = table; - regs[r].range.offset = 0; - regs[r].range.count = 0; - regs[r].entity = 0; -} + ecs_allocator_t *a = &stage->allocator; + ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); -static -ecs_table_range_t table_reg_get( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r) -{ - (void)rule; - ecs_assert(rule->vars[r].kind == EcsRuleVarKindTable, - ECS_INTERNAL_ERROR, NULL); + int32_t i; + for (i = 0; i < 2; i ++) { + flecs_commands_init(stage, &stage->cmd_stack[i]); + } - return regs[r].range; + stage->cmd = &stage->cmd_stack[0]; + return stage; } static -ecs_entity_t reg_get_entity( - const ecs_rule_t *rule, - ecs_rule_op_t *op, - ecs_var_t *regs, - int32_t r) +void flecs_stage_free( + ecs_world_t *world, + ecs_stage_t *stage) { - if (r == UINT8_MAX) { - ecs_assert(op->subject != 0, ECS_INTERNAL_ERROR, NULL); + (void)world; + flecs_poly_assert(world, ecs_world_t); + flecs_poly_assert(stage, ecs_stage_t); - /* The subject is referenced from the query string by string identifier. - * If subject entity is not valid, it could have been deletd by the - * application after the rule was created */ - ecs_check(ecs_is_valid(rule->world, op->subject), - ECS_INVALID_PARAMETER, NULL); + flecs_poly_fini(stage, ecs_stage_t); - return op->subject; - } - if (rule->vars[r].kind == EcsRuleVarKindTable) { - int32_t offset = regs[r].range.offset; + ecs_allocator_t *a = &stage->allocator; + + ecs_vec_fini_t(a, &stage->post_frame_actions, ecs_action_elem_t); + ecs_vec_fini(NULL, &stage->variables, 0); + ecs_vec_fini(NULL, &stage->operations, 0); - ecs_assert(regs[r].range.count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_data_t *data = &table_reg_get(rule, regs, r).table->storage; - ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(offset < ecs_vector_count(data->entities), - ECS_INTERNAL_ERROR, NULL); - ecs_check(ecs_is_valid(rule->world, entities[offset]), - ECS_INVALID_PARAMETER, NULL); - - return entities[offset]; + int32_t i; + for (i = 0; i < 2; i ++) { + flecs_commands_fini(stage, &stage->cmd_stack[i]); } - if (rule->vars[r].kind == EcsRuleVarKindEntity) { - return entity_reg_get(rule, regs, r); + +#ifdef FLECS_SCRIPT + if (stage->runtime) { + ecs_script_runtime_free(stage->runtime); } +#endif - /* Must return an entity */ - ecs_assert(false, ECS_INTERNAL_ERROR, NULL); + flecs_stack_fini(&stage->allocators.iter_stack); + flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); + flecs_ballocator_fini(&stage->allocators.query_impl); + flecs_ballocator_fini(&stage->allocators.query_cache); + flecs_allocator_fini(&stage->allocator); -error: - return 0; + ecs_os_free(stage); } -static -ecs_table_range_t table_from_entity( - const ecs_world_t *world, - ecs_entity_t entity) +ecs_allocator_t* flecs_stage_get_allocator( + ecs_world_t *world) { - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - - entity = ecs_get_alive(world, entity); - - ecs_table_range_t slice = {0}; - ecs_record_t *record = ecs_eis_get(world, entity); - if (record) { - slice.table = record->table; - slice.offset = ECS_RECORD_TO_ROW(record->row); - slice.count = 1; - } - - return slice; + ecs_stage_t *stage = flecs_stage_from_world( + ECS_CONST_CAST(ecs_world_t**, &world)); + return &stage->allocator; } -static -ecs_table_range_t reg_get_range( - const ecs_rule_t *rule, - ecs_rule_op_t *op, - ecs_var_t *regs, - int32_t r) +ecs_stack_t* flecs_stage_get_stack_allocator( + ecs_world_t *world) { - if (r == UINT8_MAX) { - ecs_check(ecs_is_valid(rule->world, op->subject), - ECS_INVALID_PARAMETER, NULL); - return table_from_entity(rule->world, op->subject); - } - if (rule->vars[r].kind == EcsRuleVarKindTable) { - return table_reg_get(rule, regs, r); - } - if (rule->vars[r].kind == EcsRuleVarKindEntity) { - return table_from_entity(rule->world, entity_reg_get(rule, regs, r)); - } -error: - return (ecs_table_range_t){0}; + ecs_stage_t *stage = flecs_stage_from_world( + ECS_CONST_CAST(ecs_world_t**, &world)); + return &stage->allocators.iter_stack; } -static -void reg_set_entity( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r, - ecs_entity_t entity) +ecs_world_t* ecs_stage_new( + ecs_world_t *world) { - if (rule->vars[r].kind == EcsRuleVarKindTable) { - ecs_world_t *world = rule->world; - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - regs[r].range = table_from_entity(world, entity); - regs[r].entity = entity; - } else { - entity_reg_set(rule, regs, r, entity); - } -error: - return; + ecs_stage_t *stage = flecs_stage_new(world); + stage->id = -1; + + flecs_defer_begin(world, stage); + + return (ecs_world_t*)stage; } -static -void reg_set_range( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t r, - const ecs_table_range_t *range) +void ecs_stage_free( + ecs_world_t *world) { - if (rule->vars[r].kind == EcsRuleVarKindEntity) { - ecs_check(range->count == 1, ECS_INTERNAL_ERROR, NULL); - regs[r].range = *range; - regs[r].entity = ecs_vector_get(range->table->storage.entities, - ecs_entity_t, range->offset)[0]; - } else { - regs[r].range = *range; - regs[r].entity = 0; - } + flecs_poly_assert(world, ecs_stage_t); + ecs_stage_t *stage = (ecs_stage_t*)world; + ecs_check(stage->id == -1, ECS_INVALID_PARAMETER, + "cannot free stage that's owned by world"); + flecs_stage_free(stage->world, stage); error: return; } -/* This encodes a column expression into a pair. A pair stores information about - * the variable(s) associated with the column. Pairs are used by operations to - * apply filters, and when there is a match, to reify variables. */ -static -ecs_rule_pair_t term_to_pair( - ecs_rule_t *rule, - ecs_term_t *term) +void ecs_set_stage_count( + ecs_world_t *world, + int32_t stage_count) { - ecs_rule_pair_t result = {0}; - - /* Terms must always have at least one argument (the subject) */ - ecs_assert(subj_is_set(term), ECS_INTERNAL_ERROR, NULL); - - /* If the predicate id is a variable, find the variable and encode its id - * in the pair so the operation can find it later. */ - if (term->pred.var == EcsVarIsVariable) { - if (term->pred.entity != EcsAny) { - /* Always lookup var as an entity, as pairs never refer to tables */ - const ecs_rule_var_t *var = find_variable( - rule, EcsRuleVarKindEntity, term_id_var_name(&term->pred)); + flecs_poly_assert(world, ecs_world_t); - /* Variables should have been declared */ - ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(var->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); - result.pred.reg = var->id; + /* World must have at least one default stage */ + ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), + ECS_INTERNAL_ERROR, NULL); - /* Set flag so the operation can see the predicate is a variable */ - result.reg_mask |= RULE_PAIR_PREDICATE; - result.final = true; - } else { - result.pred.ent = EcsWildcard; - result.final = true; - } - } else { - /* If the predicate is not a variable, simply store its id. */ - ecs_entity_t pred_id = term->pred.entity; - result.pred.ent = pred_id; + const ecs_entity_t *lookup_path = NULL; + if (world->stage_count >= 1) { + lookup_path = world->stages[0]->lookup_path; + } - /* Test if predicate is transitive. When evaluating the predicate, this - * will also take into account transitive relationships */ - if (ecs_has_id(rule->world, pred_id, EcsTransitive)) { - /* Transitive queries must have an object */ - if (obj_is_set(term)) { - result.transitive = true; - } + int32_t i, count = world->stage_count; + if (stage_count < count) { + for (i = stage_count; i < count; i ++) { + /* If stage contains a thread handle, ecs_set_threads was used to + * create the stages. ecs_set_threads and ecs_set_stage_count should + * not be mixed. */ + ecs_stage_t *stage = world->stages[i]; + flecs_poly_assert(stage, ecs_stage_t); + ecs_check(stage->thread == 0, ECS_INVALID_OPERATION, + "cannot mix using set_stage_count and set_threads"); + flecs_stage_free(world, stage); } + } - if (ecs_has_id(rule->world, pred_id, EcsFinal)) { - result.final = true; - } + if (stage_count) { + world->stages = ecs_os_realloc_n( + world->stages, ecs_stage_t*, stage_count); - if (ecs_has_id(rule->world, pred_id, EcsReflexive)) { - result.reflexive = true; - } + for (i = count; i < stage_count; i ++) { + ecs_stage_t *stage = world->stages[i] = flecs_stage_new(world); + stage->id = i; - if (ecs_has_id(rule->world, pred_id, EcsAcyclic)) { - result.acyclic = true; + /* Set thread_ctx to stage, as this stage might be used in a + * multithreaded context */ + stage->thread_ctx = (ecs_world_t*)stage; + stage->thread = 0; } + } else { + /* Set to NULL to prevent double frees */ + ecs_os_free(world->stages); + world->stages = NULL; } - /* The pair doesn't do anything with the subject (subjects are the things that - * are matched against pairs) so if the column does not have a object, - * there is nothing left to do. */ - if (!obj_is_set(term)) { - return result; + /* Regardless of whether the stage was just initialized or not, when the + * ecs_set_stage_count function is called, all stages inherit the auto_merge + * property from the world */ + for (i = 0; i < stage_count; i ++) { + world->stages[i]->lookup_path = lookup_path; } - /* If arguments is higher than 2 this is not a pair but a nested rule */ - ecs_assert(obj_is_set(term), ECS_INTERNAL_ERROR, NULL); + world->stage_count = stage_count; +error: + return; +} + +int32_t ecs_get_stage_count( + const ecs_world_t *world) +{ + world = ecs_get_world(world); + return world->stage_count; +} - /* Same as above, if the object is a variable, store it and flag it */ - if (term->obj.var == EcsVarIsVariable) { - if (term->obj.entity != EcsAny) { - const ecs_rule_var_t *var = find_variable( - rule, EcsRuleVarKindEntity, term_id_var_name(&term->obj)); +int32_t ecs_stage_get_id( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - /* Variables should have been declared */ - ecs_assert(var != NULL, ECS_INTERNAL_ERROR, - term_id_var_name(&term->obj)); - ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, - term_id_var_name(&term->obj)); + if (flecs_poly_is(world, ecs_stage_t)) { + ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world); - result.obj.reg = var->id; - result.reg_mask |= RULE_PAIR_OBJECT; - } else { - result.obj.ent = EcsWildcard; - } + /* Index 0 is reserved for main stage */ + return stage->id; + } else if (flecs_poly_is(world, ecs_world_t)) { + return 0; } else { - /* If the object is not a variable, simply store its id */ - result.obj.ent = term->obj.entity; - if (!result.obj.ent) { - result.obj_0 = true; - } + ecs_throw(ECS_INTERNAL_ERROR, NULL); } - - return result; +error: + return 0; } -/* When an operation has a pair, it is used to filter its input. This function - * translates a pair back into an entity id, and in the process substitutes the - * variables that have already been filled out. It's one of the most important - * functions, as a lot of the filtering logic depends on having an entity that - * has all of the reified variables correctly filled out. */ -static -ecs_rule_filter_t pair_to_filter( - ecs_rule_iter_t *it, - ecs_rule_op_t *op, - ecs_rule_pair_t pair) +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id) { - ecs_entity_t pred = pair.pred.ent; - ecs_entity_t obj = pair.obj.ent; - ecs_rule_filter_t result = { - .lo_var = -1, - .hi_var = -1 - }; + flecs_poly_assert(world, ecs_world_t); + ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL); + return (ecs_world_t*)world->stages[stage_id]; +error: + return NULL; +} - /* Get registers in case we need to resolve ids from registers. Get them - * from the previous, not the current stack frame as the current operation - * hasn't reified its variables yet. */ - ecs_var_t *regs = get_register_frame(it, op->frame - 1); +bool ecs_readonly_begin( + ecs_world_t *world, + bool multi_threaded) +{ + flecs_poly_assert(world, ecs_world_t); - if (pair.reg_mask & RULE_PAIR_OBJECT) { - obj = entity_reg_get(it->rule, regs, pair.obj.reg); - obj = ecs_entity_t_lo(obj); /* Filters don't have generations */ + ecs_dbg_3("#[bold]readonly"); + ecs_log_push_3(); - if (obj == EcsWildcard) { - result.wildcard = true; - result.obj_wildcard = true; - result.lo_var = pair.obj.reg; - } + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_stage_t *stage = world->stages[i]; + stage->lookup_path = world->stages[0]->lookup_path; + ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, + "deferred mode cannot be enabled when entering readonly mode"); + flecs_defer_begin(world, stage); } - if (pair.reg_mask & RULE_PAIR_PREDICATE) { - pred = entity_reg_get(it->rule, regs, pair.pred.reg); - pred = ecs_entity_t_lo(pred); /* Filters don't have generations */ + bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); - if (pred == EcsWildcard) { - if (result.wildcard) { - result.same_var = pair.pred.reg == pair.obj.reg; - } + /* From this point on, the world is "locked" for mutations, and it is only + * allowed to enqueue commands from stages */ + ECS_BIT_SET(world->flags, EcsWorldReadonly); + ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, multi_threaded); - result.wildcard = true; - result.pred_wildcard = true; + return is_readonly; +} - if (obj) { - result.hi_var = pair.pred.reg; - } else { - result.lo_var = pair.pred.reg; - } - } - } +void ecs_readonly_end( + ecs_world_t *world) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, + "world is not in readonly mode"); - if (!obj && !pair.obj_0) { - result.mask = pred; - } else { - result.mask = ecs_pair(pred, obj); - } + /* After this it is safe again to mutate the world directly */ + ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); + ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); - return result; + ecs_log_pop_3(); + + flecs_stage_merge(world); +error: + return; } -/* This function is responsible for reifying the variables (filling them out - * with their actual values as soon as they are known). It uses the pair - * expression returned by pair_get_most_specific_var, and attempts to fill out each of the - * wildcards in the pair. If a variable isn't reified yet, the pair expression - * will still contain one or more wildcards, which is harmless as the respective - * registers will also point to a wildcard. */ -static -void reify_variables( - ecs_rule_iter_t *it, - ecs_rule_op_t *op, - ecs_rule_filter_t *filter, - ecs_type_t type, - int32_t column) +ecs_world_t* flecs_suspend_readonly( + const ecs_world_t *stage_world, + ecs_suspend_readonly_state_t *state) { - const ecs_rule_t *rule = it->rule; - const ecs_rule_var_t *vars = rule->vars; - (void)vars; - - ecs_var_t *regs = get_registers(it, op); - ecs_entity_t *elem = ecs_vector_get(type, ecs_entity_t, column); - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t obj_var = filter->lo_var; - int32_t pred_var = filter->hi_var; + ecs_world_t *world = + ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); + flecs_poly_assert(world, ecs_world_t); - if (obj_var != -1) { - ecs_assert(vars[obj_var].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); + ecs_world_t *temp_world = world; + ecs_stage_t *stage = flecs_stage_from_world(&temp_world); - entity_reg_set(rule, regs, obj_var, - ecs_get_alive(rule->world, ECS_PAIR_SECOND(*elem))); + if (!is_readonly && !stage->defer) { + state->is_readonly = false; + state->is_deferred = false; + return world; } - if (pred_var != -1) { - ecs_assert(vars[pred_var].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + ecs_dbg_3("suspending readonly mode"); - entity_reg_set(rule, regs, pred_var, - ecs_get_alive(rule->world, - ECS_PAIR_FIRST(*elem))); - } -} + /* Cannot suspend when running with multiple threads */ + ecs_assert(!(world->flags & EcsWorldReadonly) || + !(world->flags & EcsWorldMultiThreaded), + ECS_INVALID_WHILE_READONLY, NULL); -/* Returns whether variable is a subject */ -static -bool is_subject( - ecs_rule_t *rule, - ecs_rule_var_t *var) -{ - ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); + state->is_readonly = is_readonly; + state->is_deferred = stage->defer != 0; + state->cmd_flushing = stage->cmd_flushing; - if (!var) { - return false; - } + /* Silence readonly checks */ + world->flags &= ~EcsWorldReadonly; + stage->cmd_flushing = false; - if (var->id < rule->subj_var_count) { - return true; - } + /* Hack around safety checks (this ought to look ugly) */ + state->defer_count = stage->defer; + state->cmd_stack[0] = stage->cmd_stack[0]; + state->cmd_stack[1] = stage->cmd_stack[1]; + state->cmd = stage->cmd; - return false; -} + flecs_commands_init(stage, &stage->cmd_stack[0]); + flecs_commands_init(stage, &stage->cmd_stack[1]); + stage->cmd = &stage->cmd_stack[0]; -static -bool skip_term(ecs_term_t *term) { - if (term->subj.set.mask & EcsNothing) { - return true; - } - return false; -} + state->scope = stage->scope; + state->with = stage->with; + stage->defer = 0; -static -int32_t get_variable_depth( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur); + return world; +} -static -int32_t crawl_variable( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur) +void flecs_resume_readonly( + ecs_world_t *world, + ecs_suspend_readonly_state_t *state) { - ecs_term_t *terms = rule->filter.terms; - int32_t i, count = rule->filter.term_count; - - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { - continue; - } - - ecs_rule_var_t - *pred = term_pred(rule, term), - *subj = term_subj(rule, term), - *obj = term_obj(rule, term); + flecs_poly_assert(world, ecs_world_t); + ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); - /* Variable must at least appear once in term */ - if (var != pred && var != subj && var != obj) { - continue; - } + ecs_world_t *temp_world = world; + ecs_stage_t *stage = flecs_stage_from_world(&temp_world); - if (pred && pred != var && !pred->marked) { - get_variable_depth(rule, pred, root, recur + 1); - } + if (state->is_readonly || state->is_deferred) { + ecs_dbg_3("resuming readonly mode"); - if (subj && subj != var && !subj->marked) { - get_variable_depth(rule, subj, root, recur + 1); - } + ecs_run_aperiodic(world, 0); - if (obj && obj != var && !obj->marked) { - get_variable_depth(rule, obj, root, recur + 1); - } + /* Restore readonly state / defer count */ + ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); + stage->defer = state->defer_count; + stage->cmd_flushing = state->cmd_flushing; + flecs_commands_fini(stage, &stage->cmd_stack[0]); + flecs_commands_fini(stage, &stage->cmd_stack[1]); + stage->cmd_stack[0] = state->cmd_stack[0]; + stage->cmd_stack[1] = state->cmd_stack[1]; + stage->cmd = state->cmd; + + stage->scope = state->scope; + stage->with = state->with; } +} - return 0; +void ecs_merge( + ecs_world_t *stage) +{ + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(flecs_poly_is(stage, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); + flecs_stage_merge(stage); +error: + return; } -static -int32_t get_depth_from_var( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur) +bool ecs_stage_is_readonly( + const ecs_world_t *stage) { - /* If variable is the root or if depth has been set, return depth + 1 */ - if (var == root || var->depth != UINT8_MAX) { - return var->depth + 1; - } + const ecs_world_t *world = ecs_get_world(stage); - /* Variable is already being evaluated, so this indicates a cycle. Stop */ - if (var->marked) { - return 0; + if (flecs_poly_is(stage, ecs_stage_t)) { + if (((const ecs_stage_t*)stage)->id == -1) { + /* Stage is not owned by world, so never readonly */ + return false; + } } - - /* Variable is not yet being evaluated and depth has not yet been set. - * Calculate depth. */ - int32_t depth = get_variable_depth(rule, var, root, recur + 1); - if (depth == UINT8_MAX) { - return depth; + + if (world->flags & EcsWorldReadonly) { + if (flecs_poly_is(stage, ecs_world_t)) { + return true; + } } else { - return depth + 1; + if (flecs_poly_is(stage, ecs_stage_t)) { + return true; + } } + + return false; } -static -int32_t get_depth_from_term( - ecs_rule_t *rule, - ecs_rule_var_t *cur, - ecs_rule_var_t *pred, - ecs_rule_var_t *obj, - ecs_rule_var_t *root, - int recur) +void ecs_stage_shrink( + ecs_stage_t *stage) { - int32_t result = UINT8_MAX; + flecs_sparse_shrink(&stage->cmd_stack[0].entries); + flecs_sparse_shrink(&stage->cmd_stack[1].entries); + ecs_vec_reclaim_t(&stage->allocator, &stage->cmd_stack[0].queue, ecs_cmd_t); + ecs_vec_reclaim_t(&stage->allocator, &stage->cmd_stack[1].queue, ecs_cmd_t); +} - /* If neither of the other parts of the terms are variables, this - * variable is guaranteed to have no dependencies. */ - if (!pred && !obj) { - result = 0; - } else { - /* If this is a variable that is not the same as the current, - * we can use it to determine dependency depth. */ - if (pred && cur != pred) { - int32_t depth = get_depth_from_var(rule, pred, root, recur); - if (depth == UINT8_MAX) { - return UINT8_MAX; - } +bool ecs_is_deferred( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->defer > 0; +error: + return false; +} - /* If the found depth is lower than the depth found, overwrite it */ - if (depth < result) { - result = depth; - } - } +bool ecs_is_defer_suspended( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->defer < 0; +error: + return false; +} - /* Same for obj */ - if (obj && cur != obj) { - int32_t depth = get_depth_from_var(rule, obj, root, recur); - if (depth == UINT8_MAX) { - return UINT8_MAX; - } +/** + * @file tree_spawner.c + * @brief Data structure used to speed up the creation of hierarchies. + */ - if (depth < result) { - result = depth; - } - } + +static +void flecs_tree_spawner_release_tables( + ecs_vec_t *v) +{ + int32_t i, count = ecs_vec_count(v); + ecs_tree_spawner_child_t *elems = ecs_vec_first(v); + for (i = 0; i < count; i ++) { + ecs_tree_spawner_child_t *elem = &elems[i]; + flecs_table_release(elem->table); } +} - return result; +static +void EcsTreeSpawner_free(EcsTreeSpawner *ptr) { + int32_t i; + for (i = 0; i < FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE; i ++) { + flecs_tree_spawner_release_tables(&ptr->data[i].children); + ecs_vec_fini_t(NULL, &ptr->data[i].children, ecs_tree_spawner_child_t); + } } -/* Find the depth of the dependency tree from the variable to the root */ +static ECS_COPY(EcsTreeSpawner, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "TreeSpawner component cannot be copied"); +}) + +static ECS_MOVE(EcsTreeSpawner, dst, src, { + EcsTreeSpawner_free(dst); + *dst = *src; + ecs_os_zeromem(src); +}) + +static ECS_DTOR(EcsTreeSpawner, ptr, { + EcsTreeSpawner_free(ptr); +}) + static -int32_t get_variable_depth( - ecs_rule_t *rule, - ecs_rule_var_t *var, - ecs_rule_var_t *root, - int recur) +ecs_type_t flecs_prefab_spawner_build_type( + ecs_world_t *world, + ecs_entity_t child, + ecs_table_t *table, + int32_t depth) { - var->marked = true; + ecs_type_t dst = {0}; + ecs_type_t *src = &table->type; - /* Iterate columns, find all instances where 'var' is not used as subject. - * If the subject of that column is either the root or a variable for which - * the depth is known, the depth for this variable can be determined. */ - ecs_term_t *terms = rule->filter.terms; - - int32_t i, count = rule->filter.term_count; - int32_t result = UINT8_MAX; + flecs_type_add(world, &dst, ecs_id(EcsParent)); + int32_t i, count = src->count; for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { + ecs_id_t id = src->array[i]; + ecs_table_record_t *tr = &table->_->records[i]; + ecs_component_record_t *cr = tr->hdr.cr; + + if (cr->flags & + (EcsIdOnInstantiateDontInherit|EcsIdOnInstantiateInherit)) + { continue; } - ecs_rule_var_t - *pred = term_pred(rule, term), - *subj = term_subj(rule, term), - *obj = term_obj(rule, term); - - if (subj != var) { + if (id & ECS_AUTO_OVERRIDE) { + /* If AUTO_OVERRIDE flag is set, add component to instances. This + * allows instances to end up with an owned component, even if the + * component has the (OnInstantiate, Inherit) trait. + * Additionally, this also allows for adding components to instances + * that aren't copyable, since a prefab can have a component with + * AUTO_OVERRIDE flag, but not have the actual component (which then + * isn't copied). */ + flecs_type_add(world, &dst, id & ~ECS_AUTO_OVERRIDE); continue; } - if (!is_subject(rule, pred)) { - pred = NULL; + ecs_entity_t rel = ECS_PAIR_FIRST(id); + if (rel == EcsIsA) { + /* If prefab child has IsA relationships, they will be inherited + * through the (IsA, prefab_child) relationship (added below). */ + continue; } - if (!is_subject(rule, obj)) { - obj = NULL; + if (rel == EcsParentDepth) { + /* Replace depth value with the normalized depth for spawner. */ + id = ecs_value_pair(EcsParentDepth, depth); } - int32_t depth = get_depth_from_term(rule, var, pred, obj, root, recur); - if (depth < result) { - result = depth; - } + flecs_type_add(world, &dst, id); } - if (result == UINT8_MAX) { - result = 0; - } + flecs_type_add(world, &dst, ecs_isa(child)); - var->depth = result; + return dst; +} - /* Dependencies are calculated from subject to (pred, obj). If there were - * subjects that are only related by object (like (X, Y), (Z, Y)) it is - * possible that those have not yet been found yet. To make sure those - * variables are found, loop again & follow predicate & object links */ +static +void flecs_prefab_spawner_build_from_cr( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_vec_t *spawner, + int32_t parent_index, + int32_t depth) +{ + ecs_vec_t *children_vec = &cr->pair->ordered_children; + int32_t i, count = ecs_vec_count(children_vec); + ecs_entity_t *children = ecs_vec_first(children_vec); for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { + ecs_entity_t child = children[i]; + ecs_record_t *r = flecs_entities_get(world, child); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!(table->flags & EcsTableHasParent)) { continue; } - ecs_rule_var_t - *subj = term_subj(rule, term), - *pred = term_pred(rule, term), - *obj = term_obj(rule, term); + ecs_tree_spawner_child_t *elem = ecs_vec_append_t( + NULL, spawner, ecs_tree_spawner_child_t); + elem->parent_index = parent_index; + elem->child_name = NULL; + elem->child = (uint32_t)child; - /* Only evaluate pred & obj for current subject. This ensures that we - * won't evaluate variables that are unreachable from the root. This - * must be detected as unconstrained variables are not allowed. */ - if (subj != var) { - continue; + if (table->flags & EcsTableHasName) { + elem->child_name = ecs_get_name(world, child); } - crawl_variable(rule, subj, root, recur); + ecs_type_t type = flecs_prefab_spawner_build_type( + world, child, table, depth); + elem->table = flecs_table_find_or_create(world, &type); + ecs_assert(elem->table != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_type_free(world, &type); - if (pred && pred != var) { - crawl_variable(rule, pred, root, recur); + /* Make sure table doesn't get freed by shrink() */ + flecs_table_keep(elem->table); + + if (!(r->row & EcsEntityIsTraversable)) { + continue; } - if (obj && obj != var) { - crawl_variable(rule, obj, root, recur); - } - } + ecs_component_record_t *child_cr = flecs_components_get( + world, ecs_childof(child)); + if (!child_cr) { + continue; + } - return var->depth; + flecs_prefab_spawner_build_from_cr( + world, child_cr, spawner, ecs_vec_count(spawner), depth + 1); + } } -/* Compare function used for qsort. It ensures that variables are first ordered - * by depth, followed by how often they occur. */ static -int compare_variable( - const void* ptr1, - const void *ptr2) +void flecs_spawner_transpose_depth( + ecs_world_t *world, + EcsTreeSpawner *spawner, + ecs_vec_t *dst, + int32_t depth) { - const ecs_rule_var_t *v1 = ptr1; - const ecs_rule_var_t *v2 = ptr2; + ecs_vec_t *src = &spawner->data[0].children; - if (v1->kind < v2->kind) { - return -1; - } else if (v1->kind > v2->kind) { - return 1; - } + int32_t i, count = ecs_vec_count(src); + ecs_vec_set_count_t(NULL, dst, ecs_tree_spawner_child_t, count); - if (v1->depth < v2->depth) { - return -1; - } else if (v1->depth > v2->depth) { - return 1; - } + for (i = 0; i < count; i ++) { + ecs_tree_spawner_child_t *src_elem = ecs_vec_get_t( + src, ecs_tree_spawner_child_t, i); + ecs_tree_spawner_child_t *dst_elem = ecs_vec_get_t( + dst, ecs_tree_spawner_child_t, i); + + dst_elem->child_name = src_elem->child_name; + dst_elem->parent_index = src_elem->parent_index; + dst_elem->child = src_elem->child; + + /* Get depth for source element at depth 0 */ + int32_t src_depth = flecs_relation_depth( + world, EcsChildOf, src_elem->table); - if (v1->occurs < v2->occurs) { - return 1; - } else { - return -1; - } + /* Get table for correct depth */ + ecs_id_t depth_pair = ecs_value_pair(EcsParentDepth, src_depth + depth); + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; - return (v1->id < v2->id) - (v1->id > v2->id); + dst_elem->table = flecs_table_traverse_add( + world, src_elem->table, &depth_pair, &diff); + + flecs_table_keep(dst_elem->table); + } } -/* After all subject variables have been found, inserted and sorted, the - * remaining variables (predicate & object) still need to be inserted. This - * function serves two purposes. The first purpose is to ensure that all - * variables are known before operations are emitted. This ensures that the - * variables array won't be reallocated while emitting, which simplifies code. - * The second purpose of the function is to ensure that if the root variable - * (which, if it exists has now been created with a table type) is also inserted - * with an entity type if required. This is used later to decide whether the - * rule needs to insert an each instruction. */ -static -int ensure_all_variables( - ecs_rule_t *rule) -{ - ecs_term_t *terms = rule->filter.terms; - int32_t i, count = rule->filter.term_count; - - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (skip_term(term)) { - continue; - } +EcsTreeSpawner* flecs_prefab_spawner_build( + ecs_world_t *world, + ecs_entity_t base) +{ + ecs_component_record_t *cr = flecs_components_get(world, ecs_childof(base)); + if (!cr) { + return NULL; + } - /* If predicate is a variable, make sure it has been registered */ - if (term->pred.var == EcsVarIsVariable) { - if (ensure_term_id_variable(rule, term, &term->pred) != 0) { - return -1; - } - } + ecs_vec_t spawner; + ecs_vec_init_t(NULL, &spawner, ecs_tree_spawner_child_t, 0); + flecs_prefab_spawner_build_from_cr(world, cr, &spawner, 0, 1); - /* If subject is a variable and it is not This, make sure it is - * registered as an entity variable. This ensures that the program will - * correctly return all permutations */ - if (term->subj.var == EcsVarIsVariable) { - if (term->subj.entity != EcsThis) { - if (ensure_term_id_variable(rule, term, &term->subj) != 0) { - return -1; - } - } - } + base = flecs_entities_get_alive(world, base); + EcsTreeSpawner *ts = ecs_ensure(world, base, EcsTreeSpawner); + ts->data[0].children = spawner; - /* If object is a variable, make sure it has been registered */ - if (obj_is_set(term) && (term->obj.var == EcsVarIsVariable)) { - if (ensure_term_id_variable(rule, term, &term->obj) != 0) { - return -1; - } - } + /* Initialize remaining vectors */ + int32_t i; + for (i = 1; i < FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE; i ++) { + ecs_vec_init_t( + NULL, &ts->data[i].children, ecs_tree_spawner_child_t, 0); } - return 0; + return ts; } -/* Scan for variables, put them in optimal dependency order. */ -static -int scan_variables( - ecs_rule_t *rule) +void flecs_spawner_instantiate( + ecs_world_t *world, + EcsTreeSpawner *spawner, + ecs_entity_t instance) { - /* Objects found in rule. One will be elected root */ - int32_t subject_count = 0; + ecs_record_t *r_instance = flecs_entities_get(world, instance); + int32_t depth = flecs_relation_depth(world, EcsChildOf, r_instance->table); + int32_t i, child_count = ecs_vec_count(&spawner->data[0].children); - /* If this (.) is found, it always takes precedence in root election */ - int32_t this_var = UINT8_MAX; + bool is_prefab = r_instance->table->flags & EcsTableIsPrefab; + + /* Use cached spawner for depth if available. */ + ecs_vec_t *vec, tmp_vec; + if (depth < FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE) { + vec = &spawner->data[depth].children; + } else { + vec = &tmp_vec; + ecs_vec_init_t(NULL, vec, ecs_tree_spawner_child_t, 0); + } - /* Keep track of the subject variable that occurs the most. In the absence of - * this (.) the variable with the most occurrences will be elected root. */ - int32_t max_occur = 0; - int32_t max_occur_var = UINT8_MAX; + if (depth && ecs_vec_count(vec) != child_count) { + /* Vector for depth is not yet initialized, create it. */ + flecs_spawner_transpose_depth(world, spawner, vec, depth); + } - /* Step 1: find all possible roots */ - ecs_term_t *terms = rule->filter.terms; - int32_t i, term_count = rule->filter.term_count; + ecs_tree_spawner_child_t *spawn_children = ecs_vec_first(vec); + ecs_vec_set_min_count_t(&world->allocator, &world->allocators.tree_spawner, + ecs_entity_t, child_count + 1); + ecs_entity_t *parents = ecs_vec_first(&world->allocators.tree_spawner); + parents[0] = instance; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; + ecs_component_record_t *cr = NULL; + ecs_entity_t old_parent = 0; - /* Evaluate the subject. The predicate and object are not evaluated, - * since they never can be elected as root. */ - if (term_id_is_variable(&term->subj)) { - const char *subj_name = term_id_var_name(&term->subj); - - ecs_rule_var_t *subj = find_variable( - rule, EcsRuleVarKindTable, subj_name); - if (!subj) { - subj = create_variable(rule, EcsRuleVarKindTable, subj_name); - if (subject_count >= ECS_RULE_MAX_VAR_COUNT) { - rule_error(rule, "too many variables in rule"); - goto error; - } + ecs_assert(ecs_vec_count(vec) == child_count, ECS_INTERNAL_ERROR, NULL); - /* Make sure that variable name in term array matches with the - * rule name. */ - ecs_os_strset(&term->subj.name, subj->name); - } + for (i = 0; i < child_count; i ++) { + ecs_entity_t entity = parents[i + 1] = flecs_new_id(world); + ecs_tree_spawner_child_t *spawn_child = &spawn_children[i]; + ecs_table_t *table = spawn_child->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (++ subj->occurs > max_occur) { - max_occur = subj->occurs; - max_occur_var = subj->id; + if (is_prefab) { + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_id_t id = EcsPrefab; + table = flecs_table_traverse_add(world, table, &id, &diff); + + if (spawn_child->child_name) { + id = ecs_pair_t(EcsIdentifier, EcsName); + table = flecs_table_traverse_add(world, table, &id, &diff); } } - } - rule->subj_var_count = rule->var_count; + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_flags32_t flags = table->flags & EcsTableAddEdgeFlags; - if (ensure_all_variables(rule) != 0) { - goto error; - } - - /* Variables in a term with a literal subject have depth 0 */ - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; + ecs_table_diff_t table_diff = { + .added = table->type, + .added_flags = flags + }; - if (term->subj.var == EcsVarIsEntity) { - ecs_rule_var_t - *pred = term_pred(rule, term), - *obj = term_obj(rule, term); + int32_t row = flecs_table_append(world, table, entity, true, true); + r->table = table; + r->row = (uint32_t)row; - if (pred) { - pred->depth = 0; - } - if (obj) { - obj->depth = 0; - } - } - } + flecs_actions_new(world, table, row, 1, &table_diff, 0, false, true); - /* Elect a root. This is either this (.) or the variable with the most - * occurrences. */ - int32_t root_var = this_var; - if (root_var == UINT8_MAX) { - root_var = max_occur_var; - if (root_var == UINT8_MAX) { - /* If no subject variables have been found, the rule expression only - * operates on a fixed set of entities, in which case no root - * election is required. */ - goto done; + ecs_entity_t parent = parents[spawn_child->parent_index]; + if (parent != old_parent) { + cr = flecs_components_ensure(world, ecs_childof(parent)); + old_parent = parent; } - } - ecs_rule_var_t *root = &rule->vars[root_var]; - root->depth = get_variable_depth(rule, root, root, 0); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); - /* Verify that there are no unconstrained variables. Unconstrained variables - * are variables that are unreachable from the root. */ - for (i = 0; i < rule->subj_var_count; i ++) { - if (rule->vars[i].depth == UINT8_MAX) { - rule_error(rule, "unconstrained variable '%s'", - rule->vars[i].name); - goto error; - } - } + int32_t parent_column = table->component_map[ecs_id(EcsParent)]; + ecs_assert(parent_column != 0, ECS_INTERNAL_ERROR, NULL); + EcsParent *parent_ptr = table->data.columns[parent_column - 1].data; + parent_ptr = &parent_ptr[row]; + parent_ptr->value = parent; - /* For each Not term, verify that variables are known */ - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - if (term->oper != EcsNot) { - continue; + if (is_prefab && spawn_child->child_name) { + ecs_set_name(world, entity, spawn_child->child_name); } - ecs_rule_var_t - *pred = term_pred(rule, term), - *obj = term_obj(rule, term); - - if (!pred && term_id_is_variable(&term->pred)) { - rule_error(rule, "missing predicate variable '%s'", - term_id_var_name(&term->pred)); - goto error; - } - if (!obj && term_id_is_variable(&term->obj)) { - rule_error(rule, "missing object variable '%s'", - term_id_var_name(&term->obj)); - goto error; - } - } + flecs_add_non_fragmenting_child_w_records(world, parent, entity, cr, r); - /* Order variables by depth, followed by occurrence. The variable - * array will later be used to lead the iteration over the terms, and - * determine which operations get inserted first. */ - int32_t var_count = rule->var_count; - ecs_rule_var_t vars[ECS_RULE_MAX_VAR_COUNT]; - ecs_os_memcpy_n(vars, rule->vars, ecs_rule_var_t, var_count); - ecs_qsort_t(&vars, var_count, ecs_rule_var_t, compare_variable); - for (i = 0; i < var_count; i ++) { - rule->var_eval_order[i] = vars[i].id; - } + ecs_entity_t base_child = spawn_child->child; + ecs_record_t *spawn_r = flecs_entities_get_any( + world, spawn_child->child); + ecs_assert(spawn_r != NULL, ECS_INTERNAL_ERROR, NULL); -done: - return 0; -error: - return -1; -} + ecs_table_range_t base_range = { + .table = spawn_r->table, + .offset = 0, + .count = 1 }; + flecs_instantiate_sparse(world, &base_range, &base_child, + r->table, &entity, ECS_RECORD_TO_ROW(r->row)); -/* Get entity variable from table variable */ -static -ecs_rule_var_t* to_entity( - ecs_rule_t *rule, - ecs_rule_var_t *var) -{ - if (!var) { - return NULL; + if (spawn_r->row & EcsEntityHasDontFragment) { + flecs_instantiate_dont_fragment( + world, spawn_child->child, entity); + } } - ecs_rule_var_t *evar = NULL; - if (var->kind == EcsRuleVarKindTable) { - evar = find_variable(rule, EcsRuleVarKindEntity, var->name); - } else { - evar = var; + if (vec == &tmp_vec) { + ecs_vec_fini_t(NULL, vec, ecs_tree_spawner_child_t); } - - return evar; } -/* Ensure that if a table variable has been written, the corresponding entity - * variable is populated. The function will return the most specific, populated - * variable. */ -static -ecs_rule_var_t* most_specific_var( - ecs_rule_t *rule, - ecs_rule_var_t *var, - bool *written, - bool create) +void flecs_bootstrap_spawner( + ecs_world_t *world) { - if (!var) { - return NULL; - } - - ecs_rule_var_t *tvar, *evar = to_entity(rule, var); - if (!evar) { - return var; - } - - if (var->kind == EcsRuleVarKindTable) { - tvar = var; - } else { - tvar = find_variable(rule, EcsRuleVarKindTable, var->name); - } - - /* If variable is used as predicate or object, it should have been - * registered as an entity. */ - ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Usually table variables are resolved before they are used as a predicate - * or object, but in the case of cyclic dependencies this is not guaranteed. - * Only insert an each instruction of the table variable has been written */ - if (tvar && written[tvar->id]) { - /* If the variable has been written as a table but not yet - * as an entity, insert an each operation that yields each - * entity in the table. */ - if (evar) { - if (written[evar->id]) { - return evar; - } else if (create) { - ecs_rule_op_t *op = create_operation(rule); - op->kind = EcsRuleEach; - op->on_pass = rule->operation_count; - op->on_fail = rule->operation_count - 2; - op->frame = rule->frame_count; - op->has_in = true; - op->has_out = true; - op->r_in = tvar->id; - op->r_out = evar->id; + flecs_type_info_init(world, EcsTreeSpawner, { + .ctor = flecs_default_ctor, + .copy = ecs_copy(EcsTreeSpawner), + .move = ecs_move(EcsTreeSpawner), + .dtor = ecs_dtor(EcsTreeSpawner) + }); - /* Entity will either be written or has been written */ - written[evar->id] = true; + ecs_add_pair(world, ecs_id(EcsTreeSpawner), + EcsOnInstantiate, EcsDontInherit); +} - push_frame(rule); +/** + * @file type_info.c + * @brief Component metadata and lifecycle hooks. + */ - return evar; - } else { - return tvar; - } - } - } else if (evar && written[evar->id]) { - return evar; - } - return var; +void flecs_default_ctor( + void *ptr, + int32_t count, + const ecs_type_info_t *ti) +{ + ecs_os_memset(ptr, 0, ti->size * count); } -/* Get most specific known variable */ static -ecs_rule_var_t *get_most_specific_var( - ecs_rule_t *rule, - ecs_rule_var_t *var, - bool *written) +void flecs_default_copy_ctor(void *dst_ptr, const void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - return most_specific_var(rule, var, written, false); + const ecs_type_hooks_t *cl = &ti->hooks; + cl->ctor(dst_ptr, count, ti); + cl->copy(dst_ptr, src_ptr, count, ti); } -/* Get or create most specific known variable. This will populate an entity - * variable if a table variable is known but the entity variable isn't. */ static -ecs_rule_var_t *ensure_most_specific_var( - ecs_rule_t *rule, - ecs_rule_var_t *var, - bool *written) +void flecs_default_move_ctor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - return most_specific_var(rule, var, written, true); + const ecs_type_hooks_t *cl = &ti->hooks; + cl->ctor(dst_ptr, count, ti); + cl->move(dst_ptr, src_ptr, count, ti); } - -/* Ensure that an entity variable is written before using it */ static -ecs_rule_var_t* ensure_entity_written( - ecs_rule_t *rule, - ecs_rule_var_t *var, - bool *written) +void flecs_default_ctor_w_move_w_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - if (!var) { - return NULL; - } - - /* Ensure we're working with the most specific version of subj we can get */ - ecs_rule_var_t *evar = ensure_most_specific_var(rule, var, written); - - /* The post condition of this function is that there is an entity variable, - * and that it is written. Make sure that the result is an entity */ - ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(evar->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); - - /* Make sure the variable has been written */ - ecs_assert(written[evar->id] == true, ECS_INTERNAL_ERROR, NULL); - - return evar; + const ecs_type_hooks_t *cl = &ti->hooks; + cl->ctor(dst_ptr, count, ti); + cl->move(dst_ptr, src_ptr, count, ti); + cl->dtor(src_ptr, count, ti); } static -ecs_rule_op_t* insert_operation( - ecs_rule_t *rule, - int32_t term_index, - bool *written) +void flecs_default_move_ctor_w_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - ecs_rule_pair_t pair = {0}; - - /* Parse the term's type into a pair. A pair extracts the ids from - * the term, and replaces variables with wildcards which can then - * be matched against actual relationships. A pair retains the - * information about the variables, so that when a match happens, - * the pair can be used to reify the variable. */ - if (term_index != -1) { - ecs_term_t *term = &rule->filter.terms[term_index]; - - pair = term_to_pair(rule, term); - - /* If the pair contains entity variables that have not yet been written, - * insert each instructions in case their tables are known. Variables in - * a pair that are truly unknown will be populated by the operation, - * but an operation should never overwrite an entity variable if the - * corresponding table variable has already been resolved. */ - if (pair.reg_mask & RULE_PAIR_PREDICATE) { - ecs_rule_var_t *pred = &rule->vars[pair.pred.reg]; - pred = get_most_specific_var(rule, pred, written); - pair.pred.reg = pred->id; - } - - if (pair.reg_mask & RULE_PAIR_OBJECT) { - ecs_rule_var_t *obj = &rule->vars[pair.obj.reg]; - obj = get_most_specific_var(rule, obj, written); - pair.obj.reg = obj->id; - } - } else { - /* Not all operations have a filter (like Each) */ - } - - ecs_rule_op_t *op = create_operation(rule); - op->on_pass = rule->operation_count; - op->on_fail = rule->operation_count - 2; - op->frame = rule->frame_count; - op->filter = pair; + const ecs_type_hooks_t *cl = &ti->hooks; + cl->move_ctor(dst_ptr, src_ptr, count, ti); + cl->dtor(src_ptr, count, ti); +} - /* Store corresponding signature term so we can correlate and - * store the table columns with signature columns. */ - op->term = term_index; +static +void flecs_default_move(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->move(dst_ptr, src_ptr, count, ti); +} - return op; +static +void flecs_default_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) +{ + /* When there is no move, destruct the destination component & memcpy the + * component to dst. The src component does not have to be destructed when + * a component has a trivial move. */ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->dtor(dst_ptr, count, ti); + ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } -/* Insert first operation, which is always Input. This creates an entry in - * the register stack for the initial state. */ static -void insert_input( - ecs_rule_t *rule) +void flecs_default_move_w_dtor(void *dst_ptr, void *src_ptr, + int32_t count, const ecs_type_info_t *ti) { - ecs_rule_op_t *op = create_operation(rule); - op->kind = EcsRuleInput; + /* If a component has a move, the move will take care of memcpying the data + * and destroying any data in dst. Because this is not a trivial move, the + * src component must also be destructed. */ + const ecs_type_hooks_t *cl = &ti->hooks; + cl->move(dst_ptr, src_ptr, count, ti); + cl->dtor(src_ptr, count, ti); +} - /* The first time Input is evaluated it goes to the next/first operation */ - op->on_pass = 1; +static +bool flecs_default_equals(const void *a_ptr, const void *b_ptr, const ecs_type_info_t* ti) { + return ti->hooks.cmp(a_ptr, b_ptr, ti) == 0; +} - /* When Input is evaluated with redo = true it will return false, which will - * finish the program as op becomes -1. */ - op->on_fail = -1; +ECS_NORETURN static +void flecs_ctor_illegal( + void * dst, + int32_t count, + const ecs_type_info_t *ti) { + (void)dst; /* silence unused warning */ + (void)count; + ecs_abort(ECS_INVALID_OPERATION, "invalid constructor for %s", ti->name); +} - push_frame(rule); +ECS_NORETURN static +void flecs_dtor_illegal( + void *dst, + int32_t count, + const ecs_type_info_t *ti) { + (void)dst; /* silence unused warning */ + (void)count; + ecs_abort(ECS_INVALID_OPERATION, "invalid destructor for %s", ti->name); } -/* Insert last operation, which is always Yield. When the program hits Yield, - * data is returned to the application. */ -static -void insert_yield( - ecs_rule_t *rule) +ECS_NORETURN static +void flecs_copy_illegal( + void *dst, + const void *src, + int32_t count, + const ecs_type_info_t *ti) { - ecs_rule_op_t *op = create_operation(rule); - op->kind = EcsRuleYield; - op->has_in = true; - op->on_fail = rule->operation_count - 2; - /* Yield can only "fail" since it is the end of the program */ + (void)dst; /* silence unused warning */ + (void)src; + (void)count; + ecs_abort(ECS_INVALID_OPERATION, "invalid copy assignment for %s", ti->name); +} - /* Find variable associated with this. It is possible that the variable - * exists both as a table and as an entity. This can happen when a rule - * first selects a table for this, but then subsequently needs to evaluate - * each entity in that table. In that case the yield instruction should - * return the entity, so look for that first. */ - ecs_rule_var_t *var = find_variable(rule, EcsRuleVarKindEntity, "."); - if (!var) { - var = find_variable(rule, EcsRuleVarKindTable, "."); - } +ECS_NORETURN static +void flecs_move_illegal( + void * dst, + void * src, + int32_t count, + const ecs_type_info_t *ti) { + (void)dst; /* silence unused warning */ + (void)src; + (void)count; + ecs_abort(ECS_INVALID_OPERATION, "invalid move assignment for %s", ti->name); +} - /* If there is no this, there is nothing to yield. In that case the rule - * simply returns true or false. */ - if (!var) { - op->r_in = UINT8_MAX; - } else { - op->r_in = var->id; - } +ECS_NORETURN static +void flecs_copy_ctor_illegal( + void *dst, + const void *src, + int32_t count, + const ecs_type_info_t *ti) +{ + (void)dst; /* silence unused warning */ + (void)src; + (void)count; + ecs_abort(ECS_INVALID_OPERATION, "invalid copy construct for %s", ti->name); +} - op->frame = push_frame(rule); +ECS_NORETURN static +void flecs_move_ctor_illegal( + void *dst, + void *src, + int32_t count, + const ecs_type_info_t *ti) +{ + (void)dst; /* silence unused warning */ + (void)src; + (void)count; + ecs_abort(ECS_INVALID_OPERATION, "invalid move construct for %s", ti->name); } -/* Return superset/subset including the root */ -static -void insert_reflexive_set( - ecs_rule_t *rule, - ecs_rule_op_kind_t op_kind, - ecs_rule_var_t *out, - const ecs_rule_pair_t pair, - int32_t c, - bool *written, - bool reflexive) +ECS_NORETURN static +int flecs_comp_illegal( + const void *dst, + const void *src, + const ecs_type_info_t *ti) { - ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + (void)dst; /* silence unused warning */ + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "invalid compare hook for %s", ti->name); +} + +ECS_NORETURN static +bool flecs_equals_illegal( + const void *dst, + const void *src, + const ecs_type_info_t *ti) +{ + (void)dst; /* silence unused warning */ + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "invalid equals hook for %s", ti->name); +} - ecs_rule_var_t *pred = pair_pred(rule, &pair); - ecs_rule_var_t *obj = pair_obj(rule, &pair); +void ecs_set_hooks_id( + ecs_world_t *world, + ecs_entity_t component, + const ecs_type_hooks_t *h) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, component), ECS_INVALID_PARAMETER, NULL); + + /* TODO: Refactor to enforce flags consistency: */ + ecs_flags32_t flags = h->flags; + flags &= ~((ecs_flags32_t)ECS_TYPE_HOOKS); + + ecs_check(!(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL && + h->ctor != NULL && + h->ctor != flecs_ctor_illegal), + ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " + "cannot specify both ctor hook and illegal flag", + flecs_errstr(ecs_get_path(world, component))); + + ecs_check(!(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL && + h->dtor != NULL && + h->dtor != flecs_dtor_illegal), + ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " + "cannot specify both dtor hook and illegal flag", + flecs_errstr(ecs_get_path(world, component))); + + ecs_check(!(flags & ECS_TYPE_HOOK_COPY_ILLEGAL && + h->copy != NULL && + h->copy != flecs_copy_illegal), + ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " + "cannot specify both copy hook and illegal flag", + flecs_errstr(ecs_get_path(world, component))); + + ecs_check(!(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL && + h->move != NULL && + h->move != flecs_move_illegal), + ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " + "cannot specify both move hook and illegal flag", + flecs_errstr(ecs_get_path(world, component))); + + ecs_check(!(flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL && + h->copy_ctor != NULL && + h->copy_ctor != flecs_copy_ctor_illegal), + ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " + "cannot specify both copy ctor hook and illegal flag", + flecs_errstr(ecs_get_path(world, component))); + + ecs_check(!(flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL && + h->move_ctor != NULL && + h->move_ctor != flecs_move_ctor_illegal), + ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " + "cannot specify both move ctor hook and illegal flag", + flecs_errstr(ecs_get_path(world, component))); + + ecs_check(!(flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL && + h->ctor_move_dtor != NULL && + h->ctor_move_dtor != flecs_move_ctor_illegal), + ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " + "cannot specify both ctor move dtor hook and illegal flag", + flecs_errstr(ecs_get_path(world, component))); + + ecs_check(!(flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL && + h->move_dtor != NULL && + h->move_dtor != flecs_move_ctor_illegal), + ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " + "cannot specify both move dtor hook and illegal flag", + flecs_errstr(ecs_get_path(world, component))); + + ecs_check(!(flags & ECS_TYPE_HOOK_CMP_ILLEGAL && + h->cmp != NULL && + h->cmp != flecs_comp_illegal), + ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " + "cannot specify both compare hook and illegal flag", + flecs_errstr(ecs_get_path(world, component))); + + ecs_check(!(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL && + h->equals != NULL && + h->equals != flecs_equals_illegal), + ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " + "cannot specify both equals hook and illegal flag", + flecs_errstr(ecs_get_path(world, component))); - int32_t setjmp_lbl = rule->operation_count; - int32_t store_lbl = setjmp_lbl + 1; - int32_t set_lbl = setjmp_lbl + 2; - int32_t next_op = setjmp_lbl + 4; - int32_t prev_op = setjmp_lbl - 1; - /* Insert 4 operations at once, so we don't have to worry about how - * the instruction array reallocs. If operation is not reflexive, we only - * need to insert the set operation. */ - if (reflexive) { - insert_operation(rule, -1, written); - insert_operation(rule, -1, written); - insert_operation(rule, -1, written); - } + flecs_stage_from_world(&world); - ecs_rule_op_t *op = insert_operation(rule, -1, written); - ecs_rule_op_t *setjmp = &rule->operations[setjmp_lbl]; - ecs_rule_op_t *store = &rule->operations[store_lbl]; - ecs_rule_op_t *set = &rule->operations[set_lbl]; - ecs_rule_op_t *jump = op; + /* Ensure that no tables have yet been created for the component */ + ecs_check( ecs_id_in_use(world, component) == false, + ECS_ALREADY_IN_USE, ecs_get_name(world, component)); + ecs_check( ecs_id_in_use(world, ecs_pair(component, EcsWildcard)) == false, + ECS_ALREADY_IN_USE, ecs_get_name(world, component)); - if (!reflexive) { - set_lbl = setjmp_lbl; - set = op; - setjmp = NULL; - store = NULL; - jump = NULL; - next_op = set_lbl + 1; - prev_op = set_lbl - 1; - } + ecs_type_info_t *ti = flecs_type_info_ensure(world, component); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - /* The SetJmp operation stores a conditional jump label that either - * points to the Store or *Set operation */ - if (reflexive) { - setjmp->kind = EcsRuleSetJmp; - setjmp->on_pass = store_lbl; - setjmp->on_fail = set_lbl; - } + ecs_check(!ti->component || ti->component == component, + ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - /* The Store operation yields the root of the subtree. After yielding, - * this operation will fail and return to SetJmp, which will cause it - * to switch to the *Set operation. */ - if (reflexive) { - store->kind = EcsRuleStore; - store->on_pass = next_op; - store->on_fail = setjmp_lbl; - store->has_in = true; - store->has_out = true; - store->r_out = out->id; - store->term = c; + if (!ti->size) { + const EcsComponent *component_ptr = ecs_get( + world, component, EcsComponent); - if (!pred) { - store->filter.pred = pair.pred; + /* Cannot register lifecycle actions for things that aren't a component */ + ecs_check(component_ptr != NULL, ECS_INVALID_PARAMETER, + "illegal call to set_hooks() for '%s': component cannot be a tag/zero sized", + flecs_errstr(ecs_get_path(world, component))); + ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, + "illegal call to set_hooks() for '%s': cannot register " + " component cannot be a tag/zero sized", + flecs_errstr(ecs_get_path(world, component))); + + ti->size = component_ptr->size; + ti->alignment = component_ptr->alignment; + } + + if (h->ctor) ti->hooks.ctor = h->ctor; + if (h->dtor) ti->hooks.dtor = h->dtor; + if (h->copy) ti->hooks.copy = h->copy; + if (h->move) ti->hooks.move = h->move; + if (h->copy_ctor) ti->hooks.copy_ctor = h->copy_ctor; + if (h->move_ctor) ti->hooks.move_ctor = h->move_ctor; + if (h->ctor_move_dtor) ti->hooks.ctor_move_dtor = h->ctor_move_dtor; + if (h->move_dtor) ti->hooks.move_dtor = h->move_dtor; + if (h->cmp) ti->hooks.cmp = h->cmp; + if (h->equals) ti->hooks.equals = h->equals; + + if (h->on_add) ti->hooks.on_add = h->on_add; + if (h->on_remove) ti->hooks.on_remove = h->on_remove; + if (h->on_set) ti->hooks.on_set = h->on_set; + if (h->on_replace) ti->hooks.on_replace = h->on_replace; + + if (h->ctx) ti->hooks.ctx = h->ctx; + if (h->binding_ctx) ti->hooks.binding_ctx = h->binding_ctx; + if (h->lifecycle_ctx) ti->hooks.lifecycle_ctx = h->lifecycle_ctx; + if (h->ctx_free) ti->hooks.ctx_free = h->ctx_free; + if (h->binding_ctx_free) ti->hooks.binding_ctx_free = h->binding_ctx_free; + if (h->lifecycle_ctx_free) ti->hooks.lifecycle_ctx_free = h->lifecycle_ctx_free; + + /* If no constructor is set, invoking any of the other lifecycle actions + * is not safe as they will potentially access uninitialized memory. For + * ease of use, if no constructor is specified, set a default one that + * initializes the component to 0. */ + if (!h->ctor && (h->dtor || h->copy || h->move)) { + ti->hooks.ctor = flecs_default_ctor; + } + + /* Set default copy ctor, move ctor and merge */ + if (!h->copy_ctor && !(flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL)) { + if (h->copy) { + ti->hooks.copy_ctor = flecs_default_copy_ctor; + } + } + + if (!h->move_ctor && !(flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL)) { + if (h->move) { + ti->hooks.move_ctor = flecs_default_move_ctor; + } + } + + if (!h->ctor_move_dtor) { + ecs_flags32_t illegal_check = 0; + if (h->move) { + illegal_check |= ECS_TYPE_HOOK_MOVE_ILLEGAL; + if (h->dtor) { + illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; + if (h->move_ctor) { + illegal_check |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; + /* If an explicit move ctor has been set, use callback + * that uses the move ctor vs. using a ctor+move */ + ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; + } else { + illegal_check |= ECS_TYPE_HOOK_CTOR_ILLEGAL; + /* If no explicit move_ctor has been set, use + * combination of ctor + move + dtor */ + ti->hooks.ctor_move_dtor = flecs_default_ctor_w_move_w_dtor; + } + } else { + illegal_check |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; + /* If no dtor has been set, this is just a move ctor */ + ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; + } } else { - store->filter.pred.reg = pred->id; - store->filter.reg_mask |= RULE_PAIR_PREDICATE; + /* If move is not set but move_ctor and dtor is, we can still set + * ctor_move_dtor. */ + if (h->move_ctor) { + illegal_check |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; + if (h->dtor) { + illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; + ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; + } else { + ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; + } + } + } + if(flags & illegal_check) { + flags |= ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL; } + } - /* If the object of the filter is not a variable, store literal */ - if (!obj) { - store->r_in = UINT8_MAX; - store->subject = ecs_get_alive(rule->world, pair.obj.ent); - store->filter.obj = pair.obj; + if (!h->move_dtor) { + ecs_flags32_t illegal_check = 0; + if (h->move) { + illegal_check |= ECS_TYPE_HOOK_MOVE_ILLEGAL; + if (h->dtor) { + illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; + ti->hooks.move_dtor = flecs_default_move_w_dtor; + } else { + ti->hooks.move_dtor = flecs_default_move; + } } else { - store->r_in = obj->id; - store->filter.obj.reg = obj->id; - store->filter.reg_mask |= RULE_PAIR_OBJECT; + if (h->dtor) { + illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; + ti->hooks.move_dtor = flecs_default_dtor; + } + } + if(flags & illegal_check) { + flags |= ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL; } } - /* This is either a SubSet or SuperSet operation */ - set->kind = op_kind; - set->on_pass = next_op; - set->on_fail = prev_op; - set->has_out = true; - set->r_out = out->id; - set->term = c; - - /* Predicate can be a variable if it's non-final */ - if (!pred) { - set->filter.pred = pair.pred; - } else { - set->filter.pred.reg = pred->id; - set->filter.reg_mask |= RULE_PAIR_PREDICATE; + if(!h->cmp) { + flags |= ECS_TYPE_HOOK_CMP_ILLEGAL; } - if (!obj) { - set->filter.obj = pair.obj; - } else { - set->filter.obj.reg = obj->id; - set->filter.reg_mask |= RULE_PAIR_OBJECT; + if (!h->equals || h->equals == flecs_equals_illegal) { + if(flags & ECS_TYPE_HOOK_CMP_ILLEGAL) { + flags |= ECS_TYPE_HOOK_EQUALS_ILLEGAL; + } else { + ti->hooks.equals = flecs_default_equals; + flags &= ~ECS_TYPE_HOOK_EQUALS_ILLEGAL; + } } - if (reflexive) { - /* The jump operation jumps to either the store or subset operation, - * depending on whether the store operation already yielded. The - * operation is inserted last, so that the on_fail label of the next - * operation will point to it */ - jump->kind = EcsRuleJump; - - /* The pass/fail labels of the Jump operation are not used, since it - * jumps to a variable location. Instead, the pass label is (ab)used to - * store the label of the SetJmp operation, so that the jump can access - * the label it needs to jump to from the setjmp op_ctx. */ - jump->on_pass = setjmp_lbl; - jump->on_fail = -1; - } + ti->hooks.flags = flags; - written[out->id] = true; -} + if (ti->hooks.ctor) ti->hooks.flags |= ECS_TYPE_HOOK_CTOR; + if (ti->hooks.dtor) ti->hooks.flags |= ECS_TYPE_HOOK_DTOR; + if (ti->hooks.move) ti->hooks.flags |= ECS_TYPE_HOOK_MOVE; + if (ti->hooks.move_ctor) ti->hooks.flags |= ECS_TYPE_HOOK_MOVE_CTOR; + if (ti->hooks.ctor_move_dtor) ti->hooks.flags |= ECS_TYPE_HOOK_CTOR_MOVE_DTOR; + if (ti->hooks.move_dtor) ti->hooks.flags |= ECS_TYPE_HOOK_MOVE_DTOR; + if (ti->hooks.copy) ti->hooks.flags |= ECS_TYPE_HOOK_COPY; + if (ti->hooks.copy_ctor) ti->hooks.flags |= ECS_TYPE_HOOK_COPY_CTOR; + if (ti->hooks.cmp) ti->hooks.flags |= ECS_TYPE_HOOK_CMP; + if (ti->hooks.equals) ti->hooks.flags |= ECS_TYPE_HOOK_EQUALS; -static -ecs_rule_var_t* store_reflexive_set( - ecs_rule_t *rule, - ecs_rule_op_kind_t op_kind, - ecs_rule_pair_t *pair, - bool *written, - bool reflexive, - bool as_entity) -{ - /* Ensure we're using the most specific version of obj */ - ecs_rule_var_t *obj = pair_obj(rule, pair); - if (obj) { - pair->obj.reg = obj->id; - } + if(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) ti->hooks.ctor = flecs_ctor_illegal; + if(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) ti->hooks.dtor = flecs_dtor_illegal; + if(flags & ECS_TYPE_HOOK_COPY_ILLEGAL) ti->hooks.copy = flecs_copy_illegal; + if(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) ti->hooks.move = flecs_move_illegal; + if(flags & ECS_TYPE_HOOK_CMP_ILLEGAL) ti->hooks.cmp = flecs_comp_illegal; + if(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) ti->hooks.equals = flecs_equals_illegal; - /* The subset operation returns tables */ - ecs_rule_var_kind_t var_kind = EcsRuleVarKindTable; - if (op_kind == EcsSuperSet) { - var_kind = EcsRuleVarKindEntity; + if(flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL) { + ti->hooks.copy_ctor = flecs_copy_ctor_illegal; } - /* Create anonymous variable for storing the set */ - ecs_rule_var_t *av = create_anonymous_variable(rule, var_kind); - int32_t ave_id = 0, av_id = av->id; - - /* If the variable kind is a table, also create an entity variable as the - * result of the set operation should be returned as an entity */ - if (var_kind == EcsRuleVarKindTable && as_entity) { - create_variable(rule, EcsRuleVarKindEntity, av->name); - av = &rule->vars[av_id]; - ave_id = av_id + 1; + if(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL) { + ti->hooks.move_ctor = flecs_move_ctor_illegal; } - /* Generate the operations */ - insert_reflexive_set(rule, op_kind, av, *pair, -1, written, reflexive); + if(ti->hooks.flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL) { + ti->hooks.ctor_move_dtor = flecs_move_ctor_illegal; + } - /* Make sure to return entity variable, and that it is populated */ - if (as_entity) { - return ensure_entity_written(rule, &rule->vars[ave_id], written); - } else { - return &rule->vars[av_id]; + if(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) { + ti->hooks.move_dtor = flecs_move_ctor_illegal; } -} -static -bool is_known( - ecs_rule_var_t *var, - bool *written) -{ - if (!var) { - return true; - } else { - return written[var->id]; + if (component < FLECS_HI_COMPONENT_ID) { + if (ti->hooks.on_set || ti->hooks.copy || ti->hooks.move || ti->hooks.on_replace) { + world->non_trivial_set[component] = true; + } } + +error: + return; } static -bool is_pair_known( - ecs_rule_t *rule, - ecs_rule_pair_t *pair, - bool *written) +void flecs_type_info_fini( + ecs_type_info_t *ti) { - ecs_rule_var_t *pred_var = pair_pred(rule, pair); - if (!is_known(pred_var, written) || pair->pred.ent == EcsWildcard) { - return false; + if (ti->hooks.ctx_free) { + ti->hooks.ctx_free(ti->hooks.ctx); } - - ecs_rule_var_t *obj_var = pair_obj(rule, pair); - if (!is_known(obj_var, written) || pair->obj.ent == EcsWildcard) { - return false; + if (ti->hooks.binding_ctx_free) { + ti->hooks.binding_ctx_free(ti->hooks.binding_ctx); + } + if (ti->hooks.lifecycle_ctx_free) { + ti->hooks.lifecycle_ctx_free(ti->hooks.lifecycle_ctx); + } + if (ti->name) { + /* Safe to cast away const, world has ownership over string */ + ecs_os_free(ECS_CONST_CAST(char*, ti->name)); + ti->name = NULL; } - return true; + ti->size = 0; + ti->alignment = 0; } -static -void set_input_to_subj( - ecs_rule_t *rule, - ecs_rule_op_t *op, - ecs_term_t *term, - ecs_rule_var_t *var) +void flecs_fini_type_info( + ecs_world_t *world) { - (void)rule; - - op->has_in = true; - if (!var) { - op->r_in = UINT8_MAX; - op->subject = term->subj.entity; - - /* Invalid entities should have been caught during parsing */ - ecs_assert(ecs_is_valid(rule->world, op->subject), - ECS_INTERNAL_ERROR, NULL); - } else { - op->r_in = var->id; + ecs_map_iter_t it = ecs_map_iter(&world->type_info); + while (ecs_map_next(&it)) { + ecs_type_info_t *ti = ecs_map_ptr(&it); + flecs_type_info_fini(ti); + ecs_os_free(ti); } + ecs_map_fini(&world->type_info); } -static -void set_output_to_subj( - ecs_rule_t *rule, - ecs_rule_op_t *op, - ecs_term_t *term, - ecs_rule_var_t *var) +const ecs_type_info_t* flecs_type_info_get( + const ecs_world_t *world, + ecs_entity_t component) { - (void)rule; + flecs_poly_assert(world, ecs_world_t); - op->has_out = true; - if (!var) { - op->r_out = UINT8_MAX; - op->subject = term->subj.entity; + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); - /* Invalid entities should have been caught during parsing */ - ecs_assert(ecs_is_valid(rule->world, op->subject), - ECS_INTERNAL_ERROR, NULL); - } else { - op->r_out = var->id; - } + return ecs_map_get_deref(&world->type_info, ecs_type_info_t, component); } -static -void insert_select_or_with( - ecs_rule_t *rule, - int32_t c, - ecs_term_t *term, - ecs_rule_var_t *subj, - ecs_rule_pair_t *pair, - bool *written) +ecs_type_info_t* flecs_type_info_ensure( + ecs_world_t *world, + ecs_entity_t component) { - ecs_rule_op_t *op; - bool eval_subject_supersets = false; - - /* Find any entity and/or table variables for subject */ - ecs_rule_var_t *tvar = NULL, *evar = to_entity(rule, subj), *var = evar; - if (subj && subj->kind == EcsRuleVarKindTable) { - tvar = subj; - if (!evar) { - var = tvar; - } - } + flecs_poly_assert(world, ecs_world_t); + ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - int32_t lbl_start = rule->operation_count; - ecs_rule_pair_t filter; - if (pair) { - filter = *pair; + const ecs_type_info_t *ti = flecs_type_info_get(world, component); + ecs_type_info_t *ti_mut = NULL; + if (!ti) { + ti_mut = ecs_map_ensure_alloc_t( + &world->type_info, ecs_type_info_t, component); + ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); + ti_mut->component = component; } else { - filter = term_to_pair(rule, term); + ti_mut = ECS_CONST_CAST(ecs_type_info_t*, ti); } - /* Only insert implicit IsA if filter isn't already an IsA */ - if (!filter.transitive || filter.pred.ent != EcsIsA) { - if (!var) { - ecs_rule_pair_t isa_pair = { - .pred.ent = EcsIsA, - .obj.ent = term->subj.entity - }; - - evar = subj = store_reflexive_set(rule, EcsRuleSuperSet, &isa_pair, - written, true, true); - tvar = NULL; - eval_subject_supersets = true; - - } else if (ecs_id_is_wildcard(term->id)) { - ecs_assert(subj != NULL, ECS_INTERNAL_ERROR, NULL); - - op = insert_operation(rule, -1, written); - - if (!is_known(subj, written)) { - op->kind = EcsRuleSelect; - set_output_to_subj(rule, op, term, subj); - written[subj->id] = true; - } else { - op->kind = EcsRuleWith; - set_input_to_subj(rule, op, term, subj); + if (!ti_mut->name) { + const char *sym = ecs_get_symbol(world, component); + if (sym) { + ti_mut->name = ecs_os_strdup(sym); + } else { + const char *name = ecs_get_name(world, component); + if (name) { + ti_mut->name = ecs_os_strdup(name); } + } + } - ecs_rule_pair_t isa_pair = { - .pred.ent = EcsIsA, - .obj.reg = subj->id, - .reg_mask = RULE_PAIR_OBJECT - }; - - op->filter = filter; - if (op->filter.reg_mask & RULE_PAIR_PREDICATE) { - op->filter.pred.ent = EcsWildcard; - } - if (op->filter.reg_mask & RULE_PAIR_OBJECT) { - op->filter.obj.ent = EcsWildcard; - } - op->filter.reg_mask = 0; + return ti_mut; +} - push_frame(rule); +bool flecs_type_info_init_id( + ecs_world_t *world, + ecs_entity_t component, + ecs_size_t size, + ecs_size_t alignment, + const ecs_type_hooks_t *li) +{ + bool changed = false; - tvar = subj = store_reflexive_set(rule, EcsRuleSuperSet, &isa_pair, - written, true, false); + flecs_entities_ensure(world, component); - evar = NULL; + ecs_type_info_t *ti = NULL; + if (!size || !alignment) { + ecs_assert(size == 0 && alignment == 0, + ECS_INVALID_COMPONENT_SIZE, NULL); + ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + ecs_map_remove_free(&world->type_info, component); + } else { + ti = flecs_type_info_ensure(world, component); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + changed |= ti->size != size; + changed |= ti->alignment != alignment; + ti->size = size; + ti->alignment = alignment; + if (li) { + ecs_set_hooks_id(world, component, li); } } - /* If no pair is provided, create operation from specified term */ - if (!pair) { - op = insert_operation(rule, c, written); + /* Set type info for component record of component */ + ecs_component_record_t *cr = flecs_components_get(world, component); + if (cr) { + changed |= flecs_component_set_type_info(world, cr, ti); + } - /* If an explicit pair is provided, override the default one from the - * term. This allows for using a predicate or object variable different - * from what is in the term. One application of this is to substitute a - * predicate with its subsets, if it is non final */ - } else { - op = insert_operation(rule, -1, written); - op->filter = *pair; + bool is_tag = ecs_has_id(world, component, EcsPairIsTag); - /* Assign the term id, so that the operation will still be correctly - * associated with the correct expression term. */ - op->term = c; + /* All id records with component as relationship inherit type info */ + cr = flecs_components_get(world, ecs_pair(component, EcsWildcard)); + if (cr) { + while ((cr = flecs_component_first_next(cr))) { + if (is_tag) { + changed |= flecs_component_set_type_info(world, cr, NULL); + } else if (ti) { + changed |= flecs_component_set_type_info(world, cr, ti); + } else if ((cr->type_info != NULL) && + (cr->type_info->component == component)) + { + changed |= flecs_component_set_type_info(world, cr, NULL); + } + } } - /* If entity variable is known and resolved, create with for it */ - if (evar && is_known(evar, written)) { - op->kind = EcsRuleWith; - op->r_in = evar->id; - set_input_to_subj(rule, op, term, subj); + /* All non-tag id records with component as object inherit type info, + * if relationship doesn't have type info */ + cr = flecs_components_get(world, ecs_pair(EcsWildcard, component)); + if (cr) { + while ((cr = flecs_component_second_next(cr))) { + if (!(cr->flags & EcsIdPairIsTag) && !cr->type_info) { + changed |= flecs_component_set_type_info(world, cr, ti); + } + } + } - /* If table variable is known and resolved, create with for it */ - } else if (tvar && is_known(tvar, written)) { - op->kind = EcsRuleWith; - op->r_in = tvar->id; - set_input_to_subj(rule, op, term, subj); + return changed; +} - /* If subject is neither table nor entitiy, with operates on literal */ - } else if (!tvar && !evar) { - op->kind = EcsRuleWith; - set_input_to_subj(rule, op, term, subj); +void flecs_type_info_free( + ecs_world_t *world, + ecs_entity_t component) +{ + flecs_poly_assert(world, ecs_world_t); - /* If subject is table or entity but not known, use select */ - } else { - ecs_assert(subj != NULL, ECS_INTERNAL_ERROR, NULL); - op->kind = EcsRuleSelect; - set_output_to_subj(rule, op, term, subj); - written[subj->id] = true; + if (world->flags & EcsWorldQuit) { + /* If world is in the final teardown stages, cleanup policies are no + * longer applied and it can't be guaranteed that a component is not + * deleted before entities that use it. The remaining type info elements + * will be deleted after the store is finalized. */ + return; } - /* If supersets of subject are being evaluated, and we're looking for a - * specific filter, stop as soon as the filter has been matched. */ - if (eval_subject_supersets && is_pair_known(rule, &op->filter, written)) { - op = insert_operation(rule, -1, written); - - /* When the next operation returns, it will first hit SetJmp with a redo - * which will switch the jump label to the previous operation */ - op->kind = EcsRuleSetJmp; - op->on_pass = rule->operation_count; - op->on_fail = lbl_start - 1; + ecs_type_info_t *ti = ecs_map_get_deref( + &world->type_info, ecs_type_info_t, component); + if (ti) { + flecs_type_info_fini(ti); + ecs_map_remove_free(&world->type_info, component); } +} - if (op->filter.reg_mask & RULE_PAIR_PREDICATE) { - written[op->filter.pred.reg] = true; - } +ecs_size_t flecs_type_size( + ecs_world_t *world, + ecs_entity_t type) +{ + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + return comp->size; +} - if (op->filter.reg_mask & RULE_PAIR_OBJECT) { - written[op->filter.obj.reg] = true; +const ecs_type_hooks_t* ecs_get_hooks_id( + const ecs_world_t *world, + ecs_entity_t id) +{ + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (ti) { + return &ti->hooks; } + return NULL; } -static -void prepare_predicate( - ecs_rule_t *rule, - ecs_rule_pair_t *pair, - int32_t term, - bool *written) +const ecs_type_info_t* flecs_determine_type_info_for_component( + const ecs_world_t *world, + ecs_id_t id) { - /* If pair is not final, resolve term for all IsA relationships of the - * predicate. Note that if the pair has final set to true, it is guaranteed - * that the predicate can be used in an IsA query */ - if (!pair->final) { - ecs_rule_pair_t isa_pair = { - .pred.ent = EcsIsA, - .obj.ent = pair->pred.ent - }; + if (!ECS_IS_PAIR(id)) { + if (!(id & ECS_ID_FLAGS_MASK)) { + return flecs_type_info_get(world, id); + } + } else { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + rel = flecs_entities_get_alive(world, rel); + ecs_assert(ecs_is_alive(world, rel), ECS_INTERNAL_ERROR, NULL); - ecs_rule_var_t *pred = store_reflexive_set(rule, EcsRuleSubSet, - &isa_pair, written, true, true); + if (ecs_owns_id(world, rel, EcsPairIsTag)) { + return NULL; + } - pair->pred.reg = pred->id; - pair->reg_mask |= RULE_PAIR_PREDICATE; + const ecs_type_info_t *ti = flecs_type_info_get(world, rel); + if (ti) { + return ti; + } - if (term != -1) { - rule->term_vars[term].pred = pred->id; + if (!ECS_IS_VALUE_PAIR(id)) { + ecs_entity_t tgt = ecs_pair_second(world, id); + if (tgt) { + return flecs_type_info_get(world, tgt); + } } } + + return NULL; } -static -void insert_term_2( - ecs_rule_t *rule, - ecs_term_t *term, - ecs_rule_pair_t *filter, - int32_t c, - bool *written) +const ecs_type_info_t* ecs_get_type_info( + const ecs_world_t *world, + ecs_id_t id) { - int32_t subj_id = -1, obj_id = -1; - ecs_rule_var_t *subj = term_subj(rule, term); - if ((subj = get_most_specific_var(rule, subj, written))) { - subj_id = subj->id; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_rule_var_t *obj = term_obj(rule, term); - if ((obj = get_most_specific_var(rule, obj, written))) { - obj_id = obj->id; - } + world = ecs_get_world(world); - bool subj_known = is_known(subj, written); - bool same_obj_subj = false; - if (subj && obj) { - same_obj_subj = !ecs_os_strcmp(subj->name, obj->name); + ecs_component_record_t *cr = flecs_components_get(world, id); + if (cr) { + return cr->type_info; + } else { + return flecs_determine_type_info_for_component(world, id); } +error: + return NULL; +} - if (!filter->transitive) { - insert_select_or_with(rule, c, term, subj, filter, written); - if (subj) subj = &rule->vars[subj_id]; - if (obj) obj = &rule->vars[obj_id]; - - } else if (filter->transitive) { - if (subj_known) { - if (is_known(obj, written)) { - if (filter->obj.ent != EcsWildcard) { - ecs_rule_var_t *obj_subsets = store_reflexive_set( - rule, EcsRuleSubSet, filter, written, true, true); - - if (subj) { - subj = &rule->vars[subj_id]; - } - - rule->term_vars[c].obj = obj_subsets->id; +/** + * @file value.c + * @brief Utility functions to work with non-trivial pointers of user types. + */ - ecs_rule_pair_t pair = *filter; - pair.obj.reg = obj_subsets->id; - pair.reg_mask |= RULE_PAIR_OBJECT; - insert_select_or_with(rule, c, term, subj, &pair, written); - } else { - insert_select_or_with(rule, c, term, subj, filter, written); - } - } else { - ecs_assert(obj != NULL, ECS_INTERNAL_ERROR, NULL); +int ecs_value_init_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void *ptr) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - /* If subject is literal, find supersets for subject */ - if (subj == NULL || subj->kind == EcsRuleVarKindEntity) { - obj = to_entity(rule, obj); + ecs_xtor_t ctor; + if ((ctor = ti->hooks.ctor)) { + ctor(ptr, 1, ti); + } else { + ecs_os_memset(ptr, 0, ti->size); + } - ecs_rule_pair_t set_pair = *filter; - set_pair.reg_mask &= RULE_PAIR_PREDICATE; + return 0; +error: + return -1; +} - if (subj) { - set_pair.obj.reg = subj->id; - set_pair.reg_mask |= RULE_PAIR_OBJECT; - } else { - set_pair.obj.ent = term->subj.entity; - } - - insert_reflexive_set(rule, EcsRuleSuperSet, obj, set_pair, - c, written, filter->reflexive); - - /* If subject is variable, first find matching pair for the - * evaluated entity(s) and return supersets */ - } else { - ecs_rule_var_t *av = create_anonymous_variable( - rule, EcsRuleVarKindEntity); - - subj = &rule->vars[subj_id]; - obj = &rule->vars[obj_id]; - obj = to_entity(rule, obj); - - ecs_rule_pair_t set_pair = *filter; - set_pair.obj.reg = av->id; - set_pair.reg_mask |= RULE_PAIR_OBJECT; - - /* Insert with to find initial object for relation */ - insert_select_or_with( - rule, c, term, subj, &set_pair, written); - - push_frame(rule); - - /* Find supersets for returned initial object. Make sure - * this is always reflexive since it needs to return the - * object from the pair that the entity has itself. */ - insert_reflexive_set(rule, EcsRuleSuperSet, obj, set_pair, - c, written, true); - } - } - - /* subj is not known */ - } else { - ecs_assert(subj != NULL, ECS_INTERNAL_ERROR, NULL); - - if (is_known(obj, written)) { - ecs_rule_pair_t set_pair = *filter; - set_pair.reg_mask &= RULE_PAIR_PREDICATE; /* clear object mask */ - - if (obj) { - set_pair.obj.reg = obj->id; - set_pair.reg_mask |= RULE_PAIR_OBJECT; - } else { - set_pair.obj.ent = term->obj.entity; - } - - if (obj) { - rule->term_vars[c].obj = obj->id; - } else { - ecs_rule_var_t *av = create_anonymous_variable(rule, - EcsRuleVarKindEntity); - rule->term_vars[c].obj = av->id; - written[av->id] = true; - } - - insert_reflexive_set(rule, EcsRuleSubSet, subj, set_pair, c, - written, filter->reflexive); - } else if (subj == obj) { - insert_select_or_with(rule, c, term, subj, filter, written); - } else { - ecs_assert(obj != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_rule_var_t *av = NULL; - if (!filter->reflexive) { - av = create_anonymous_variable(rule, EcsRuleVarKindEntity); - } - - subj = &rule->vars[subj_id]; - obj = &rule->vars[obj_id]; - obj = to_entity(rule, obj); - - /* Insert instruction to find all subjects and objects */ - ecs_rule_op_t *op = insert_operation(rule, -1, written); - op->kind = EcsRuleSelect; - set_output_to_subj(rule, op, term, subj); - op->filter.pred = filter->pred; - - if (filter->reflexive) { - op->filter.obj.ent = EcsWildcard; - op->filter.reg_mask = filter->reg_mask & RULE_PAIR_PREDICATE; - } else { - op->filter.obj.reg = av->id; - op->filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT; - written[av->id] = true; - } - - written[subj->id] = true; - - /* Create new frame for operations that create reflexive set */ - push_frame(rule); - - /* Insert superset instruction to find all supersets */ - if (filter->reflexive) { - subj = ensure_most_specific_var(rule, subj, written); - ecs_assert(subj->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(written[subj->id] == true, - ECS_INTERNAL_ERROR, NULL); - - ecs_rule_pair_t super_filter = {0}; - super_filter.pred = filter->pred; - super_filter.obj.reg = subj->id; - super_filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT; - - insert_reflexive_set(rule, EcsRuleSuperSet, obj, - super_filter, c, written, true); - } else { - insert_reflexive_set(rule, EcsRuleSuperSet, obj, - op->filter, c, written, true); - } - } - } - } - - if (same_obj_subj) { - /* Can't have relation with same variables that is acyclic and not - * reflexive, this should've been caught earlier. */ - ecs_assert(!filter->acyclic || filter->reflexive, - ECS_INTERNAL_ERROR, NULL); - - /* If relation is reflexive and entity has an instance of R, no checks - * are needed because R(X, X) is always true. */ - if (!filter->reflexive) { - push_frame(rule); - - /* Insert check if the (R, X) pair that was found matches with one - * of the entities in the table with the pair. */ - ecs_rule_op_t *op = insert_operation(rule, -1, written); - obj = get_most_specific_var(rule, obj, written); - ecs_assert(obj->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(written[subj->id] == true, ECS_INTERNAL_ERROR, NULL); - ecs_assert(written[obj->id] == true, ECS_INTERNAL_ERROR, NULL); - - set_input_to_subj(rule, op, term, subj); - op->filter.obj.reg = obj->id; - op->filter.reg_mask = RULE_PAIR_OBJECT; - - if (subj->kind == EcsRuleVarKindTable) { - op->kind = EcsRuleInTable; - } else { - op->kind = EcsRuleEq; - } - } - } -} - -static -void insert_term_1( - ecs_rule_t *rule, - ecs_term_t *term, - ecs_rule_pair_t *filter, - int32_t c, - bool *written) +int ecs_value_init( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr) { - ecs_rule_var_t *subj = term_subj(rule, term); - subj = get_most_specific_var(rule, subj, written); - insert_select_or_with(rule, c, term, subj, filter, written); + flecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity '%s' is not a type", + flecs_errstr(ecs_get_path(world, type))); + return ecs_value_init_w_type_info(world, ti, ptr); +error: + return -1; } -static -void insert_term( - ecs_rule_t *rule, - ecs_term_t *term, - int32_t c, - bool *written) +void* ecs_value_new_w_type_info( + ecs_world_t *world, + const ecs_type_info_t *ti) { - bool obj_set = obj_is_set(term); - - ensure_most_specific_var(rule, term_pred(rule, term), written); - if (obj_set) { - ensure_most_specific_var(rule, term_obj(rule, term), written); - } - - /* If term has Not operator, prepend Not which turns a fail into a pass */ - int32_t prev = rule->operation_count; - ecs_rule_op_t *not_pre; - if (term->oper == EcsNot) { - not_pre = insert_operation(rule, -1, written); - not_pre->kind = EcsRuleNot; - not_pre->has_in = false; - not_pre->has_out = false; - } - - ecs_rule_pair_t filter = term_to_pair(rule, term); - prepare_predicate(rule, &filter, c, written); - - if (subj_is_set(term) && !obj_set) { - insert_term_1(rule, term, &filter, c, written); - } else if (obj_set) { - insert_term_2(rule, term, &filter, c, written); - } - - /* If term has Not operator, append Not which turns a pass into a fail */ - if (term->oper == EcsNot) { - ecs_rule_op_t *not_post = insert_operation(rule, -1, written); - not_post->kind = EcsRuleNot; - not_post->has_in = false; - not_post->has_out = false; - - not_post->on_pass = prev - 1; - not_post->on_fail = prev - 1; - not_pre = &rule->operations[prev]; - not_pre->on_fail = rule->operation_count; - } - - if (term->oper == EcsOptional) { - /* Insert Not instruction that ensures that the optional term is only - * executed once */ - ecs_rule_op_t *jump = insert_operation(rule, -1, written); - jump->kind = EcsRuleNot; - jump->has_in = false; - jump->has_out = false; - jump->on_pass = rule->operation_count; - jump->on_fail = prev - 1; - - /* Find exit instruction for optional term, and make the fail label - * point to the Not operation, so that even when the operation fails, - * it won't discard the result */ - int i, min_fail = -1, exit_op = -1; - for (i = prev; i < rule->operation_count; i ++) { - ecs_rule_op_t *op = &rule->operations[i]; - if (min_fail == -1 || (op->on_fail >= 0 && op->on_fail < min_fail)){ - min_fail = op->on_fail; - exit_op = i; - } - } + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - ecs_assert(exit_op != -1, ECS_INTERNAL_ERROR, NULL); - ecs_rule_op_t *op = &rule->operations[exit_op]; - op->on_fail = rule->operation_count - 1; + void *result = flecs_alloc_w_dbg_info( + &world->allocator, ti->size, ti->name); + if (ecs_value_init_w_type_info(world, ti, result) != 0) { + flecs_free(&world->allocator, ti->size, result); + goto error; } - push_frame(rule); + return result; +error: + return NULL; } -/* Create program from operations that will execute the query */ -static -void compile_program( - ecs_rule_t *rule) +void* ecs_value_new( + ecs_world_t *world, + ecs_entity_t type) { - /* Trace which variables have been written while inserting instructions. - * This determines which instruction needs to be inserted */ - bool written[ECS_RULE_MAX_VAR_COUNT] = { false }; - - ecs_term_t *terms = rule->filter.terms; - int32_t v, c, term_count = rule->filter.term_count; - ecs_rule_op_t *op; - - /* Insert input, which is always the first instruction */ - insert_input(rule); - - /* First insert all instructions that do not have a variable subject. Such - * instructions iterate the type of an entity literal and are usually good - * candidates for quickly narrowing down the set of potential results. */ - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; - if (skip_term(term)) { - continue; - } + flecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); - if (term->oper == EcsOptional || term->oper == EcsNot) { - continue; - } + return ecs_value_new_w_type_info(world, ti); +error: + return NULL; +} - ecs_rule_var_t* subj = term_subj(rule, term); - if (subj) { - continue; - } +int ecs_value_fini_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void *ptr) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - insert_term(rule, term, c, written); + ecs_xtor_t dtor; + if ((dtor = ti->hooks.dtor)) { + dtor(ptr, 1, ti); } - /* Insert variables based on dependency order */ - for (v = 0; v < rule->subj_var_count; v ++) { - int32_t var_id = rule->var_eval_order[v]; - ecs_rule_var_t *var = &rule->vars[var_id]; - - ecs_assert(var->kind == EcsRuleVarKindTable, ECS_INTERNAL_ERROR, NULL); - - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; - if (skip_term(term)) { - continue; - } - - if (term->oper == EcsOptional || term->oper == EcsNot) { - continue; - } - - /* Only process columns for which variable is subject */ - ecs_rule_var_t* subj = term_subj(rule, term); - if (subj != var) { - continue; - } - - insert_term(rule, term, c, written); - - var = &rule->vars[var_id]; - } - } + return 0; +error: + return -1; +} - /* Insert terms with Not operators */ - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; - if (term->oper != EcsNot) { - continue; - } +int ecs_value_fini( + const ecs_world_t *world, + ecs_entity_t type, + void* ptr) +{ + flecs_poly_assert(world, ecs_world_t); + (void)world; + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + return ecs_value_fini_w_type_info(world, ti, ptr); +error: + return -1; +} - insert_term(rule, term, c, written); +int ecs_value_free( + ecs_world_t *world, + ecs_entity_t type, + void* ptr) +{ + flecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + if (ecs_value_fini_w_type_info(world, ti, ptr) != 0) { + goto error; } - /* Insert terms with Optional operators last, as optional terms cannot - * eliminate results, and would just add overhead to evaluation of - * non-matching entities. */ - for (c = 0; c < term_count; c ++) { - ecs_term_t *term = &terms[c]; - if (term->oper != EcsOptional) { - continue; - } - - insert_term(rule, term, c, written); - } - - /* Verify all subject variables have been written. Subject variables are of - * the table type, and a select/subset should have been inserted for each */ - for (v = 0; v < rule->subj_var_count; v ++) { - if (!written[v]) { - /* If the table variable hasn't been written, this can only happen - * if an instruction wrote the variable before a select/subset could - * have been inserted for it. Make sure that this is the case by - * testing if an entity variable exists and whether it has been - * written. */ - ecs_rule_var_t *var = find_variable( - rule, EcsRuleVarKindEntity, rule->vars[v].name); - ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(written[var->id], ECS_INTERNAL_ERROR, var->name); - (void)var; - } - } - - /* Make sure that all entity variables are written. With the exception of - * the this variable, which can be returned as a table, other variables need - * to be available as entities. This ensures that all permutations for all - * variables are correctly returned by the iterator. When an entity variable - * hasn't been written yet at this point, it is because it only constrained - * through a common predicate or object. */ - for (; v < rule->var_count; v ++) { - if (!written[v]) { - ecs_rule_var_t *var = &rule->vars[v]; - ecs_assert(var->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); - - ecs_rule_var_t *table_var = find_variable( - rule, EcsRuleVarKindTable, var->name); - - /* A table variable must exist if the variable hasn't been resolved - * yet. If there doesn't exist one, this could indicate an - * unconstrained variable which should have been caught earlier */ - ecs_assert(table_var != NULL, ECS_INTERNAL_ERROR, var->name); - - /* Insert each operation that takes the table variable as input, and - * yields each entity in the table */ - op = insert_operation(rule, -1, written); - op->kind = EcsRuleEach; - op->r_in = table_var->id; - op->r_out = var->id; - op->frame = rule->frame_count; - op->has_in = true; - op->has_out = true; - written[var->id] = true; - - push_frame(rule); - } - } + flecs_free(&world->allocator, ti->size, ptr); - /* Insert yield, which is always the last operation */ - insert_yield(rule); + return 0; +error: + return -1; } -static -void create_variable_name_array( - ecs_rule_t *rule) +int ecs_value_copy_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + const void *src) { - if (rule->var_count) { - int i; - for (i = 0; i < rule->var_count; i ++) { - ecs_rule_var_t *var = &rule->vars[i]; + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - if (var->kind != EcsRuleVarKindEntity) { - /* Table variables are hidden for applications. */ - rule->var_names[var->id] = NULL; - } else { - rule->var_names[var->id] = var->name; - } - } + ecs_copy_t copy; + if ((copy = ti->hooks.copy)) { + copy(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); } + + return 0; +error: + return -1; } -static -void create_variable_cross_references( - ecs_rule_t *rule) +int ecs_value_copy( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + const void *src) { - if (rule->var_count) { - int i; - for (i = 0; i < rule->var_count; i ++) { - ecs_rule_var_t *var = &rule->vars[i]; - if (var->kind == EcsRuleVarKindEntity) { - ecs_rule_var_t *tvar = find_variable( - rule, EcsRuleVarKindTable, var->name); - if (tvar) { - var->other = tvar->id; - } else { - var->other = -1; - } - } else { - ecs_rule_var_t *evar = find_variable( - rule, EcsRuleVarKindEntity, var->name); - if (evar) { - var->other = evar->id; - } else { - var->other = -1; - } - } - } - } + flecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + return ecs_value_copy_w_type_info(world, ti, dst, src); +error: + return -1; } -/* Implementation for iterable mixin */ -static -void rule_iter_init( +int ecs_value_move_w_type_info( const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) + const ecs_type_info_t *ti, + void* dst, + void *src) { - ecs_poly_assert(poly, ecs_rule_t); + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - if (filter) { - iter[1] = ecs_rule_iter(world, (ecs_rule_t*)poly); - iter[0] = ecs_term_chain_iter(&iter[1], filter); + ecs_move_t move; + if ((move = ti->hooks.move)) { + move(dst, src, 1, ti); } else { - iter[0] = ecs_rule_iter(world, (ecs_rule_t*)poly); + ecs_os_memcpy(dst, src, ti->size); } + + return 0; +error: + return -1; } -static -int32_t find_term_var_id( - ecs_rule_t *rule, - ecs_term_id_t *term_id) +int ecs_value_move( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + void *src) { - if (term_id_is_variable(term_id)) { - const char *var_name = term_id_var_name(term_id); - ecs_rule_var_t *var = find_variable( - rule, EcsRuleVarKindEntity, var_name); - if (var) { - return var->id; - } else { - /* If this is Any look for table variable. Since Any is only - * required to return a single result, there is no need to - * insert an each instruction for a matching table. */ - if (term_id->entity == EcsAny) { - var = find_variable( - rule, EcsRuleVarKindTable, var_name); - if (var) { - return var->id; - } - } - } - } - + flecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + return ecs_value_move_w_type_info(world, ti, dst, src); +error: return -1; } -ecs_rule_t* ecs_rule_init( - ecs_world_t *world, - const ecs_filter_desc_t *desc) +int ecs_value_move_ctor_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + void *src) { - ecs_rule_t *result = ecs_poly_new(ecs_rule_t); + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - /* Parse the signature expression. This initializes the columns array which - * contains the information about which components/pairs are requested. */ - if (ecs_filter_init(world, &result->filter, desc)) { - goto error; + ecs_move_t move; + if ((move = ti->hooks.move_ctor)) { + move(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); } - result->world = world; - - /* Rule has no terms */ - if (!result->filter.term_count) { - rule_error(result, "rule has no terms"); - goto error; - } + return 0; +error: + return -1; +} - ecs_term_t *terms = result->filter.terms; - int32_t i, term_count = result->filter.term_count; +int ecs_value_move_ctor( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + void *src) +{ + flecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + return ecs_value_move_w_type_info(world, ti, dst, src); +error: + return -1; +} - /* Make sure rule doesn't just have Not terms */ - for (i = 0; i < term_count; i++) { - ecs_term_t *term = &terms[i]; - if (term->oper != EcsNot) { - break; - } - } - if (i == term_count) { - rule_error(result, "rule cannot only have terms with Not operator"); - goto error; - } +/** + * @file world.c + * @brief World-level API. + */ - /* Find all variables & resolve dependencies */ - if (scan_variables(result) != 0) { - goto error; - } +#include - /* Create lookup array for subject variables */ - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_rule_term_vars_t *vars = &result->term_vars[i]; - vars->pred = find_term_var_id(result, &term->pred); - vars->subj = find_term_var_id(result, &term->subj); - vars->obj = find_term_var_id(result, &term->obj); - } +/* Id flags */ +const ecs_id_t ECS_PAIR = (1ull << 63); +const ecs_id_t ECS_AUTO_OVERRIDE = (1ull << 62); +const ecs_id_t ECS_TOGGLE = (1ull << 61); +const ecs_id_t ECS_VALUE_PAIR = ((1ull << 60) | (1ull << 63)); - /* Generate the opcode array */ - compile_program(result); +/** Builtin component ids */ +const ecs_entity_t ecs_id(EcsComponent) = 1; +const ecs_entity_t ecs_id(EcsIdentifier) = 2; +const ecs_entity_t ecs_id(EcsPoly) = 3; +const ecs_entity_t ecs_id(EcsParent) = 4; +const ecs_entity_t ecs_id(EcsTreeSpawner) = 5; +const ecs_entity_t EcsParentDepth = 6; + +/* Poly target components */ +const ecs_entity_t EcsQuery = FLECS_HI_COMPONENT_ID + 0; +const ecs_entity_t EcsObserver = FLECS_HI_COMPONENT_ID + 1; +const ecs_entity_t EcsSystem = FLECS_HI_COMPONENT_ID + 2; - /* Create array with variable names so this can be easily accessed by - * iterators without requiring access to the ecs_rule_t */ - create_variable_name_array(result); +/* Core scopes & entities */ +const ecs_entity_t EcsWorld = FLECS_HI_COMPONENT_ID + 3; +const ecs_entity_t EcsFlecs = FLECS_HI_COMPONENT_ID + 4; +const ecs_entity_t EcsFlecsCore = FLECS_HI_COMPONENT_ID + 5; +const ecs_entity_t EcsModule = FLECS_HI_COMPONENT_ID + 7; +const ecs_entity_t EcsPrefab = FLECS_HI_COMPONENT_ID + 9; +const ecs_entity_t EcsDisabled = FLECS_HI_COMPONENT_ID + 10; +const ecs_entity_t EcsNotQueryable = FLECS_HI_COMPONENT_ID + 11; + +const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 12; +const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 13; + +/* Marker entities for query encoding */ +const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 14; +const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 15; +const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 16; +const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 17; + +/* Traits */ +const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 18; +const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 19; +const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 20; +const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 21; +const ecs_entity_t EcsInheritable = FLECS_HI_COMPONENT_ID + 22; +const ecs_entity_t EcsSingleton = FLECS_HI_COMPONENT_ID + 23; + +const ecs_entity_t EcsOnInstantiate = FLECS_HI_COMPONENT_ID + 24; +const ecs_entity_t EcsOverride = FLECS_HI_COMPONENT_ID + 25; +const ecs_entity_t EcsInherit = FLECS_HI_COMPONENT_ID + 26; +const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 27; +const ecs_entity_t EcsPairIsTag = FLECS_HI_COMPONENT_ID + 28; +const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 29; +const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 30; +const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 31; +const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 32; +const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 33; +const ecs_entity_t EcsCanToggle = FLECS_HI_COMPONENT_ID + 34; +const ecs_entity_t EcsTrait = FLECS_HI_COMPONENT_ID + 35; +const ecs_entity_t EcsRelationship = FLECS_HI_COMPONENT_ID + 36; +const ecs_entity_t EcsTarget = FLECS_HI_COMPONENT_ID + 37; + +/* Builtin relationships */ +const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 38; +const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 39; +const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 40; - /* Create cross-references between variables so it's easy to go from entity - * to table variable and vice versa */ - create_variable_cross_references(result); +/* Identifier tags */ +const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 41; +const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 42; +const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 43; - result->iterable.init = rule_iter_init; +/* Events */ +const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 44; +const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 45; +const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 46; +const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 47; +const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 48; +const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 49; +const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 50; + +/* Timers */ +const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 51; +const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 52; +const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 53; - return result; -error: - ecs_rule_fini(result); - return NULL; -} +/* Actions */ +const ecs_entity_t EcsRemove = FLECS_HI_COMPONENT_ID + 54; +const ecs_entity_t EcsDelete = FLECS_HI_COMPONENT_ID + 55; +const ecs_entity_t EcsPanic = FLECS_HI_COMPONENT_ID + 56; -void ecs_rule_fini( - ecs_rule_t *rule) -{ - int32_t i; - for (i = 0; i < rule->var_count; i ++) { - ecs_os_free(rule->vars[i].name); - } +/* Storage */ +const ecs_entity_t EcsSparse = FLECS_HI_COMPONENT_ID + 57; +const ecs_entity_t EcsDontFragment = FLECS_HI_COMPONENT_ID + 58; - ecs_filter_fini(&rule->filter); +/* Misc */ +const ecs_entity_t ecs_id(EcsDefaultChildComponent) = FLECS_HI_COMPONENT_ID + 59; +const ecs_entity_t EcsOrderedChildren = FLECS_HI_COMPONENT_ID + 60; - ecs_os_free(rule->operations); - ecs_os_free(rule); -} +/* Builtin predicate ids (used by query engine) */ +const ecs_entity_t EcsPredEq = FLECS_HI_COMPONENT_ID + 61; +const ecs_entity_t EcsPredMatch = FLECS_HI_COMPONENT_ID + 62; +const ecs_entity_t EcsPredLookup = FLECS_HI_COMPONENT_ID + 63; +const ecs_entity_t EcsScopeOpen = FLECS_HI_COMPONENT_ID + 64; +const ecs_entity_t EcsScopeClose = FLECS_HI_COMPONENT_ID + 65; -const ecs_filter_t* ecs_rule_get_filter( - const ecs_rule_t *rule) -{ - return &rule->filter; -} +/* Systems */ +const ecs_entity_t EcsMonitor = FLECS_HI_COMPONENT_ID + 66; +const ecs_entity_t EcsEmpty = FLECS_HI_COMPONENT_ID + 67; +const ecs_entity_t ecs_id(EcsPipeline) = FLECS_HI_COMPONENT_ID + 68; +const ecs_entity_t EcsOnStart = FLECS_HI_COMPONENT_ID + 69; +const ecs_entity_t EcsPreFrame = FLECS_HI_COMPONENT_ID + 70; +const ecs_entity_t EcsOnLoad = FLECS_HI_COMPONENT_ID + 71; +const ecs_entity_t EcsPostLoad = FLECS_HI_COMPONENT_ID + 72; +const ecs_entity_t EcsPreUpdate = FLECS_HI_COMPONENT_ID + 73; +const ecs_entity_t EcsOnUpdate = FLECS_HI_COMPONENT_ID + 74; +const ecs_entity_t EcsOnValidate = FLECS_HI_COMPONENT_ID + 75; +const ecs_entity_t EcsPostUpdate = FLECS_HI_COMPONENT_ID + 76; +const ecs_entity_t EcsPreStore = FLECS_HI_COMPONENT_ID + 77; +const ecs_entity_t EcsOnStore = FLECS_HI_COMPONENT_ID + 78; +const ecs_entity_t EcsPostFrame = FLECS_HI_COMPONENT_ID + 79; +const ecs_entity_t EcsPhase = FLECS_HI_COMPONENT_ID + 80; -/* Quick convenience function to get a variable from an id */ -static -ecs_rule_var_t* get_variable( - const ecs_rule_t *rule, - int32_t var_id) -{ - if (var_id == UINT8_MAX) { - return NULL; - } +/* Meta primitive components (don't use low ids to save id space) */ +#ifdef FLECS_META +const ecs_entity_t ecs_id(ecs_bool_t) = FLECS_HI_COMPONENT_ID + 81; +const ecs_entity_t ecs_id(ecs_char_t) = FLECS_HI_COMPONENT_ID + 82; +const ecs_entity_t ecs_id(ecs_byte_t) = FLECS_HI_COMPONENT_ID + 83; +const ecs_entity_t ecs_id(ecs_u8_t) = FLECS_HI_COMPONENT_ID + 84; +const ecs_entity_t ecs_id(ecs_u16_t) = FLECS_HI_COMPONENT_ID + 85; +const ecs_entity_t ecs_id(ecs_u32_t) = FLECS_HI_COMPONENT_ID + 86; +const ecs_entity_t ecs_id(ecs_u64_t) = FLECS_HI_COMPONENT_ID + 87; +const ecs_entity_t ecs_id(ecs_uptr_t) = FLECS_HI_COMPONENT_ID + 88; +const ecs_entity_t ecs_id(ecs_i8_t) = FLECS_HI_COMPONENT_ID + 89; +const ecs_entity_t ecs_id(ecs_i16_t) = FLECS_HI_COMPONENT_ID + 90; +const ecs_entity_t ecs_id(ecs_i32_t) = FLECS_HI_COMPONENT_ID + 91; +const ecs_entity_t ecs_id(ecs_i64_t) = FLECS_HI_COMPONENT_ID + 92; +const ecs_entity_t ecs_id(ecs_iptr_t) = FLECS_HI_COMPONENT_ID + 93; +const ecs_entity_t ecs_id(ecs_f32_t) = FLECS_HI_COMPONENT_ID + 94; +const ecs_entity_t ecs_id(ecs_f64_t) = FLECS_HI_COMPONENT_ID + 95; +const ecs_entity_t ecs_id(ecs_string_t) = FLECS_HI_COMPONENT_ID + 96; +const ecs_entity_t ecs_id(ecs_entity_t) = FLECS_HI_COMPONENT_ID + 97; +const ecs_entity_t ecs_id(ecs_id_t) = FLECS_HI_COMPONENT_ID + 98; - return (ecs_rule_var_t*)&rule->vars[var_id]; -} +/** Meta module component ids */ +const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 99; +const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 100; +const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 101; +const ecs_entity_t ecs_id(EcsConstants) = FLECS_HI_COMPONENT_ID + 102; +const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 103; +const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 104; +const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 105; +const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 106; +const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 107; +const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 108; +const ecs_entity_t ecs_id(EcsTypeSerializer) = FLECS_HI_COMPONENT_ID + 109; +const ecs_entity_t ecs_id(EcsType) = FLECS_HI_COMPONENT_ID + 110; + +const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 111; +const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 112; +const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 113; +#endif -/* Convert the program to a string. This can be useful to analyze how a rule is - * being evaluated. */ -char* ecs_rule_str( - ecs_rule_t *rule) -{ - ecs_check(rule != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_world_t *world = rule->world; - ecs_strbuf_t buf = ECS_STRBUF_INIT; - char filter_expr[256]; +const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 114; - int32_t i, count = rule->operation_count; - for (i = 1; i < count; i ++) { - ecs_rule_op_t *op = &rule->operations[i]; - ecs_rule_pair_t pair = op->filter; - ecs_entity_t pred = pair.pred.ent; - ecs_entity_t obj = pair.obj.ent; - const char *pred_name = NULL, *obj_name = NULL; - char *pred_name_alloc = NULL, *obj_name_alloc = NULL; - - if (pair.reg_mask & RULE_PAIR_PREDICATE) { - ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_rule_var_t *type_var = &rule->vars[pair.pred.reg]; - pred_name = type_var->name; - } else if (pred) { - pred_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, pred)); - pred_name = pred_name_alloc; - } - - if (pair.reg_mask & RULE_PAIR_OBJECT) { - ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_rule_var_t *obj_var = &rule->vars[pair.obj.reg]; - obj_name = obj_var->name; - } else if (obj) { - obj_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, obj)); - obj_name = obj_name_alloc; - } else if (pair.obj_0) { - obj_name = "0"; - } - - ecs_strbuf_append(&buf, "%2d: [S:%2d, P:%2d, F:%2d, T:%2d] ", i, - op->frame, op->on_pass, op->on_fail, op->term); - - bool has_filter = false; +/* Doc module components */ +#ifdef FLECS_DOC +const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 115; +const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 116; +const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 117; +const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 118; +const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 119; +const ecs_entity_t EcsDocUuid = FLECS_HI_COMPONENT_ID + 120; +#endif - switch(op->kind) { - case EcsRuleSelect: - ecs_strbuf_append(&buf, "select "); - has_filter = true; - break; - case EcsRuleWith: - ecs_strbuf_append(&buf, "with "); - has_filter = true; - break; - case EcsRuleStore: - ecs_strbuf_append(&buf, "store "); - break; - case EcsRuleSuperSet: - ecs_strbuf_append(&buf, "superset "); - has_filter = true; - break; - case EcsRuleSubSet: - ecs_strbuf_append(&buf, "subset "); - has_filter = true; - break; - case EcsRuleEach: - ecs_strbuf_append(&buf, "each "); - break; - case EcsRuleSetJmp: - ecs_strbuf_append(&buf, "setjmp "); - break; - case EcsRuleJump: - ecs_strbuf_append(&buf, "jump "); - break; - case EcsRuleNot: - ecs_strbuf_append(&buf, "not "); - break; - case EcsRuleInTable: - ecs_strbuf_append(&buf, "intable "); - has_filter = true; - break; - case EcsRuleEq: - ecs_strbuf_append(&buf, "eq "); - has_filter = true; - break; - case EcsRuleYield: - ecs_strbuf_append(&buf, "yield "); - break; - default: - continue; - } +/* REST module components */ +#ifdef FLECS_REST +const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 121; +#endif - if (op->has_out) { - ecs_rule_var_t *r_out = get_variable(rule, op->r_out); - if (r_out) { - ecs_strbuf_append(&buf, "O:%s%s ", - r_out->kind == EcsRuleVarKindTable ? "t" : "", - r_out->name); - } else if (op->subject) { - char *subj_path = ecs_get_fullpath(world, op->subject); - ecs_strbuf_append(&buf, "O:%s ", subj_path); - ecs_os_free(subj_path); - } - } +/* Max static id: + * #define EcsFirstUserEntityId (FLECS_HI_COMPONENT_ID + 128) */ - if (op->has_in) { - ecs_rule_var_t *r_in = get_variable(rule, op->r_in); - if (r_in) { - ecs_strbuf_append(&buf, "I:%s%s ", - r_in->kind == EcsRuleVarKindTable ? "t" : "", - r_in->name); - } - if (op->subject) { - char *subj_path = ecs_get_fullpath(world, op->subject); - ecs_strbuf_append(&buf, "I:%s ", subj_path); - ecs_os_free(subj_path); - } - } +/* Default lookup path */ +static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; - if (has_filter) { - if (!pred_name) { - pred_name = "-"; - } - if (!obj_name && !pair.obj_0) { - ecs_os_sprintf(filter_expr, "(%s)", pred_name); - } else { - ecs_os_sprintf(filter_expr, "(%s, %s)", pred_name, obj_name); - } - ecs_strbuf_append(&buf, "F:%s", filter_expr); - } +/* Declarations for addons. Located in world.c to avoid issues during linking of + * static library */ + +#ifdef FLECS_ALERTS +ECS_COMPONENT_DECLARE(EcsAlert); +ECS_COMPONENT_DECLARE(EcsAlertInstance); +ECS_COMPONENT_DECLARE(EcsAlertsActive); +ECS_TAG_DECLARE(EcsAlertInfo); +ECS_TAG_DECLARE(EcsAlertWarning); +ECS_TAG_DECLARE(EcsAlertError); +ECS_TAG_DECLARE(EcsAlertCritical); +#endif +#ifdef FLECS_UNITS +ecs_entity_t EcsUnitPrefixes; + +ecs_entity_t EcsYocto; +ecs_entity_t EcsZepto; +ecs_entity_t EcsAtto; +ecs_entity_t EcsFemto; +ecs_entity_t EcsPico; +ecs_entity_t EcsNano; +ecs_entity_t EcsMicro; +ecs_entity_t EcsMilli; +ecs_entity_t EcsCenti; +ecs_entity_t EcsDeci; +ecs_entity_t EcsDeca; +ecs_entity_t EcsHecto; +ecs_entity_t EcsKilo; +ecs_entity_t EcsMega; +ecs_entity_t EcsGiga; +ecs_entity_t EcsTera; +ecs_entity_t EcsPeta; +ecs_entity_t EcsExa; +ecs_entity_t EcsZetta; +ecs_entity_t EcsYotta; + +ecs_entity_t EcsKibi; +ecs_entity_t EcsMebi; +ecs_entity_t EcsGibi; +ecs_entity_t EcsTebi; +ecs_entity_t EcsPebi; +ecs_entity_t EcsExbi; +ecs_entity_t EcsZebi; +ecs_entity_t EcsYobi; + +ecs_entity_t EcsDuration; + ecs_entity_t EcsPicoSeconds; + ecs_entity_t EcsNanoSeconds; + ecs_entity_t EcsMicroSeconds; + ecs_entity_t EcsMilliSeconds; + ecs_entity_t EcsSeconds; + ecs_entity_t EcsMinutes; + ecs_entity_t EcsHours; + ecs_entity_t EcsDays; + +ecs_entity_t EcsTime; + ecs_entity_t EcsDate; + +ecs_entity_t EcsMass; + ecs_entity_t EcsGrams; + ecs_entity_t EcsKiloGrams; + +ecs_entity_t EcsElectricCurrent; + ecs_entity_t EcsAmpere; + +ecs_entity_t EcsAmount; + ecs_entity_t EcsMole; + +ecs_entity_t EcsLuminousIntensity; + ecs_entity_t EcsCandela; + +ecs_entity_t EcsForce; + ecs_entity_t EcsNewton; + +ecs_entity_t EcsLength; + ecs_entity_t EcsMeters; + ecs_entity_t EcsPicoMeters; + ecs_entity_t EcsNanoMeters; + ecs_entity_t EcsMicroMeters; + ecs_entity_t EcsMilliMeters; + ecs_entity_t EcsCentiMeters; + ecs_entity_t EcsKiloMeters; + ecs_entity_t EcsMiles; + ecs_entity_t EcsPixels; + +ecs_entity_t EcsPressure; + ecs_entity_t EcsPascal; + ecs_entity_t EcsBar; + +ecs_entity_t EcsSpeed; + ecs_entity_t EcsMetersPerSecond; + ecs_entity_t EcsKiloMetersPerSecond; + ecs_entity_t EcsKiloMetersPerHour; + ecs_entity_t EcsMilesPerHour; + +ecs_entity_t EcsAcceleration; + +ecs_entity_t EcsTemperature; + ecs_entity_t EcsKelvin; + ecs_entity_t EcsCelsius; + ecs_entity_t EcsFahrenheit; + +ecs_entity_t EcsData; + ecs_entity_t EcsBits; + ecs_entity_t EcsKiloBits; + ecs_entity_t EcsMegaBits; + ecs_entity_t EcsGigaBits; + ecs_entity_t EcsBytes; + ecs_entity_t EcsKiloBytes; + ecs_entity_t EcsMegaBytes; + ecs_entity_t EcsGigaBytes; + ecs_entity_t EcsKibiBytes; + ecs_entity_t EcsGibiBytes; + ecs_entity_t EcsMebiBytes; + +ecs_entity_t EcsDataRate; + ecs_entity_t EcsBitsPerSecond; + ecs_entity_t EcsKiloBitsPerSecond; + ecs_entity_t EcsMegaBitsPerSecond; + ecs_entity_t EcsGigaBitsPerSecond; + ecs_entity_t EcsBytesPerSecond; + ecs_entity_t EcsKiloBytesPerSecond; + ecs_entity_t EcsMegaBytesPerSecond; + ecs_entity_t EcsGigaBytesPerSecond; + +ecs_entity_t EcsPercentage; + +ecs_entity_t EcsAngle; + ecs_entity_t EcsRadians; + ecs_entity_t EcsDegrees; + +ecs_entity_t EcsColor; + ecs_entity_t EcsColorRgb; + ecs_entity_t EcsColorHsl; + ecs_entity_t EcsColorCss; + +ecs_entity_t EcsBel; +ecs_entity_t EcsDeciBel; + +ecs_entity_t EcsFrequency; + ecs_entity_t EcsHertz; + ecs_entity_t EcsKiloHertz; + ecs_entity_t EcsMegaHertz; + ecs_entity_t EcsGigaHertz; + +ecs_entity_t EcsUri; + ecs_entity_t EcsUriHyperlink; + ecs_entity_t EcsUriImage; + ecs_entity_t EcsUriFile; +#endif - ecs_strbuf_appendstr(&buf, "\n"); +ecs_stage_t* flecs_stage_from_readonly_world( + const ecs_world_t *world) +{ + ecs_assert(flecs_poly_is(world, ecs_world_t) || + flecs_poly_is(world, ecs_stage_t), + ECS_INTERNAL_ERROR, + NULL); - ecs_os_free(pred_name_alloc); - ecs_os_free(obj_name_alloc); + if (flecs_poly_is(world, ecs_world_t)) { + return ECS_CONST_CAST(ecs_stage_t*, world->stages[0]); + } else if (flecs_poly_is(world, ecs_stage_t)) { + return ECS_CONST_CAST(ecs_stage_t*, world); } - return ecs_strbuf_get(&buf); -error: return NULL; } -/* Public function that returns number of variables. This enables an application - * to iterate the variables and obtain their values. */ -int32_t ecs_rule_var_count( - const ecs_rule_t *rule) +ecs_stage_t* flecs_stage_from_world( + ecs_world_t **world_ptr) { - ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); - return rule->var_count; -} + ecs_world_t *world = *world_ptr; -/* Public function to find a variable by name */ -int32_t ecs_rule_find_var( - const ecs_rule_t *rule, - const char *name) -{ - ecs_rule_var_t *v = find_variable(rule, EcsRuleVarKindEntity, name); - if (v) { - return v->id; - } else { - return -1; - } -} + ecs_assert(flecs_poly_is(world, ecs_world_t) || + flecs_poly_is(world, ecs_stage_t), + ECS_INTERNAL_ERROR, + NULL); -/* Public function to get the name of a variable. */ -const char* ecs_rule_var_name( - const ecs_rule_t *rule, - int32_t var_id) -{ - return rule->vars[var_id].name; -} + if (flecs_poly_is(world, ecs_world_t)) { + return world->stages[0]; + } -/* Public function to get the type of a variable. */ -bool ecs_rule_var_is_entity( - const ecs_rule_t *rule, - int32_t var_id) -{ - return rule->vars[var_id].kind == EcsRuleVarKindEntity; + *world_ptr = ((ecs_stage_t*)world)->world; + return ECS_CONST_CAST(ecs_stage_t*, world); } +/* Evaluate component monitor. If a monitored entity changed it will have set a + * flag in one of the world's component monitors. Queries can register + * themselves with component monitors to determine whether they need to rematch + * with tables. */ static -void ecs_rule_iter_free( - ecs_iter_t *iter) -{ - ecs_rule_iter_t *it = &iter->priv.iter.rule; - ecs_os_free(it->registers); - ecs_os_free(it->columns); - ecs_os_free(it->op_ctx); - iter->columns = NULL; - it->registers = NULL; - it->columns = NULL; - it->op_ctx = NULL; -} - -/* Create rule iterator */ -ecs_iter_t ecs_rule_iter( - const ecs_world_t *world, - const ecs_rule_t *rule) +void flecs_eval_component_monitor( + ecs_world_t *world) { - ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(rule != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_iter_t result = {0}; - int i; - - result.world = (ecs_world_t*)world; - result.real_world = (ecs_world_t*)ecs_get_world(rule->world); + flecs_poly_assert(world, ecs_world_t); - flecs_process_pending_tables(result.real_world); + if (!world->monitors.is_dirty) { + return; + } - ecs_rule_iter_t *it = &result.priv.iter.rule; - it->rule = rule; + world->info.eval_comp_monitors_total ++; - if (rule->operation_count) { - if (rule->var_count) { - it->registers = ecs_os_malloc_n(ecs_var_t, - rule->operation_count * rule->var_count); - } - - it->op_ctx = ecs_os_calloc_n(ecs_rule_op_ctx_t, rule->operation_count); + ecs_os_perf_trace_push("flecs.component_monitor.eval"); - if (rule->filter.term_count) { - it->columns = ecs_os_malloc_n(int32_t, - rule->operation_count * rule->filter.term_count); - } + world->monitors.is_dirty = false; - for (i = 0; i < rule->filter.term_count; i ++) { - it->columns[i] = -1; + ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); + while (ecs_map_next(&it)) { + ecs_monitor_t *m = ecs_map_ptr(&it); + if (!m->is_dirty) { + continue; } - } - it->op = 0; + m->is_dirty = false; - for (i = 0; i < rule->var_count; i ++) { - if (rule->vars[i].kind == EcsRuleVarKindEntity) { - entity_reg_set(rule, it->registers, i, EcsWildcard); - } else { - table_reg_set(rule, it->registers, i, NULL); + int32_t i, count = ecs_vec_count(&m->queries); + ecs_query_t **elems = ecs_vec_first(&m->queries); + for (i = 0; i < count; i ++) { + ecs_query_t *q = elems[i]; + flecs_poly_assert(q, ecs_query_t); + flecs_query_rematch(world, q); } } - result.variable_names = (char**)rule->var_names; - result.variable_count = rule->var_count; - result.term_count = rule->filter.term_count; - result.terms = rule->filter.terms; - result.next = ecs_rule_next; - result.fini = ecs_rule_iter_free; - result.flags |= EcsIterIsFilter * (rule->filter.filter == true); - - flecs_iter_init(&result, - flecs_iter_cache_ids | - /* flecs_iter_cache_columns | provided by rule iterator */ - flecs_iter_cache_subjects | - flecs_iter_cache_sizes | - flecs_iter_cache_ptrs | - /* flecs_iter_cache_match_indices | not necessary for iteration */ - flecs_iter_cache_variables); - - result.columns = it->columns; /* prevent alloc */ - - return result; + ecs_os_perf_trace_pop("flecs.component_monitor.eval"); } -/* Edge case: if the filter has the same variable for both predicate and - * object, they are both resolved at the same time but at the time of - * evaluating the filter they're still wildcards which would match columns - * that have different predicates/objects. Do an additional scan to make - * sure the column we're returning actually matches. */ static -int32_t find_next_same_var( - ecs_type_t type, - int32_t column, - ecs_id_t pattern) +void flecs_monitor_mark_dirty( + ecs_world_t *world, + ecs_entity_t id) { - /* If same_var is true, this has to be a wildcard pair. We cannot have - * the same variable in a pair, and one part of a pair resolved with - * another part unresolved. */ - ecs_assert(pattern == ecs_pair(EcsWildcard, EcsWildcard), - ECS_INTERNAL_ERROR, NULL); - (void)pattern; - - /* Keep scanning for an id where rel and obj are the same */ - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); - for (i = column + 1; i < count; i ++) { - ecs_id_t id = ids[i]; - if (!ECS_HAS_ROLE(id, PAIR)) { - /* If id is not a pair, this will definitely not match, and we - * will find no further matches. */ - return -1; - } + ecs_map_t *monitors = &world->monitors.monitors; - if (ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id)) { - /* Found a match! */ - return i; + /* Only flag if there are actually monitors registered, so that we + * don't waste cycles evaluating monitors if there's no interest */ + if (ecs_map_is_init(monitors)) { + ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); + if (m) { + if (!world->monitors.is_dirty) { + world->monitor_generation ++; + } + m->is_dirty = true; + world->monitors.is_dirty = true; } } +} - /* No pairs found with same rel/obj */ - return -1; +void flecs_monitor_register( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_poly_assert(query, ecs_query_t); + + ecs_map_t *monitors = &world->monitors.monitors; + ecs_map_init_if(monitors, &world->allocator); + ecs_monitor_t *m = ecs_map_ensure_alloc_t(monitors, ecs_monitor_t, id); + ecs_vec_init_if_t(&m->queries, ecs_query_t*); + ecs_query_t **q = ecs_vec_append_t( + &world->allocator, &m->queries, ecs_query_t*); + *q = query; } -static -int32_t find_next_column( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t column, - ecs_rule_filter_t *filter) +void flecs_monitor_unregister( + ecs_world_t *world, + ecs_entity_t id, + ecs_query_t *query) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_poly_assert(query, ecs_query_t); - ecs_entity_t pattern = filter->mask; - ecs_type_t type = table->type; + ecs_map_t *monitors = &world->monitors.monitors; + if (!ecs_map_is_init(monitors)) { + return; + } - if (column == -1) { - ecs_table_record_t *tr = flecs_get_table_record(world, table, pattern); - if (!tr) { - return -1; - } - column = tr->column; - } else { - column = ecs_search_offset(world, table, column + 1, filter->mask, 0); - if (column == -1) { - return -1; + ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); + if (!m) { + return; + } + + int32_t i, count = ecs_vec_count(&m->queries); + ecs_query_t **queries = ecs_vec_first(&m->queries); + for (i = 0; i < count; i ++) { + if (queries[i] == query) { + ecs_vec_remove_t(&m->queries, ecs_query_t*, i); + count --; + break; } } - if (filter->same_var) { - column = find_next_same_var(type, column - 1, filter->mask); + if (!count) { + ecs_vec_fini_t(&world->allocator, &m->queries, ecs_query_t*); + ecs_map_remove_free(monitors, id); } - return column; + if (!ecs_map_count(monitors)) { + ecs_map_fini(monitors); + } } -/* This function finds the next table in a table set, and is used by the select - * operation. The function automatically skips empty tables, so that subsequent - * operations don't waste a lot of processing for nothing. */ +/* Updating component monitors is a relatively expensive operation that only + * happens for entities that are monitored. The approach balances the amount of + * processing between the operation on the entity vs the amount of work that + * needs to be done to rematch queries, as a simple brute force approach does + * not scale when there are many tables / queries. Therefore we need to do a bit + * of bookkeeping that is more intelligent than simply flipping a flag */ static -ecs_table_record_t find_next_table( - ecs_rule_filter_t *filter, - ecs_rule_with_ctx_t *op_ctx) +void flecs_update_component_monitor_w_array( + ecs_world_t *world, + ecs_type_t *ids) { - ecs_table_cache_iter_t *it = &op_ctx->it; - ecs_table_t *table = NULL; - int32_t column = -1; - - const ecs_table_record_t *tr; - while ((column == -1) && (tr = flecs_table_cache_next(it, ecs_table_record_t))) { - table = tr->hdr.table; + if (!ids) { + return; + } - /* Should only iterate non-empty tables */ - ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); + int i; + for (i = 0; i < ids->count; i ++) { + ecs_entity_t id = ids->array[i]; - column = tr->column; - if (filter->same_var) { - column = find_next_same_var(table->type, column - 1, filter->mask); + if (ECS_HAS_ID_FLAG(id, PAIR)) { + flecs_monitor_mark_dirty(world, + ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); } - } - if (column == -1) { - table = NULL; + flecs_monitor_mark_dirty(world, id); } - - return (ecs_table_record_t){.hdr.table = table, .column = column}; } - -static -ecs_id_record_t* find_tables( +void flecs_update_component_monitors( ecs_world_t *world, - ecs_id_t id) + ecs_type_t *added, + ecs_type_t *removed) { - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr || !ecs_table_cache_count(&idr->cache)) { - /* Skip ids that don't have (non-empty) tables */ - return NULL; - } - return idr; + flecs_update_component_monitor_w_array(world, added); + flecs_update_component_monitor_w_array(world, removed); } static -ecs_id_t rule_get_column( - ecs_type_t type, - int32_t column) +void flecs_init_store( + ecs_world_t *world) { - ecs_id_t *comp = ecs_vector_get(type, ecs_id_t, column); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - return *comp; + ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); + + ecs_allocator_t *a = &world->allocator; + ecs_vec_init_t(a, &world->store.records, ecs_table_record_t, 0); + ecs_vec_init_t(a, &world->store.marked_ids, ecs_marked_id_t, 0); + ecs_vec_init_t(a, &world->store.deleted_components, ecs_entity_t, 0); + + /* Initialize entity index */ + flecs_entities_init(world); + + /* Initialize table sparse set */ + flecs_sparse_init_t(&world->store.tables, + a, &world->allocators.sparse_chunk, ecs_table_t); + + /* Initialize table map */ + flecs_table_hashmap_init(world, &world->store.table_map); } static -void set_source( - ecs_iter_t *it, - ecs_rule_op_t *op, - ecs_var_t *regs, - int32_t r) +void flecs_clean_tables( + ecs_world_t *world) { - if (op->term == -1) { - /* If operation is not associated with a term, don't set anything */ - return; + int32_t i, count = flecs_sparse_count(&world->store.tables); + for (i = 1; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, + ecs_table_t, i); + flecs_table_fini(world, t); } - ecs_assert(op->term >= 0, ECS_INTERNAL_ERROR, NULL); + /* Free table types separately so that if application destructors rely on + * a type it's still valid. */ + for (i = 1; i < count; i ++) { + ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, + ecs_table_t, i); + flecs_table_free_type(world, t); + } - const ecs_rule_t *rule = it->priv.iter.rule.rule; - if ((r != UINT8_MAX) && rule->vars[r].kind == EcsRuleVarKindEntity) { - it->subjects[op->term] = reg_get_entity(rule, op, regs, r); - } else { - it->subjects[op->term] = 0; + /* Clear the root table */ + if (count) { + flecs_table_reset(world, &world->store.root); } } static -void set_term_vars( - const ecs_rule_t *rule, - ecs_var_t *regs, - int32_t term, - ecs_id_t id) +void flecs_fini_root_tables( + ecs_world_t *world, + ecs_component_record_t *cr, + bool fini_targets) { - if (term != -1) { - ecs_world_t *world = rule->world; - const ecs_rule_term_vars_t *vars = &rule->term_vars[term]; - if (vars->pred != -1) { - regs[vars->pred].entity = ecs_pair_first(world, id); - ecs_assert(ecs_is_valid(world, regs[vars->pred].entity), - ECS_INTERNAL_ERROR, NULL); - } - if (vars->obj != -1) { - regs[vars->obj].entity = ecs_pair_second(world, id); - ecs_assert(ecs_is_valid(world, regs[vars->obj].entity), - ECS_INTERNAL_ERROR, NULL); + ecs_stage_t *stage0 = world->stages[0]; + bool finished = false; + const ecs_size_t MAX_DEFERRED_DELETE_QUEUE_SIZE = 4096; + while (!finished) { + ecs_table_cache_iter_t it; + ecs_size_t queue_size = 0; + finished = true; + + bool has_roots = flecs_table_cache_iter(&cr->cache, &it); + ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); + (void)has_roots; + + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + + if (table->flags & (EcsTableHasBuiltins|EcsTableHasModule)) { + continue; /* Query out modules */ + } + + int32_t i, count = ecs_table_count(table); + const ecs_entity_t *entities = ecs_table_entities(table); + + if (fini_targets) { + /* Only delete entities that are used as pair target. Iterate + * backwards to minimize moving entities around in table. */ + for (i = count - 1; i >= 0; i --) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); + if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { + ecs_delete(world, entities[i]); + queue_size++; + /* Flush the queue before it grows too big: */ + if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) { + finished = false; + break; /* restart iteration */ + } + } + } + } else { + /* Delete remaining entities that are not in use (added to another + * entity). This limits table moves during cleanup and delays + * cleanup of tags. */ + for (i = count - 1; i >= 0; i --) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); + if (!(ECS_RECORD_TO_ROW_FLAGS(r->row) & ~EcsEntityHasDontFragment)) { + ecs_delete(world, entities[i]); + queue_size++; + /* Flush the queue before it grows too big: */ + if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) { + finished = false; + break; /* restart iteration */ + } + } + } + } + if(!finished) { + /* flush queue and restart iteration */ + flecs_defer_end(world, stage0); + flecs_defer_begin(world, stage0); + break; + } } } } -/* Input operation. The input operation acts as a placeholder for the start of - * the program, and creates an entry in the register array that can serve to - * store variables passed to an iterator. */ static -bool eval_input( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void flecs_fini_roots( + ecs_world_t *world) { - (void)it; - (void)op; - (void)op_index; + ecs_component_record_t *cr = flecs_components_get(world, ecs_pair(EcsChildOf, 0)); - if (!redo) { - /* First operation executed by the iterator. Always return true. */ - return true; - } else { - /* When Input is asked to redo, it means that all other operations have - * exhausted their results. Input itself does not yield anything, so - * return false. This will terminate rule execution. */ - return false; - } + /* Delete root entities that are not modules. This prioritizes deleting + * regular entities first, which reduces the chance of components getting + * destructed in random order because it got deleted before entities, + * thereby bypassing the OnDeleteTarget policy. */ + flecs_defer_begin(world, world->stages[0]); + flecs_fini_root_tables(world, cr, true); + flecs_defer_end(world, world->stages[0]); + + flecs_defer_begin(world, world->stages[0]); + flecs_fini_root_tables(world, cr, false); + flecs_defer_end(world, world->stages[0]); } static -bool eval_superset( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) -{ - ecs_rule_iter_t *iter = &it->priv.iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_rule_superset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.superset; - ecs_rule_superset_frame_t *frame = NULL; - ecs_var_t *regs = get_registers(iter, op); +void flecs_fini_store(ecs_world_t *world) { + flecs_clean_tables(world); + flecs_sparse_fini(&world->store.tables); + flecs_table_fini(world, &world->store.root); + flecs_entities_clear(world); + flecs_hashmap_fini(&world->store.table_map); - /* Get register indices for output */ - int32_t sp; - int32_t r = op->r_out; + ecs_assert(ecs_vec_count(&world->store.marked_ids) == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vec_count(&world->store.deleted_components) == 0, + ECS_INTERNAL_ERROR, NULL); - /* Register cannot be a literal, since we need to store things in it */ - ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); - - /* Get queried for id, fill out potential variables */ - ecs_rule_pair_t pair = op->filter; - - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - ecs_entity_t rel = ECS_PAIR_FIRST(filter.mask); - ecs_rule_filter_t super_filter = { - .mask = ecs_pair(rel, EcsWildcard) - }; - ecs_table_t *table = NULL; - - /* Check if input register is constrained */ - ecs_entity_t result = iter->registers[r].entity; - bool output_is_input = ecs_iter_var_is_constrained(it, r); - if (output_is_input && !redo) { - ecs_assert(regs[r].entity == iter->registers[r].entity, - ECS_INTERNAL_ERROR, NULL); - } - - if (!redo) { - op_ctx->stack = op_ctx->storage; - sp = op_ctx->sp = 0; - frame = &op_ctx->stack[sp]; - - /* Get table of object for which to get supersets */ - ecs_entity_t obj = ECS_PAIR_SECOND(filter.mask); - if (obj == EcsWildcard) { - ecs_assert(pair.reg_mask & RULE_PAIR_OBJECT, - ECS_INTERNAL_ERROR, NULL); - table = regs[pair.obj.reg].range.table; - } else { - table = table_from_entity(world, obj).table; - } - - int32_t column; - - /* If output variable is already set, check if it matches */ - if (output_is_input) { - ecs_id_t id = ecs_pair(rel, result); - ecs_entity_t subj = 0; - column = ecs_search_relation(world, table, 0, id, rel, - 0, 0, &subj, 0, 0, 0); - if (column != -1) { - if (subj != 0) { - table = ecs_get_table(world, subj); - } - } - } else { - column = find_next_column(world, table, -1, &super_filter); - } - - /* If no matching column was found, there are no supersets */ - if (column == -1) { - return false; - } - - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_id_t col_id = rule_get_column(table->type, column); - ecs_assert(ECS_HAS_ROLE(col_id, PAIR), ECS_INTERNAL_ERROR, NULL); - ecs_entity_t col_obj = ecs_pair_second(world, col_id); - - reg_set_entity(rule, regs, r, col_obj); - - frame->table = table; - frame->column = column; - - return true; - } else if (output_is_input) { - return false; - } - - sp = op_ctx->sp; - frame = &op_ctx->stack[sp]; - table = frame->table; - int32_t column = frame->column; - - ecs_id_t col_id = rule_get_column(table->type, column); - ecs_entity_t col_obj = ecs_pair_second(world, col_id); - ecs_table_t *next_table = table_from_entity(world, col_obj).table; - - if (next_table) { - sp ++; - frame = &op_ctx->stack[sp]; - frame->table = next_table; - frame->column = -1; - } - - do { - frame = &op_ctx->stack[sp]; - table = frame->table; - column = frame->column; + ecs_allocator_t *a = &world->allocator; + ecs_vec_fini_t(a, &world->store.records, ecs_table_record_t); + ecs_vec_fini_t(a, &world->store.marked_ids, ecs_marked_id_t); + ecs_vec_fini_t(a, &world->store.deleted_components, ecs_entity_t); +} - column = find_next_column(world, table, column, &super_filter); - if (column != -1) { - op_ctx->sp = sp; - frame->column = column; - col_id = rule_get_column(table->type, column); - col_obj = ecs_pair_second(world, col_id); - reg_set_entity(rule, regs, r, col_obj); - return true; - } +static +void flecs_world_allocators_init( + ecs_world_t *world) +{ + ecs_world_allocators_t *a = &world->allocators; - sp --; - } while (sp >= 0); + flecs_allocator_init(&world->allocator); - return false; + flecs_ballocator_init_n(&a->graph_edge_lo, ecs_graph_edge_t, FLECS_HI_COMPONENT_ID); + flecs_ballocator_init_t(&a->graph_edge, ecs_graph_edge_t); + flecs_ballocator_init_t(&a->component_record, ecs_component_record_t); + flecs_ballocator_init_t(&a->pair_record, ecs_pair_record_t); + flecs_ballocator_init_t(&a->table_diff, ecs_table_diff_t); + flecs_ballocator_init_n(&a->sparse_chunk, int32_t, FLECS_SPARSE_PAGE_SIZE); + flecs_table_diff_builder_init(world, &world->allocators.diff_builder); + ecs_vec_init_t(&world->allocator, + &world->allocators.tree_spawner, ecs_entity_t, 0); } static -bool eval_subset( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void flecs_world_allocators_fini( + ecs_world_t *world) { - ecs_rule_iter_t *iter = &it->priv.iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_rule_subset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.subset; - ecs_rule_subset_frame_t *frame = NULL; - ecs_table_record_t table_record; - ecs_var_t *regs = get_registers(iter, op); - - /* Get register indices for output */ - int32_t sp, row; - int32_t r = op->r_out; - ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); - - /* Get queried for id, fill out potential variables */ - ecs_rule_pair_t pair = op->filter; - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - ecs_id_record_t *idr; - ecs_table_t *table = NULL; + ecs_world_allocators_t *a = &world->allocators; - if (!redo) { - op_ctx->stack = op_ctx->storage; - sp = op_ctx->sp = 0; - frame = &op_ctx->stack[sp]; - idr = frame->with_ctx.idr = find_tables(world, filter.mask); - if (!idr) { - return false; - } - - flecs_table_cache_iter(&idr->cache, &frame->with_ctx.it); - table_record = find_next_table(&filter, &frame->with_ctx); - - /* If first table set has no non-empty table, yield nothing */ - if (!table_record.hdr.table) { - return false; - } - - frame->row = 0; - frame->column = table_record.column; - table_reg_set(rule, regs, r, (frame->table = table_record.hdr.table)); - goto yield; - } + flecs_ballocator_fini(&a->graph_edge_lo); + flecs_ballocator_fini(&a->graph_edge); + flecs_ballocator_fini(&a->component_record); + flecs_ballocator_fini(&a->pair_record); + flecs_ballocator_fini(&a->table_diff); + flecs_ballocator_fini(&a->sparse_chunk); + flecs_table_diff_builder_fini(world, &world->allocators.diff_builder); + ecs_vec_fini_t( + &world->allocator, &world->allocators.tree_spawner, ecs_entity_t); - do { - sp = op_ctx->sp; - frame = &op_ctx->stack[sp]; - table = frame->table; - row = frame->row; - - /* If row exceeds number of elements in table, find next table in frame that - * still has entities */ - while ((sp >= 0) && (row >= ecs_table_count(table))) { - table_record = find_next_table(&filter, &frame->with_ctx); - - if (table_record.hdr.table) { - table = frame->table = table_record.hdr.table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - frame->row = 0; - frame->column = table_record.column; - table_reg_set(rule, regs, r, table); - goto yield; - } else { - sp = -- op_ctx->sp; - if (sp < 0) { - /* If none of the frames yielded anything, no more data */ - return false; - } - frame = &op_ctx->stack[sp]; - table = frame->table; - idr = frame->with_ctx.idr; - row = ++ frame->row; + flecs_allocator_fini(&world->allocator); +} - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - } - } +#define ECS_STRINGIFY_INNER(x) #x +#define ECS_STRINGIFY(x) ECS_STRINGIFY_INNER(x) - int32_t row_count = ecs_table_count(table); +static const char flecs_compiler_info[] +#if defined(__clang__) + = "clang " __clang_version__; +#elif defined(__GNUC__) + = "gcc " ECS_STRINGIFY(__GNUC__) "." ECS_STRINGIFY(__GNUC_MINOR__); +#elif defined(_MSC_VER) + = "msvc " ECS_STRINGIFY(_MSC_VER); +#elif defined(__TINYC__) + = "tcc " ECS_STRINGIFY(__TINYC__); +#else + = "unknown compiler"; +#endif - /* Table must have at least row elements */ - ecs_assert(row_count > row, ECS_INTERNAL_ERROR, NULL); +static const char *flecs_addons_info[] = { +#ifdef FLECS_CPP + "FLECS_CPP", +#endif +#ifdef FLECS_MODULE + "FLECS_MODULE", +#endif +#ifdef FLECS_STATS + "FLECS_STATS", +#endif +#ifdef FLECS_METRICS + "FLECS_METRICS", +#endif +#ifdef FLECS_ALERTS + "FLECS_ALERTS", +#endif +#ifdef FLECS_SYSTEM + "FLECS_SYSTEM", +#endif +#ifdef FLECS_PIPELINE + "FLECS_PIPELINE", +#endif +#ifdef FLECS_TIMER + "FLECS_TIMER", +#endif +#ifdef FLECS_META + "FLECS_META", +#endif +#ifdef FLECS_UNITS + "FLECS_UNITS", +#endif +#ifdef FLECS_JSON + "FLECS_JSON", +#endif +#ifdef FLECS_DOC + "FLECS_DOC", +#endif +#ifdef FLECS_LOG + "FLECS_LOG", +#endif +#ifdef FLECS_JOURNAL + "FLECS_JOURNAL", +#endif +#ifdef FLECS_APP + "FLECS_APP", +#endif +#ifdef FLECS_OS_API_IMPL + "FLECS_OS_API_IMPL", +#endif +#ifdef FLECS_PARSER + "FLECS_PARSER", +#endif +#ifdef FLECS_QUERY_DSL + "FLECS_QUERY_DSL", +#endif +#ifdef FLECS_SCRIPT + "FLECS_SCRIPT", +#endif +#ifdef FLECS_HTTP + "FLECS_HTTP", +#endif +#ifdef FLECS_REST + "FLECS_REST", +#endif +NULL +}; - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); +static const char *flecs_compiler_flags[] = { +#ifdef FLECS_DEBUG + "FLECS_DEBUG", +#endif +#ifdef FLECS_NDEBUG + "FLECS_NDEBUG", +#endif +#ifdef FLECS_SANITIZE + "FLECS_SANITIZE", +#endif +#ifdef FLECS_CONFIG_HEADER + "FLECS_CONFIG_HEADER", +#endif +#ifdef FLECS_ACCURATE_COUNTERS + "FLECS_ACCURATE_COUNTERS", +#endif +#ifdef FLECS_DISABLE_COUNTERS + "FLECS_DISABLE_COUNTERS", +#endif +#ifdef FLECS_DEBUG_INFO + "FLECS_DEBUG_INFO", +#endif +#ifdef FLECS_DEFAULT_TO_UNCACHED_QUERIES + "FLECS_DEFAULT_TO_UNCACHED_QUERIES", +#endif +#ifdef FLECS_SOFT_ASSERT + "FLECS_SOFT_ASSERT", +#endif +#ifdef FLECS_KEEP_ASSERT + "FLECS_KEEP_ASSERT", +#endif +#ifdef FLECS_CPP_NO_AUTO_REGISTRATION + "FLECS_CPP_NO_AUTO_REGISTRATION", +#endif +#ifdef FLECS_CPP_NO_ENUM_REFLECTION + "FLECS_CPP_NO_ENUM_REFLECTION", +#endif +#ifdef FLECS_NO_ALWAYS_INLINE + "FLECS_NO_ALWAYS_INLINE", +#endif +#ifdef FLECS_CUSTOM_BUILD + "FLECS_CUSTOM_BUILD", +#endif +#ifdef FLECS_LOW_FOOTPRINT + "FLECS_LOW_FOOTPRINT", +#endif +#ifdef FLECS_PERF_TRACE + "FLECS_PERF_TRACE", +#endif +#ifdef FLECS_USE_OS_ALLOC + "FLECS_USE_OS_ALLOC", +#endif +#ifdef FLECS_HI_COMPONENT_ID + "FLECS_HI_COMPONENT_ID=" ECS_STRINGIFY(FLECS_HI_COMPONENT_ID), +#endif +#ifdef FLECS_HI_ID_RECORD_ID + "FLECS_HI_ID_RECORD_ID=" ECS_STRINGIFY(FLECS_HI_ID_RECORD_ID), +#endif +#ifdef FLECS_ENTITY_PAGE_BITS + "FLECS_ENTITY_PAGE_BITS=" ECS_STRINGIFY(FLECS_ENTITY_PAGE_BITS), +#endif +#ifdef FLECS_SPARSE_PAGE_BITS + "FLECS_SPARSE_PAGE_BITS=" ECS_STRINGIFY(FLECS_SPARSE_PAGE_BITS), +#endif +#ifdef FLECS_ID_DESC_MAX + "FLECS_ID_DESC_MAX=" ECS_STRINGIFY(FLECS_ID_DESC_MAX), +#endif +#ifdef FLECS_EVENT_DESC_MAX + "FLECS_EVENT_DESC_MAX=" ECS_STRINGIFY(FLECS_EVENT_DESC_MAX), +#endif +#ifdef FLECS_VARIABLE_COUNT_MAX + "FLECS_VARIABLE_COUNT_MAX=" ECS_STRINGIFY(FLECS_VARIABLE_COUNT_MAX), +#endif +#ifdef FLECS_TERM_COUNT_MAX + "FLECS_TERM_COUNT_MAX=" ECS_STRINGIFY(FLECS_TERM_COUNT_MAX), +#endif +#ifdef FLECS_TERM_ARG_COUNT_MAX + "FLECS_TERM_ARG_COUNT_MAX=" ECS_STRINGIFY(FLECS_TERM_ARG_COUNT_MAX), +#endif +#ifdef FLECS_QUERY_VARIABLE_COUNT_MAX + "FLECS_QUERY_VARIABLE_COUNT_MAX=" ECS_STRINGIFY(FLECS_QUERY_VARIABLE_COUNT_MAX), +#endif +#ifdef FLECS_QUERY_SCOPE_NESTING_MAX + "FLECS_QUERY_SCOPE_NESTING_MAX=" ECS_STRINGIFY(FLECS_QUERY_SCOPE_NESTING_MAX), +#endif +#ifdef FLECS_DAG_DEPTH_MAX + "FLECS_DAG_DEPTH_MAX=" ECS_STRINGIFY(FLECS_DAG_DEPTH_MAX), +#endif +NULL +}; - /* The entity used to find the next table set */ - do { - ecs_entity_t e = entities[row]; +static const ecs_build_info_t flecs_build_info = { + .compiler = flecs_compiler_info, + .addons = flecs_addons_info, + .flags = flecs_compiler_flags, +#ifdef FLECS_DEBUG + .debug = true, +#endif +#ifdef FLECS_SANITIZE + .sanitize = true, +#endif +#ifdef FLECS_PERF_TRACE + .perf_trace = true, +#endif + .version = FLECS_VERSION, + .version_major = FLECS_VERSION_MAJOR, + .version_minor = FLECS_VERSION_MINOR, + .version_patch = FLECS_VERSION_PATCH +}; - /* Create look_for expression with the resolved entity as object */ - pair.reg_mask &= ~RULE_PAIR_OBJECT; /* turn of bit because it's not a reg */ - pair.obj.ent = e; - filter = pair_to_filter(iter, op, pair); +static +void flecs_log_build_info(void) { + const ecs_build_info_t *bi = ecs_get_build_info(); + ecs_assert(bi != NULL, ECS_INTERNAL_ERROR, NULL); - /* Find table set for expression */ - table = NULL; - idr = find_tables(world, filter.mask); + ecs_trace("flecs version %s", bi->version); - /* If table set is found, find first non-empty table */ - if (idr) { - ecs_rule_subset_frame_t *new_frame = &op_ctx->stack[sp + 1]; - new_frame->with_ctx.idr = idr; - flecs_table_cache_iter(&idr->cache, &new_frame->with_ctx.it); - table_record = find_next_table(&filter, &new_frame->with_ctx); + ecs_trace("addons included in build:"); + ecs_log_push(); - /* If set contains non-empty table, push it to stack */ - if (table_record.hdr.table) { - table = table_record.hdr.table; - op_ctx->sp ++; - new_frame->table = table; - new_frame->row = 0; - new_frame->column = table_record.column; - frame = new_frame; - } - } + const char **addon = bi->addons; + do { + ecs_trace(addon[0]); + } while ((++ addon)[0]); + ecs_log_pop(); - /* If no table was found for the current entity, advance row */ - if (!table) { - row = ++ frame->row; - } - } while (!table && row < row_count); - } while (!table); + if (bi->sanitize) { + ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " + "improved performance"); + } else if (bi->debug) { + ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for " + "improved performance"); + } else { + ecs_trace("#[green]release#[reset] build"); + } - table_reg_set(rule, regs, r, table); + ecs_trace("compiled with %s", bi->compiler); +} -yield: - set_term_vars(rule, regs, op->term, ecs_vector_get(frame->table->type, - ecs_id_t, frame->column)[0]); +/* -- Public functions -- */ - return true; +const ecs_build_info_t* ecs_get_build_info(void) { + return &flecs_build_info; } -/* Select operation. The select operation finds and iterates a table set that - * corresponds to its pair expression. */ -static -bool eval_select( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +const ecs_world_info_t* ecs_get_world_info( + const ecs_world_t *world) { - ecs_rule_iter_t *iter = &it->priv.iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with; - ecs_table_record_t table_record; - ecs_var_t *regs = get_registers(iter, op); + world = ecs_get_world(world); + return &world->info; +} - /* Get register indices for output */ - int32_t r = op->r_out; - ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); +ecs_world_t *ecs_mini(void) { +#ifdef FLECS_OS_API_IMPL + ecs_set_os_api_impl(); +#endif + ecs_os_init(); - /* Get queried for id, fill out potential variables */ - ecs_rule_pair_t pair = op->filter; - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - ecs_entity_t pattern = filter.mask; - int32_t *columns = rule_get_columns(iter, op); + ecs_assert(ECS_ALIGNOF(ecs_query_triv_cache_match_t) == + ECS_ALIGNOF(ecs_query_cache_match_t), ECS_INTERNAL_ERROR, NULL); - int32_t column = -1; - ecs_table_t *table = NULL; - ecs_id_record_t *idr; + ecs_assert(EcsQueryNothing < 256, ECS_INTERNAL_ERROR, NULL); - if (!redo && op->term != -1) { - columns[op->term] = -1; - } + ecs_trace("#[bold]bootstrapping world"); + ecs_log_push(); - /* If this is a redo, we already looked up the table set */ - if (redo) { - idr = op_ctx->idr; - - /* If this is not a redo lookup the table set. Even though this may not be - * the first time the operation is evaluated, variables may have changed - * since last time, which could change the table set to lookup. */ - } else { - /* A table set is a set of tables that all contain at least the - * requested look_for expression. What is returned is a table record, - * which in addition to the table also stores the first occurrance at - * which the requested expression occurs in the table. This reduces (and - * in most cases eliminates) any searching that needs to occur in a - * table type. Tables are also registered under wildcards, which is why - * this operation can simply use the look_for variable directly */ + ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); - idr = op_ctx->idr = find_tables(world, pattern); + if (!ecs_os_has_heap()) { + ecs_abort(ECS_MISSING_OS_API, NULL); } - /* If no table set was found for queried for entity, there are no results */ - if (!idr) { - return false; + if (!ecs_os_has_threading()) { + ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); } - /* If the input register is not NULL, this is a variable that's been set by - * the application. */ - table = iter->registers[r].range.table; - bool output_is_input = table != NULL; - - if (output_is_input && !redo) { - ecs_assert(regs[r].range.table == iter->registers[r].range.table, - ECS_INTERNAL_ERROR, NULL); + if (!ecs_os_has_time()) { + ecs_trace("time management not available"); + } - table = iter->registers[r].range.table; + flecs_log_build_info(); - /* Check if table can be found in the id record. If not, the provided - * table does not match with the query. */ - ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); - if (!tr) { - return false; - } + ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); + ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); + flecs_poly_init(world, ecs_world_t); - column = op_ctx->column = tr->column; + if (ecs_os_has_time()) { + ecs_time_t now; + ecs_os_get_time(&now); + world->info.creation_time = now.sec; } - /* If this is not a redo, start at the beginning */ - if (!redo) { - if (!table) { - flecs_table_cache_iter(&idr->cache, &op_ctx->it); + world->flags |= EcsWorldInit; - /* Return the first table_record in the table set. */ - table_record = find_next_table(&filter, op_ctx); - - /* If no table record was found, there are no results. */ - if (!table_record.hdr.table) { - return false; - } + flecs_world_allocators_init(world); + ecs_allocator_t *a = &world->allocator; - table = table_record.hdr.table; + ecs_map_init(&world->type_info, a); +#ifdef FLECS_DEBUG + ecs_map_init(&world->locked_components, a); + ecs_map_init(&world->locked_entities, a); +#endif + ecs_map_init(&world->id_index_hi, &world->allocator); + world->id_index_lo = ecs_os_calloc_n( + ecs_component_record_t*, FLECS_HI_ID_RECORD_ID); + flecs_observable_init(&world->observable); - /* Set current column to first occurrence of queried for entity */ - column = op_ctx->column = table_record.column; + flecs_name_index_init(&world->aliases, a); + flecs_name_index_init(&world->symbols, a); + ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); + ecs_vec_init_t(a, &world->component_ids, ecs_id_t, 0); - /* Store table in register */ - table_reg_set(rule, regs, r, table); - } - - /* If this is a redo, progress to the next match */ - } else { - /* First test if there are any more matches for the current table, in - * case we're looking for a wildcard. */ - if (filter.wildcard) { - table = table_reg_get(rule, regs, r).table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + world->info.time_scale = 1.0; + if (ecs_os_has_time()) { + ecs_os_get_time(&world->world_start_time); + } - column = op_ctx->column; - column = find_next_column(world, table, column, &filter); - op_ctx->column = column; - } + ecs_map_init(&world->prefab_child_indices, a); - /* If no next match was found for this table, move to next table */ - if (column == -1) { - if (output_is_input) { - return false; - } + ecs_set_stage_count(world, 1); + ecs_default_lookup_path[0] = EcsFlecsCore; + ecs_set_lookup_path(world, ecs_default_lookup_path); + flecs_init_store(world); - table_record = find_next_table(&filter, op_ctx); - if (!table_record.hdr.table) { - return false; - } + flecs_bootstrap(world); - /* Assign new table to table register */ - table_reg_set(rule, regs, r, (table = table_record.hdr.table)); + world->flags &= ~EcsWorldInit; - /* Assign first matching column */ - column = op_ctx->column = table_record.column; - } - } + #ifdef FLECS_LOW_FOOTPRINT + ecs_shrink(world); + #endif - /* If we got here, we found a match. Table and column must be set */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_trace("world ready!"); + ecs_log_pop(); - if (op->term != -1) { - columns[op->term] = column; - } + return world; +} - /* If this is a wildcard query, fill out the variable registers */ - if (filter.wildcard) { - reify_variables(iter, op, &filter, table->type, column); - } +ecs_world_t *ecs_init(void) { + ecs_world_t *world = ecs_mini(); - return true; +#ifdef FLECS_MODULE_H + ecs_trace("#[bold]import addons"); + ecs_log_push(); + ecs_trace("use ecs_mini to create world without importing addons"); +#ifdef FLECS_SYSTEM + ECS_IMPORT(world, FlecsSystem); +#endif +#ifdef FLECS_PIPELINE + ECS_IMPORT(world, FlecsPipeline); +#endif +#ifdef FLECS_TIMER + ECS_IMPORT(world, FlecsTimer); +#endif +#ifdef FLECS_META + ECS_IMPORT(world, FlecsMeta); +#endif +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); +#endif +#ifdef FLECS_SCRIPT + ECS_IMPORT(world, FlecsScript); +#endif +#ifdef FLECS_REST + ECS_IMPORT(world, FlecsRest); +#endif +#ifdef FLECS_UNITS + ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); +#endif + ecs_trace("addons imported!"); + ecs_log_pop(); +#endif +#ifdef FLECS_LOW_FOOTPRINT + ecs_shrink(world); +#endif + return world; } -/* With operation. The With operation always comes after either the Select or - * another With operation, and applies additional filters to the table. */ -static -bool eval_with( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +ecs_world_t* ecs_init_w_args( + int argc, + char *argv[]) { - ecs_rule_iter_t *iter = &it->priv.iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with; - ecs_var_t *regs = get_registers(iter, op); - - /* Get register indices for input */ - int32_t r = op->r_in; + ecs_world_t *world = ecs_init(); - /* Get queried for id, fill out potential variables */ - ecs_rule_pair_t pair = op->filter; - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - int32_t *columns = rule_get_columns(iter, op); + (void)argc; + (void)argv; - /* If looked for entity is not a wildcard (meaning there are no unknown/ - * unconstrained variables) and this is a redo, nothing more to yield. */ - if (redo && !filter.wildcard) { - return false; +#ifdef FLECS_DOC + if (argc) { + char *app = argv[0]; + char *last_elem = strrchr(app, '/'); + if (!last_elem) { + last_elem = strrchr(app, '\\'); + } + if (last_elem) { + app = last_elem + 1; + } + ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); } +#endif - int32_t column = -1; - ecs_table_t *table = NULL; - ecs_id_record_t *idr; - - if (op->term != -1) { - columns[op->term] = -1; - } + return world; +} - /* If this is a redo, we already looked up the table set */ - if (redo) { - idr = op_ctx->idr; - - /* If this is not a redo lookup the table set. Even though this may not be - * the first time the operation is evaluated, variables may have changed - * since last time, which could change the table set to lookup. */ - } else { - /* Predicates can be reflexive, which means that if we have a - * transitive predicate which is provided with the same subject and - * object, it should return true. By default with will not return true - * as the subject likely does not have itself as a relationship, which - * is why this is a special case. - * - * TODO: might want to move this code to a separate with_reflexive - * instruction to limit branches for non-transitive queries (and to keep - * code more readable). - */ - if (pair.transitive && pair.reflexive) { - ecs_entity_t subj = 0, obj = 0; - - if (r == UINT8_MAX) { - subj = op->subject; - } else { - const ecs_rule_var_t *v_subj = &rule->vars[r]; +void ecs_quit( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_stage_from_world(&world); + world->flags |= EcsWorldQuit; +error: + return; +} - if (v_subj->kind == EcsRuleVarKindEntity) { - subj = entity_reg_get(rule, regs, r); +bool ecs_should_quit( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return ECS_BIT_IS_SET(world->flags, EcsWorldQuit); +error: + return true; +} - /* This is the input for the op, so should always be set */ - ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); - } - } +void flecs_notify_tables( + ecs_world_t *world, + ecs_id_t id, + ecs_table_event_t *event) +{ + flecs_poly_assert(world, ecs_world_t); - /* If subj is set, it means that it is an entity. Try to also - * resolve the object. */ - if (subj) { - /* If the object is not a wildcard, it has been reified. Get the - * value from either the register or as a literal */ - if (!filter.obj_wildcard) { - obj = ECS_PAIR_SECOND(filter.mask); - if (ecs_strip_generation(subj) == obj) { - return true; - } - } - } + /* If no id is specified, broadcast to all tables */ + if (!id || id == EcsAny) { + ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + flecs_table_notify(world, table, id, event); } - /* The With operation finds the table set that belongs to its pair - * filter. The table set is a sparse set that provides an O(1) operation - * to check whether the current table has the required expression. */ - idr = op_ctx->idr = find_tables(world, filter.mask); - } - - /* If no table set was found for queried for entity, there are no results. - * If this result is a transitive query, the table we're evaluating may not - * be in the returned table set. Regardless, if the filter that contains a - * transitive predicate does not have any tables associated with it, there - * can be no transitive matches for the filter. */ - if (!idr) { - return false; - } + /* If id is specified, only broadcast to tables with id */ + } else { + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { + return; + } - table = reg_get_range(rule, op, regs, r).table; - if (!table) { - return false; - } + ecs_table_cache_iter_t it; + const ecs_table_record_t *tr; - /* If this is not a redo, start at the beginning */ - if (!redo) { - column = op_ctx->column = find_next_column(world, table, -1, &filter); - - /* If this is a redo, progress to the next match */ - } else { - if (!filter.wildcard) { - return false; + flecs_table_cache_all_iter(&cr->cache, &it); + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + flecs_table_notify(world, tr->hdr.table, id, event); } - - /* Find the next match for the expression in the column. The columns - * array keeps track of the state for each With operation, so that - * even after redoing a With, the search doesn't have to start from - * the beginning. */ - column = find_next_column(world, table, op_ctx->column, &filter); - op_ctx->column = column; } +} - /* If no next match was found for this table, no more data */ - if (column == -1) { - return false; - } +void ecs_atfini( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); - if (op->term != -1) { - columns[op->term] = column; - } + ecs_action_elem_t *elem = ecs_vec_append_t(NULL, &world->fini_actions, + ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); - /* If we got here, we found a match. Table and column must be set */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); + elem->action = action; + elem->ctx = ctx; +error: + return; +} - /* If this is a wildcard query, fill out the variable registers */ - if (filter.wildcard) { - reify_variables(iter, op, &filter, table->type, column); - } +void ecs_run_post_frame( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); - set_source(it, op, regs, r); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check((world->flags & EcsWorldFrameInProgress), ECS_INVALID_OPERATION, + "cannot register post frame action while frame is not in progress"); - return true; + ecs_action_elem_t *elem = ecs_vec_append_t(&stage->allocator, + &stage->post_frame_actions, ecs_action_elem_t); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + elem->action = action; + elem->ctx = ctx; +error: + return; } -/* Each operation. The each operation is a simple operation that takes a table - * as input, and outputs each of the entities in a table. This operation is - * useful for rules that match a table, and where the entities of the table are - * used as predicate or object. If a rule contains an each operation, an - * iterator is guaranteed to yield an entity instead of a table. The input for - * an each operation can only be the root variable. */ +/* Unset data in tables */ static -bool eval_each( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void flecs_fini_unset_tables( + ecs_world_t *world) { - ecs_rule_iter_t *iter = &it->priv.iter.rule; - ecs_rule_each_ctx_t *op_ctx = &iter->op_ctx[op_index].is.each; - ecs_var_t *regs = get_registers(iter, op); - int32_t r_in = op->r_in; - int32_t r_out = op->r_out; - ecs_entity_t e; - - /* Make sure in/out registers are of the correct kind */ - ecs_assert(iter->rule->vars[r_in].kind == EcsRuleVarKindTable, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(iter->rule->vars[r_out].kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); + ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); - /* Get table, make sure that it contains data. The select operation should - * ensure that empty tables are never forwarded. */ - ecs_table_range_t slice = table_reg_get(iter->rule, regs, r_in); - ecs_table_t *table = slice.table; - if (table) { - int32_t row, count = slice.count; - int32_t offset = slice.offset; + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + flecs_table_remove_actions(world, table); + } +} - if (!count) { - count = ecs_table_count(table); - ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); - } else { - count += offset; - } +/* Invoke fini actions */ +static +void flecs_fini_actions( + ecs_world_t *world) +{ + int32_t i, count = ecs_vec_count(&world->fini_actions); + ecs_action_elem_t *elems = ecs_vec_first(&world->fini_actions); + for (i = 0; i < count; i ++) { + elems[i].action(world, elems[i].ctx); + } - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_fini_t(NULL, &world->fini_actions, ecs_action_elem_t); +} - /* If this is is not a redo, start from row 0, otherwise go to the - * next entity. */ - if (!redo) { - row = op_ctx->row = offset; +ecs_entity_t flecs_get_oneof( + const ecs_world_t *world, + ecs_entity_t e) +{ + if (ecs_is_alive(world, e)) { + if (ecs_has_id(world, e, EcsOneOf)) { + return e; } else { - row = ++ op_ctx->row; - } - - /* If row exceeds number of entities in table, return false */ - if (row >= count) { - return false; - } - - /* Skip builtin entities that could confuse operations */ - e = entities[row]; - while (e == EcsWildcard || e == EcsThis || e == EcsAny) { - row ++; - if (row == count) { - return false; - } - e = entities[row]; + return ecs_get_target(world, e, EcsOneOf, 0); } } else { - if (!redo) { - e = entity_reg_get(iter->rule, regs, r_in); - } else { - return false; - } + return 0; } - - /* Assign entity */ - entity_reg_set(iter->rule, regs, r_out, e); - - return true; } -/* Store operation. Stores entity in register. This can either be an entity - * literal or an entity variable that will be stored in a table register. The - * latter facilitates scenarios where an iterator only need to return a single - * entity but where the Yield returns tables. */ -static -bool eval_store( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +/* The destroyer of worlds */ +int ecs_fini( + ecs_world_t *world) { - (void)op_index; + flecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, + "cannot fini world while it is in readonly mode"); + ecs_assert(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, + "cannot fini world when it is already being deleted"); + ecs_assert(world->stages[0]->defer == 0, ECS_INVALID_OPERATION, + "call defer_end before destroying world"); - if (redo) { - /* Only ever return result once */ - return false; - } + ecs_trace("#[bold]shutting down world"); + ecs_log_push(); - ecs_rule_iter_t *iter = &it->priv.iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_var_t *regs = get_registers(iter, op); - int32_t r_in = op->r_in; - int32_t r_out = op->r_out; + world->flags |= EcsWorldQuit; - (void)world; + /* Delete root entities first using regular APIs. This ensures that cleanup + * policies get a chance to execute. */ + ecs_dbg_1("#[bold]cleanup root entities"); + ecs_log_push_1(); + flecs_fini_roots(world); + ecs_log_pop_1(); - const ecs_rule_var_t *var_out = &rule->vars[r_out]; - if (var_out->kind == EcsRuleVarKindEntity) { - ecs_entity_t out, in = reg_get_entity(rule, op, regs, r_in); - ecs_assert(in != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_is_valid(world, in), ECS_INTERNAL_ERROR, NULL); + world->flags |= EcsWorldFini; - out = iter->registers[r_out].entity; - bool output_is_input = ecs_iter_var_is_constrained(it, r_out); - if (output_is_input && !redo) { - ecs_assert(regs[r_out].entity == iter->registers[r_out].entity, - ECS_INTERNAL_ERROR, NULL); + /* Run fini actions (simple callbacks ran when world is deleted) before + * destroying the storage */ + ecs_dbg_1("#[bold]run fini actions"); + ecs_log_push_1(); + flecs_fini_actions(world); + ecs_log_pop_1(); - if (out != in) { - /* If output variable is set it must match the input */ - return false; - } - } + ecs_dbg_1("#[bold]cleanup remaining entities"); + ecs_log_push_1(); - reg_set_entity(rule, regs, r_out, in); - } else { - ecs_table_range_t out, in = reg_get_range(rule, op, regs, r_in); + /* Operations invoked during OnRemove/destructors are deferred and + * will be discarded after world cleanup */ + flecs_defer_begin(world, world->stages[0]); - out = iter->registers[r_out].range; - bool output_is_input = out.table != NULL; + /* Run OnRemove actions for components while the store is still + * unmodified by cleanup. */ + flecs_fini_unset_tables(world); - if (output_is_input && !redo) { - ecs_assert(regs[r_out].entity == iter->registers[r_out].entity, - ECS_INTERNAL_ERROR, NULL); + /* This will destroy all entities and components. */ + flecs_fini_store(world); - if (ecs_os_memcmp_t(&out, &in, ecs_table_range_t)) { - /* If output variable is set it must match the input */ - return false; - } - } + /* Purge deferred operations from the queue. This discards operations but + * makes sure that any resources in the queue are freed */ + flecs_defer_purge(world, world->stages[0]); + ecs_log_pop_1(); - reg_set_range(rule, regs, r_out, &in); + /* All queries are cleaned up, so monitors should've been cleaned up too */ + ecs_assert(!ecs_map_is_init(&world->monitors.monitors), + ECS_INTERNAL_ERROR, NULL); - /* Ensure that if the input was an empty entity, information is not - * lost */ - if (!regs[r_out].range.table) { - regs[r_out].entity = reg_get_entity(rule, op, regs, r_in); - ecs_assert(ecs_is_valid(world, regs[r_out].entity), - ECS_INTERNAL_ERROR, NULL); - } + /* Cleanup world ctx and binding_ctx */ + if (world->ctx_free) { + world->ctx_free(world->ctx); + } + if (world->binding_ctx_free) { + world->binding_ctx_free(world->binding_ctx); } - ecs_rule_filter_t filter = pair_to_filter(iter, op, op->filter); - set_term_vars(rule, regs, op->term, filter.mask); + /* After this point no more user code is invoked */ - return true; -} + ecs_dbg_1("#[bold]cleanup world data structures"); + ecs_log_push_1(); + flecs_observable_fini(&world->observable); + flecs_entities_fini(world); + flecs_components_fini(world); + flecs_fini_type_info(world); +#ifdef FLECS_DEBUG + ecs_map_fini(&world->locked_components); + ecs_map_fini(&world->locked_entities); +#endif + flecs_name_index_fini(&world->aliases); + flecs_name_index_fini(&world->symbols); + ecs_set_stage_count(world, 0); + ecs_map_fini(&world->prefab_child_indices); + ecs_vec_fini_t(&world->allocator, &world->component_ids, ecs_id_t); + ecs_log_pop_1(); -/* A setjmp operation sets the jump label for a subsequent jump label. When the - * operation is first evaluated (redo=false) it sets the label to the on_pass - * label, and returns true. When the operation is evaluated again (redo=true) - * the label is set to on_fail and the operation returns false. */ -static -bool eval_setjmp( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) -{ - ecs_rule_iter_t *iter = &it->priv.iter.rule; - ecs_rule_setjmp_ctx_t *ctx = &iter->op_ctx[op_index].is.setjmp; + flecs_world_allocators_fini(world); - if (!redo) { - ctx->label = op->on_pass; - return true; - } else { - ctx->label = op->on_fail; - return false; - } -} + /* End of the world */ + flecs_poly_free(world, ecs_world_t); + ecs_os_fini(); -/* The jump operation jumps to an operation label. The operation always returns - * true. Since the operation modifies the control flow of the program directly, - * the dispatcher does not look at the on_pass or on_fail labels of the jump - * instruction. Instead, the on_pass label is used to store the label of the - * operation that contains the label to jump to. */ -static -bool eval_jump( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) -{ - (void)it; - (void)op; - (void)op_index; + ecs_trace("world destroyed, bye!"); + ecs_log_pop(); - /* Passthrough, result is not used for control flow */ - return !redo; + return 0; } -/* The not operation reverts the result of the operation it embeds */ -static -bool eval_not( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +bool ecs_is_fini( + const ecs_world_t *world) { - (void)it; - (void)op; - (void)op_index; + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return ECS_BIT_IS_SET(world->flags, EcsWorldFini); +} - return !redo; +void ecs_dim( + ecs_world_t *world, + int32_t entity_count) +{ + flecs_poly_assert(world, ecs_world_t); + flecs_entities_set_size(world, entity_count + FLECS_HI_COMPONENT_ID); } -/* Check if entity is stored in table */ -static -bool eval_intable( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void flecs_eval_component_monitors( + ecs_world_t *world) { - (void)op_index; - - if (redo) { - return false; + flecs_poly_assert(world, ecs_world_t); + flecs_eval_component_monitor(world); +} + +void ecs_measure_frame_time( + ecs_world_t *world, + bool enable) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + + if (ECS_EQZERO(world->info.target_fps) || enable) { + ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); } +error: + return; +} - ecs_rule_iter_t *iter = &it->priv.iter.rule; - const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; - ecs_var_t *regs = get_registers(iter, op); - ecs_table_t *table = table_reg_get(rule, regs, op->r_in).table; +void ecs_measure_system_time( + ecs_world_t *world, + bool enable) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + ECS_BIT_COND(world->flags, EcsWorldMeasureSystemTime, enable); +error: + return; +} - ecs_rule_pair_t pair = op->filter; - ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); - ecs_entity_t obj = ECS_PAIR_SECOND(filter.mask); - ecs_assert(obj != 0 && obj != EcsWildcard, ECS_INTERNAL_ERROR, NULL); - obj = ecs_get_alive(world, obj); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); +void ecs_set_target_fps( + ecs_world_t *world, + ecs_ftime_t fps) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); - ecs_table_t *obj_table = ecs_get_table(world, obj); - return obj_table == table; + ecs_measure_frame_time(world, true); + world->info.target_fps = fps; +error: + return; } -/* Yield operation. This is the simplest operation, as all it does is return - * false. This will move the solver back to the previous instruction which - * forces redo's on previous operations, for as long as there are matching - * results. */ -static -bool eval_yield( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void ecs_set_default_query_flags( + ecs_world_t *world, + ecs_flags32_t flags) { - (void)it; - (void)op; - (void)op_index; - (void)redo; + flecs_poly_assert(world, ecs_world_t); + world->default_query_flags = flags; +} - /* Yield always returns false, because there are never any operations after - * a yield. */ - return false; +void* ecs_get_ctx( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->ctx; +error: + return NULL; } -/* Dispatcher for operations */ -static -bool eval_op( - ecs_iter_t *it, - ecs_rule_op_t *op, - int32_t op_index, - bool redo) +void* ecs_get_binding_ctx( + const ecs_world_t *world) { - switch(op->kind) { - case EcsRuleInput: - return eval_input(it, op, op_index, redo); - case EcsRuleSelect: - return eval_select(it, op, op_index, redo); - case EcsRuleWith: - return eval_with(it, op, op_index, redo); - case EcsRuleSubSet: - return eval_subset(it, op, op_index, redo); - case EcsRuleSuperSet: - return eval_superset(it, op, op_index, redo); - case EcsRuleEach: - return eval_each(it, op, op_index, redo); - case EcsRuleStore: - return eval_store(it, op, op_index, redo); - case EcsRuleSetJmp: - return eval_setjmp(it, op, op_index, redo); - case EcsRuleJump: - return eval_jump(it, op, op_index, redo); - case EcsRuleNot: - return eval_not(it, op, op_index, redo); - case EcsRuleInTable: - return eval_intable(it, op, op_index, redo); - case EcsRuleYield: - return eval_yield(it, op, op_index, redo); - default: - return false; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->binding_ctx; +error: + return NULL; } -/* Utility to copy all registers to the next frame. Keeping track of register - * values for each operation is necessary, because if an operation is asked to - * redo matching, it must to be able to pick up from where it left of */ -static -void push_registers( - ecs_rule_iter_t *it, - int32_t cur, - int32_t next) +void ecs_set_ctx( + ecs_world_t *world, + void *ctx, + ecs_ctx_free_t ctx_free) { - if (!it->rule->var_count) { - return; + flecs_poly_assert(world, ecs_world_t); + world->ctx = ctx; + world->ctx_free = ctx_free; +} + +void ecs_set_binding_ctx( + ecs_world_t *world, + void *ctx, + ecs_ctx_free_t ctx_free) +{ + flecs_poly_assert(world, ecs_world_t); + world->binding_ctx = ctx; + world->binding_ctx_free = ctx_free; +} + +void ecs_set_entity_range( + ecs_world_t *world, + ecs_entity_t id_start, + ecs_entity_t id_end) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); + + if (id_start == 0) { + id_start = flecs_entities_max_id(world) + 1; } - ecs_var_t *src_regs = get_register_frame(it, cur); - ecs_var_t *dst_regs = get_register_frame(it, next); + uint32_t start = (uint32_t)id_start; + uint32_t end = (uint32_t)id_end; - ecs_os_memcpy_n(dst_regs, src_regs, - ecs_var_t, it->rule->var_count); + flecs_entities_max_id(world) = start - 1; + + world->info.min_id = start; + world->info.max_id = end; +error: + return; } -/* Utility to copy all columns to the next frame. Columns keep track of which - * columns are currently being evaluated for a table, and are populated by the - * Select and With operations. The columns array is important, as it is used - * to tell the application where to find component data. */ -static -void push_columns( - ecs_rule_iter_t *it, - int32_t cur, - int32_t next) +bool ecs_enable_range_check( + ecs_world_t *world, + bool enable) { - if (!it->rule->filter.term_count) { - return; - } + flecs_poly_assert(world, ecs_world_t); + bool old_value = world->range_check_enabled; + world->range_check_enabled = enable; + return old_value; +} + +ecs_entity_t ecs_get_max_id( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return flecs_entities_max_id(world); +error: + return 0; +} - int32_t *src_cols = rule_get_columns_frame(it, cur); - int32_t *dst_cols = rule_get_columns_frame(it, next); +void flecs_increment_table_version( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_os_memcpy_n(dst_cols, src_cols, int32_t, it->rule->filter.term_count); + world->table_version[table->id & ECS_TABLE_VERSION_ARRAY_BITMASK] ++; + table->version ++; } -/* Populate iterator with data before yielding to application */ -static -void populate_iterator( - const ecs_rule_t *rule, - ecs_iter_t *iter, - ecs_rule_iter_t *it, - ecs_rule_op_t *op) +uint32_t flecs_get_table_version_fast( + const ecs_world_t *world, + const uint64_t table_id) { - ecs_world_t *world = rule->world; - int32_t r = op->r_in; - ecs_var_t *regs = get_register_frame(it, op->frame); - ecs_table_t *table = NULL; - int32_t count = 0; - int32_t offset = 0; + flecs_poly_assert(world, ecs_world_t); + return world->table_version[table_id & ECS_TABLE_VERSION_ARRAY_BITMASK]; +} - /* If the input register for the yield does not point to a variable, - * the rule doesn't contain a this (.) variable. In that case, the - * iterator doesn't contain any data, and this function will simply - * return true or false. An application will still be able to obtain - * the variables that were resolved. */ - if (r != UINT8_MAX) { - const ecs_rule_var_t *var = &rule->vars[r]; - ecs_var_t *reg = ®s[r]; - - if (var->kind == EcsRuleVarKindTable) { - ecs_table_range_t slice = table_reg_get(rule, regs, r); - table = slice.table; - count = slice.count; - offset = slice.offset; - } else { - /* If a single entity is returned, simply return the - * iterator with count 1 and a pointer to the entity id */ - ecs_assert(var->kind == EcsRuleVarKindEntity, - ECS_INTERNAL_ERROR, NULL); +void flecs_increment_table_column_version( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_entity_t e = reg->entity; - ecs_assert(ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - ecs_record_t *record = ecs_eis_get(world, e); - offset = ECS_RECORD_TO_ROW(record->row); + int32_t index = table->id & ECS_TABLE_VERSION_ARRAY_BITMASK; + world->table_column_version[index] ++; + if (world->table_column_version[index] == UINT32_MAX) { + /* Skip sentinel value */ + world->table_column_version[index] = 0; + } +} - /* If an entity is not stored in a table, it could not have - * been matched by anything */ - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - table = record->table; - count = 1; - } +uint32_t flecs_get_table_column_version( + const ecs_world_t *world, + const uint64_t table_id) +{ + flecs_poly_assert(world, ecs_world_t); + return world->table_column_version[table_id & ECS_TABLE_VERSION_ARRAY_BITMASK]; +} + +static int32_t flecs_component_ids_last_index = 0; + +int32_t flecs_component_ids_index_get(void) { + if (ecs_os_api.ainc_) { + return ecs_os_ainc(&flecs_component_ids_last_index); + } else { + return ++ flecs_component_ids_last_index; } +} - int32_t i, var_count = rule->var_count; - int32_t term_count = rule->filter.term_count; +ecs_entity_t flecs_component_ids_get( + const ecs_world_t *stage_world, + int32_t index) +{ + ecs_world_t *world = + ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); + flecs_poly_assert(world, ecs_world_t); - for (i = 0; i < var_count; i ++) { - iter->variables[i] = regs[i]; + if (index >= ecs_vec_count(&world->component_ids)) { + return 0; } - for (i = 0; i < term_count; i ++) { - int32_t v = rule->term_vars[i].subj; - if (v != -1) { - const ecs_rule_var_t *var = &rule->vars[v]; - if (var->name[0] != '.') { - if (var->kind == EcsRuleVarKindEntity) { - iter->subjects[i] = regs[var->id].entity; - } else { - /* This can happen for Any variables, where the actual - * content of the variable is not of interest to the query. - * Just pick the first entity from the table, so that the - * column can be correctly resolved */ - ecs_table_t *t = regs[var->id].range.table; - if (t) { - iter->subjects[i] = ecs_vector_first( - t->storage.entities, ecs_entity_t)[0]; - } else { - /* Can happen if term is optional */ - iter->subjects[i] = 0; - } - } - } - } + return ecs_vec_get_t( + &world->component_ids, ecs_entity_t, index)[0]; +} + +ecs_entity_t flecs_component_ids_get_alive( + const ecs_world_t *stage_world, + int32_t index) +{ + ecs_world_t *world = + ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); + flecs_poly_assert(world, ecs_world_t); + + if (index >= ecs_vec_count(&world->component_ids)) { + return 0; } - /* Iterator expects column indices to start at 1 */ - iter->columns = rule_get_columns_frame(it, op->frame); - for (i = 0; i < term_count; i ++) { - ecs_assert(iter->subjects != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t subj = iter->subjects[i]; - int32_t c = ++ iter->columns[i]; - if (!subj) { - subj = iter->terms[i].subj.entity; - if (subj != EcsThis && subj != EcsAny) { - iter->columns[i] = 0; - } - } else if (c) { - iter->columns[i] = -1; - } + ecs_entity_t result = ecs_vec_get_t( + &world->component_ids, ecs_entity_t, index)[0]; + if (!flecs_entities_is_alive(world, result)) { + return 0; } - /* Set iterator ids */ - for (i = 0; i < term_count; i ++) { - const ecs_rule_term_vars_t *vars = &rule->term_vars[i]; - ecs_term_t *term = &rule->filter.terms[i]; - if (term->oper == EcsOptional || term->oper == EcsNot) { - if (iter->columns[i] == 0) { - iter->ids[i] = term->id; - continue; - } - } + return result; +} - ecs_id_t id = term->id; - ecs_entity_t pred = 0; - ecs_entity_t obj = 0; - bool is_pair = ECS_HAS_ROLE(id, PAIR); +void flecs_component_ids_set( + ecs_world_t *stage_world, + int32_t index, + ecs_entity_t component) +{ + ecs_world_t *world = + ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); + flecs_poly_assert(world, ecs_world_t); - if (!is_pair) { - pred = id; - } else { - pred = ECS_PAIR_FIRST(id); - obj = ECS_PAIR_SECOND(id); - } + ecs_vec_set_min_count_zeromem_t( + &world->allocator, &world->component_ids, ecs_entity_t, index + 1); + ecs_vec_get_t(&world->component_ids, ecs_entity_t, index)[0] = component; +} - if (vars->pred != -1) { - pred = regs[vars->pred].entity; - } - if (vars->obj != -1) { - ecs_assert(is_pair, ECS_INTERNAL_ERROR, NULL); - obj = regs[vars->obj].entity; - } +#ifdef FLECS_DEBUG - if (!is_pair) { - id = pred; - } else { - id = ecs_pair(pred, obj); - } +void flecs_check_exclusive_world_access_write( + const ecs_world_t *world) +{ + flecs_poly_assert(world, ecs_world_t); - iter->ids[i] = id; + if (!world->exclusive_access) { + return; /* Exclusive access is not enabled */ } - flecs_iter_populate_data(world, iter, table, offset, count, - iter->ptrs, iter->sizes); -} + ecs_os_thread_id_t thr_self = ecs_os_thread_self(); + (void)thr_self; -static -bool is_control_flow( - ecs_rule_op_t *op) -{ - switch(op->kind) { - case EcsRuleSetJmp: - case EcsRuleJump: - return true; - default: - return false; + if (world->exclusive_access == UINT64_MAX) { + ecs_throw(ECS_ACCESS_VIOLATION, + "invalid access: world is locked for write operations " + "(call exclusive_access_begin() first)"); + } else + if (world->exclusive_thread_name) { + ecs_assert(world->exclusive_access == ecs_os_thread_self(), + ECS_ACCESS_VIOLATION, + "invalid access to world by thread %" PRIu64 ": " + "thread %" PRIu64 " (%s) has exclusive access", + thr_self, world->exclusive_access, + world->exclusive_thread_name); + } else { + ecs_assert(world->exclusive_access == ecs_os_thread_self(), + ECS_ACCESS_VIOLATION, + "invalid access to world by thread %" PRIu64 ": " + "thread %" PRIu64 " has exclusive access", + thr_self, world->exclusive_access); } +error: + return; } -static -void rule_iter_set_initial_state( - ecs_iter_t *it, - ecs_rule_iter_t *iter, - const ecs_rule_t *rule) +void flecs_check_exclusive_world_access_read( + const ecs_world_t *world) { - int32_t i; + flecs_poly_assert(world, ecs_world_t); - /* Make sure that if there are any terms with literal subjects, they're - * initialized in the subjects array */ - const ecs_filter_t *filter = &rule->filter; - int32_t term_count = filter->term_count; - for (i = 0; i < term_count; i ++) { - ecs_term_t *t = &filter->terms[i]; - ecs_term_id_t *subj = &t->subj; - ecs_assert(subj->var == EcsVarIsVariable || subj->entity != EcsThis, - ECS_INTERNAL_ERROR, NULL); + if (!world->exclusive_access) { + return; /* Exclusive access is not enabled */ + } - if (subj->var == EcsVarIsEntity) { - it->subjects[i] = subj->entity; - } + if (world->exclusive_access == UINT64_MAX) { + return; /* World is locked, so read access is allowed */ } - /* Initialize registers of constrained variables */ - if (it->constrained_vars) { - ecs_var_t *regs = get_register_frame(iter, 0); + ecs_os_thread_id_t thr_self = ecs_os_thread_self(); + (void)thr_self; - for (i = 0; i < it->variable_count; i ++) { - if (ecs_iter_var_is_constrained(it, i)) { - const ecs_rule_var_t *var = &rule->vars[i]; - ecs_assert(var->id == i, ECS_INTERNAL_ERROR, NULL); - - int32_t other_var = var->other; - ecs_var_t *it_var = &it->variables[i]; - ecs_entity_t e = it_var->entity; + if (world->exclusive_thread_name) { + ecs_assert(world->exclusive_access == ecs_os_thread_self(), + ECS_ACCESS_VIOLATION, + "invalid access to world by thread %" PRIu64 ": " + "(thread %" PRIu64 " (%s) has exclusive access)", + thr_self, world->exclusive_access, + world->exclusive_thread_name); + } else { + ecs_assert(world->exclusive_access == ecs_os_thread_self(), + ECS_ACCESS_VIOLATION, + "invalid access to world by thread %" PRIu64 ": " + "thread %" PRIu64 " has exclusive access", + thr_self, world->exclusive_access); + } +} - if (e) { - ecs_assert(ecs_is_valid(it->world, e), - ECS_INTERNAL_ERROR, NULL); - reg_set_entity(rule, regs, i, e); - if (other_var != -1) { - reg_set_entity(rule, regs, other_var, e); - } - } else { - ecs_assert(it_var->range.table != NULL, - ECS_INVALID_PARAMETER, NULL); - reg_set_range(rule, regs, i, &it_var->range); - if (other_var != -1) { - reg_set_range(rule, regs, other_var, &it_var->range); - } - } - } +#endif + +static +void flecs_process_empty_queries( + ecs_world_t *world) +{ + flecs_poly_assert(world, ecs_world_t); + + /* Make sure that we defer adding the inactive tags until after iterating + * the query */ + flecs_defer_begin(world, world->stages[0]); + + FLECS_EACH_QUERY(query, { + if (!ecs_query_is_true(query)) { + ecs_add_id(world, query->entity, EcsEmpty); } - } + }) + + flecs_defer_end(world, world->stages[0]); } -bool ecs_rule_next( - ecs_iter_t *it) +void ecs_run_aperiodic( + ecs_world_t *world, + ecs_flags32_t flags) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); + flecs_poly_assert(world, ecs_world_t); - if (flecs_iter_next_row(it)) { - return true; + if ((flags & EcsAperiodicEmptyQueries)) { + flecs_process_empty_queries(world); } - return flecs_iter_next_instanced(it, ecs_rule_next_instanced(it)); -error: - return false; + if (!flags || (flags & EcsAperiodicComponentMonitors)) { + flecs_eval_component_monitors(world); + } } -/* Iterator next function. This evaluates the program until it reaches a Yield - * operation, and returns the intermediate result(s) to the application. An - * iterator can, depending on the program, either return a table, entity, or - * just true/false, in case a rule doesn't contain the this variable. */ -bool ecs_rule_next_instanced( - ecs_iter_t *it) +int32_t ecs_delete_empty_tables( + ecs_world_t *world, + const ecs_delete_empty_tables_desc_t *desc) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); + flecs_poly_assert(world, ecs_world_t); - ecs_rule_iter_t *iter = &it->priv.iter.rule; - const ecs_rule_t *rule = iter->rule; - bool redo = iter->redo; - int32_t last_frame = -1; - bool first_time = !ECS_BIT_IS_SET(it->flags, EcsIterIsValid); + ecs_os_perf_trace_push("flecs.delete_empty_tables"); - ecs_poly_assert(rule, ecs_rule_t); + ecs_time_t start = {0}, cur = {0}; + int32_t delete_count = 0; + bool time_budget = false; + int32_t measure_budget_after = 100; - /* Mark iterator as valid & ensure iterator resources are up to date */ - flecs_iter_validate(it); + uint16_t clear_generation = desc->clear_generation; + uint16_t delete_generation = desc->delete_generation; + double time_budget_seconds = desc->time_budget_seconds; - /* Can't iterate an iterator that's already depleted */ - ecs_check(iter->op != -1, ECS_INVALID_PARAMETER, NULL); + if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { + ecs_time_measure(&start); + } - /* If this is the first time the iterator is iterated, set initial state */ - if (first_time) { - ecs_assert(redo == false, ECS_INTERNAL_ERROR, NULL); - rule_iter_set_initial_state(it, iter, rule); + if (ECS_NEQZERO(time_budget_seconds)) { + time_budget = true; } - do { - /* Evaluate an operation. The result of an operation determines the - * flow of the program. If an operation returns true, the program - * continues to the operation pointed to by 'on_pass'. If the operation - * returns false, the program continues to the operation pointed to by - * 'on_fail'. - * - * In most scenarios, on_pass points to the next operation, and on_fail - * points to the previous operation. - * - * When an operation fails, the previous operation will be invoked with - * redo=true. This will cause the operation to continue its search from - * where it left off. When the operation succeeds, the next operation - * will be invoked with redo=false. This causes the operation to start - * from the beginning, which is necessary since it just received a new - * input. */ - int32_t op_index = iter->op; - ecs_rule_op_t *op = &rule->operations[op_index]; - int32_t cur = op->frame; - - /* If this is not the first operation and is also not a control flow - * operation, push a new frame on the stack for the next operation */ - if (!redo && !is_control_flow(op) && cur && cur != last_frame) { - int32_t prev = cur - 1; - push_registers(iter, prev, cur); - push_columns(iter, prev, cur); - } - - /* Dispatch the operation */ - bool result = eval_op(it, op, op_index, redo); - iter->op = result ? op->on_pass : op->on_fail; - - /* If the current operation is yield, return results */ - if (op->kind == EcsRuleYield) { - populate_iterator(rule, it, iter, op); - iter->redo = true; - return true; + int32_t i, count = flecs_sparse_count(&world->store.tables); + + for (i = count - 1; i >= 0; i --) { + ecs_table_t *table = flecs_sparse_get_dense_t(&world->store.tables, + ecs_table_t, i); + + if (table->keep) { + continue; } - /* If the current operation is a jump, goto stored label */ - if (op->kind == EcsRuleJump) { - /* Label is stored in setjmp context */ - iter->op = iter->op_ctx[op->on_pass].is.setjmp.label; + measure_budget_after --; + + if (time_budget && !measure_budget_after) { + cur = start; + if (ecs_time_measure(&cur) > time_budget_seconds) { + goto done; + } + + measure_budget_after = 100; } - /* If jumping backwards, it's a redo */ - redo = iter->op <= op_index; + if (!table->id || ecs_table_count(table) != 0) { + continue; + } - if (!is_control_flow(op)) { - last_frame = op->frame; + uint16_t gen = ++ table->_->generation; + if (delete_generation && (gen > delete_generation)) { + flecs_table_fini(world, table); + delete_count ++; + measure_budget_after = 1; + } else if (clear_generation && (gen > clear_generation)) { + flecs_table_shrink(world, table); + measure_budget_after = 1; } - } while (iter->op != -1); + } - ecs_iter_fini(it); +done: + ecs_os_perf_trace_pop("flecs.delete_empty_tables"); -error: - return false; + return delete_count; } -#endif - +ecs_entities_t ecs_get_entities( + const ecs_world_t *world) +{ + ecs_entities_t result; + result.ids = flecs_entities_ids(world); + result.count = flecs_entities_size(world); + result.alive_count = flecs_entities_count(world); + return result; +} -#ifdef FLECS_MODULE +ecs_flags32_t ecs_world_get_flags( + const ecs_world_t *world) +{ + if (flecs_poly_is(world, ecs_world_t)) { + return world->flags; + } else { + flecs_poly_assert(world, ecs_stage_t); + const ecs_stage_t *stage = (const ecs_stage_t*)world; + return stage->world->flags; + } +} -#include +static +bool flecs_component_record_in_use( + const ecs_component_record_t *cr) +{ + if (cr->flags & EcsIdDontFragment) { + return flecs_sparse_count(cr->sparse) != 0; + } else if (cr->flags & EcsIdOrderedChildren) { + return ecs_vec_count(&cr->pair->ordered_children) != 0; + } else { + return cr->cache.tables.count != 0; + } +} -char* ecs_module_path_from_c( - const char *c_name) +void ecs_shrink( + ecs_world_t *world) { - ecs_strbuf_t str = ECS_STRBUF_INIT; - const char *ptr; - char ch; + /* This can invalidate ecs_record_t pointers for entities that are no longer + * alive. If you're sure an application doesn't store any ecs_record_t ptrs + * or ecs_ref_t's for not-alive entities, you can uncomment this line. */ + // flecs_entity_index_shrink(&world->store.entity_index); - for (ptr = c_name; (ch = *ptr); ptr++) { - if (isupper(ch)) { - ch = flecs_ito(char, tolower(ch)); - if (ptr != c_name) { - ecs_strbuf_appendstrn(&str, ".", 1); + ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + + for (i = count - 1; i > 0; i --) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + if (ecs_table_count(table)) { + flecs_table_shrink(world, table); + } else if (!table->keep) { + flecs_table_fini(world, table); + } + } + + flecs_table_shrink(world, &world->store.root); + + ecs_map_reclaim(&world->store.table_map.impl); + + flecs_sparse_shrink(&world->store.tables); + + ecs_vec_t cr_to_release; + ecs_vec_init_t( + &world->allocator, &cr_to_release, ecs_component_record_t*, 0); + + FLECS_EACH_COMPONENT_RECORD(cr, { + if (flecs_component_record_in_use(cr)) { + flecs_component_shrink(cr); + } else { + if (!ecs_id_is_wildcard(cr->id)) { + ecs_vec_append_t( + &world->allocator, &cr_to_release, ecs_component_record_t*)[0] = cr; + } } + }) - ecs_strbuf_appendstrn(&str, &ch, 1); + count = ecs_vec_count(&cr_to_release); + for (i = 0; i < count; i ++) { + ecs_component_record_t *cr = ecs_vec_get_t( + &cr_to_release, ecs_component_record_t*, i)[0]; + flecs_component_release(world, cr); } - return ecs_strbuf_get(&str); -} - -ecs_entity_t ecs_import( - ecs_world_t *world, - ecs_module_action_t init_action, - const char *module_name) -{ - ecs_check(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); + ecs_vec_fini_t(&world->allocator, &cr_to_release, ecs_component_record_t*); - ecs_entity_t old_scope = ecs_set_scope(world, 0); - const char *old_name_prefix = world->name_prefix; + FLECS_EACH_QUERY(query, { + flecs_query_reclaim(query); + }) - char *path = ecs_module_path_from_c(module_name); - ecs_entity_t e = ecs_lookup_fullpath(world, path); - ecs_os_free(path); + ecs_map_reclaim(&world->id_index_hi); - if (!e) { - ecs_trace("#[magenta]import#[reset] %s", module_name); - ecs_log_push(); + ecs_map_reclaim(&world->type_info); - /* Load module */ - init_action(world); + for (i = 0; i < world->stage_count; i ++) { + ecs_stage_shrink(world->stages[i]); + } +} - /* Lookup module entity (must be registered by module) */ - e = ecs_lookup_fullpath(world, module_name); - ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); +void ecs_exclusive_access_begin( + ecs_world_t *world, + const char *thread_name) +{ + flecs_poly_assert(world, ecs_world_t); - ecs_log_pop(); + /* If world was locked, one thread can get exclusive access */ + if (world->exclusive_access == UINT64_MAX) { + world->exclusive_access = 0; } - /* Restore to previous state */ - ecs_set_scope(world, old_scope); - world->name_prefix = old_name_prefix; + ecs_assert(!world->exclusive_access, ECS_INVALID_OPERATION, + "cannot begin exclusive access: world already in exclusive mode " + "by thread %" PRIu64 " (%s)", + world->exclusive_access, + world->exclusive_thread_name + ? world->exclusive_thread_name + : "no thread name"); - return e; -error: - return 0; + world->exclusive_access = ecs_os_thread_self(); + world->exclusive_thread_name = thread_name; } -ecs_entity_t ecs_import_from_library( +void ecs_exclusive_access_end( ecs_world_t *world, - const char *library_name, - const char *module_name) + bool lock_world) { - ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); - - char *import_func = (char*)module_name; /* safe */ - char *module = (char*)module_name; + flecs_poly_assert(world, ecs_world_t); - if (!ecs_os_has_modules() || !ecs_os_has_dl()) { - ecs_err( - "library loading not supported, set module_to_dl, dlopen, dlclose " - "and dlproc os API callbacks first"); - return 0; + /* If the world is locked (not exclusively accessed by a specific thread) + * this allows for unlocking the world without first calling access_begin */ + if (world->exclusive_access == UINT64_MAX) { + world->exclusive_access = 0; + return; } - /* If no module name is specified, try default naming convention for loading - * the main module from the library */ - if (!import_func) { - import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); - ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); - - const char *ptr; - char ch, *bptr = import_func; - bool capitalize = true; - for (ptr = library_name; (ch = *ptr); ptr ++) { - if (ch == '.') { - capitalize = true; - } else { - if (capitalize) { - *bptr = flecs_ito(char, toupper(ch)); - bptr ++; - capitalize = false; - } else { - *bptr = flecs_ito(char, tolower(ch)); - bptr ++; - } - } - } - - *bptr = '\0'; + ecs_assert(world->exclusive_access != 0, ECS_INVALID_OPERATION, + "cannot end exclusive access: world is not in exclusive mode"); - module = ecs_os_strdup(import_func); - ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); + ecs_os_thread_id_t thr_self = ecs_os_thread_self(); + (void)thr_self; - ecs_os_strcat(bptr, "Import"); - } + ecs_assert(world->exclusive_access == thr_self, ECS_INVALID_OPERATION, + "cannot end exclusive access from thread that does not have exclusive access"); - char *library_filename = ecs_os_module_to_dl(library_name); - if (!library_filename) { - ecs_err("failed to find library file for '%s'", library_name); - if (module != module_name) { - ecs_os_free(module); - } - return 0; + if (!lock_world) { + world->exclusive_access = 0; } else { - ecs_trace("found file '%s' for library '%s'", - library_filename, library_name); + /* Prevent any mutations on the world */ + world->exclusive_access = UINT64_MAX; + world->exclusive_thread_name = "locked world"; } +} - ecs_os_dl_t dl = ecs_os_dlopen(library_filename); - if (!dl) { - ecs_err("failed to load library '%s' ('%s')", - library_name, library_filename); - - ecs_os_free(library_filename); +ecs_entity_t ecs_set_with( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_id_t prev = stage->with; + stage->with = id; + return prev; +error: + return 0; +} - if (module != module_name) { - ecs_os_free(module); - } +ecs_id_t ecs_get_with( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->with; +error: + return 0; +} - return 0; +#ifdef FLECS_DEBUG +static +void flecs_component_lock_inc( + ecs_map_t *locked_map, + ecs_id_t component) +{ + if (!component) { + return; + } + + int32_t *rc = (int32_t*)ecs_map_ensure(locked_map, component); + if (ecs_os_has_threading()) { + ecs_os_ainc(rc); } else { - ecs_trace("library '%s' ('%s') loaded", - library_name, library_filename); + rc[0] ++; } +} - ecs_module_action_t action = (ecs_module_action_t) - ecs_os_dlproc(dl, import_func); - if (!action) { - ecs_err("failed to load import function %s from library %s", - import_func, library_name); - ecs_os_free(library_filename); - ecs_os_dlclose(dl); - return 0; +static +void flecs_component_lock_dec( + ecs_world_t *world, + ecs_map_t *locked_map, + ecs_id_t component) +{ + if (!component) { + return; + } + + int32_t *rc = (int32_t*)ecs_map_get(locked_map, component); + + ecs_assert(rc != NULL, ECS_INTERNAL_ERROR, + "'%s' is unlocked more times than it was locked", + flecs_errstr(ecs_id_str(world, component))); + + if (ecs_os_has_threading()) { + ecs_os_adec(rc); } else { - ecs_trace("found import function '%s' in library '%s' for module '%s'", - import_func, library_name, module); + rc[0] --; } - /* Do not free id, as it will be stored as the component identifier */ - ecs_entity_t result = ecs_import(world, action, module); + ecs_assert(rc[0] >= 0, ECS_INTERNAL_ERROR, + "'%s' is unlocked more times than it was locked", + flecs_errstr(ecs_id_str(world, component))); - if (import_func != module_name) { - ecs_os_free(import_func); + if (!rc[0]) { + ecs_map_remove(locked_map, component); } +} - if (module != module_name) { - ecs_os_free(module); +void flecs_component_lock( + ecs_world_t *world, + ecs_id_t component) +{ + flecs_component_lock_inc(&world->locked_components, component); + if (ECS_IS_PAIR(component)) { + flecs_component_lock_inc(&world->locked_components, ECS_PAIR_FIRST(component)); + flecs_component_lock_inc(&world->locked_entities, ECS_PAIR_SECOND(component)); } +} - ecs_os_free(library_filename); +void flecs_component_unlock( + ecs_world_t *world, + ecs_id_t component) +{ + flecs_component_lock_dec(world, &world->locked_components, component); + if (ECS_IS_PAIR(component)) { + flecs_component_lock_dec(world, &world->locked_components, ECS_PAIR_FIRST(component)); + flecs_component_lock_dec(world, &world->locked_entities, ECS_PAIR_SECOND(component)); + } +} - return result; -error: - return 0; +bool flecs_component_is_trait_locked( + ecs_world_t *world, + ecs_id_t component) +{ + return ecs_map_get(&world->locked_components, component) != NULL; } -ecs_entity_t ecs_module_init( +bool flecs_component_is_delete_locked( ecs_world_t *world, - const ecs_component_desc_t *desc) + ecs_id_t component) { - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_poly_assert(world, ecs_world_t); + return + (ecs_map_get(&world->locked_components, component) != NULL) || + (ecs_map_get(&world->locked_entities, component) != NULL); +} +#endif - const char *name = desc->entity.name; +/** + * @file addons/alerts.c + * @brief Alerts addon. + */ - char *module_path = ecs_module_path_from_c(name); - ecs_entity_t e = ecs_new_from_fullpath(world, module_path); - ecs_set_symbol(world, e, module_path); - ecs_os_free(module_path); - ecs_component_desc_t private_desc = *desc; - private_desc.entity.entity = e; - private_desc.entity.name = NULL; +#ifdef FLECS_ALERTS - if (desc->size) { - ecs_entity_t result = ecs_component_init(world, &private_desc); - ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); - (void)result; - } else { - ecs_entity_t result = ecs_entity_init(world, &private_desc.entity); - ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); - (void)result; - } +ECS_COMPONENT_DECLARE(FlecsAlerts); - return e; -error: - return 0; -} +typedef struct EcsAlert { + char *message; + ecs_map_t instances; /* Active instances for metric */ + ecs_ftime_t retain_period; /* How long to retain the alert */ + ecs_vec_t severity_filters; /* Severity filters */ + + /* Member range monitoring */ + ecs_id_t id; /* (Component) id that contains to monitor member */ + ecs_entity_t member; /* Member to monitor */ + int32_t offset; /* Offset of member in component */ + int32_t size; /* Size of component */ + ecs_primitive_kind_t kind; /* Primitive type kind */ + ecs_ref_t ranges; /* Reference to ranges component */ + int32_t var_id; /* Variable from which to obtain data (0 = $this) */ +} EcsAlert; + +typedef struct EcsAlertTimeout { + ecs_ftime_t inactive_time; /* Time the alert has been inactive */ + ecs_ftime_t expire_time; /* Expiration duration */ +} EcsAlertTimeout; + +ECS_COMPONENT_DECLARE(EcsAlertTimeout); + +static +ECS_CTOR(EcsAlert, ptr, { + ecs_os_zeromem(ptr); + ecs_map_init(&ptr->instances, NULL); + ecs_vec_init_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t, 0); +}) -#endif +static +ECS_DTOR(EcsAlert, ptr, { + ecs_os_free(ptr->message); + ecs_map_fini(&ptr->instances); + ecs_vec_fini_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t); +}) -#ifndef FLECS_META_PRIVATE_H -#define FLECS_META_PRIVATE_H +static +ECS_MOVE(EcsAlert, dst, src, { + ecs_os_free(dst->message); + dst->message = src->message; + src->message = NULL; + ecs_map_fini(&dst->instances); + dst->instances = src->instances; + src->instances = (ecs_map_t){0}; -#ifdef FLECS_META + ecs_vec_fini_t(NULL, &dst->severity_filters, ecs_alert_severity_filter_t); + dst->severity_filters = src->severity_filters; + src->severity_filters = (ecs_vec_t){0}; -void ecs_meta_type_serialized_init( - ecs_iter_t *it); + dst->retain_period = src->retain_period; + dst->id = src->id; + dst->member = src->member; + dst->offset = src->offset; + dst->size = src->size; + dst->kind = src->kind; + dst->ranges = src->ranges; + dst->var_id = src->var_id; +}) -void ecs_meta_dtor_serialized( - EcsMetaTypeSerialized *ptr); +static +ECS_CTOR(EcsAlertsActive, ptr, { + ecs_map_init(&ptr->alerts, NULL); + ptr->info_count = 0; + ptr->warning_count = 0; + ptr->error_count = 0; +}) +static +ECS_DTOR(EcsAlertsActive, ptr, { + ecs_map_fini(&ptr->alerts); +}) -bool flecs_unit_validate( - ecs_world_t *world, - ecs_entity_t t, - EcsUnit *data); +static +ECS_MOVE(EcsAlertsActive, dst, src, { + ecs_map_fini(&dst->alerts); + dst->alerts = src->alerts; + dst->info_count = src->info_count; + dst->warning_count = src->warning_count; + dst->error_count = src->error_count; + src->alerts = (ecs_map_t){0}; +}) -#endif - -#endif +static +ECS_DTOR(EcsAlertInstance, ptr, { + ecs_os_free(ptr->message); +}) +static +ECS_MOVE(EcsAlertInstance, dst, src, { + ecs_os_free(dst->message); + dst->message = src->message; + src->message = NULL; +}) -#ifdef FLECS_META +static +ECS_COPY(EcsAlertInstance, dst, src, { + ecs_os_free(dst->message); + dst->message = ecs_os_strdup(src->message); +}) -ecs_entity_t ecs_primitive_init( +static +void flecs_alerts_add_alert_to_src( ecs_world_t *world, - const ecs_primitive_desc_t *desc) + ecs_entity_t source, + ecs_entity_t alert, + ecs_entity_t alert_instance) { - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - return 0; - } + EcsAlertsActive *active = ecs_ensure( + world, source, EcsAlertsActive); + ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_set(world, t, EcsPrimitive, { desc->kind }); + ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); + if (severity == EcsAlertInfo) { + active->info_count ++; + } else if (severity == EcsAlertWarning) { + active->warning_count ++; + } else if (severity == EcsAlertError) { + active->error_count ++; + } - return t; + ecs_entity_t *ptr = ecs_map_ensure(&active->alerts, alert); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ptr[0] = alert_instance; + ecs_modified(world, source, EcsAlertsActive); } -ecs_entity_t ecs_enum_init( +static +void flecs_alerts_remove_alert_from_src( ecs_world_t *world, - const ecs_enum_desc_t *desc) + ecs_entity_t source, + ecs_entity_t alert) { - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - return 0; - } + EcsAlertsActive *active = ecs_ensure( + world, source, EcsAlertsActive); + ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_remove(&active->alerts, alert); - ecs_add(world, t, EcsEnum); + ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); + if (severity == EcsAlertInfo) { + active->info_count --; + } else if (severity == EcsAlertWarning) { + active->warning_count --; + } else if (severity == EcsAlertError) { + active->error_count --; + } - ecs_entity_t old_scope = ecs_set_scope(world, t); + if (!ecs_map_count(&active->alerts)) { + ecs_remove(world, source, EcsAlertsActive); + } else { + ecs_modified(world, source, EcsAlertsActive); + } +} - int i; - for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { - const ecs_enum_constant_t *m_desc = &desc->constants[i]; - if (!m_desc->name) { - break; - } - - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = m_desc->name - }); - - if (!m_desc->value) { - ecs_add_id(world, c, EcsConstant); +static +ecs_entity_t flecs_alert_get_severity( + ecs_world_t *world, + ecs_iter_t *it, + EcsAlert *alert) +{ + int32_t i, filter_count = ecs_vec_count(&alert->severity_filters); + ecs_alert_severity_filter_t *filters = + ecs_vec_first(&alert->severity_filters); + for (i = 0; i < filter_count; i ++) { + ecs_alert_severity_filter_t *filter = &filters[i]; + if (!filter->var) { + if (ecs_table_has_id(world, it->table, filters[i].with)) { + return filters[i].severity; + } } else { - ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, - {m_desc->value}); + ecs_entity_t src = ecs_iter_get_var(it, filter->_var_index); + if (src && src != EcsWildcard) { + if (ecs_has_id(world, src, filters[i].with)) { + return filters[i].severity; + } + } } } - ecs_set_scope(world, old_scope); + return 0; +} - if (i == 0) { - ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); - ecs_delete(world, t); +static +ecs_entity_t flecs_alert_out_of_range_kind( + EcsAlert *alert, + const EcsMemberRanges *ranges, + const void *value_ptr) +{ + double value = 0; + + switch(alert->kind) { + case EcsU8: value = *(const uint8_t*)value_ptr; break; + case EcsU16: value = *(const uint16_t*)value_ptr; break; + case EcsU32: value = *(const uint32_t*)value_ptr; break; + case EcsU64: value = (double)*(const uint64_t*)value_ptr; break; + case EcsI8: value = *(const int8_t*)value_ptr; break; + case EcsI16: value = *(const int16_t*)value_ptr; break; + case EcsI32: value = *(const int32_t*)value_ptr; break; + case EcsI64: value = (double)*(const int64_t*)value_ptr; break; + case EcsF32: value = (double)*(const float*)value_ptr; break; + case EcsF64: value = *(const double*)value_ptr; break; + case EcsBool: + case EcsChar: + case EcsByte: + case EcsUPtr: + case EcsIPtr: + case EcsString: + case EcsEntity: + case EcsId: + default: return 0; } - return t; -} + bool has_error = ECS_NEQ(ranges->error.min, ranges->error.max); + bool has_warning = ECS_NEQ(ranges->warning.min, ranges->warning.max); -ecs_entity_t ecs_bitmask_init( - ecs_world_t *world, - const ecs_bitmask_desc_t *desc) -{ - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { + if (has_error && (value < ranges->error.min || value > ranges->error.max)) { + return EcsAlertError; + } else if (has_warning && + (value < ranges->warning.min || value > ranges->warning.max)) + { + return EcsAlertWarning; + } else { return 0; } +} - ecs_add(world, t, EcsBitmask); +static +void MonitorAlerts(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsAlert *alert = ecs_field(it, EcsAlert, 0); + EcsPoly *poly = ecs_field(it, EcsPoly, 1); - ecs_entity_t old_scope = ecs_set_scope(world, t); + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t a = it->entities[i]; /* Alert entity */ + ecs_entity_t default_severity = ecs_get_target( + world, a, ecs_id(EcsAlert), 0); + ecs_query_t *q = poly[i].poly; + if (!q) { + continue; + } - int i; - for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { - const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; - if (!m_desc->name) { - break; + flecs_poly_assert(q, ecs_query_t); + + ecs_id_t member_id = alert[i].id; + const EcsMemberRanges *ranges = NULL; + if (member_id) { + ranges = ecs_ref_get(world, &alert[i].ranges, EcsMemberRanges); } - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = m_desc->name - }); + ecs_iter_t rit = ecs_query_iter(world, q); + rit.flags |= EcsIterNoData; - if (!m_desc->value) { - ecs_add_id(world, c, EcsConstant); - } else { - ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, - {m_desc->value}); + while (ecs_query_next(&rit)) { + ecs_entity_t severity = flecs_alert_get_severity( + world, &rit, &alert[i]); + if (!severity) { + severity = default_severity; + } + + const void *member_data = NULL; + ecs_entity_t member_src = 0; + if (ranges) { + if (alert[i].var_id) { + member_src = ecs_iter_get_var(&rit, alert[i].var_id); + if (!member_src || member_src == EcsWildcard) { + continue; + } + } + if (!member_src) { + member_data = ecs_table_get_id( + world, rit.table, member_id, rit.offset); + } else { + member_data = ecs_get_id(world, member_src, member_id); + } + if (!member_data) { + continue; + } + member_data = ECS_OFFSET(member_data, alert[i].offset); + } + + int32_t j, alert_src_count = rit.count; + for (j = 0; j < alert_src_count; j ++) { + ecs_entity_t src_severity = severity; + ecs_entity_t e = rit.entities[j]; + if (member_data) { + ecs_entity_t range_severity = flecs_alert_out_of_range_kind( + &alert[i], ranges, member_data); + if (!member_src) { + member_data = ECS_OFFSET(member_data, alert[i].size); + } + if (!range_severity) { + continue; + } + if (range_severity < src_severity) { + /* Range severity should not exceed alert severity */ + src_severity = range_severity; + } + } + + ecs_entity_t *aptr = ecs_map_ensure(&alert[i].instances, e); + ecs_assert(aptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (!aptr[0]) { + /* Alert does not yet exist for entity */ + ecs_entity_t ai = ecs_new_w_pair(world, EcsChildOf, a); + ecs_set(world, ai, EcsAlertInstance, { .message = NULL }); + ecs_set(world, ai, EcsMetricSource, { .entity = e }); + ecs_set(world, ai, EcsMetricValue, { .value = 0 }); + ecs_add_pair(world, ai, ecs_id(EcsAlert), src_severity); + if (ECS_NEQZERO(alert[i].retain_period)) { + ecs_set(world, ai, EcsAlertTimeout, { + .inactive_time = 0, + .expire_time = alert[i].retain_period + }); + } + + ecs_defer_suspend(it->world); + flecs_alerts_add_alert_to_src(world, e, a, ai); + ecs_defer_resume(it->world); + aptr[0] = ai; + } else { + /* Make sure alert severity is up to date */ + if (ecs_vec_count(&alert[i].severity_filters) || member_data) { + ecs_entity_t cur_severity = ecs_get_target( + world, aptr[0], ecs_id(EcsAlert), 0); + if (cur_severity != src_severity) { + ecs_add_pair(world, aptr[0], ecs_id(EcsAlert), + src_severity); + } + } + } + } } } +} - ecs_set_scope(world, old_scope); +static +void MonitorAlertInstances(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsAlertInstance *alert_instance = ecs_field(it, EcsAlertInstance, 0); + EcsMetricSource *source = ecs_field(it, EcsMetricSource, 1); + EcsMetricValue *value = ecs_field(it, EcsMetricValue, 2); + EcsAlertTimeout *timeout = ecs_field(it, EcsAlertTimeout, 3); + + /* Get alert component from alert instance parent (the alert) */ + ecs_id_t childof_pair; + if (ecs_search(world, it->table, ecs_childof(EcsWildcard), &childof_pair) == -1) { + ecs_err("alert instances must be a child of an alert"); + return; + } - if (i == 0) { - ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; + ecs_entity_t parent = ecs_pair_second(world, childof_pair); + ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, parent, EcsAlert), ECS_INVALID_OPERATION, + "alert entity does not have Alert component"); + + EcsAlert *alert = ecs_ensure(world, parent, EcsAlert); + const EcsPoly *poly = ecs_get_pair(world, parent, EcsPoly, EcsQuery); + ecs_assert(poly != NULL, ECS_INVALID_OPERATION, + "alert entity does not have (Poly, Query) component"); + + ecs_query_t *query = poly->poly; + if (!query) { + return; } - return t; -} + flecs_poly_assert(query, ecs_query_t); -ecs_entity_t ecs_array_init( - ecs_world_t *world, - const ecs_array_desc_t *desc) -{ - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - return 0; + ecs_id_t member_id = alert->id; + const EcsMemberRanges *ranges = NULL; + if (member_id) { + ranges = ecs_ref_get(world, &alert->ranges, EcsMemberRanges); } - ecs_set(world, t, EcsArray, { - .type = desc->type, - .count = desc->count - }); + ecs_script_vars_t *vars = ecs_script_vars_init(it->world); + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t ai = it->entities[i]; + ecs_entity_t e = source[i].entity; - return t; -} + /* If source of alert is no longer alive, delete alert instance even if + * the alert has a retain period. */ + if (!ecs_is_alive(world, e)) { + ecs_delete(world, ai); + continue; + } -ecs_entity_t ecs_vector_init( - ecs_world_t *world, - const ecs_vector_desc_t *desc) -{ - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - return 0; - } + /* Check if alert instance still matches query */ + ecs_iter_t rit = ecs_query_iter(world, query); + rit.flags |= EcsIterNoData; + ecs_iter_set_var(&rit, 0, e); - ecs_set(world, t, EcsVector, { - .type = desc->type - }); + if (ecs_query_next(&rit)) { + bool match = true; - return t; -} + /* If alert is monitoring member range, test value against range */ + if (ranges) { + ecs_entity_t member_src = e; + if (alert->var_id) { + member_src = ecs_iter_get_var(&rit, alert->var_id); + } -ecs_entity_t ecs_struct_init( - ecs_world_t *world, - const ecs_struct_desc_t *desc) -{ - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - return 0; - } + const void *member_data = ecs_get_id( + world, member_src, member_id); + if (!member_data) { + match = false; + } else { + member_data = ECS_OFFSET(member_data, alert->offset); + if (flecs_alert_out_of_range_kind( + alert, ranges, member_data) == 0) + { + match = false; + } + } + } - ecs_entity_t old_scope = ecs_set_scope(world, t); + if (match) { + /* Only increase alert duration if the alert was active */ + value[i].value += (double)it->delta_system_time; + + bool generate_message = alert->message; + if (generate_message) { + if (alert_instance[i].message) { + /* If a message was already generated, only regenerate if + * query has multiple variables. Variable values could have + * changed, this ensures the message remains up to date. */ + generate_message = ecs_iter_get_var_count(&rit) > 1; + } + } - int i; - for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { - const ecs_member_t *m_desc = &desc->members[i]; - if (!m_desc->type) { - break; + if (generate_message) { + if (alert_instance[i].message) { + ecs_os_free(alert_instance[i].message); + } + + ecs_script_vars_from_iter(&rit, vars, 0); + alert_instance[i].message = ecs_script_string_interpolate( + world, alert->message, vars); + } + + if (timeout) { + if (ECS_NEQZERO(timeout[i].inactive_time)) { + /* The alert just became active. Remove Disabled tag */ + flecs_alerts_add_alert_to_src(world, e, parent, ai); + ecs_remove_id(world, ai, EcsDisabled); + } + timeout[i].inactive_time = 0; + } + + /* Alert instance still matches query, keep it alive */ + ecs_iter_fini(&rit); + continue; + } + + ecs_iter_fini(&rit); } - if (!m_desc->name) { - ecs_err("member %d of struct '%s' does not have a name", i, - ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; + /* Alert instance is no longer active */ + if (timeout) { + if (ECS_EQZERO(timeout[i].inactive_time)) { + /* The alert just became inactive. Add Disabled tag */ + flecs_alerts_remove_alert_from_src(world, e, parent); + ecs_add_id(world, ai, EcsDisabled); + } + ecs_ftime_t t = timeout[i].inactive_time; + timeout[i].inactive_time += it->delta_system_time; + if (t < timeout[i].expire_time) { + /* Alert instance no longer matches query, but is still + * within the timeout period. Keep it alive. */ + continue; + } } - ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = m_desc->name - }); + /* Alert instance no longer matches query, remove it */ + flecs_alerts_remove_alert_from_src(world, e, parent); + ecs_map_remove(&alert->instances, e); + ecs_delete(world, ai); + } - ecs_set(world, m, EcsMember, { - .type = m_desc->type, - .count = m_desc->count, - .offset = m_desc->offset, - .unit = m_desc->unit - }); + ecs_script_vars_fini(vars); +} + +ecs_entity_t ecs_alert_init( + ecs_world_t *world, + const ecs_alert_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_alert_desc_t was not initialized to zero"); + ecs_check(!desc->query.entity || desc->entity == desc->query.entity, + ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new(world); } - ecs_set_scope(world, old_scope); + ecs_query_desc_t private_desc = desc->query; + private_desc.entity = result; - if (i == 0) { - ecs_err("struct '%s' has no members", ecs_get_name(world, t)); - ecs_delete(world, t); + ecs_query_t *q = ecs_query_init(world, &private_desc); + if (!q) { + ecs_err("failed to create alert filter"); return 0; } - if (!ecs_has(world, t, EcsStruct)) { - /* Invalid members */ - ecs_delete(world, t); + if (!(q->flags & EcsQueryMatchThis)) { + ecs_err("alert filter must have at least one '$this' term"); + ecs_query_fini(q); return 0; } - return t; -} + /* Initialize Alert component which identifiers entity as alert */ + EcsAlert *alert = ecs_ensure(world, result, EcsAlert); + ecs_assert(alert != NULL, ECS_INTERNAL_ERROR, NULL); + alert->message = ecs_os_strdup(desc->message); + alert->retain_period = desc->retain_period; -ecs_entity_t ecs_unit_init( - ecs_world_t *world, - const ecs_unit_desc_t *desc) -{ - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { - goto error; + /* Initialize severity filters */ + int32_t i; + for (i = 0; i < 4; i ++) { + if (desc->severity_filters[i].with) { + if (!desc->severity_filters[i].severity) { + ecs_err("severity filter must have severity"); + goto error; + } + ecs_alert_severity_filter_t *sf = ecs_vec_append_t(NULL, + &alert->severity_filters, ecs_alert_severity_filter_t); + *sf = desc->severity_filters[i]; + if (sf->var) { + sf->_var_index = ecs_query_find_var(q, sf->var); + if (sf->_var_index == -1) { + ecs_err("unresolved variable '%s' in alert severity filter", + sf->var); + goto error; + } + } + } } - ecs_entity_t quantity = desc->quantity; - if (quantity) { - if (!ecs_has_id(world, quantity, EcsQuantity)) { - ecs_err("entity '%s' for unit '%s' is not a quantity", - ecs_get_name(world, quantity), ecs_get_name(world, t)); + /* Fetch data for member monitoring */ + if (desc->member) { + alert->member = desc->member; + if (!desc->id) { + alert->id = ecs_get_parent(world, desc->member); + if (!alert->id) { + ecs_err("ecs_alert_desc_t::member is not a member"); + goto error; + } + ecs_check(alert->id != 0, ECS_INVALID_PARAMETER, NULL); + } else { + alert->id = desc->id; + } + + ecs_component_record_t *cr = flecs_components_ensure(world, alert->id); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + if (!cr->type_info) { + ecs_err("ecs_alert_desc_t::id must be a component"); goto error; } - ecs_add_pair(world, t, EcsQuantity, desc->quantity); - } else { - ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); + ecs_entity_t type = cr->type_info->component; + if (type != ecs_get_parent(world, desc->member)) { + char *type_name = ecs_get_path(world, type); + ecs_err("member '%s' is not a member of '%s'", + ecs_get_name(world, desc->member), type_name); + ecs_os_free(type_name); + goto error; + } + + const EcsMember *member = ecs_get(world, alert->member, EcsMember); + if (!member) { + ecs_err("ecs_alert_desc_t::member is not a member"); + goto error; + } + if (!member->type) { + ecs_err("ecs_alert_desc_t::member must have a type"); + goto error; + } + + const EcsPrimitive *pr = ecs_get(world, member->type, EcsPrimitive); + if (!pr) { + ecs_err("ecs_alert_desc_t::member must be of a primitive type"); + goto error; + } + + if (!ecs_has(world, desc->member, EcsMemberRanges)) { + ecs_err("ecs_alert_desc_t::member must have warning/error ranges"); + goto error; + } + + int32_t var_id = 0; + if (desc->var) { + var_id = ecs_query_find_var(q, desc->var); + if (var_id == -1) { + ecs_err("unresolved variable '%s' in alert member", desc->var); + goto error; + } + } + + alert->offset = member->offset; + alert->size = cr->type_info->size; + alert->kind = pr->kind; + alert->ranges = ecs_ref_init(world, desc->member, EcsMemberRanges); + alert->var_id = var_id; } - EcsUnit *value = ecs_get_mut(world, t, EcsUnit, 0); - value->base = desc->base; - value->over = desc->over; - value->translation = desc->translation; - value->prefix = desc->prefix; - ecs_os_strset(&value->symbol, desc->symbol); + ecs_modified(world, result, EcsAlert); - if (!flecs_unit_validate(world, t, value)) { + /* Register alert as metric */ + ecs_add(world, result, EcsMetric); + ecs_add_pair(world, result, EcsMetric, EcsCounter); + + /* Add severity to alert */ + ecs_entity_t severity = desc->severity; + if (!severity) { + severity = EcsAlertError; + } + + ecs_add_pair(world, result, ecs_id(EcsAlert), severity); + + if (desc->doc_name) { +#ifdef FLECS_DOC + ecs_doc_set_name(world, result, desc->doc_name); +#else + ecs_err("cannot set doc_name for alert, requires FLECS_DOC addon"); goto error; +#endif } - ecs_modified(world, t, EcsUnit); + if (desc->brief) { +#ifdef FLECS_DOC + ecs_doc_set_brief(world, result, desc->brief); +#else + ecs_err("cannot set brief for alert, requires FLECS_DOC addon"); + goto error; +#endif + } - return t; + return result; error: - if (t) { - ecs_delete(world, t); + if (result) { + ecs_delete(world, result); } return 0; } -ecs_entity_t ecs_unit_prefix_init( - ecs_world_t *world, - const ecs_unit_prefix_desc_t *desc) +int32_t ecs_get_alert_count( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert) { - ecs_entity_t t = ecs_entity_init(world, &desc->entity); - if (!t) { + flecs_poly_assert(world, ecs_world_t); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!alert || ecs_has(world, alert, EcsAlert), + ECS_INVALID_PARAMETER, NULL); + + const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); + if (!active) { return 0; } - ecs_set(world, t, EcsUnitPrefix, { - .symbol = (char*)desc->symbol, - .translation = desc->translation - }); + if (alert) { + return ecs_map_get(&active->alerts, alert) != NULL; + } - return t; + return ecs_map_count(&active->alerts); +error: + return 0; } -ecs_entity_t ecs_quantity_init( - ecs_world_t *world, - const ecs_entity_desc_t *desc) +ecs_entity_t ecs_get_alert( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert) { - ecs_entity_t t = ecs_entity_init(world, desc); - if (!t) { + flecs_poly_assert(world, ecs_world_t); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(alert != 0, ECS_INVALID_PARAMETER, NULL); + + const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); + if (!active) { return 0; } - ecs_add_id(world, t, EcsQuantity); + ecs_entity_t *ptr = ecs_map_get(&active->alerts, alert); + if (ptr) { + return ptr[0]; + } - return t; +error: + return 0; } -#endif +void FlecsAlertsImport(ecs_world_t *world) { + ECS_MODULE_DEFINE(world, FlecsAlerts); + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsTimer); + ECS_IMPORT(world, FlecsMetrics); -#ifdef FLECS_META + ecs_set_name_prefix(world, "Ecs"); + ECS_COMPONENT_DEFINE(world, EcsAlert); + ecs_remove_pair(world, ecs_id(EcsAlert), ecs_id(EcsIdentifier), EcsSymbol); + ECS_COMPONENT_DEFINE(world, EcsAlertsActive); -static -ecs_vector_t* serialize_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops); + ecs_set_name_prefix(world, "EcsAlert"); + ECS_COMPONENT_DEFINE(world, EcsAlertInstance); + ECS_COMPONENT_DEFINE(world, EcsAlertTimeout); -static -ecs_meta_type_op_kind_t primitive_to_op_kind(ecs_primitive_kind_t kind) { - return EcsOpPrimitive + kind; -} + ECS_TAG_DEFINE(world, EcsAlertInfo); + ECS_TAG_DEFINE(world, EcsAlertWarning); + ECS_TAG_DEFINE(world, EcsAlertError); + ECS_TAG_DEFINE(world, EcsAlertCritical); -static -ecs_size_t type_size(ecs_world_t *world, ecs_entity_t type) { - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - return comp->size; -} + ecs_add_id(world, ecs_id(EcsAlert), EcsPairIsTag); + ecs_add_id(world, ecs_id(EcsAlert), EcsExclusive); -static -ecs_meta_type_op_t* ops_add(ecs_vector_t **ops, ecs_meta_type_op_kind_t kind) { - ecs_meta_type_op_t *op = ecs_vector_add(ops, ecs_meta_type_op_t); - op->kind = kind; - op->offset = 0; - op->count = 1; - op->op_count = 1; - op->size = 0; - op->name = NULL; - op->members = NULL; - op->type = 0; - op->unit = 0; - return op; -} + ecs_struct(world, { + .entity = ecs_id(EcsAlertInstance), + .members = { + { .name = "message", .type = ecs_id(ecs_string_t) } + } + }); -static -ecs_meta_type_op_t* ops_get(ecs_vector_t *ops, int32_t index) { - ecs_meta_type_op_t* op = ecs_vector_get(ops, ecs_meta_type_op_t, index); - ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); - return op; -} + ecs_set_hooks(world, EcsAlert, { + .ctor = ecs_ctor(EcsAlert), + .dtor = ecs_dtor(EcsAlert), + .move = ecs_move(EcsAlert) + }); -static -ecs_vector_t* serialize_primitive( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) -{ - const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_set_hooks(world, EcsAlertsActive, { + .ctor = ecs_ctor(EcsAlertsActive), + .dtor = ecs_dtor(EcsAlertsActive), + .move = ecs_move(EcsAlertsActive) + }); - ecs_meta_type_op_t *op = ops_add(&ops, primitive_to_op_kind(ptr->kind)); - op->offset = offset, - op->type = type; - op->size = type_size(world, type); + ecs_set_hooks(world, EcsAlertInstance, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsAlertInstance), + .move = ecs_move(EcsAlertInstance), + .copy = ecs_copy(EcsAlertInstance) + }); - return ops; -} + ecs_struct(world, { + .entity = ecs_id(EcsAlertsActive), + .members = { + { .name = "info_count", .type = ecs_id(ecs_i32_t) }, + { .name = "warning_count", .type = ecs_id(ecs_i32_t) }, + { .name = "error_count", .type = ecs_id(ecs_i32_t) } + } + }); -static -ecs_vector_t* serialize_enum( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) -{ - (void)world; - - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpEnum); - op->offset = offset, - op->type = type; - op->size = ECS_SIZEOF(ecs_i32_t); + ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, + Alert, + (Poly, Query)); - return ops; -} + ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance, + flecs.metrics.Source, + flecs.metrics.Value, + ?Timeout, + ?Disabled); -static -ecs_vector_t* serialize_bitmask( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) -{ - (void)world; - - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpBitmask); - op->offset = offset, - op->type = type; - op->size = ECS_SIZEOF(ecs_u32_t); + ecs_system(world, { + .entity = ecs_id(MonitorAlerts), + .immediate = true, + .interval = (ecs_ftime_t)0.5 + }); - return ops; + ecs_system(world, { + .entity = ecs_id(MonitorAlertInstances), + .interval = (ecs_ftime_t)0.5 + }); } -static -ecs_vector_t* serialize_array( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) -{ - (void)world; +#endif - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpArray); - op->offset = offset; - op->type = type; - op->size = type_size(world, type); +/** + * @file addons/app.c + * @brief App addon. + */ - return ops; -} + +#ifdef FLECS_APP static -ecs_vector_t* serialize_array_component( +int flecs_default_run_action( ecs_world_t *world, - ecs_entity_t type) + ecs_app_desc_t *desc) { - const EcsArray *ptr = ecs_get(world, type, EcsArray); - if (!ptr) { - return NULL; /* Should never happen, will trigger internal error */ + if (desc->init) { + desc->init(world); } - ecs_vector_t *ops = serialize_type(world, ptr->type, 0, NULL); - ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL); + int result = 0; + if (desc->frames) { + int32_t i; + for (i = 0; i < desc->frames; i ++) { + if ((result = ecs_app_run_frame(world, desc)) != 0) { + break; + } + } + } else { + while ((result = ecs_app_run_frame(world, desc)) == 0) { } + } - ecs_meta_type_op_t *first = ecs_vector_first(ops, ecs_meta_type_op_t); - first->count = ptr->count; + /* Ensure quit flag is set on world, which can be used to determine if + * world needs to be cleaned up. */ +#ifndef __EMSCRIPTEN__ + ecs_quit(world); +#endif - return ops; + if (result == 1) { + return 0; /* Normal exit */ + } else { + return result; /* Error code */ + } } static -ecs_vector_t* serialize_vector( +int flecs_default_frame_action( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) + const ecs_app_desc_t *desc) { - (void)world; - - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpVector); - op->offset = offset; - op->type = type; - op->size = type_size(world, type); - - return ops; + return !ecs_progress(world, desc->delta_time); } -static -ecs_vector_t* serialize_struct( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) -{ - const EcsStruct *ptr = ecs_get(world, type, EcsStruct); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t cur, first = ecs_vector_count(ops); - ecs_meta_type_op_t *op = ops_add(&ops, EcsOpPush); - op->offset = offset; - op->type = type; - op->size = type_size(world, type); - - ecs_member_t *members = ecs_vector_first(ptr->members, ecs_member_t); - int32_t i, count = ecs_vector_count(ptr->members); - - ecs_hashmap_t *member_index = NULL; - if (count) { - op->members = member_index = flecs_name_index_new(); - } +static ecs_app_run_action_t run_action = flecs_default_run_action; +static ecs_app_frame_action_t frame_action = flecs_default_frame_action; +static ecs_app_desc_t ecs_app_desc; - for (i = 0; i < count; i ++) { - ecs_member_t *member = &members[i]; +/* Serve REST API from wasm image when running in emscripten */ +#ifdef ECS_TARGET_EM +#include - cur = ecs_vector_count(ops); - ops = serialize_type(world, member->type, offset + member->offset, ops); +ecs_http_server_t *flecs_wasm_rest_server = NULL; - op = ops_get(ops, cur); - if (!op->type) { - op->type = member->type; - } +EMSCRIPTEN_KEEPALIVE +char* flecs_explorer_request(const char *method, char *request, char *body) { + ecs_assert(flecs_wasm_rest_server != NULL, ECS_INVALID_OPERATION, + "wasm REST server is not initialized yet"); - if (op->count <= 1) { - op->count = member->count; + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; + ecs_http_server_request( + flecs_wasm_rest_server, method, request, body, &reply); + if (reply.code == 200) { + return ecs_strbuf_get(&reply.body); + } else { + char *body = ecs_strbuf_get(&reply.body); + if (body) { + return body; + } else { + return flecs_asprintf( + "{\"error\": \"bad request\", \"status\": %d}", reply.code); } - - const char *member_name = member->name; - op->name = member_name; - op->unit = member->unit; - op->op_count = ecs_vector_count(ops) - cur; - - flecs_name_index_ensure( - member_index, flecs_ito(uint64_t, cur - first - 1), - member_name, 0, 0); } - - ops_add(&ops, EcsOpPop); - ops_get(ops, first)->op_count = ecs_vector_count(ops) - first; - - return ops; } +#endif -static -ecs_vector_t* serialize_type( +int ecs_app_run( ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vector_t *ops) + ecs_app_desc_t *desc) { - const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); - if (!ptr) { - char *path = ecs_get_fullpath(world, type); - ecs_err("missing EcsMetaType for type %s'", path); - ecs_os_free(path); - return NULL; - } - - switch(ptr->kind) { - case EcsPrimitiveType: - ops = serialize_primitive(world, type, offset, ops); - break; - - case EcsEnumType: - ops = serialize_enum(world, type, offset, ops); - break; - - case EcsBitmaskType: - ops = serialize_bitmask(world, type, offset, ops); - break; + ecs_app_desc = *desc; - case EcsStructType: - ops = serialize_struct(world, type, offset, ops); - break; +#ifndef ECS_TARGET_EM + if (ECS_NEQZERO(ecs_app_desc.target_fps)) { + ecs_set_target_fps(world, ecs_app_desc.target_fps); + } + if (ecs_app_desc.threads) { + ecs_set_threads(world, ecs_app_desc.threads); + } +#endif - case EcsArrayType: - ops = serialize_array(world, type, offset, ops); - break; + /* REST server enables connecting to app with explorer */ + if (desc->enable_rest) { +#ifdef FLECS_REST +#ifdef ECS_TARGET_EM + flecs_wasm_rest_server = ecs_rest_server_init(world, NULL); + ecs_assert(flecs_wasm_rest_server != NULL, ECS_INTERNAL_ERROR, + "failed to create wasm REST server (unexpected error)"); +#else + ECS_IMPORT(world, FlecsRest); + ecs_set(world, EcsWorld, EcsRest, {.port = desc->port }); +#endif +#else + ecs_warn("cannot enable remote API, REST addon not available"); +#endif + } - case EcsVectorType: - ops = serialize_vector(world, type, offset, ops); - break; + /* Monitoring periodically collects statistics */ + if (desc->enable_stats) { +#ifdef FLECS_STATS + ECS_IMPORT(world, FlecsStats); +#else + ecs_warn("cannot enable monitoring, MONITOR addon not available"); +#endif } - return ops; + return run_action(world, &ecs_app_desc); } -static -ecs_vector_t* serialize_component( +int ecs_app_run_frame( ecs_world_t *world, - ecs_entity_t type) + const ecs_app_desc_t *desc) { - const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); - if (!ptr) { - char *path = ecs_get_fullpath(world, type); - ecs_err("missing EcsMetaType for type %s'", path); - ecs_os_free(path); - return NULL; - } - - ecs_vector_t *ops = NULL; + return frame_action(world, desc); +} - switch(ptr->kind) { - case EcsArrayType: - ops = serialize_array_component(world, type); - break; - default: - ops = serialize_type(world, type, 0, NULL); - break; +int ecs_app_set_run_action( + ecs_app_run_action_t callback) +{ + if (run_action != flecs_default_run_action && run_action != callback) { + ecs_err("run action already set"); + return -1; } - return ops; + run_action = callback; + + return 0; } -void ecs_meta_type_serialized_init( - ecs_iter_t *it) +int ecs_app_set_frame_action( + ecs_app_frame_action_t callback) { - ecs_world_t *world = it->world; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_vector_t *ops = serialize_component(world, e); - ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL); + if (frame_action != flecs_default_frame_action && frame_action != callback) { + ecs_err("frame action already set"); + return -1; + } - EcsMetaTypeSerialized *ptr = ecs_get_mut( - world, e, EcsMetaTypeSerialized, NULL); - if (ptr->ops) { - ecs_meta_dtor_serialized(ptr); - } + frame_action = callback; - ptr->ops = ops; - } + return 0; } #endif +/** + * @file addons/doc.c + * @brief Doc addon. + */ -#ifdef FLECS_META -/* EcsMetaTypeSerialized lifecycle */ - -void ecs_meta_dtor_serialized( - EcsMetaTypeSerialized *ptr) -{ - int32_t i, count = ecs_vector_count(ptr->ops); - ecs_meta_type_op_t *ops = ecs_vector_first(ptr->ops, ecs_meta_type_op_t); - - for (i = 0; i < count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; - if (op->members) { - flecs_hashmap_fini(op->members); - ecs_os_free(op->members); - } - } - - ecs_vector_free(ptr->ops); -} - -static ECS_COPY(EcsMetaTypeSerialized, dst, src, { - ecs_meta_dtor_serialized(dst); +#ifdef FLECS_DOC - dst->ops = ecs_vector_copy(src->ops, ecs_meta_type_op_t); +static ECS_COPY(EcsDocDescription, dst, src, { + ecs_os_strset((char**)&dst->value, src->value); - int32_t o, count = ecs_vector_count(src->ops); - ecs_meta_type_op_t *ops = ecs_vector_first(src->ops, ecs_meta_type_op_t); - - for (o = 0; o < count; o ++) { - ecs_meta_type_op_t *op = &ops[o]; - if (op->members) { - op->members = ecs_os_memdup_t(op->members, ecs_hashmap_t); - flecs_hashmap_copy(op->members, op->members); - } - } }) -static ECS_MOVE(EcsMetaTypeSerialized, dst, src, { - ecs_meta_dtor_serialized(dst); - dst->ops = src->ops; - src->ops = NULL; +static ECS_MOVE(EcsDocDescription, dst, src, { + ecs_os_free((char*)dst->value); + dst->value = src->value; + src->value = NULL; }) -static ECS_DTOR(EcsMetaTypeSerialized, ptr, { - ecs_meta_dtor_serialized(ptr); +static ECS_DTOR(EcsDocDescription, ptr, { + ecs_os_free((char*)ptr->value); }) - -/* EcsStruct lifecycle */ - -static void dtor_struct( - EcsStruct *ptr) +static +void flecs_doc_set( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t kind, + const char *value) { - ecs_member_t *members = ecs_vector_first(ptr->members, ecs_member_t); - int32_t i, count = ecs_vector_count(ptr->members); - for (i = 0; i < count; i ++) { - ecs_os_free((char*)members[i].name); + if (value) { + ecs_set_pair(world, entity, EcsDocDescription, kind, { + /* Safe, value gets copied by copy hook */ + .value = ECS_CONST_CAST(char*, value) + }); + } else { + ecs_remove_pair(world, entity, ecs_id(EcsDocDescription), kind); } - ecs_vector_free(ptr->members); } -static ECS_COPY(EcsStruct, dst, src, { - dtor_struct(dst); - - dst->members = ecs_vector_copy(src->members, ecs_member_t); - - ecs_member_t *members = ecs_vector_first(dst->members, ecs_member_t); - int32_t m, count = ecs_vector_count(dst->members); +void ecs_doc_set_uuid( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + flecs_doc_set(world, entity, EcsDocUuid, name); +} - for (m = 0; m < count; m ++) { - members[m].name = ecs_os_strdup(members[m].name); - } -}) +void ecs_doc_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + flecs_doc_set(world, entity, EcsName, name); +} -static ECS_MOVE(EcsStruct, dst, src, { - dtor_struct(dst); - dst->members = src->members; - src->members = NULL; -}) +void ecs_doc_set_brief( + ecs_world_t *world, + ecs_entity_t entity, + const char *brief) +{ + flecs_doc_set(world, entity, EcsDocBrief, brief); +} -static ECS_DTOR(EcsStruct, ptr, { dtor_struct(ptr); }) +void ecs_doc_set_detail( + ecs_world_t *world, + ecs_entity_t entity, + const char *detail) +{ + flecs_doc_set(world, entity, EcsDocDetail, detail); +} +void ecs_doc_set_link( + ecs_world_t *world, + ecs_entity_t entity, + const char *link) +{ + flecs_doc_set(world, entity, EcsDocLink, link); +} -/* EcsEnum lifecycle */ +void ecs_doc_set_color( + ecs_world_t *world, + ecs_entity_t entity, + const char *color) +{ + flecs_doc_set(world, entity, EcsDocColor, color); +} -static void dtor_enum( - EcsEnum *ptr) +const char* ecs_doc_get_uuid( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_map_iter_t it = ecs_map_iter(ptr->constants); - ecs_enum_constant_t *c; - while ((c = ecs_map_next(&it, ecs_enum_constant_t, NULL))) { - ecs_os_free((char*)c->name); + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocUuid); + if (ptr) { + return ptr->value; + } else { + return NULL; } - ecs_map_free(ptr->constants); } -static ECS_COPY(EcsEnum, dst, src, { - dtor_enum(dst); - - dst->constants = ecs_map_copy(src->constants); - ecs_assert(ecs_map_count(dst->constants) == ecs_map_count(src->constants), - ECS_INTERNAL_ERROR, NULL); - - ecs_map_iter_t it = ecs_map_iter(dst->constants); - ecs_enum_constant_t *c; - while ((c = ecs_map_next(&it, ecs_enum_constant_t, NULL))) { - c->name = ecs_os_strdup(c->name); +const char* ecs_doc_get_name( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsName); + if (ptr) { + return ptr->value; + } else { + return ecs_get_name(world, entity); } -}) - -static ECS_MOVE(EcsEnum, dst, src, { - dtor_enum(dst); - dst->constants = src->constants; - src->constants = NULL; -}) - -static ECS_DTOR(EcsEnum, ptr, { dtor_enum(ptr); }) - - -/* EcsBitmask lifecycle */ +} -static void dtor_bitmask( - EcsBitmask *ptr) +const char* ecs_doc_get_brief( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_map_iter_t it = ecs_map_iter(ptr->constants); - ecs_bitmask_constant_t *c; - while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, NULL))) { - ecs_os_free((char*)c->name); + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocBrief); + if (ptr) { + return ptr->value; + } else { + return NULL; } - ecs_map_free(ptr->constants); } -static ECS_COPY(EcsBitmask, dst, src, { - dtor_bitmask(dst); - - dst->constants = ecs_map_copy(src->constants); - ecs_assert(ecs_map_count(dst->constants) == ecs_map_count(src->constants), - ECS_INTERNAL_ERROR, NULL); - - ecs_map_iter_t it = ecs_map_iter(dst->constants); - ecs_bitmask_constant_t *c; - while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, NULL))) { - c->name = ecs_os_strdup(c->name); +const char* ecs_doc_get_detail( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocDetail); + if (ptr) { + return ptr->value; + } else { + return NULL; } -}) - -static ECS_MOVE(EcsBitmask, dst, src, { - dtor_bitmask(dst); - dst->constants = src->constants; - src->constants = NULL; -}) - -static ECS_DTOR(EcsBitmask, ptr, { dtor_bitmask(ptr); }) - - -/* EcsUnit lifecycle */ +} -static void dtor_unit( - EcsUnit *ptr) +const char* ecs_doc_get_link( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_os_free(ptr->symbol); + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocLink); + if (ptr) { + return ptr->value; + } else { + return NULL; + } } -static ECS_COPY(EcsUnit, dst, src, { - dtor_unit(dst); - dst->symbol = ecs_os_strdup(src->symbol); - dst->base = src->base; - dst->over = src->over; - dst->prefix = src->prefix; - dst->translation = src->translation; -}) +const char* ecs_doc_get_color( + const ecs_world_t *world, + ecs_entity_t entity) +{ + const EcsDocDescription *ptr = ecs_get_pair( + world, entity, EcsDocDescription, EcsDocColor); + if (ptr) { + return ptr->value; + } else { + return NULL; + } +} -static ECS_MOVE(EcsUnit, dst, src, { - dtor_unit(dst); - dst->symbol = src->symbol; - dst->base = src->base; - dst->over = src->over; - dst->prefix = src->prefix; - dst->translation = src->translation; +void FlecsDocImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsDoc); - src->symbol = NULL; - src->base = 0; - src->over = 0; - src->prefix = 0; - src->translation = (ecs_unit_translation_t){0}; -}) + ecs_set_name_prefix(world, "EcsDoc"); -static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) + flecs_bootstrap_component(world, EcsDocDescription); + flecs_bootstrap_tag(world, EcsDocUuid); + flecs_bootstrap_tag(world, EcsDocBrief); + flecs_bootstrap_tag(world, EcsDocDetail); + flecs_bootstrap_tag(world, EcsDocLink); + flecs_bootstrap_tag(world, EcsDocColor); + ecs_set_hooks(world, EcsDocDescription, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsDocDescription), + .copy = ecs_copy(EcsDocDescription), + .dtor = ecs_dtor(EcsDocDescription) + }); -/* EcsUnitPrefix lifecycle */ +#ifdef FLECS_META + ecs_struct(world, { + .entity = ecs_id(EcsDocDescription), + .members = { + { .name = "value", .type = ecs_id(ecs_string_t) } + } + }); +#endif -static void dtor_unit_prefix( - EcsUnitPrefix *ptr) -{ - ecs_os_free(ptr->symbol); + ecs_add_pair(world, ecs_id(EcsDocDescription), EcsOnInstantiate, EcsDontInherit); } -static ECS_COPY(EcsUnitPrefix, dst, src, { - dtor_unit_prefix(dst); - dst->symbol = ecs_os_strdup(src->symbol); - dst->translation = src->translation; -}) +#endif -static ECS_MOVE(EcsUnitPrefix, dst, src, { - dtor_unit_prefix(dst); - dst->symbol = src->symbol; - dst->translation = src->translation; +/** + * @file addons/flecs_cpp.c + * @brief Utilities for C++ addon. + */ - src->symbol = NULL; - src->translation = (ecs_unit_translation_t){0}; -}) -static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) +/* Utilities for C++ API */ + +#ifdef FLECS_CPP + +/* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to + * a uniform identifier */ +#define ECS_CONST_PREFIX "const " +#define ECS_STRUCT_PREFIX "struct " +#define ECS_CLASS_PREFIX "class " +#define ECS_ENUM_PREFIX "enum " -/* Type initialization */ +#define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX)) +#define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX)) +#define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX)) +#define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX)) static -int init_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_type_kind_t kind, - ecs_size_t size, - ecs_size_t alignment) +ecs_size_t ecs_cpp_strip_prefix( + char *typeName, + ecs_size_t len, + const char *prefix, + ecs_size_t prefix_len) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) { + ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len); + typeName[len - prefix_len] = '\0'; + len -= prefix_len; + } + return len; +} - bool is_added = false; - EcsMetaType *meta_type = ecs_get_mut(world, type, EcsMetaType, &is_added); - if (is_added) { - meta_type->existing = ecs_has(world, type, EcsComponent); +static +void ecs_cpp_trim_type_name( + char *typeName) +{ + ecs_size_t len = ecs_os_strlen(typeName); - /* Ensure that component has a default constructor, to prevent crashing - * serializers on uninitialized values. */ - ecs_type_info_t *ti = flecs_ensure_type_info(world, type); - if (!ti->lifecycle.ctor) { - ti->lifecycle.ctor = ecs_default_ctor; - } - } else { - if (meta_type->kind != kind) { - ecs_err("type '%s' reregistered with different kind", - ecs_get_name(world, type)); - return -1; - } + len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN); + len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN); + len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN); + len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN); + + while (typeName[len - 1] == ' ' || + typeName[len - 1] == '&' || + typeName[len - 1] == '*') + { + len --; + typeName[len] = '\0'; } - if (!meta_type->existing) { - EcsComponent *comp = ecs_get_mut(world, type, EcsComponent, NULL); - comp->size = size; - comp->alignment = alignment; - ecs_modified(world, type, EcsComponent); - } else { - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - if (comp->size < size) { - ecs_err("computed size for '%s' is larger than actual type", - ecs_get_name(world, type)); - return -1; + /* Remove const at end of string */ + if (len > ECS_CONST_LEN) { + if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) { + typeName[len - ECS_CONST_LEN] = '\0'; } - if (comp->alignment < alignment) { - ecs_err("computed alignment for '%s' is larger than actual type", - ecs_get_name(world, type)); - return -1; + len -= ECS_CONST_LEN; + } + + /* Check if there are any remaining "struct " strings, which can happen + * if this is a template type on msvc. */ + if (len > ECS_STRUCT_LEN) { + char *ptr = typeName; + while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) { + /* Make sure we're not matched with part of a longer identifier + * that contains 'struct' */ + if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) { + ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN, + ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1); + len -= ECS_STRUCT_LEN; + } } - if (comp->size == size && comp->alignment != alignment) { - ecs_err("computed size for '%s' matches with actual type but " - "alignment is different", ecs_get_name(world, type)); - return -1; + } +} + +char* ecs_cpp_get_type_name( + char *type_name, + const char *func_name, + size_t len, + size_t front_len) +{ + memcpy(type_name, func_name + front_len, len); + type_name[len] = '\0'; + ecs_cpp_trim_type_name(type_name); + return type_name; +} + +char* ecs_cpp_get_symbol_name( + char *symbol_name, + const char *type_name, + size_t len) +{ + const char *ptr; + size_t i; + for (i = 0, ptr = type_name; i < len && *ptr; i ++, ptr ++) { + if (*ptr == ':') { + symbol_name[i] = '.'; + ptr ++; + } else { + symbol_name[i] = *ptr; } - - meta_type->partial = comp->size != size; } - meta_type->kind = kind; - meta_type->size = size; - meta_type->alignment = alignment; - ecs_modified(world, type, EcsMetaType); + symbol_name[i] = '\0'; - return 0; + return symbol_name; } -#define init_type_t(world, type, kind, T) \ - init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) - static -void set_struct_member( - ecs_member_t *member, - ecs_entity_t entity, - const char *name, - ecs_entity_t type, - int32_t count, - int32_t offset, - ecs_entity_t unit) +const char* flecs_cpp_func_rchr( + const char *func_name, + ecs_size_t func_name_len, + ecs_size_t func_back_len, + char ch) { - member->member = entity; - member->type = type; - member->count = count; - member->unit = unit; - member->offset = offset; - - if (!count) { - member->count = 1; + const char *r = strrchr(func_name, ch); + if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, func_back_len))) { + return NULL; } - - ecs_os_strset((char**)&member->name, name); + return r; } static -int add_member_to_struct( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t member, - EcsMember *m) +const char* flecs_cpp_func_max( + const char *a, + const char *b) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(member != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + if (a > b) return a; + return b; +} - const char *name = ecs_get_name(world, member); - if (!name) { - char *path = ecs_get_fullpath(world, type); - ecs_err("member for struct '%s' does not have a name", path); - ecs_os_free(path); - return -1; - } +char* ecs_cpp_get_constant_name( + char *constant_name, + const char *func_name, + size_t func_name_len, + size_t func_back_len) +{ + ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); + ecs_size_t fb_len = flecs_uto(ecs_size_t, func_back_len); + const char *start = flecs_cpp_func_rchr(func_name, f_len, fb_len, ' '); + start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( + func_name, f_len, fb_len, ')')); + start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( + func_name, f_len, fb_len, ':')); + start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( + func_name, f_len, fb_len, ',')); + ecs_assert(start != NULL, ECS_INVALID_PARAMETER, func_name); + start ++; + + ecs_size_t len = flecs_uto(ecs_size_t, + (f_len - (start - func_name) - fb_len)); + ecs_os_memcpy_n(constant_name, start, char, len); + constant_name[len] = '\0'; + return constant_name; +} - if (!m->type) { - char *path = ecs_get_fullpath(world, member); - ecs_err("member '%s' does not have a type", path); - ecs_os_free(path); - return -1; +// Names returned from the name_helper class do not start with :: +// but are relative to the root. If the namespace of the type +// overlaps with the namespace of the current module, strip it from +// the implicit identifier. +// This allows for registration of component types that are not in the +// module namespace to still be registered under the module scope. +const char* ecs_cpp_trim_module( + ecs_world_t *world, + const char *type_name) +{ + ecs_entity_t scope = ecs_get_scope(world); + if (!scope) { + return type_name; } - if (ecs_get_typeid(world, m->type) == 0) { - char *path = ecs_get_fullpath(world, member); - char *ent_path = ecs_get_fullpath(world, m->type); - ecs_err("member '%s.type' is '%s' which is not a type", path, ent_path); - ecs_os_free(path); - ecs_os_free(ent_path); - return -1; + char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); + if (path) { + ecs_size_t len = ecs_os_strlen(path); + if (!ecs_os_strncmp(path, type_name, len) && type_name[len] == ':') { + // Type is a child of current parent, trim name of parent + type_name += len; + ecs_assert(type_name[0], ECS_INVALID_PARAMETER, + "invalid C++ type name"); + ecs_assert(type_name[0] == ':', ECS_INVALID_PARAMETER, + "invalid C++ type name"); + ecs_assert(type_name[1] == ':', ECS_INVALID_PARAMETER, + "invalid C++ type name"); + type_name += 2; + } else { + // Type is not a child of current parent, trim entire path + char *ptr = strrchr(type_name, ':'); + if (ptr) { + type_name = ptr + 1; + } + + } } - ecs_entity_t unit = m->unit; + ecs_os_free(path); - if (unit) { - if (!ecs_has(world, unit, EcsUnit)) { - ecs_err("entity '%s' for member '%s' is not a unit", - ecs_get_name(world, unit), name); - return -1; - } + return type_name; +} - if (ecs_has(world, m->type, EcsUnit) && m->type != unit) { - ecs_err("unit mismatch for type '%s' and unit '%s' for member '%s'", - ecs_get_name(world, m->type), ecs_get_name(world, unit), name); - return -1; - } +ecs_entity_t ecs_cpp_component_register( + ecs_world_t *world, + ecs_entity_t id, + int32_t ids_index, + const char *name, + const char *cpp_name, + const char *cpp_symbol, + size_t size, + size_t alignment, + bool is_component, + bool explicit_registration, + bool *registered_out, + bool *existing_out) +{ + ecs_assert(registered_out != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(existing_out != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t c = flecs_component_ids_get(world, ids_index); + + if (!c || !ecs_is_alive(world, c)) { } else { - if (ecs_has(world, m->type, EcsUnit)) { - unit = m->type; - m->unit = unit; - } + return c; } - EcsStruct *s = ecs_get_mut(world, type, EcsStruct, NULL); - ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); + const char *user_name = NULL; + bool implicit_name = true; + ecs_entity_t module = 0; - /* First check if member is already added to struct */ - ecs_member_t *members = ecs_vector_first(s->members, ecs_member_t); - int32_t i, count = ecs_vector_count(s->members); - for (i = 0; i < count; i ++) { - if (members[i].member == member) { - set_struct_member( - &members[i], member, name, m->type, m->count, m->offset, unit); - break; + if (explicit_registration) { + user_name = name; + implicit_name = false; + + if (!user_name) { + user_name = cpp_name; + + /* Keep track of whether name was explicitly set. If not, and + * the component was already registered, just use the registered + * name. The registered name may differ from the typename as the + * registered name includes the flecs scope. This can in theory + * be different from the C++ namespace though it is good + * practice to keep them the same */ + implicit_name = true; + } + + /* If component is registered by module, ensure it's registered in + * the scope of the module. */ + module = ecs_get_scope(world); + + /* Strip off the namespace part of the component name, unless a name + * was explicitly provided by the application. */ + if (module && implicit_name) { + /* If the type is a template type, make sure to ignore :: + * inside the template parameter list. */ + const char *start = strchr(user_name, '<'), *last_elem = NULL; + if (start) { + const char *ptr = start; + while (ptr[0] && (ptr[0] != ':') && (ptr > user_name)) { + ptr --; + } + if (ptr[0] == ':') { + last_elem = ptr; + } + } + + if (last_elem) { + name = last_elem + 1; + } } } - /* If member wasn't added yet, add a new element to vector */ - if (i == count) { - ecs_member_t *elem = ecs_vector_add(&s->members, ecs_member_t); - elem->name = NULL; - set_struct_member(elem, member, name, m->type, - m->count, m->offset, unit); - - /* Reobtain members array in case it was reallocated */ - members = ecs_vector_first(s->members, ecs_member_t); - count ++; - } + /* At this point it is possible that the type was already registered + * with the world, just not for this binary. The registration code + * uses the type symbol to check if it was already registered. Note + * that the symbol is separate from the typename, as an application + * can override a component name when registering a type. */ - bool explicit_offset = false; - if (m->offset) { - explicit_offset = true; + /* If the component is not yet registered, ensure no other component + * or entity has been registered with this name. Ensure component is + * looked up from root. */ + ecs_entity_t prev_scope = ecs_set_scope(world, 0); + if (id) { + c = id; + } else { + c = ecs_lookup_path_w_sep(world, 0, user_name, "::", "::", false); + *existing_out = c != 0 && ecs_has(world, c, EcsComponent); } + ecs_set_scope(world, prev_scope); - /* Compute member offsets and size & alignment of struct */ - ecs_size_t size = 0; - ecs_size_t alignment = 0; - - if (!explicit_offset) { - for (i = 0; i < count; i ++) { - ecs_member_t *elem = &members[i]; + /* If entity exists, compare symbol name to ensure that the component + * we are trying to register under this name is the same */ + if (c) { + const EcsComponent *component = ecs_get(world, c, EcsComponent); + if (component != NULL) { + const char *sym = ecs_get_symbol(world, c); + if (sym && ecs_os_strcmp(sym, cpp_symbol)) { + /* Application is trying to register a type with an entity + * that was already associated with another type. In most + * cases this is an error, with the exception of a scenario + * where the application is wrapping a C type with a C++ + * type. + * + * In this case the C++ type typically inherits from the C + * type, and adds convenience methods to the derived class + * without changing anything that would change the size or + * layout. + * + * To meet this condition, the new type must have the same + * size and alignment as the existing type, and the name of + * the type type must be equal to the registered name. + * + * The latter ensures that it was the intent of the + * application to alias the type, vs. accidentally + * registering an unrelated type with the same + * size/alignment. */ + char *type_path = ecs_get_path(world, c); + if (ecs_os_strcmp(type_path, cpp_symbol)) { + ecs_err( + "component with name '%s' is already registered for"\ + " type '%s' (trying to register for type '%s')", + name, sym, cpp_symbol); + ecs_abort(ECS_NAME_IN_USE, NULL); + } - ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); + if (flecs_itosize(component->size) != size || + flecs_itosize(component->alignment) != alignment) + { + ecs_err( + "component with name '%s' is already registered with"\ + " mismatching size/alignment)", name); + ecs_abort(ECS_INVALID_COMPONENT_SIZE, NULL); + } - /* Get component of member type to get its size & alignment */ - const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); - if (!mbr_comp) { - char *path = ecs_get_fullpath(world, member); - ecs_err("member '%s' is not a type", path); - ecs_os_free(path); - return -1; + ecs_os_free(type_path); + } else if (!sym) { + ecs_set_symbol(world, c, cpp_symbol); } + } - ecs_size_t member_size = mbr_comp->size; - ecs_size_t member_alignment = mbr_comp->alignment; + /* If no entity is found, lookup symbol to check if the component was + * registered under a different name. */ + } else if (!implicit_name) { + c = ecs_lookup_symbol(world, cpp_symbol, false, false); + ecs_assert(c == 0 || (c == id), + ECS_INCONSISTENT_COMPONENT_ID, cpp_symbol); + } - if (!member_size || !member_alignment) { - char *path = ecs_get_fullpath(world, member); - ecs_err("member '%s' has 0 size/alignment"); - ecs_os_free(path); - return -1; - } + const char *symbol = NULL; - member_size *= elem->count; - size = ECS_ALIGN(size, member_alignment); - elem->size = member_size; - elem->offset = size; + if (c) { + symbol = ecs_get_symbol(world, c); + } - size += member_size; + if (!symbol) { + symbol = cpp_symbol; + } - if (member_alignment > alignment) { - alignment = member_alignment; + /* When a component is implicitly registered, ensure that it's not + * registered in the current scope of the application/that "with" + * components get added to the component entity. */ + prev_scope = ecs_set_scope(world, module); + ecs_entity_t prev_with = ecs_set_with(world, 0); + char *existing_name = NULL; + + /* If an explicit id is provided, it is possible that the symbol and + * name differ from the actual type, as the application may alias + * one type to another. */ + if (!c) { + if (!name) { + /* If no name was provided first check if a type with the + * provided symbol was already registered. */ + ecs_id_t e = ecs_lookup_symbol(world, symbol, false, false); + if (e) { + existing_name = ecs_get_path_w_sep(world, 0, e, "::", "::"); + name = existing_name; + *existing_out = true; + } else { + /* If type is not yet known, derive from type name */ + name = ecs_cpp_trim_module(world, cpp_name); } } } else { - /* If members have explicit offsets, we can't rely on computed - * size/alignment values. Grab size of just added member instead. It - * doesn't matter if the size doesn't correspond to the actual struct - * size. The init_type function compares computed size with actual - * (component) size to determine if the type is partial. */ - const EcsComponent *cptr = ecs_get(world, m->type, EcsComponent); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); - size = cptr->size; - alignment = cptr->alignment; - } - - if (size == 0) { - ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); - return -1; + /* If an explicit id is provided but it has no name, inherit + * the name from the type. */ + if (!ecs_is_valid(world, c) || !ecs_get_name(world, c)) { + name = ecs_cpp_trim_module(world, cpp_name); + } } - if (alignment == 0) { - ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); - return -1; - } + if (is_component || size != 0) { + c = ecs_entity(world, { + .id = c, + .name = name, + .sep = "::", + .root_sep = "::", + .symbol = symbol, + .use_low_id = true + }); - /* Align struct size to struct alignment */ - size = ECS_ALIGN(size, alignment); + ecs_assert(c != 0, ECS_INVALID_OPERATION, + "registration failed for component %s", name); - ecs_modified(world, type, EcsStruct); + c = ecs_component_init(world, &(ecs_component_desc_t){ + .entity = c, + .type.size = flecs_uto(int32_t, size), + .type.alignment = flecs_uto(int32_t, alignment) + }); - /* Do this last as it triggers the update of EcsMetaTypeSerialized */ - if (init_type(world, type, EcsStructType, size, alignment)) { - return -1; + ecs_assert(c != 0, ECS_INVALID_OPERATION, + "registration failed for component %s", name); + } else { + c = ecs_entity(world, { + .id = c, + .name = name, + .sep = "::", + .root_sep = "::", + .symbol = symbol, + .use_low_id = true + }); } - /* If current struct is also a member, assign to itself */ - if (ecs_has(world, type, EcsMember)) { - EcsMember *type_mbr = ecs_get_mut(world, type, EcsMember, NULL); - ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(existing_name); - type_mbr->type = type; - type_mbr->count = 1; + ecs_set_with(world, prev_with); + ecs_set_scope(world, prev_scope); - ecs_modified(world, type, EcsMember); - } + /* Set world local component id */ + flecs_component_ids_set(world, ids_index, c); - return 0; + *registered_out = true; + + return c; } -static -int add_constant_to_enum( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t e, - ecs_id_t constant_id) +void ecs_cpp_enum_init( + ecs_world_t *world, + ecs_entity_t id, + ecs_entity_t underlying_type) { - EcsEnum *ptr = ecs_get_mut(world, type, EcsEnum, NULL); - - /* Remove constant from map if it was already added */ - ecs_map_iter_t it = ecs_map_iter(ptr->constants); - ecs_enum_constant_t *c; - ecs_map_key_t key; - while ((c = ecs_map_next(&it, ecs_enum_constant_t, &key))) { - if (c->constant == e) { - ecs_os_free((char*)c->name); - ecs_map_remove(ptr->constants, key); - } - } + (void)world; + (void)id; + (void)underlying_type; +#ifdef FLECS_META + ecs_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); + EcsEnum *ptr = ecs_ensure(world, id, EcsEnum); + ptr->underlying_type = underlying_type; + ecs_modified(world, id, EcsEnum); + flecs_resume_readonly(world, &readonly_state); +#else + /* Make sure that enums still behave the same even without meta */ + ecs_add_id(world, id, EcsExclusive); + ecs_add_id(world, id, EcsOneOf); +#endif +} - /* Check if constant sets explicit value */ - int32_t value = 0; - bool value_set = false; - if (ecs_id_is_pair(constant_id)) { - if (ecs_pair_second(world, constant_id) != ecs_id(ecs_i32_t)) { - char *path = ecs_get_fullpath(world, e); - ecs_err("expected i32 type for enum constant '%s'", path); - ecs_os_free(path); - return -1; - } +ecs_entity_t ecs_cpp_enum_constant_register( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t id, + const char *name, + void *value, + ecs_entity_t value_type, + size_t value_size) +{ + ecs_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); - const int32_t *value_ptr = ecs_get_pair_object( - world, e, EcsConstant, ecs_i32_t); - ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - value = *value_ptr; - value_set = true; - } + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); - /* Make sure constant value doesn't conflict if set / find the next value */ - it = ecs_map_iter(ptr->constants); - while ((c = ecs_map_next(&it, ecs_enum_constant_t, &key))) { - if (value_set) { - if (c->value == value) { - char *path = ecs_get_fullpath(world, e); - ecs_err("conflicting constant value for '%s' (other is '%s')", - path, c->name); - ecs_os_free(path); - return -1; - } - } else { - if (c->value >= value) { - value = c->value + 1; - } + const char *parent_name = ecs_get_name(world, parent); + ecs_size_t parent_name_len = ecs_os_strlen(parent_name); + if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { + name += parent_name_len; + if (name[0] == '_') { + name ++; } } - if (!ptr->constants) { - ptr->constants = ecs_map_new(ecs_enum_constant_t, 1); - } - - c = ecs_map_ensure(ptr->constants, ecs_enum_constant_t, value); - c->name = ecs_os_strdup(ecs_get_name(world, e)); - c->value = value; - c->constant = e; - - ecs_i32_t *cptr = ecs_get_mut_pair_object( - world, e, EcsConstant, ecs_i32_t, NULL); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); - cptr[0] = value; + ecs_entity_t prev = ecs_set_scope(world, parent); + id = ecs_entity(world, { + .id = id, + .name = name + }); + ecs_assert(id != 0, ECS_INVALID_OPERATION, name); + ecs_set_scope(world, prev); - return 0; -} +#ifdef FLECS_DEBUG + const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); + ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); +#endif -static -int add_constant_to_bitmask( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t e, - ecs_id_t constant_id) -{ - EcsBitmask *ptr = ecs_get_mut(world, type, EcsBitmask, NULL); - - /* Remove constant from map if it was already added */ - ecs_map_iter_t it = ecs_map_iter(ptr->constants); - ecs_bitmask_constant_t *c; - ecs_map_key_t key; - while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { - if (c->constant == e) { - ecs_os_free((char*)c->name); - ecs_map_remove(ptr->constants, key); - } - } + ecs_set_id(world, id, ecs_pair(EcsConstant, value_type), value_size, value); - /* Check if constant sets explicit value */ - uint32_t value = 1; - if (ecs_id_is_pair(constant_id)) { - if (ecs_pair_second(world, constant_id) != ecs_id(ecs_u32_t)) { - char *path = ecs_get_fullpath(world, e); - ecs_err("expected u32 type for bitmask constant '%s'", path); - ecs_os_free(path); - return -1; - } + flecs_resume_readonly(world, &readonly_state); - const uint32_t *value_ptr = ecs_get_pair_object( - world, e, EcsConstant, ecs_u32_t); - ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - value = *value_ptr; - } else { - value = 1u << (ecs_u32_t)ecs_map_count(ptr->constants); +#ifdef FLECS_META + if (ecs_should_log(0)) { + ecs_value_t v = { .type = value_type, .ptr = value }; + char *str = NULL; + ecs_meta_cursor_t cur = ecs_meta_cursor(world, + ecs_id(ecs_string_t), &str); + ecs_meta_set_value(&cur, &v); + ecs_trace("#[green]constant#[reset] %s.%s created with value %s", + ecs_get_name(world, parent), name, str); + ecs_os_free(str); } +#endif - /* Make sure constant value doesn't conflict */ - it = ecs_map_iter(ptr->constants); - while ((c = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { - if (c->value == value) { - char *path = ecs_get_fullpath(world, e); - ecs_err("conflicting constant value for '%s' (other is '%s')", - path, c->name); - ecs_os_free(path); - return -1; - } - } + return id; +} - if (!ptr->constants) { - ptr->constants = ecs_map_new(ecs_bitmask_constant_t, 1); +#ifdef FLECS_META +ecs_member_t* ecs_cpp_last_member( + const ecs_world_t *world, + ecs_entity_t type) +{ + const EcsStruct *st = ecs_get(world, type, EcsStruct); + if (!st) { + char *type_str = ecs_get_path(world, type); + ecs_err("entity '%s' is not a struct", type_str); + ecs_os_free(type_str); + return 0; } - c = ecs_map_ensure(ptr->constants, ecs_bitmask_constant_t, value); - c->name = ecs_os_strdup(ecs_get_name(world, e)); - c->value = value; - c->constant = e; - - ecs_u32_t *cptr = ecs_get_mut_pair_object( - world, e, EcsConstant, ecs_u32_t, NULL); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); - cptr[0] = value; + ecs_member_t *m = ecs_vec_get_t(&st->members, ecs_member_t, + ecs_vec_count(&st->members) - 1); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); - return 0; + return m; } +#endif -static -void set_primitive(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsPrimitive *type = ecs_term(it, EcsPrimitive, 1); +ecs_cpp_get_mut_t ecs_cpp_set( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + const void *new_ptr, + size_t size) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - switch(type->kind) { - case EcsBool: - init_type_t(world, e, EcsPrimitiveType, bool); - break; - case EcsChar: - init_type_t(world, e, EcsPrimitiveType, char); - break; - case EcsByte: - init_type_t(world, e, EcsPrimitiveType, bool); - break; - case EcsU8: - init_type_t(world, e, EcsPrimitiveType, uint8_t); - break; - case EcsU16: - init_type_t(world, e, EcsPrimitiveType, uint16_t); - break; - case EcsU32: - init_type_t(world, e, EcsPrimitiveType, uint32_t); - break; - case EcsU64: - init_type_t(world, e, EcsPrimitiveType, uint64_t); - break; - case EcsI8: - init_type_t(world, e, EcsPrimitiveType, int8_t); - break; - case EcsI16: - init_type_t(world, e, EcsPrimitiveType, int16_t); - break; - case EcsI32: - init_type_t(world, e, EcsPrimitiveType, int32_t); - break; - case EcsI64: - init_type_t(world, e, EcsPrimitiveType, int64_t); - break; - case EcsF32: - init_type_t(world, e, EcsPrimitiveType, float); - break; - case EcsF64: - init_type_t(world, e, EcsPrimitiveType, double); - break; - case EcsUPtr: - init_type_t(world, e, EcsPrimitiveType, uintptr_t); - break; - case EcsIPtr: - init_type_t(world, e, EcsPrimitiveType, intptr_t); - break; - case EcsString: - init_type_t(world, e, EcsPrimitiveType, char*); - break; - case EcsEntity: - init_type_t(world, e, EcsPrimitiveType, ecs_entity_t); - break; - } + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_cpp_get_mut_t result; + + if (flecs_defer_cmd(stage)) { + result.ptr = flecs_defer_cpp_set(world, stage, entity, id, + flecs_utosize(size), new_ptr); + /* Modified command is already inserted */ + result.call_modified = false; + result.stage = NULL; + return result; } -} -static -void set_member(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsMember *member = ecs_term(it, EcsMember, 1); + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r, + flecs_uto(int32_t, size)); + + result.ptr = dst.ptr; + result.world = world; + result.stage = stage; - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t parent = ecs_get_object(world, e, EcsChildOf, 0); - if (!parent) { - ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); - continue; + if (id < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_set[id]) { + result.call_modified = false; + goto done; } - - add_member_to_struct(world, parent, e, &member[i]); } -} - -static -void add_enum(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - if (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { - continue; - } + /* Not deferring, so need to call modified after setting the component */ + result.call_modified = true; - ecs_add_id(world, e, EcsExclusive); - ecs_add_id(world, e, EcsOneOf); - ecs_add_id(world, e, EcsTag); + if (dst.ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, entity, id, dst.ptr, new_ptr, dst.ti); } -} -static -void add_bitmask(ecs_iter_t *it) { - ecs_world_t *world = it->world; +done: + return result; +error: + return (ecs_cpp_get_mut_t){0}; +} - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; +ecs_cpp_get_mut_t ecs_cpp_assign( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + const void *new_ptr, + size_t size) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - if (init_type_t(world, e, EcsBitmaskType, ecs_u32_t)) { - continue; - } + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_cpp_get_mut_t result; + + if (flecs_defer_cmd(stage)) { + result.ptr = flecs_defer_cpp_assign( + world, stage, entity, id, flecs_uto(int32_t, size), new_ptr); + /* Modified command is already inserted */ + result.call_modified = false; + result.stage = NULL; + return result; } -} -static -void add_constant(ecs_iter_t *it) { - ecs_world_t *world = it->world; + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r, + flecs_uto(int32_t, size)); - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t parent = ecs_get_object(world, e, EcsChildOf, 0); - if (!parent) { - ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); - continue; - } + ecs_assert(dst.ptr != NULL, ECS_INVALID_OPERATION, + "entity does not have component, use set() instead"); + + result.ptr = dst.ptr; + result.world = world; + result.stage = stage; - if (ecs_has(world, parent, EcsEnum)) { - add_constant_to_enum(world, parent, e, it->event_id); - } else if (ecs_has(world, parent, EcsBitmask)) { - add_constant_to_bitmask(world, parent, e, it->event_id); + if (id < FLECS_HI_COMPONENT_ID) { + if (!world->non_trivial_set[id]) { + result.call_modified = false; + goto done; } } -} -static -void set_array(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsArray *array = ecs_term(it, EcsArray, 1); + /* Not deferring, so need to call modified after setting the component */ + result.call_modified = true; - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t elem_type = array[i].type; - int32_t elem_count = array[i].count; + if (dst.ti->hooks.on_replace) { + flecs_invoke_replace_hook( + world, r->table, entity, id, dst.ptr, new_ptr, dst.ti); + } - if (!elem_type) { - ecs_err("array '%s' has no element type", ecs_get_name(world, e)); - continue; - } +done: + return result; +error: + return (ecs_cpp_get_mut_t){0}; +} - if (!elem_count) { - ecs_err("array '%s' has size 0", ecs_get_name(world, e)); - continue; - } +ecs_entity_t ecs_cpp_new( + ecs_world_t *world, + ecs_entity_t parent, + const char *name, + const char *sep, + const char *root_sep) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); - const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); - if (init_type(world, e, EcsArrayType, - elem_ptr->size * elem_count, elem_ptr->alignment)) - { - continue; + if (!parent && !name) { + if (!stage->scope && !stage->with) { + ecs_entity_t result = flecs_new_id(world); + flecs_add_to_root_table(world, result); + return result; } } + + ecs_entity_desc_t desc = {0}; + desc.parent = parent; + desc.name = name; + desc.sep = sep; + desc.root_sep = root_sep; + return ecs_entity_init(world, &desc); } -static -void set_vector(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsVector *array = ecs_term(it, EcsVector, 1); +#endif - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t elem_type = array[i].type; +/** + * @file addons/journal.c + * @brief Journal addon. + */ - if (!elem_type) { - ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); - continue; - } - if (init_type_t(world, e, EcsVectorType, ecs_vector_t*)) { - continue; +#ifdef FLECS_JOURNAL + +static +char* flecs_journal_entitystr( + ecs_world_t *world, + ecs_entity_t entity) +{ + char *path; + const char *_path = ecs_get_symbol(world, entity); + if (_path && !strchr(_path, '.')) { + path = flecs_asprintf("#[blue]%s", _path); + } else { + uint32_t gen = entity >> 32; + if (gen) { + path = flecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen); + } else { + path = flecs_asprintf("#[normal]_%u", (uint32_t)entity); } } + return path; } -bool flecs_unit_validate( +static +char* flecs_journal_idstr( ecs_world_t *world, - ecs_entity_t t, - EcsUnit *data) + ecs_id_t id) { - char *derived_symbol = NULL; - const char *symbol = data->symbol; - - ecs_entity_t base = data->base; - ecs_entity_t over = data->over; - ecs_entity_t prefix = data->prefix; - ecs_unit_translation_t translation = data->translation; - - if (base) { - if (!ecs_has(world, base, EcsUnit)) { - ecs_err("entity '%s' for unit '%s' used as base is not a unit", - ecs_get_name(world, base), ecs_get_name(world, t)); - goto error; - } + if (ECS_IS_VALUE_PAIR(id)) { + char *first_path = flecs_journal_entitystr(world, + ecs_pair_first(world, id)); + char *result = flecs_asprintf("#[cyan]ecs_value_pair#[normal](%s, %u)", + first_path, ECS_PAIR_SECOND(id)); + ecs_os_free(first_path); + return result; + } else if (ECS_IS_PAIR(id)) { + char *first_path = flecs_journal_entitystr(world, + ecs_pair_first(world, id)); + char *second_path = flecs_journal_entitystr(world, + ecs_pair_second(world, id)); + char *result = flecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)", + first_path, second_path); + ecs_os_free(first_path); + ecs_os_free(second_path); + return result; + } else if (!(id & ECS_ID_FLAGS_MASK)) { + return flecs_journal_entitystr(world, id); + } else { + return ecs_id_str(world, id); } +} - if (over) { - if (!base) { - ecs_err("invalid unit '%s': cannot specify over without base", - ecs_get_name(world, t)); - goto error; - } - if (!ecs_has(world, over, EcsUnit)) { - ecs_err("entity '%s' for unit '%s' used as over is not a unit", - ecs_get_name(world, over), ecs_get_name(world, t)); - goto error; - } - } +static int flecs_journal_counter = 0; +static int flecs_journal_sp = 0; - if (prefix) { - if (!base) { - ecs_err("invalid unit '%s': cannot specify prefix without base", - ecs_get_name(world, t)); - goto error; - } - const EcsUnitPrefix *prefix_ptr = ecs_get(world, prefix, EcsUnitPrefix); - if (!prefix_ptr) { - ecs_err("entity '%s' for unit '%s' used as prefix is not a prefix", - ecs_get_name(world, over), ecs_get_name(world, t)); - goto error; - } +void flecs_journal_begin( + ecs_world_t *world, + ecs_journal_kind_t kind, + ecs_entity_t entity, + ecs_type_t *add, + ecs_type_t *remove) +{ + flecs_journal_sp ++; + flecs_journal_counter ++; - if (translation.factor || translation.power) { - if (prefix_ptr->translation.factor != translation.factor || - prefix_ptr->translation.power != translation.power) - { - ecs_err( - "factor for unit '%s' is inconsistent with prefix '%s'", - ecs_get_name(world, t), ecs_get_name(world, prefix)); - goto error; - } - } else { - translation = prefix_ptr->translation; - } + if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { + return; } - if (base) { - bool must_match = false; /* Must base symbol match symbol? */ - ecs_strbuf_t sbuf = ECS_STRBUF_INIT; - if (prefix) { - const EcsUnitPrefix *ptr = ecs_get(world, prefix, EcsUnitPrefix); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (ptr->symbol) { - ecs_strbuf_appendstr(&sbuf, ptr->symbol); - must_match = true; + char *path = NULL; + char *var_id = NULL; + if (entity) { + if (kind != EcsJournalDeleteWith && kind != EcsJournalRemoveAll) { + path = ecs_get_path(world, entity); + var_id = flecs_journal_entitystr(world, entity); + } else { + path = ecs_id_str(world, entity); + var_id = flecs_journal_idstr(world, entity); + } + } + + if (kind == EcsJournalNew) { + ecs_print(4, "%d: #[magenta]#ifndef #[normal]_var_%s", flecs_journal_counter, var_id); + ecs_print(4, "%d: #[magenta]#define #[normal]_var_%s", flecs_journal_counter, var_id); + ecs_print(4, "%d: #[green]ecs_entity_t %s;", flecs_journal_counter, var_id); + ecs_print(4, "%d: #[magenta]#endif", flecs_journal_counter); + ecs_print(4, "%d: %s = #[cyan]ecs_new#[reset](world); " + "#[grey] // %s = new()", flecs_journal_counter, var_id, path); + } + if (kind == EcsJournalSetParent) { + ecs_entity_t parent = add->array[0]; + char *parentstr = flecs_journal_entitystr(world, parent); + ecs_print(4, "%d: #[cyan]ecs_set#[reset](world, %s, EcsParent, {%s}); " + "#[grey] // set(%s, EcsParent, %s)", flecs_journal_counter, var_id, parentstr, + path, parentstr); + ecs_os_free(parentstr); + } else { + if (add) { + for (int i = 0; i < add->count; i ++) { + char *jidstr = flecs_journal_idstr(world, add->array[i]); + char *idstr = ecs_id_str(world, add->array[i]); + ecs_print(4, "%d: #[cyan]ecs_add_id#[reset](world, %s, %s); " + "#[grey] // add(%s, %s)", flecs_journal_counter, var_id, jidstr, + path, idstr); + ecs_os_free(idstr); + ecs_os_free(jidstr); } } - - const EcsUnit *uptr = ecs_get(world, base, EcsUnit); - ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (uptr->symbol) { - ecs_strbuf_appendstr(&sbuf, uptr->symbol); - } - - if (over) { - uptr = ecs_get(world, over, EcsUnit); - ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (uptr->symbol) { - ecs_strbuf_appendstr(&sbuf, "/"); - ecs_strbuf_appendstr(&sbuf, uptr->symbol); - must_match = true; + if (remove) { + for (int i = 0; i < remove->count; i ++) { + char *jidstr = flecs_journal_idstr(world, remove->array[i]); + char *idstr = ecs_id_str(world, remove->array[i]); + ecs_print(4, "%d: #[cyan]ecs_remove_id#[reset](world, %s, %s); " + "#[grey] // remove(%s, %s)", flecs_journal_counter, var_id, jidstr, + path, idstr); + ecs_os_free(idstr); + ecs_os_free(jidstr); } } - derived_symbol = ecs_strbuf_get(&sbuf); - if (derived_symbol && !ecs_os_strlen(derived_symbol)) { - ecs_os_free(derived_symbol); - derived_symbol = NULL; - } - - if (derived_symbol && symbol && ecs_os_strcmp(symbol, derived_symbol)) { - if (must_match) { - ecs_err("symbol '%s' for unit '%s' does not match base" - " symbol '%s'", symbol, - ecs_get_name(world, t), derived_symbol); - goto error; - } - } - if (!symbol && derived_symbol && (prefix || over)) { - ecs_os_free(data->symbol); - data->symbol = derived_symbol; - } else { - ecs_os_free(derived_symbol); + if (kind == EcsJournalClear) { + ecs_print(4, "%d: #[cyan]ecs_clear#[reset](world, %s); " + "#[grey] // clear(%s)", flecs_journal_counter, var_id, path); + } else if (kind == EcsJournalDelete) { + ecs_print(4, "%d: #[cyan]ecs_delete#[reset](world, %s); " + "#[grey] // delete(%s)", flecs_journal_counter, var_id, path); + } else if (kind == EcsJournalDeleteWith) { + ecs_print(4, "%d: #[cyan]ecs_delete_with#[reset](world, %s); " + "#[grey] // delete_with(%s)", flecs_journal_counter, var_id, path); + } else if (kind == EcsJournalRemoveAll) { + ecs_print(4, "%d: #[cyan]ecs_remove_all#[reset](world, %s); " + "#[grey] // remove_all(%s)", flecs_journal_counter, var_id, path); } } - data->base = base; - data->over = over; - data->prefix = prefix; - data->translation = translation; + ecs_os_free(var_id); + ecs_os_free(path); + ecs_log_push(); +} - return true; -error: - ecs_os_free(derived_symbol); - return false; +void flecs_journal_end(void) { + flecs_journal_sp --; + ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_log_pop(); } -static -void set_unit(ecs_iter_t *it) { - EcsUnit *u = ecs_term(it, EcsUnit, 1); +int flecs_journal_get_counter(void) { + return flecs_journal_counter; +} - ecs_world_t *world = it->world; +#endif + +/** + * @file addons/log.c + * @brief Log addon. + */ - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - flecs_unit_validate(world, e, &u[i]); - } -} + +#ifdef FLECS_LOG + +static char *flecs_log_last_err = NULL; +static ecs_os_api_log_t flecs_log_prev_log = NULL; +static ecs_os_api_log_t flecs_log_prev_fatal_log = NULL; +static bool flecs_log_prev_color = false; +static int flecs_log_prev_level = 0; static -void unit_quantity_monitor(ecs_iter_t *it) { - ecs_world_t *world = it->world; +void flecs_set_prev_log( + ecs_os_api_log_t prev_log, + bool try) +{ + flecs_log_prev_log = try ? NULL : prev_log; + flecs_log_prev_fatal_log = prev_log; +} - int i, count = it->count; - if (it->event == EcsOnAdd) { - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_add_pair(world, e, EcsQuantity, e); +static +void flecs_log_capture_log( + int32_t level, + const char *file, + int32_t line, + const char *msg) +{ + (void)file; (void)line; + + if (level <= -4) { + /* Make sure to always log fatal errors */ + if (flecs_log_prev_fatal_log) { + ecs_log_enable_colors(true); + flecs_log_prev_fatal_log(level, file, line, msg); + ecs_log_enable_colors(false); + return; + } else { + fprintf(stderr, "%s:%d: %s", file, line, msg); } - } else { - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_remove_pair(world, e, EcsQuantity, e); + } + +#ifdef FLECS_DEBUG + /* In debug mode, log unexpected errors to the console */ + if (level < 0) { + /* Also log to previous log function in debug mode */ + if (flecs_log_prev_log) { + ecs_log_enable_colors(true); + flecs_log_prev_log(level, file, line, msg); + ecs_log_enable_colors(false); } } +#endif + + if (!flecs_log_last_err && level <= -3) { + flecs_log_last_err = ecs_os_strdup(msg); + } } static -void ecs_meta_type_init_default_ctor(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsMetaType *type = ecs_term(it, EcsMetaType, 1); +char* flecs_log_get_captured_log(void) { + char *result = flecs_log_last_err; + flecs_log_last_err = NULL; + return result; +} - int i; - for (i = 0; i < it->count; i ++) { - /* If a component is defined from reflection data, configure it with the - * default constructor. This ensures that a new component value does not - * contain uninitialized memory, which could cause serializers to crash - * when for example inspecting string fields. */ - if (!type->existing) { - ecs_set_component_actions_w_id(world, it->entities[i], - &(EcsComponentLifecycle){ - .ctor = ecs_default_ctor - }); - } - } +void ecs_log_start_capture(bool try) { + flecs_log_prev_color = ecs_log_enable_colors(false); + flecs_log_prev_log = ecs_os_api.log_; + flecs_log_prev_level = ecs_os_api.log_level_; + flecs_set_prev_log(ecs_os_api.log_, try); + ecs_os_api.log_ = flecs_log_capture_log; + ecs_os_api.log_level_ = -1; /* Ignore debug tracing, log warnings/errors */ } -static -void member_on_set(ecs_iter_t *it) { - EcsMember *mbr = it->ptrs[0]; - if (!mbr->count) { - mbr->count = 1; - } +char* ecs_log_stop_capture(void) { + ecs_os_api.log_ = flecs_log_prev_fatal_log; + ecs_os_api.log_level_ = flecs_log_prev_level; + ecs_log_enable_colors(flecs_log_prev_color); + return flecs_log_get_captured_log(); } -void FlecsMetaImport( - ecs_world_t *world) +void flecs_colorize_buf( + char *msg, + bool enable_colors, + ecs_strbuf_t *buf) { - ECS_MODULE(world, FlecsMeta); + ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_set_name_prefix(world, "Ecs"); - - flecs_bootstrap_component(world, EcsMetaType); - flecs_bootstrap_component(world, EcsMetaTypeSerialized); - flecs_bootstrap_component(world, EcsPrimitive); - flecs_bootstrap_component(world, EcsEnum); - flecs_bootstrap_component(world, EcsBitmask); - flecs_bootstrap_component(world, EcsMember); - flecs_bootstrap_component(world, EcsStruct); - flecs_bootstrap_component(world, EcsArray); - flecs_bootstrap_component(world, EcsVector); - flecs_bootstrap_component(world, EcsUnit); - flecs_bootstrap_component(world, EcsUnitPrefix); - - flecs_bootstrap_tag(world, EcsConstant); - flecs_bootstrap_tag(world, EcsQuantity); - - ecs_set_component_actions(world, EcsMetaType, { .ctor = ecs_default_ctor }); - - ecs_set_component_actions(world, EcsMetaTypeSerialized, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsMetaTypeSerialized), - .copy = ecs_copy(EcsMetaTypeSerialized), - .dtor = ecs_dtor(EcsMetaTypeSerialized) - }); + char *ptr, ch, prev = '\0'; + bool isNum = false; + char isStr = '\0'; + bool isVar = false; + bool overrideColor = false; + bool autoColor = true; + bool dontAppend = false; - ecs_set_component_actions(world, EcsStruct, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsStruct), - .copy = ecs_copy(EcsStruct), - .dtor = ecs_dtor(EcsStruct) - }); + for (ptr = msg; (ch = *ptr); ptr++) { + dontAppend = false; - ecs_set_component_actions(world, EcsMember, { - .ctor = ecs_default_ctor, - .on_set = member_on_set - }); + if (!overrideColor) { + if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + isNum = false; + } + if (isStr && (isStr == ch) && prev != '\\') { + isStr = '\0'; + } else if (((ch == '\'') || (ch == '"')) && !isStr && + !isalpha(prev) && (prev != '\\')) + { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); + isStr = ch; + } - ecs_set_component_actions(world, EcsEnum, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsEnum), - .copy = ecs_copy(EcsEnum), - .dtor = ecs_dtor(EcsEnum) - }); + if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || + (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && + !isalpha(prev) && !isdigit(prev) && (prev != '_') && + (prev != '.')) + { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); + isNum = true; + } - ecs_set_component_actions(world, EcsBitmask, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsBitmask), - .copy = ecs_copy(EcsBitmask), - .dtor = ecs_dtor(EcsBitmask) - }); + if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + isVar = false; + } - ecs_set_component_actions(world, EcsUnit, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsUnit), - .copy = ecs_copy(EcsUnit), - .dtor = ecs_dtor(EcsUnit) - }); - - ecs_set_component_actions(world, EcsUnitPrefix, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsUnitPrefix), - .copy = ecs_copy(EcsUnitPrefix), - .dtor = ecs_dtor(EcsUnitPrefix) - }); - - /* Register triggers to finalize type information from component data */ - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsPrimitive), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = set_primitive - }); - - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsMember), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = set_member - }); - - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsEnum), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnAdd}, - .callback = add_enum - }); - - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsBitmask), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnAdd}, - .callback = add_bitmask - }); - - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = EcsConstant, - .term.subj.set.mask = EcsSelf, - .events = {EcsOnAdd}, - .callback = add_constant - }); - - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_pair(EcsConstant, EcsWildcard), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = add_constant - }); - - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsArray), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = set_array - }); - - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsVector), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = set_vector - }); - - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsUnit), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = set_unit - }); - - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsMetaType), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = ecs_meta_type_serialized_init - }); - - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_id(EcsMetaType), - .term.subj.set.mask = EcsSelf, - .events = {EcsOnSet}, - .callback = ecs_meta_type_init_default_ctor - }); - - ecs_observer_init(world, &(ecs_observer_desc_t) { - .filter.terms = { - { .id = ecs_id(EcsUnit) }, - { .id = EcsQuantity } - }, - .events = { EcsMonitor }, - .callback = unit_quantity_monitor - }); - - /* Initialize primitive types */ - #define ECS_PRIMITIVE(world, type, primitive_kind)\ - ecs_entity_init(world, &(ecs_entity_desc_t) {\ - .entity = ecs_id(ecs_##type##_t),\ - .name = #type,\ - .symbol = #type });\ - ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ - .kind = primitive_kind\ - }); - - ECS_PRIMITIVE(world, bool, EcsBool); - ECS_PRIMITIVE(world, char, EcsChar); - ECS_PRIMITIVE(world, byte, EcsByte); - ECS_PRIMITIVE(world, u8, EcsU8); - ECS_PRIMITIVE(world, u16, EcsU16); - ECS_PRIMITIVE(world, u32, EcsU32); - ECS_PRIMITIVE(world, u64, EcsU64); - ECS_PRIMITIVE(world, uptr, EcsUPtr); - ECS_PRIMITIVE(world, i8, EcsI8); - ECS_PRIMITIVE(world, i16, EcsI16); - ECS_PRIMITIVE(world, i32, EcsI32); - ECS_PRIMITIVE(world, i64, EcsI64); - ECS_PRIMITIVE(world, iptr, EcsIPtr); - ECS_PRIMITIVE(world, f32, EcsF32); - ECS_PRIMITIVE(world, f64, EcsF64); - ECS_PRIMITIVE(world, string, EcsString); - ECS_PRIMITIVE(world, entity, EcsEntity); - - #undef ECS_PRIMITIVE - - /* Set default child components */ - ecs_add_pair(world, ecs_id(EcsStruct), - EcsDefaultChildComponent, ecs_id(EcsMember)); - - ecs_add_pair(world, ecs_id(EcsMember), - EcsDefaultChildComponent, ecs_id(EcsMember)); - - ecs_add_pair(world, ecs_id(EcsEnum), - EcsDefaultChildComponent, EcsConstant); - - ecs_add_pair(world, ecs_id(EcsBitmask), - EcsDefaultChildComponent, EcsConstant); - - /* Relationship properties */ - ecs_add_id(world, EcsQuantity, EcsExclusive); - ecs_add_id(world, EcsQuantity, EcsTag); - - /* Initialize reflection data for meta components */ - ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t) { - .entity.name = "TypeKind", - .constants = { - {.name = "PrimitiveType"}, - {.name = "BitmaskType"}, - {.name = "EnumType"}, - {.name = "StructType"}, - {.name = "ArrayType"}, - {.name = "VectorType"} - } - }); - - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsMetaType), - .members = { - {.name = (char*)"kind", .type = type_kind} - } - }); - - ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t) { - .entity.name = "PrimitiveKind", - .constants = { - {.name = "Bool", 1}, - {.name = "Char"}, - {.name = "Byte"}, - {.name = "U8"}, - {.name = "U16"}, - {.name = "U32"}, - {.name = "U64"}, - {.name = "I8"}, - {.name = "I16"}, - {.name = "I32"}, - {.name = "I64"}, - {.name = "F32"}, - {.name = "F64"}, - {.name = "UPtr"}, - {.name = "IPtr"}, - {.name = "String"}, - {.name = "Entity"} - } - }); - - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsPrimitive), - .members = { - {.name = (char*)"kind", .type = primitive_kind} + if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); + isVar = true; + } } - }); - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsMember), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, - {.name = (char*)"unit", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"offset", .type = ecs_id(ecs_i32_t)} - } - }); + if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { + bool isColor = true; + overrideColor = true; - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsArray), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, - } - }); + /* Custom colors */ + if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { + autoColor = false; + } else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); + } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_RED); + } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("blue]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BLUE); + } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_MAGENTA); + } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); + } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_YELLOW); + } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREY); + } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BOLD); + } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { + overrideColor = false; + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + } else { + isColor = false; + overrideColor = false; + } - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsVector), - .members = { - {.name = (char*)"type", .type = ecs_id(ecs_entity_t)} + if (isColor) { + ptr += 2; + while ((ch = *ptr) != ']') ptr ++; + dontAppend = true; + } + if (!autoColor) { + overrideColor = true; + } } - }); - ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.name = "unit_translation", - .members = { - {.name = (char*)"factor", .type = ecs_id(ecs_i32_t)}, - {.name = (char*)"power", .type = ecs_id(ecs_i32_t)} + if (ch == '\n') { + if (isNum || isStr || isVar || overrideColor) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + overrideColor = false; + isNum = false; + isStr = false; + isVar = false; + } } - }); - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsUnit), - .members = { - {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, - {.name = (char*)"prefix", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"base", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"over", .type = ecs_id(ecs_entity_t)}, - {.name = (char*)"translation", .type = ut} + if (!dontAppend) { + ecs_strbuf_appendstrn(buf, ptr, 1); } - }); - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsUnitPrefix), - .members = { - {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, - {.name = (char*)"translation", .type = ut} + if (!overrideColor) { + if (((ch == '\'') || (ch == '"')) && !isStr) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); + } } - }); -} - -#endif - - -#ifdef FLECS_META -static -const char* op_kind_str( - ecs_meta_type_op_kind_t kind) -{ - switch(kind) { + prev = ch; + } - case EcsOpEnum: return "Enum"; - case EcsOpBitmask: return "Bitmask"; - case EcsOpArray: return "Array"; - case EcsOpVector: return "Vector"; - case EcsOpPush: return "Push"; - case EcsOpPop: return "Pop"; - case EcsOpPrimitive: return "Primitive"; - case EcsOpBool: return "Bool"; - case EcsOpChar: return "Char"; - case EcsOpByte: return "Byte"; - case EcsOpU8: return "U8"; - case EcsOpU16: return "U16"; - case EcsOpU32: return "U32"; - case EcsOpU64: return "U64"; - case EcsOpI8: return "I8"; - case EcsOpI16: return "I16"; - case EcsOpI32: return "I32"; - case EcsOpI64: return "I64"; - case EcsOpF32: return "F32"; - case EcsOpF64: return "F64"; - case EcsOpUPtr: return "UPtr"; - case EcsOpIPtr: return "IPtr"; - case EcsOpString: return "String"; - case EcsOpEntity: return "Entity"; - default: return "<< invalid kind >>"; + if (isNum || isStr || isVar || overrideColor) { + if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } -/* Get current scope */ -static -ecs_meta_scope_t* get_scope( - const ecs_meta_cursor_t *cursor) +void ecs_printv_( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args) { - ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); - return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; -error: - return NULL; -} + (void)level; + (void)line; -/* Get previous scope */ -static -ecs_meta_scope_t* get_prev_scope( - ecs_meta_cursor_t *cursor) -{ - ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(cursor->depth > 0, ECS_INVALID_PARAMETER, NULL); - return &cursor->scope[cursor->depth - 1]; -error: - return NULL; -} + ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; -/* Get current operation for scope */ -static -ecs_meta_type_op_t* get_op( - ecs_meta_scope_t *scope) -{ - return &scope->ops[scope->op_cur]; -} + /* Apply color. Even if we don't want color, we still need to call the + * colorize function to get rid of the color tags (e.g. #[green]) */ + char *msg_nocolor = flecs_vasprintf(fmt, args); + flecs_colorize_buf(msg_nocolor, + ecs_os_api.flags_ & EcsOsApiLogWithColors, &msg_buf); + ecs_os_free(msg_nocolor); -/* Get component for type in current scope */ -static -const EcsComponent* get_component_ptr( - const ecs_world_t *world, - ecs_meta_scope_t *scope) -{ - const EcsComponent *comp = scope->comp; - if (!comp) { - comp = scope->comp = ecs_get(world, scope->type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + char *msg = ecs_strbuf_get(&msg_buf); + + if (msg) { + ecs_os_api.log_(level, file, line, msg); + ecs_os_free(msg); + } else { + ecs_os_api.log_(level, file, line, ""); } - return comp; } -/* Get size for type in current scope */ -static -ecs_size_t get_size( - const ecs_world_t *world, - ecs_meta_scope_t *scope) +void ecs_print_( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) { - return get_component_ptr(world, scope)->size; + va_list args; + va_start(args, fmt); + ecs_printv_(level, file, line, fmt, args); + va_end(args); } -/* Get alignment for type in current scope */ -static -ecs_size_t get_alignment( - const ecs_world_t *world, - ecs_meta_scope_t *scope) +void ecs_logv_( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args) { - return get_component_ptr(world, scope)->alignment; -} - -static -int32_t get_elem_count( - ecs_meta_scope_t *scope) -{ - if (scope->vector) { - return ecs_vector_count(*(scope->vector)); + if (level > ecs_os_api.log_level_) { + return; } - ecs_meta_type_op_t *op = get_op(scope); - return op->count; + ecs_printv_(level, file, line, fmt, args); } -/* Get pointer to current field/element */ -static -ecs_meta_type_op_t* get_ptr( - const ecs_world_t *world, - ecs_meta_scope_t *scope) +void ecs_log_( + int level, + const char *file, + int32_t line, + const char *fmt, + ...) { - ecs_meta_type_op_t *op = get_op(scope); - ecs_size_t size = get_size(world, scope); - - if (scope->vector) { - ecs_size_t align = get_alignment(world, scope); - ecs_vector_set_min_count_t( - scope->vector, size, align, scope->elem_cur + 1); - scope->ptr = ecs_vector_first_t(*(scope->vector), size, align); + if (level > ecs_os_api.log_level_) { + return; } - return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); + va_list args; + va_start(args, fmt); + ecs_printv_(level, file, line, fmt, args); + va_end(args); } -static -int push_type( - const ecs_world_t *world, - ecs_meta_scope_t *scope, - ecs_entity_t type, - void *ptr) -{ - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (ser == NULL) { - char *str = ecs_id_str(world, type); - ecs_err("cannot open scope for entity '%s' which is not a type", str); - ecs_os_free(str); - return -1; - } - - scope[0] = (ecs_meta_scope_t) { - .type = type, - .ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t), - .op_count = ecs_vector_count(ser->ops), - .ptr = ptr - }; - - return 0; -} -ecs_meta_cursor_t ecs_meta_cursor( - const ecs_world_t *world, - ecs_entity_t type, - void *ptr) +void ecs_log_push_( + int32_t level) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_meta_cursor_t result = { - .world = world, - .valid = true - }; - - if (push_type(world, result.scope, type, ptr) != 0) { - result.valid = false; + if (level <= ecs_os_api.log_level_) { + ecs_os_api.log_indent_ ++; } - - return result; -error: - return (ecs_meta_cursor_t){ 0 }; } -void* ecs_meta_get_ptr( - ecs_meta_cursor_t *cursor) -{ - return get_ptr(cursor->world, get_scope(cursor)); -} - -int ecs_meta_next( - ecs_meta_cursor_t *cursor) +void ecs_log_pop_( + int32_t level) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - - if (scope->is_collection) { - scope->elem_cur ++; - scope->op_cur = 0; - if (scope->elem_cur >= get_elem_count(scope)) { - ecs_err("out of collection bounds (%d)", scope->elem_cur); - return -1; - } - - return 0; - } - - scope->op_cur += op->op_count; - if (scope->op_cur >= scope->op_count) { - ecs_err("out of bounds"); - return -1; + if (level <= ecs_os_api.log_level_) { + ecs_os_api.log_indent_ --; + ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL); } - - return 0; } -int ecs_meta_member( - ecs_meta_cursor_t *cursor, - const char *name) +static +void flecs_parser_errorv( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args, + bool is_warning) { - if (cursor->depth == 0) { - ecs_err("cannot move to member in root scope"); - return -1; + if (column_arg > 65536) { + /* Limit column size, which prevents the code from throwing up when the + * function is called with (expr - ptr), and expr is NULL. */ + column_arg = 0; } - - ecs_meta_scope_t *prev_scope = get_prev_scope(cursor); - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *push_op = get_op(prev_scope); - const ecs_world_t *world = cursor->world; - - ecs_assert(push_op->kind == EcsOpPush, ECS_INTERNAL_ERROR, NULL); - if (!push_op->members) { - ecs_err("cannot move to member '%s' for non-struct type", name); - return -1; - } - - const uint64_t *cur_ptr = flecs_name_index_find_ptr(push_op->members, name, 0, 0); - if (!cur_ptr) { - char *path = ecs_get_fullpath(world, scope->type); - ecs_err("unknown member '%s' for type '%s'", name, path); - ecs_os_free(path); - return -1; - } - - scope->op_cur = flecs_uto(int32_t, cur_ptr[0]); - - return 0; -} + int32_t column = flecs_itoi32(column_arg); -int ecs_meta_push( - ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - const ecs_world_t *world = cursor->world; + if (ecs_os_api.log_level_ >= -2) { + ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; - if (cursor->depth == 0) { - if (!cursor->is_primitive_scope) { - if (op->kind > EcsOpScope) { - cursor->is_primitive_scope = true; - return 0; + /* Count number of newlines up until column_arg */ + int32_t i, line = 1; + if (expr) { + for (i = 0; i < column; i ++) { + if (expr[i] == '\n') { + line ++; + } } + + ecs_strbuf_append(&msg_buf, "%d: ", line); } - } - void *ptr = get_ptr(world, scope); - cursor->depth ++; - ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, - ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_vappend(&msg_buf, fmt, args); - ecs_meta_scope_t *next_scope = get_scope(cursor); - - /* If we're not already in an inline array and this operation is an inline - * array, push a frame for the array. - * Doing this first ensures that inline arrays take precedence over other - * kinds of push operations, such as for a struct element type. */ - if (!scope->is_inline_array && op->count > 1 && !scope->is_collection) { - /* Push a frame just for the element type, with inline_array = true */ - next_scope[0] = (ecs_meta_scope_t){ - .ops = op, - .op_count = op->op_count, - .ptr = scope->ptr, - .type = op->type, - .is_collection = true, - .is_inline_array = true - }; + if (expr) { + ecs_strbuf_appendch(&msg_buf, '\n'); - /* With 'is_inline_array' set to true we ensure that we can never push - * the same inline array twice */ + /* Find start of line by taking column and looking for the + * last occurring newline */ + if (column != -1) { + const char *ptr = &expr[column]; + if (ptr[0] == '\n') { + ptr --; + } - return 0; - } + while (ptr[0] != '\n' && ptr > expr) { + ptr --; + } + + if (ptr[0] == '\n') { + ptr ++; + } - switch(op->kind) { - case EcsOpPush: - next_scope[0] = (ecs_meta_scope_t) { - .ops = &op[1], /* op after push */ - .op_count = op->op_count - 1, /* don't include pop */ - .ptr = scope->ptr, - .type = op->type - }; - break; + if (ptr == expr) { + /* ptr is already at start of line */ + } else { + column -= (int32_t)(ptr - expr); + expr = ptr; + } + } - case EcsOpArray: { - if (push_type(world, next_scope, op->type, ptr) != 0) { - goto error; - } + /* Strip newlines from current statement, if any */ + char *newline_ptr = strchr(expr, '\n'); + if (newline_ptr) { + /* Strip newline from expr */ + ecs_strbuf_appendstrn(&msg_buf, expr, + (int32_t)(newline_ptr - expr)); + } else { + ecs_strbuf_appendstr(&msg_buf, expr); + } - const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); - next_scope->type = type_ptr->type; - next_scope->is_collection = true; - break; - } + ecs_strbuf_appendch(&msg_buf, '\n'); - case EcsOpVector: - next_scope->vector = ptr; - if (push_type(world, next_scope, op->type, NULL) != 0) { - goto error; + if (column != -1) { + int32_t c; + for (c = 0; c < column; c ++) { + ecs_strbuf_appendch(&msg_buf, ' '); + } + ecs_strbuf_appendch(&msg_buf, '^'); + } } - const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); - next_scope->type = type_ptr->type; - next_scope->is_collection = true; - break; - - default: { - char *path = ecs_get_fullpath(world, scope->type); - ecs_err("invalid push for type '%s'", path); - ecs_os_free(path); - goto error; - } - } - - if (scope->is_collection) { - next_scope[0].ptr = ECS_OFFSET(next_scope[0].ptr, - scope->elem_cur * get_size(world, scope)); - } - - return 0; -error: - return -1; -} - -int ecs_meta_pop( - ecs_meta_cursor_t *cursor) -{ - if (cursor->is_primitive_scope) { - cursor->is_primitive_scope = false; - return 0; - } - - ecs_meta_scope_t *scope = get_scope(cursor); - cursor->depth --; - if (cursor->depth < 0) { - ecs_err("unexpected end of scope"); - return -1; - } - - ecs_meta_scope_t *next_scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(next_scope); - - if (!scope->is_inline_array) { - if (op->kind == EcsOpPush) { - next_scope->op_cur += op->op_count - 1; - - /* push + op_count should point to the operation after pop */ - op = get_op(next_scope); - ecs_assert(op->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); - } else if (op->kind == EcsOpArray || op->kind == EcsOpVector) { - /* Collection type, nothing else to do */ + char *msg = ecs_strbuf_get(&msg_buf); + if (is_warning) { + ecs_os_warn(name, 0, msg); } else { - /* should not have been able to push if the previous scope was not - * a complex or collection type */ - ecs_assert(false, ECS_INTERNAL_ERROR, NULL); + ecs_os_err(name, 0, msg); } - } else { - /* Make sure that this was an inline array */ - ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(msg); } - - return 0; } -bool ecs_meta_is_collection( - const ecs_meta_cursor_t *cursor) +void ecs_parser_errorv_( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) { - ecs_meta_scope_t *scope = get_scope(cursor); - return scope->is_collection; + flecs_parser_errorv(name, expr, column_arg, fmt, args, false); } -ecs_entity_t ecs_meta_get_type( - const ecs_meta_cursor_t *cursor) +void ecs_parser_warningv_( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - return op->type; + flecs_parser_errorv(name, expr, column_arg, fmt, args, true); } -ecs_entity_t ecs_meta_get_unit( - const ecs_meta_cursor_t *cursor) +void ecs_parser_error_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - return op->unit; + if (ecs_os_api.log_level_ >= -2) { + va_list args; + va_start(args, fmt); + ecs_parser_errorv_(name, expr, column, fmt, args); + va_end(args); + } } -const char* ecs_meta_get_member( - const ecs_meta_cursor_t *cursor) +void ecs_parser_warning_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - return op->name; + if (ecs_os_api.log_level_ >= -2) { + va_list args; + va_start(args, fmt); + ecs_parser_warningv_(name, expr, column, fmt, args); + va_end(args); + } } -/* Utility macro's to let the compiler do the conversion work for us */ -#define set_T(T, ptr, value)\ - ((T*)ptr)[0] = ((T)value) +void ecs_abort_( + int32_t err, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + if (fmt) { + va_list args; + va_start(args, fmt); + char *msg = flecs_vasprintf(fmt, args); + va_end(args); + ecs_fatal_(file, line, "#[red]abort()#[reset]: %s (#[blue]%s#[reset])", + msg, ecs_strerror(err)); + ecs_os_free(msg); + } else { + ecs_fatal_(file, line, "#[red]abort()#[reset]: #[blue]%s#[reset]", + ecs_strerror(err)); + } + ecs_os_api.log_last_error_ = err; +} -#define case_T(kind, T, dst, src)\ -case kind:\ - set_T(T, dst, src);\ - break +void ecs_assert_log_( + int32_t err, + const char *cond_str, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + if (fmt) { + va_list args; + va_start(args, fmt); + char *msg = flecs_vasprintf(fmt, args); + va_end(args); + ecs_fatal_(file, line, "#[red]assert(%s)#[reset]: %s (#[blue]%s#[reset])", + cond_str, msg, ecs_strerror(err)); + ecs_os_free(msg); + } else { + ecs_fatal_(file, line, "#[red]assert(%s)#[reset] (#[blue]%s#[reset])", + cond_str, ecs_strerror(err)); + } + ecs_os_api.log_last_error_ = err; +} -#define cases_T_float(dst, src)\ - case_T(EcsOpF32, ecs_f32_t, dst, src);\ - case_T(EcsOpF64, ecs_f64_t, dst, src) +void ecs_deprecated_( + const char *file, + int32_t line, + const char *msg) +{ + ecs_err_(file, line, "%s", msg); +} + +bool ecs_should_log(int32_t level) { +# if !defined(FLECS_LOG_3) + if (level == 3) { + return false; + } +# endif +# if !defined(FLECS_LOG_2) + if (level == 2) { + return false; + } +# endif +# if !defined(FLECS_LOG_1) + if (level == 1) { + return false; + } +# endif -#define cases_T_signed(dst, src)\ - case_T(EcsOpChar, ecs_char_t, dst, src);\ - case_T(EcsOpI8, ecs_i8_t, dst, src);\ - case_T(EcsOpI16, ecs_i16_t, dst, src);\ - case_T(EcsOpI32, ecs_i32_t, dst, src);\ - case_T(EcsOpI64, ecs_i64_t, dst, src);\ - case_T(EcsOpIPtr, ecs_iptr_t, dst, src) - -#define cases_T_unsigned(dst, src)\ - case_T(EcsOpByte, ecs_byte_t, dst, src);\ - case_T(EcsOpU8, ecs_u8_t, dst, src);\ - case_T(EcsOpU16, ecs_u16_t, dst, src);\ - case_T(EcsOpU32, ecs_u32_t, dst, src);\ - case_T(EcsOpU64, ecs_u64_t, dst, src);\ - case_T(EcsOpUPtr, ecs_uptr_t, dst, src);\ + return level <= ecs_os_api.log_level_; +} -#define cases_T_bool(dst, src)\ -case EcsOpBool:\ - set_T(ecs_bool_t, dst, value != 0);\ - break +#define ECS_ERR_STR(code) case code: return &(#code[4]) -static -void conversion_error( - ecs_meta_cursor_t *cursor, - ecs_meta_type_op_t *op, - const char *from) +const char* ecs_strerror( + int32_t error_code) { - char *path = ecs_get_fullpath(cursor->world, op->type); - ecs_err("unsupported conversion from %s to '%s'", from, path); - ecs_os_free(path); + switch (error_code) { + ECS_ERR_STR(ECS_INVALID_PARAMETER); + ECS_ERR_STR(ECS_INTERNAL_ERROR); + ECS_ERR_STR(ECS_ALREADY_DEFINED); + ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); + ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); + ECS_ERR_STR(ECS_NAME_IN_USE); + ECS_ERR_STR(ECS_OUT_OF_MEMORY); + ECS_ERR_STR(ECS_DOUBLE_FREE); + ECS_ERR_STR(ECS_OPERATION_FAILED); + ECS_ERR_STR(ECS_INVALID_CONVERSION); + ECS_ERR_STR(ECS_MODULE_UNDEFINED); + ECS_ERR_STR(ECS_MISSING_SYMBOL); + ECS_ERR_STR(ECS_ALREADY_IN_USE); + ECS_ERR_STR(ECS_CYCLE_DETECTED); + ECS_ERR_STR(ECS_LEAK_DETECTED); + ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); + ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); + ECS_ERR_STR(ECS_COLUMN_IS_SHARED); + ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); + ECS_ERR_STR(ECS_INVALID_WHILE_READONLY); + ECS_ERR_STR(ECS_INVALID_FROM_WORKER); + ECS_ERR_STR(ECS_OUT_OF_RANGE); + ECS_ERR_STR(ECS_MISSING_OS_API); + ECS_ERR_STR(ECS_UNSUPPORTED); + ECS_ERR_STR(ECS_ACCESS_VIOLATION); + ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); + ECS_ERR_STR(ECS_INCONSISTENT_NAME); + ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); + ECS_ERR_STR(ECS_INVALID_OPERATION); + ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); + ECS_ERR_STR(ECS_LOCKED_STORAGE); + } + + return "unknown error code"; } -int ecs_meta_set_bool( - ecs_meta_cursor_t *cursor, - bool value) +#else + +/* Empty bodies for when logging is disabled */ + +void ecs_log_( + int32_t level, + const char *file, + int32_t line, + const char *fmt, + ...) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + (void)level; + (void)file; + (void)line; + (void)fmt; +} - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_unsigned(ptr, value); - default: - conversion_error(cursor, op, "bool"); - return -1; - } +void ecs_parser_error_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; +} - return 0; +void ecs_parser_errorv_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; + (void)args; } -int ecs_meta_set_char( - ecs_meta_cursor_t *cursor, - char value) + +void ecs_parser_warning_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + (void)name; + (void)expr; + (void)column; + (void)fmt; +} - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_signed(ptr, value); - default: - conversion_error(cursor, op, "char"); - return -1; - } +void ecs_parser_warningv_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args) +{ + (void)name; + (void)expr; + (void)column; + (void)fmt; + (void)args; +} - return 0; +void ecs_abort_( + int32_t error_code, + const char *file, + int32_t line, + const char *fmt, + ...) +{ + (void)error_code; + (void)file; + (void)line; + (void)fmt; } -int ecs_meta_set_int( - ecs_meta_cursor_t *cursor, - int64_t value) +void ecs_assert_log_( + int32_t error_code, + const char *condition_str, + const char *file, + int32_t line, + const char *fmt, + ...) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + (void)error_code; + (void)condition_str; + (void)file; + (void)line; + (void)fmt; +} - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_signed(ptr, value); - cases_T_float(ptr, value); - default: { - conversion_error(cursor, op, "int"); - return -1; - } - } +void ecs_log_start_capture(bool try) { + (void)try; +} - return 0; +char* ecs_log_stop_capture(void) { + return NULL; } -int ecs_meta_set_uint( - ecs_meta_cursor_t *cursor, - uint64_t value) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); +#endif - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_unsigned(ptr, value); - cases_T_float(ptr, value); - case EcsOpEntity: - set_T(ecs_entity_t, ptr, value); - break; - default: - conversion_error(cursor, op, "uint"); - return -1; - } +int ecs_log_get_level(void) { + return ecs_os_api.log_level_; +} - return 0; +int ecs_log_set_level( + int level) +{ + int prev = ecs_os_api.log_level_; + ecs_os_api.log_level_ = level; + return prev; } -int ecs_meta_set_float( - ecs_meta_cursor_t *cursor, - double value) +bool ecs_log_enable_colors( + bool enabled) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; + ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); + return prev; +} - switch(op->kind) { - cases_T_bool(ptr, value); - cases_T_signed(ptr, value); - cases_T_unsigned(ptr, value); - cases_T_float(ptr, value); - default: - conversion_error(cursor, op, "float"); - return -1; - } +bool ecs_log_enable_timestamp( + bool enabled) +{ + bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; + ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); + return prev; +} - return 0; +bool ecs_log_enable_timedelta( + bool enabled) +{ + bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; + ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); + return prev; } -static -int add_bitmask_constant( - ecs_meta_cursor_t *cursor, - ecs_meta_type_op_t *op, - void *out, - const char *value) +int ecs_log_last_error(void) { - ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); + int result = ecs_os_api.log_last_error_; + ecs_os_api.log_last_error_ = 0; + return result; +} - if (!ecs_os_strcmp(value, "0")) { - return 0; - } +/** + * @file addons/metrics.c + * @brief Metrics addon. + */ - ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); - if (!c) { - char *path = ecs_get_fullpath(cursor->world, op->type); - ecs_err("unresolved bitmask constant '%s' for type '%s'", value, path); - ecs_os_free(path); - return -1; + +#ifdef FLECS_METRICS + +/* Public components */ +ECS_COMPONENT_DECLARE(FlecsMetrics); +ECS_TAG_DECLARE(EcsMetricInstance); +ECS_COMPONENT_DECLARE(EcsMetricValue); +ECS_COMPONENT_DECLARE(EcsMetricSource); +ECS_TAG_DECLARE(EcsMetric); +ECS_TAG_DECLARE(EcsCounter); +ECS_TAG_DECLARE(EcsCounterIncrement); +ECS_TAG_DECLARE(EcsCounterId); +ECS_TAG_DECLARE(EcsGauge); + +/* Internal components */ +static ECS_COMPONENT_DECLARE(EcsMetricMember); +static ECS_COMPONENT_DECLARE(EcsMetricId); +static ECS_COMPONENT_DECLARE(EcsMetricOneOf); +static ECS_COMPONENT_DECLARE(EcsMetricCountIds); +static ECS_COMPONENT_DECLARE(EcsMetricCountTargets); +static ECS_COMPONENT_DECLARE(EcsMetricMemberInstance); +static ECS_COMPONENT_DECLARE(EcsMetricIdInstance); +static ECS_COMPONENT_DECLARE(EcsMetricOneOfInstance); + +/** Context for metric */ +typedef struct { + ecs_entity_t metric; /**< Metric entity */ + ecs_entity_t kind; /**< Metric kind (gauge, counter) */ +} ecs_metric_ctx_t; + +/** Context for metric that monitors member */ +typedef struct { + ecs_metric_ctx_t metric; + ecs_primitive_kind_t type_kind; /**< Primitive type kind of member */ + uint16_t offset; /**< Offset of member in component */ +} ecs_member_metric_ctx_t; + +/** Context for metric that monitors whether entity has id */ +typedef struct { + ecs_metric_ctx_t metric; + ecs_component_record_t *cr; /**< component record for monitored component */ +} ecs_id_metric_ctx_t; + +/** Context for metric that monitors whether entity has pair target */ +typedef struct { + ecs_metric_ctx_t metric; + ecs_component_record_t *cr; /**< component record for monitored component */ + ecs_size_t size; /**< Size of metric type */ + ecs_map_t target_offset; /**< Pair target to metric type offset */ +} ecs_oneof_metric_ctx_t; + +/** Context for metric that monitors how many entities have a pair target */ +typedef struct { + ecs_metric_ctx_t metric; + ecs_component_record_t *cr; /**< component record for monitored component */ + ecs_map_t targets; /**< Map of counters for each target */ +} ecs_count_targets_metric_ctx_t; + +/** Stores context shared for all instances of member metric */ +typedef struct { + ecs_member_metric_ctx_t *ctx; +} EcsMetricMember; + +/** Stores context shared for all instances of id metric */ +typedef struct { + ecs_id_metric_ctx_t *ctx; +} EcsMetricId; + +/** Stores context shared for all instances of oneof metric */ +typedef struct { + ecs_oneof_metric_ctx_t *ctx; +} EcsMetricOneOf; + +/** Stores context shared for all instances of id counter metric */ +typedef struct { + ecs_id_t id; +} EcsMetricCountIds; + +/** Stores context shared for all instances of target counter metric */ +typedef struct { + ecs_count_targets_metric_ctx_t *ctx; +} EcsMetricCountTargets; + +/** Instance of member metric */ +typedef struct { + ecs_ref_t ref; + ecs_member_metric_ctx_t *ctx; +} EcsMetricMemberInstance; + +/** Instance of id metric */ +typedef struct { + ecs_record_t *r; + ecs_id_metric_ctx_t *ctx; +} EcsMetricIdInstance; + +/** Instance of oneof metric */ +typedef struct { + ecs_record_t *r; + ecs_oneof_metric_ctx_t *ctx; +} EcsMetricOneOfInstance; + +/** Component lifecycle */ + +static ECS_DTOR(EcsMetricMember, ptr, { + ecs_os_free(ptr->ctx); +}) + +static ECS_MOVE(EcsMetricMember, dst, src, { + *dst = *src; + src->ctx = NULL; +}) + +static ECS_DTOR(EcsMetricId, ptr, { + ecs_os_free(ptr->ctx); +}) + +static ECS_MOVE(EcsMetricId, dst, src, { + *dst = *src; + src->ctx = NULL; +}) + +static ECS_DTOR(EcsMetricOneOf, ptr, { + if (ptr->ctx) { + ecs_map_fini(&ptr->ctx->target_offset); + ecs_os_free(ptr->ctx); } +}) - const ecs_u32_t *v = ecs_get_pair_object( - cursor->world, c, EcsConstant, ecs_u32_t); - if (v == NULL) { - char *path = ecs_get_fullpath(cursor->world, op->type); - ecs_err("'%s' is not an bitmask constant for type '%s'", value, path); - ecs_os_free(path); - return -1; +static ECS_MOVE(EcsMetricOneOf, dst, src, { + *dst = *src; + src->ctx = NULL; +}) + +static ECS_DTOR(EcsMetricCountTargets, ptr, { + if (ptr->ctx) { + ecs_map_fini(&ptr->ctx->targets); + ecs_os_free(ptr->ctx); } +}) - *(ecs_u32_t*)out |= v[0]; +static ECS_MOVE(EcsMetricCountTargets, dst, src, { + *dst = *src; + src->ctx = NULL; +}) - return 0; -} +/** Observer used for creating new instances of member metric */ +static void flecs_metrics_on_member_metric(ecs_iter_t *it) { + ecs_world_t *world = it->world; + ecs_member_metric_ctx_t *ctx = it->ctx; + ecs_id_t id = ecs_field_id(it, 0); -static -int parse_bitmask( - ecs_meta_cursor_t *cursor, - ecs_meta_type_op_t *op, - void *out, - const char *value) -{ - char token[ECS_MAX_TOKEN_SIZE]; + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); - const char *prev = value, *ptr = value; + EcsMetricMemberInstance *src = ecs_emplace( + world, m, EcsMetricMemberInstance, NULL); + src->ref = ecs_ref_init_id(world, e, id); + src->ctx = ctx; + ecs_modified(world, m, EcsMetricMemberInstance); + ecs_set(world, m, EcsMetricValue, { 0 }); + ecs_set(world, m, EcsMetricSource, { e }); + ecs_add(world, m, EcsMetricInstance); + ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); + } +} - *(ecs_u32_t*)out = 0; +/** Observer used for creating new instances of id metric */ +static void flecs_metrics_on_id_metric(ecs_iter_t *it) { + ecs_world_t *world = it->world; + ecs_id_metric_ctx_t *ctx = it->ctx; - while ((ptr = strchr(ptr, '|'))) { - ecs_os_memcpy(token, prev, ptr - prev); - token[ptr - prev] = '\0'; - if (add_bitmask_constant(cursor, op, out, token) != 0) { - return -1; - } + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); - ptr ++; - prev = ptr; + EcsMetricIdInstance *src = ecs_emplace( + world, m, EcsMetricIdInstance, NULL); + src->r = ecs_record_find(world, e); + src->ctx = ctx; + ecs_modified(world, m, EcsMetricIdInstance); + ecs_set(world, m, EcsMetricValue, { 0 }); + ecs_set(world, m, EcsMetricSource, { e }); + ecs_add(world, m, EcsMetricInstance); + ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); } +} - if (add_bitmask_constant(cursor, op, out, prev) != 0) { - return -1; +/** Observer used for creating new instances of oneof metric */ +static void flecs_metrics_on_oneof_metric(ecs_iter_t *it) { + if (it->event == EcsOnRemove) { + return; } - return 0; + ecs_world_t *world = it->world; + ecs_oneof_metric_ctx_t *ctx = it->ctx; + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); + + EcsMetricOneOfInstance *src = ecs_emplace( + world, m, EcsMetricOneOfInstance, NULL); + src->r = ecs_record_find(world, e); + src->ctx = ctx; + ecs_modified(world, m, EcsMetricOneOfInstance); + ecs_add_pair(world, m, ctx->metric.metric, ecs_id(EcsMetricValue)); + ecs_set(world, m, EcsMetricSource, { e }); + ecs_add(world, m, EcsMetricInstance); + ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); + } } -int ecs_meta_set_string( - ecs_meta_cursor_t *cursor, - const char *value) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); +/** Set doc name of metric instance to name of source entity */ +#ifdef FLECS_DOC +static void SetMetricDocName(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMetricSource *src = ecs_field(it, EcsMetricSource, 0); - switch(op->kind) { - case EcsOpBool: - if (!ecs_os_strcmp(value, "true")) { - set_T(ecs_bool_t, ptr, true); - } else if (!ecs_os_strcmp(value, "false")) { - set_T(ecs_bool_t, ptr, false); - } else { - ecs_err("invalid value for boolean '%s'", value); - return -1; + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t src_e = src[i].entity; + const char *name = ecs_get_name(world, src_e); + if (name) { + ecs_doc_set_name(world, it->entities[i], name); } - break; - case EcsOpI8: - case EcsOpU8: - case EcsOpChar: - case EcsOpByte: - set_T(ecs_i8_t, ptr, atol(value)); - break; - case EcsOpI16: - case EcsOpU16: - set_T(ecs_i16_t, ptr, atol(value)); - break; - case EcsOpI32: - case EcsOpU32: - set_T(ecs_i32_t, ptr, atol(value)); - break; - case EcsOpI64: - case EcsOpU64: - set_T(ecs_i64_t, ptr, atol(value)); - break; - case EcsOpIPtr: - case EcsOpUPtr: - set_T(ecs_iptr_t, ptr, atol(value)); - break; - case EcsOpF32: - set_T(ecs_f32_t, ptr, atof(value)); - break; - case EcsOpF64: - set_T(ecs_f64_t, ptr, atof(value)); - break; - case EcsOpString: { - ecs_os_free(*(char**)ptr); - char *result = ecs_os_strdup(value); - set_T(ecs_string_t, ptr, result); - break; } - case EcsOpEnum: { - ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); - if (!c) { - char *path = ecs_get_fullpath(cursor->world, op->type); - ecs_err("unresolved enum constant '%s' for type '%s'", value, path); - ecs_os_free(path); - return -1; - } +} +#endif - const ecs_i32_t *v = ecs_get_pair_object( - cursor->world, c, EcsConstant, ecs_i32_t); - if (v == NULL) { - char *path = ecs_get_fullpath(cursor->world, op->type); - ecs_err("'%s' is not an enum constant for type '%s'", value, path); - ecs_os_free(path); - return -1; - } +/** Delete metric instances for entities that are no longer alive */ +static void ClearMetricInstance(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMetricSource *src = ecs_field(it, EcsMetricSource, 0); - set_T(ecs_i32_t, ptr, v[0]); - break; + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t src_e = src[i].entity; + if (!ecs_is_alive(world, src_e)) { + ecs_delete(world, it->entities[i]); + } } - case EcsOpBitmask: - if (parse_bitmask(cursor, op, ptr, value) != 0) { - return -1; +} + +/** Update member metric */ +static void UpdateMemberInstance(ecs_iter_t *it, bool counter) { + ecs_world_t *world = it->real_world; + EcsMetricValue *m = ecs_field(it, EcsMetricValue, 0); + EcsMetricMemberInstance *mi = ecs_field(it, EcsMetricMemberInstance, 1); + ecs_ftime_t dt = it->delta_time; + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_member_metric_ctx_t *ctx = mi[i].ctx; + ecs_ref_t *ref = &mi[i].ref; + if (!ref->entity) { + continue; } - break; - case EcsOpEntity: { - ecs_entity_t e = 0; - if (ecs_os_strcmp(value, "0")) { - if (cursor->lookup_action) { - e = cursor->lookup_action( - cursor->world, value, - cursor->lookup_ctx); + const void *ptr = ecs_ref_get_id(world, ref, ref->id); + if (ptr) { + ptr = ECS_OFFSET(ptr, ctx->offset); + if (!counter) { + m[i].value = ecs_meta_ptr_to_float(ctx->type_kind, ptr); } else { - e = ecs_lookup_path(cursor->world, 0, value); - } - - if (!e) { - ecs_err("unresolved entity identifier '%s'", value); - return -1; + m[i].value += + ecs_meta_ptr_to_float(ctx->type_kind, ptr) * (double)dt; } + } else { + ecs_delete(it->world, it->entities[i]); } - - set_T(ecs_entity_t, ptr, e); - break; - } - case EcsOpPop: - ecs_err("excess element '%s' in scope", value); - return -1; - default: - ecs_err("unsupported conversion from string '%s' to '%s'", - value, op_kind_str(op->kind)); - return -1; } - - return 0; } -int ecs_meta_set_string_literal( - ecs_meta_cursor_t *cursor, - const char *value) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); +static void UpdateGaugeMemberInstance(ecs_iter_t *it) { + UpdateMemberInstance(it, false); +} - ecs_size_t len = ecs_os_strlen(value); - if (value[0] != '\"' || value[len - 1] != '\"') { - ecs_err("invalid string literal '%s'", value); - return -1; - } +static void UpdateCounterMemberInstance(ecs_iter_t *it) { + UpdateMemberInstance(it, false); +} - switch(op->kind) { - case EcsOpChar: - set_T(ecs_char_t, ptr, value[1]); - break; - - default: - case EcsOpEntity: - case EcsOpString: - len -= 2; +static void UpdateCounterIncrementMemberInstance(ecs_iter_t *it) { + UpdateMemberInstance(it, true); +} - char *result = ecs_os_malloc(len + 1); - ecs_os_memcpy(result, value + 1, len); - result[len] = '\0'; +/** Update id metric */ +static void UpdateIdInstance(ecs_iter_t *it, bool counter) { + ecs_world_t *world = it->real_world; + EcsMetricValue *m = ecs_field(it, EcsMetricValue, 0); + EcsMetricIdInstance *mi = ecs_field(it, EcsMetricIdInstance, 1); + ecs_ftime_t dt = it->delta_time; - if (ecs_meta_set_string(cursor, result)) { - ecs_os_free(result); - return -1; + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_record_t *r = mi[i].r; + if (!r) { + continue; } - ecs_os_free(result); + ecs_table_t *table = r->table; + if (!table) { + ecs_delete(it->world, it->entities[i]); + continue; + } - break; + ecs_id_metric_ctx_t *ctx = mi[i].ctx; + ecs_component_record_t *cr = ctx->cr; + if (ecs_search(world, table, cr->id, NULL) != -1) { + if (!counter) { + m[i].value = 1.0; + } else { + m[i].value += 1.0 * (double)dt; + } + } else { + ecs_delete(it->world, it->entities[i]); + } } +} - return 0; +static void UpdateGaugeIdInstance(ecs_iter_t *it) { + UpdateIdInstance(it, false); } -int ecs_meta_set_entity( - ecs_meta_cursor_t *cursor, - ecs_entity_t value) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); +static void UpdateCounterIdInstance(ecs_iter_t *it) { + UpdateIdInstance(it, true); +} - switch(op->kind) { - case EcsOpEntity: - set_T(ecs_entity_t, ptr, value); - break; - default: - conversion_error(cursor, op, "entity"); - return -1; - } +/** Update oneof metric */ +static void UpdateOneOfInstance(ecs_iter_t *it, bool counter) { + ecs_world_t *world = it->real_world; + ecs_table_t *table = it->table; + void *m = ecs_table_get_column(table, it->trs[0]->column, it->offset); + EcsMetricOneOfInstance *mi = ecs_field(it, EcsMetricOneOfInstance, 1); + ecs_ftime_t dt = it->delta_time; - return 0; -} + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_oneof_metric_ctx_t *ctx = mi[i].ctx; + ecs_record_t *r = mi[i].r; + if (!r) { + continue; + } -int ecs_meta_set_null( - ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - switch (op->kind) { - case EcsOpString: - ecs_os_free(*(char**)ptr); - set_T(ecs_string_t, ptr, NULL); - break; - default: - conversion_error(cursor, op, "null"); - return -1; - } + ecs_table_t *mtable = r->table; - return 0; -} + double *value = ECS_ELEM(m, ctx->size, i); + if (!counter) { + ecs_os_memset(value, 0, ctx->size); + } -bool ecs_meta_get_bool( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpBool: return *(ecs_bool_t*)ptr; - case EcsOpI8: return *(ecs_i8_t*)ptr != 0; - case EcsOpU8: return *(ecs_u8_t*)ptr != 0; - case EcsOpChar: return *(ecs_char_t*)ptr != 0; - case EcsOpByte: return *(ecs_u8_t*)ptr != 0; - case EcsOpI16: return *(ecs_i16_t*)ptr != 0; - case EcsOpU16: return *(ecs_u16_t*)ptr != 0; - case EcsOpI32: return *(ecs_i32_t*)ptr != 0; - case EcsOpU32: return *(ecs_u32_t*)ptr != 0; - case EcsOpI64: return *(ecs_i64_t*)ptr != 0; - case EcsOpU64: return *(ecs_u64_t*)ptr != 0; - case EcsOpIPtr: return *(ecs_iptr_t*)ptr != 0; - case EcsOpUPtr: return *(ecs_uptr_t*)ptr != 0; - case EcsOpF32: return *(ecs_f32_t*)ptr != 0; - case EcsOpF64: return *(ecs_f64_t*)ptr != 0; - case EcsOpString: return *(const char**)ptr != NULL; - case EcsOpEnum: return *(ecs_i32_t*)ptr != 0; - case EcsOpBitmask: return *(ecs_u32_t*)ptr != 0; - case EcsOpEntity: return *(ecs_entity_t*)ptr != 0; - default: ecs_throw(ECS_INVALID_PARAMETER, - "invalid element for bool"); - } -error: - return 0; -} + if (!mtable) { + ecs_delete(it->world, it->entities[i]); + continue; + } -char ecs_meta_get_char( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpChar: return *(ecs_char_t*)ptr != 0; - default: ecs_throw(ECS_INVALID_PARAMETER, - "invalid element for char"); - } -error: - return 0; -} + ecs_component_record_t *cr = ctx->cr; + ecs_id_t id; + if (ecs_search(world, mtable, cr->id, &id) == -1) { + ecs_delete(it->world, it->entities[i]); + continue; + } -int64_t ecs_meta_get_int( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpBool: return *(ecs_bool_t*)ptr; - case EcsOpI8: return *(ecs_i8_t*)ptr; - case EcsOpU8: return *(ecs_u8_t*)ptr; - case EcsOpChar: return *(ecs_char_t*)ptr; - case EcsOpByte: return *(ecs_u8_t*)ptr; - case EcsOpI16: return *(ecs_i16_t*)ptr; - case EcsOpU16: return *(ecs_u16_t*)ptr; - case EcsOpI32: return *(ecs_i32_t*)ptr; - case EcsOpU32: return *(ecs_u32_t*)ptr; - case EcsOpI64: return *(ecs_i64_t*)ptr; - case EcsOpU64: return flecs_uto(int64_t, *(ecs_u64_t*)ptr); - case EcsOpIPtr: return *(ecs_iptr_t*)ptr; - case EcsOpUPtr: return flecs_uto(int64_t, *(ecs_uptr_t*)ptr); - case EcsOpF32: return (int64_t)*(ecs_f32_t*)ptr; - case EcsOpF64: return (int64_t)*(ecs_f64_t*)ptr; - case EcsOpString: return atoi(*(const char**)ptr); - case EcsOpEnum: return *(ecs_i32_t*)ptr; - case EcsOpBitmask: return *(ecs_u32_t*)ptr; - case EcsOpEntity: - ecs_throw(ECS_INVALID_PARAMETER, - "invalid conversion from entity to int"); - break; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); - } -error: - return 0; -} + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + uint64_t *offset = ecs_map_get(&ctx->target_offset, tgt); + if (!offset) { + ecs_err("unexpected relationship target for metric"); + continue; + } -uint64_t ecs_meta_get_uint( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpBool: return *(ecs_bool_t*)ptr; - case EcsOpI8: return flecs_ito(uint64_t, *(ecs_i8_t*)ptr); - case EcsOpU8: return *(ecs_u8_t*)ptr; - case EcsOpChar: return flecs_ito(uint64_t, *(ecs_char_t*)ptr); - case EcsOpByte: return flecs_ito(uint64_t, *(ecs_u8_t*)ptr); - case EcsOpI16: return flecs_ito(uint64_t, *(ecs_i16_t*)ptr); - case EcsOpU16: return *(ecs_u16_t*)ptr; - case EcsOpI32: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); - case EcsOpU32: return *(ecs_u32_t*)ptr; - case EcsOpI64: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); - case EcsOpU64: return *(ecs_u64_t*)ptr; - case EcsOpIPtr: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); - case EcsOpUPtr: return *(ecs_uptr_t*)ptr; - case EcsOpF32: return flecs_ito(uint64_t, *(ecs_f32_t*)ptr); - case EcsOpF64: return flecs_ito(uint64_t, *(ecs_f64_t*)ptr); - case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); - case EcsOpEnum: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); - case EcsOpBitmask: return *(ecs_u32_t*)ptr; - case EcsOpEntity: return *(ecs_entity_t*)ptr; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); - } -error: - return 0; -} + value = ECS_OFFSET(value, *offset); -double ecs_meta_get_float( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpBool: return *(ecs_bool_t*)ptr; - case EcsOpI8: return *(ecs_i8_t*)ptr; - case EcsOpU8: return *(ecs_u8_t*)ptr; - case EcsOpChar: return *(ecs_char_t*)ptr; - case EcsOpByte: return *(ecs_u8_t*)ptr; - case EcsOpI16: return *(ecs_i16_t*)ptr; - case EcsOpU16: return *(ecs_u16_t*)ptr; - case EcsOpI32: return *(ecs_i32_t*)ptr; - case EcsOpU32: return *(ecs_u32_t*)ptr; - case EcsOpI64: return (double)*(ecs_i64_t*)ptr; - case EcsOpU64: return (double)*(ecs_u64_t*)ptr; - case EcsOpIPtr: return (double)*(ecs_iptr_t*)ptr; - case EcsOpUPtr: return (double)*(ecs_uptr_t*)ptr; - case EcsOpF32: return (double)*(ecs_f32_t*)ptr; - case EcsOpF64: return *(ecs_f64_t*)ptr; - case EcsOpString: return atof(*(const char**)ptr); - case EcsOpEnum: return *(ecs_i32_t*)ptr; - case EcsOpBitmask: return *(ecs_u32_t*)ptr; - case EcsOpEntity: - ecs_throw(ECS_INVALID_PARAMETER, - "invalid conversion from entity to float"); - break; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); + if (!counter) { + *value = 1.0; + } else { + *value += 1.0 * (double)dt; + } } -error: - return 0; } -const char* ecs_meta_get_string( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpString: return *(const char**)ptr; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); - } -error: - return 0; +static void UpdateGaugeOneOfInstance(ecs_iter_t *it) { + UpdateOneOfInstance(it, false); } -ecs_entity_t ecs_meta_get_entity( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpEntity: return *(ecs_entity_t*)ptr; - default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); - } -error: - return 0; +static void UpdateCounterOneOfInstance(ecs_iter_t *it) { + UpdateOneOfInstance(it, true); } -#endif +static void UpdateCountTargets(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsMetricCountTargets *m = ecs_field(it, EcsMetricCountTargets, 0); + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_count_targets_metric_ctx_t *ctx = m[i].ctx; + ecs_component_record_t *cur = ctx->cr; + while ((cur = flecs_component_first_next(cur))) { + ecs_id_t id = cur->id; + ecs_entity_t *mi = ecs_map_ensure(&ctx->targets, id); + if (!mi[0]) { + mi[0] = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); + ecs_entity_t tgt = ecs_pair_second(world, cur->id); + const char *name = ecs_get_name(world, tgt); + if (name) { + ecs_set_name(world, mi[0], name); + } + EcsMetricSource *source = ecs_ensure( + world, mi[0], EcsMetricSource); + source->entity = tgt; + } -#ifdef FLECS_EXPR + EcsMetricValue *value = ecs_ensure(world, mi[0], EcsMetricValue); + value->value += (double)ecs_count_id(world, cur->id) * + (double)it->delta_system_time; + } + } +} -static -int expr_ser_type( - const ecs_world_t *world, - ecs_vector_t *ser, - const void *base, - ecs_strbuf_t *str); +static void UpdateCountIds(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + EcsMetricCountIds *m = ecs_field(it, EcsMetricCountIds, 0); + EcsMetricValue *v = ecs_field(it, EcsMetricValue, 1); -static -int expr_ser_type_ops( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - ecs_strbuf_t *str); + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + v[i].value += (double)ecs_count_id(world, m[i].id) * + (double)it->delta_system_time; + } +} +/** Initialize member metric */ static -int expr_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str); +int flecs_member_metric_init( + ecs_world_t *world, + ecs_entity_t metric, + const ecs_metric_desc_t *desc) +{ + ecs_entity_t type = 0, member_type = 0, member = 0, id = 0; + uintptr_t offset = 0; + + if (desc->dotmember) { + if (!desc->id) { + char *metric_name = ecs_get_path(world, metric); + ecs_err("missing id for metric '%s' with member '%s", + metric_name, desc->dotmember); + ecs_os_free(metric_name); + goto error; + } -static -ecs_primitive_kind_t expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { - return kind - EcsOpPrimitive; -} + if (desc->member) { + char *metric_name = ecs_get_path(world, metric); + ecs_err("cannot set both member and dotmember for metric '%s'", + metric_name); + ecs_os_free(metric_name); + goto error; + } -/* Serialize a primitive value */ -static -int expr_ser_primitive( - const ecs_world_t *world, - ecs_primitive_kind_t kind, - const void *base, - ecs_strbuf_t *str) -{ - const char *bool_str[] = { "false", "true" }; + type = ecs_get_typeid(world, desc->id); - switch(kind) { - case EcsBool: - ecs_strbuf_appendstr(str, bool_str[(int)*(bool*)base]); - break; - case EcsChar: { - char chbuf[3]; - char ch = *(char*)base; - if (ch) { - ecs_chresc(chbuf, *(char*)base, '"'); - ecs_strbuf_appendstrn(str, "\"", 1); - ecs_strbuf_appendstr(str, chbuf); - ecs_strbuf_appendstrn(str, "\"", 1); - } else { - ecs_strbuf_appendstr(str, "0"); + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL); + if (ecs_meta_push(&cur)) { + char *metric_name = ecs_get_path(world, metric); + ecs_err("invalid type for metric '%s'", metric_name); + ecs_os_free(metric_name); + goto error; } - break; - } - case EcsByte: - ecs_strbuf_append(str, "%u", *(uint8_t*)base); - break; - case EcsU8: - ecs_strbuf_append(str, "%u", *(uint8_t*)base); - break; - case EcsU16: - ecs_strbuf_append(str, "%u", *(uint16_t*)base); - break; - case EcsU32: - ecs_strbuf_append(str, "%u", *(uint32_t*)base); - break; - case EcsU64: - ecs_strbuf_append(str, "%llu", *(uint64_t*)base); - break; - case EcsI8: - ecs_strbuf_append(str, "%d", *(int8_t*)base); - break; - case EcsI16: - ecs_strbuf_append(str, "%d", *(int16_t*)base); - break; - case EcsI32: - ecs_strbuf_append(str, "%d", *(int32_t*)base); - break; - case EcsI64: - ecs_strbuf_append(str, "%lld", *(int64_t*)base); - break; - case EcsF32: - ecs_strbuf_appendflt(str, (double)*(float*)base, 0); - break; - case EcsF64: - ecs_strbuf_appendflt(str, *(double*)base, 0); - break; - case EcsIPtr: - ecs_strbuf_append(str, "%i", *(intptr_t*)base); - break; - case EcsUPtr: - ecs_strbuf_append(str, "%u", *(uintptr_t*)base); - break; - case EcsString: { - char *value = *(char**)base; - if (value) { - ecs_size_t length = ecs_stresc(NULL, 0, '"', value); - if (length == ecs_os_strlen(value)) { - ecs_strbuf_appendstrn(str, "\"", 1); - ecs_strbuf_appendstr(str, value); - ecs_strbuf_appendstrn(str, "\"", 1); - } else { - char *out = ecs_os_malloc(length + 3); - ecs_stresc(out + 1, length, '"', value); - out[0] = '"'; - out[length + 1] = '"'; - out[length + 2] = '\0'; - ecs_strbuf_appendstr_zerocpy(str, out); + if (ecs_meta_dotmember(&cur, desc->dotmember)) { + char *metric_name = ecs_get_path(world, metric); + ecs_err("invalid dotmember '%s' for metric '%s'", + desc->dotmember, metric_name); + ecs_os_free(metric_name); + goto error; + } + + id = desc->id; + member_type = ecs_meta_get_type(&cur); + offset = (uintptr_t)ecs_meta_get_ptr(&cur); + member = ecs_meta_get_member_id(&cur); + } else { + const EcsMember *m = ecs_get(world, desc->member, EcsMember); + if (!m) { + char *metric_name = ecs_get_path(world, metric); + char *member_name = ecs_get_path(world, desc->member); + ecs_err("entity '%s' provided for metric '%s' is not a member", + member_name, metric_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; + } + + type = ecs_get_parent(world, desc->member); + if (!type) { + char *metric_name = ecs_get_path(world, metric); + char *member_name = ecs_get_path(world, desc->member); + ecs_err("member '%s' provided for metric '%s' is not part of a type", + member_name, metric_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; + } + + id = type; + if (desc->id) { + if (type != ecs_get_typeid(world, desc->id)) { + char *metric_name = ecs_get_path(world, metric); + char *member_name = ecs_get_path(world, desc->member); + char *id_name = ecs_get_path(world, desc->id); + ecs_err("member '%s' for metric '%s' is not of type '%s'", + member_name, metric_name, id_name); + ecs_os_free(id_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; } - } else { - ecs_strbuf_appendstr(str, "null"); + id = desc->id; } - break; + + member = desc->member; + member_type = m->type; + offset = flecs_ito(uintptr_t, m->offset); } - case EcsEntity: { - ecs_entity_t e = *(ecs_entity_t*)base; - if (!e) { - ecs_strbuf_appendstr(str, "0"); - } else { - char *path = ecs_get_fullpath(world, e); - ecs_assert(path != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_strbuf_appendstr(str, path); - ecs_os_free(path); - } - break; + + const EcsPrimitive *p = ecs_get(world, member_type, EcsPrimitive); + if (!p) { + char *metric_name = ecs_get_path(world, metric); + char *member_name = ecs_get_path(world, desc->member); + ecs_err("member '%s' provided for metric '%s' must have primitive type", + member_name, metric_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; } - default: - ecs_err("invalid primitive kind"); - return -1; + + const EcsType *mt = ecs_get(world, type, EcsType); + if (!mt) { + char *metric_name = ecs_get_path(world, metric); + char *member_name = ecs_get_path(world, desc->member); + ecs_err("parent of member '%s' for metric '%s' is not a type", + member_name, metric_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; + } + + if (mt->kind != EcsStructType) { + char *metric_name = ecs_get_path(world, metric); + char *member_name = ecs_get_path(world, desc->member); + ecs_err("parent of member '%s' for metric '%s' is not a struct", + member_name, metric_name); + ecs_os_free(member_name); + ecs_os_free(metric_name); + goto error; } + ecs_member_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_member_metric_ctx_t); + ctx->metric.metric = metric; + ctx->metric.kind = desc->kind; + ctx->type_kind = p->kind; + ctx->offset = flecs_uto(uint16_t, offset); + + ecs_observer(world, { + .entity = metric, + .events = { EcsOnAdd }, + .query.terms[0] = { .id = id }, + .callback = flecs_metrics_on_member_metric, + .yield_existing = true, + .ctx = ctx + }); + + ecs_set_pair(world, metric, EcsMetricMember, member, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); + return 0; +error: + return -1; } -/* Serialize enumeration */ +/** Update id metric */ static -int expr_ser_enum( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str) -{ - const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); - ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); - - int32_t value = *(int32_t*)base; - - /* Enumeration constants are stored in a map that is keyed on the - * enumeration value. */ - ecs_enum_constant_t *constant = ecs_map_get( - enum_type->constants, ecs_enum_constant_t, value); - if (!constant) { - char *path = ecs_get_fullpath(world, op->type); - ecs_err("value %d is not valid for enum type '%s'", value, path); - ecs_os_free(path); - goto error; - } +int flecs_id_metric_init( + ecs_world_t *world, + ecs_entity_t metric, + const ecs_metric_desc_t *desc) +{ + ecs_id_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_id_metric_ctx_t); + ctx->metric.metric = metric; + ctx->metric.kind = desc->kind; + ctx->cr = flecs_components_ensure(world, desc->id); + ecs_check(ctx->cr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_observer(world, { + .entity = metric, + .events = { EcsOnAdd }, + .query.terms[0] = { .id = desc->id }, + .callback = flecs_metrics_on_id_metric, + .yield_existing = true, + .ctx = ctx + }); - ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); + ecs_set(world, metric, EcsMetricId, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } -/* Serialize bitmask */ +/** Update oneof metric */ static -int expr_ser_bitmask( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) -{ - const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); - ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); +int flecs_oneof_metric_init( + ecs_world_t *world, + ecs_entity_t metric, + ecs_entity_t scope, + const ecs_metric_desc_t *desc) +{ + ecs_oneof_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_oneof_metric_ctx_t); + ctx->metric.metric = metric; + ctx->metric.kind = desc->kind; + ctx->cr = flecs_components_ensure(world, desc->id); + ecs_check(ctx->cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_init(&ctx->target_offset, NULL); + + /* Add member for each child of oneof to metric, so it can be used as metric + * instance type that holds values for all targets */ + ecs_iter_t it = ecs_children(world, scope); + uint64_t offset = 0; + while (ecs_children_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_entity_t tgt = it.entities[i]; + const char *name = ecs_get_name(world, tgt); + if (!name) { + /* Member must have name */ + continue; + } - uint32_t value = *(uint32_t*)ptr; - ecs_map_key_t key; - ecs_bitmask_constant_t *constant; - int count = 0; + char *to_snake_case = flecs_to_snake_case(name); - ecs_strbuf_list_push(str, "", "|"); + ecs_entity_t mbr = ecs_entity(world, { + .name = to_snake_case, + .parent = metric + }); - /* Multiple flags can be set at a given time. Iterate through all the flags - * and append the ones that are set. */ - ecs_map_iter_t it = ecs_map_iter(bitmask_type->constants); - while ((constant = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { - if ((value & key) == key) { - ecs_strbuf_list_appendstr(str, - ecs_get_name(world, constant->constant)); - count ++; - value -= (uint32_t)key; + ecs_os_free(to_snake_case); + + ecs_set(world, mbr, EcsMember, { + .type = ecs_id(ecs_f64_t), + .unit = EcsSeconds + }); + + /* Truncate upper 32 bits of target so we can lookup the offset + * with the id we get from the pair */ + ecs_map_ensure(&ctx->target_offset, (uint32_t)tgt)[0] = offset; + + offset += sizeof(double); } } - if (value != 0) { - /* All bits must have been matched by a constant */ - char *path = ecs_get_fullpath(world, op->type); - ecs_err( - "value for bitmask %s contains bits (%u) that cannot be mapped to constant", - path, value); - ecs_os_free(path); - goto error; - } + ctx->size = flecs_uto(ecs_size_t, offset); - if (!count) { - ecs_strbuf_list_appendstr(str, "0"); - } + ecs_observer(world, { + .entity = metric, + .events = { EcsMonitor }, + .query.terms[0] = { .id = desc->id }, + .callback = flecs_metrics_on_oneof_metric, + .yield_existing = true, + .ctx = ctx + }); - ecs_strbuf_list_pop(str, ""); + ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } -/* Serialize elements of a contiguous array */ static -int expr_ser_elements( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - int32_t elem_count, - int32_t elem_size, - ecs_strbuf_t *str) +int flecs_count_id_targets_metric_init( + ecs_world_t *world, + ecs_entity_t metric, + const ecs_metric_desc_t *desc) { - ecs_strbuf_list_push(str, "[", ", "); - - const void *ptr = base; - - int i; - for (i = 0; i < elem_count; i ++) { - ecs_strbuf_list_next(str); - if (expr_ser_type_ops(world, ops, op_count, ptr, str)) { - return -1; - } - ptr = ECS_OFFSET(ptr, elem_size); - } + ecs_count_targets_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_count_targets_metric_ctx_t); + ctx->metric.metric = metric; + ctx->metric.kind = desc->kind; + ctx->cr = flecs_components_ensure(world, desc->id); + ecs_check(ctx->cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_init(&ctx->targets, NULL); - ecs_strbuf_list_pop(str, "]"); + ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx }); + ecs_add_pair(world, metric, EcsMetric, desc->kind); + ecs_add_id(world, metric, EcsMetric); return 0; +error: + return -1; } static -int expr_ser_type_elements( - const ecs_world_t *world, - ecs_entity_t type, - const void *base, - int32_t elem_count, - ecs_strbuf_t *str) +int flecs_count_ids_metric_init( + ecs_world_t *world, + ecs_entity_t metric, + const ecs_metric_desc_t *desc) { - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); - - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t); - int32_t op_count = ecs_vector_count(ser->ops); - - return expr_ser_elements( - world, ops, op_count, base, elem_count, comp->size, str); + ecs_set(world, metric, EcsMetricCountIds, { .id = desc->id }); + ecs_set(world, metric, EcsMetricValue, { .value = 0 }); + return 0; } -/* Serialize array */ -static -int expr_ser_array( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) +ecs_entity_t ecs_metric_init( + ecs_world_t *world, + const ecs_metric_desc_t *desc) { - const EcsArray *a = ecs_get(world, op->type, EcsArray); - ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_metric_desc_t was not initialized to zero"); + flecs_poly_assert(world, ecs_world_t); - return expr_ser_type_elements( - world, a->type, ptr, a->count, str); -} + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new(world); + } -/* Serialize vector */ -static -int expr_ser_vector( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str) -{ - ecs_vector_t *value = *(ecs_vector_t**)base; - if (!value) { - ecs_strbuf_appendstr(str, "null"); - return 0; + ecs_entity_t kind = desc->kind; + if (!kind) { + ecs_err("missing metric kind"); + goto error; } - const EcsVector *v = ecs_get(world, op->type, EcsVector); - ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); + if (kind != EcsGauge && + kind != EcsCounter && + kind != EcsCounterId && + kind != EcsCounterIncrement) + { + ecs_err("invalid metric kind %s", ecs_get_path(world, kind)); + goto error; + } - const EcsComponent *comp = ecs_get(world, v->type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + if (kind == EcsCounterIncrement && !desc->member && !desc->dotmember) { + ecs_err("CounterIncrement can only be used in combination with member"); + goto error; + } - int32_t count = ecs_vector_count(value); - void *array = ecs_vector_first_t(value, comp->size, comp->alignment); + if (kind == EcsCounterId && (desc->member || desc->dotmember)) { + ecs_err("CounterId cannot be used in combination with member"); + goto error; + } - /* Serialize contiguous buffer of vector */ - return expr_ser_type_elements(world, v->type, array, count, str); -} + if (desc->brief) { +#ifdef FLECS_DOC + ecs_doc_set_brief(world, result, desc->brief); +#else + ecs_warn("FLECS_DOC is not enabled, ignoring metrics brief"); +#endif + } -/* Forward serialization to the different type kinds */ -static -int expr_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) -{ - switch(op->kind) { - case EcsOpPush: - case EcsOpPop: - /* Should not be parsed as single op */ - ecs_throw(ECS_INVALID_PARAMETER, NULL); - break; - case EcsOpEnum: - if (expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpBitmask: - if (expr_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpArray: - if (expr_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpVector: - if (expr_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { + if (desc->member || desc->dotmember) { + if (flecs_member_metric_init(world, result, desc)) { goto error; } - break; - default: - if (expr_ser_primitive(world, expr_op_to_primitive_kind(op->kind), - ECS_OFFSET(ptr, op->offset), str)) - { - /* Unknown operation */ - ecs_err("unknown serializer operation kind (%d)", op->kind); - goto error; + } else if (desc->id) { + if (desc->targets) { + if (!ecs_id_is_pair(desc->id)) { + ecs_err("cannot specify targets for id that is not a pair"); + goto error; + } + if (ECS_PAIR_FIRST(desc->id) == EcsWildcard) { + ecs_err("first element of pair cannot be wildcard with " + " targets enabled"); + goto error; + } + if (ECS_PAIR_SECOND(desc->id) != EcsWildcard) { + ecs_err("second element of pair must be wildcard with " + " targets enabled"); + goto error; + } + + if (kind == EcsCounterId) { + if (flecs_count_id_targets_metric_init(world, result, desc)) { + goto error; + } + } else { + ecs_entity_t first = ecs_pair_first(world, desc->id); + ecs_entity_t scope = flecs_get_oneof(world, first); + if (!scope) { + ecs_err("first element of pair must have OneOf with " + " targets enabled"); + goto error; + } + + if (flecs_oneof_metric_init(world, result, scope, desc)) { + goto error; + } + } + } else { + if (kind == EcsCounterId) { + if (flecs_count_ids_metric_init(world, result, desc)) { + goto error; + } + } else { + if (flecs_id_metric_init(world, result, desc)) { + goto error; + } + } } - break; + } else { + ecs_err("missing source specified for metric"); + goto error; } - return 0; + return result; error: - return -1; + if (result && result != desc->entity) { + ecs_delete(world, result); + } + return 0; } -/* Iterate over a slice of the type ops array */ -static -int expr_ser_type_ops( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - ecs_strbuf_t *str) -{ - for (int i = 0; i < op_count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; +void FlecsMetricsImport(ecs_world_t *world) { + ECS_MODULE_DEFINE(world, FlecsMetrics); - if (op != ops) { - if (op->name) { - ecs_strbuf_list_next(str); - ecs_strbuf_append(str, "%s: ", op->name); - } + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsMeta); + ECS_IMPORT(world, FlecsUnits); - int32_t elem_count = op->count; - if (elem_count > 1 && op != ops) { - /* Serialize inline array */ - if (expr_ser_elements(world, op, op->op_count, base, - elem_count, op->size, str)) - { - return -1; - } + ecs_set_name_prefix(world, "Ecs"); + ECS_TAG_DEFINE(world, EcsMetric); + ecs_entity_t old_scope = ecs_set_scope(world, EcsMetric); + ECS_TAG_DEFINE(world, EcsCounter); + ECS_TAG_DEFINE(world, EcsCounterIncrement); + ECS_TAG_DEFINE(world, EcsCounterId); + ECS_TAG_DEFINE(world, EcsGauge); + ecs_set_scope(world, old_scope); - i += op->op_count - 1; - continue; - } + ecs_set_name_prefix(world, "EcsMetric"); + ECS_TAG_DEFINE(world, EcsMetricInstance); + ECS_COMPONENT_DEFINE(world, EcsMetricValue); + ECS_COMPONENT_DEFINE(world, EcsMetricSource); + ECS_COMPONENT_DEFINE(world, EcsMetricMemberInstance); + ECS_COMPONENT_DEFINE(world, EcsMetricIdInstance); + ECS_COMPONENT_DEFINE(world, EcsMetricOneOfInstance); + ECS_COMPONENT_DEFINE(world, EcsMetricMember); + ECS_COMPONENT_DEFINE(world, EcsMetricId); + ECS_COMPONENT_DEFINE(world, EcsMetricOneOf); + ECS_COMPONENT_DEFINE(world, EcsMetricCountIds); + ECS_COMPONENT_DEFINE(world, EcsMetricCountTargets); + + ecs_struct(world, { + .entity = ecs_id(EcsMetricValue), + .members = { + { .name = "value", .type = ecs_id(ecs_f64_t) } } - - switch(op->kind) { - case EcsOpPush: - ecs_strbuf_list_push(str, "{", ", "); - break; - case EcsOpPop: - ecs_strbuf_list_pop(str, "}"); - break; - default: - if (expr_ser_type_op(world, op, base, str)) { - goto error; - } - break; + }); + + ecs_struct(world, { + .entity = ecs_id(EcsMetricSource), + .members = { + { .name = "entity", .type = ecs_id(ecs_entity_t) } } - } + }); - return 0; -error: - return -1; -} + ecs_set_hooks(world, EcsMetricMember, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsMetricMember), + .move = ecs_move(EcsMetricMember) + }); -/* Iterate over the type ops of a type */ -static -int expr_ser_type( - const ecs_world_t *world, - ecs_vector_t *v_ops, - const void *base, - ecs_strbuf_t *str) -{ - ecs_meta_type_op_t *ops = ecs_vector_first(v_ops, ecs_meta_type_op_t); - int32_t count = ecs_vector_count(v_ops); - return expr_ser_type_ops(world, ops, count, base, str); -} + ecs_set_hooks(world, EcsMetricId, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsMetricId), + .move = ecs_move(EcsMetricId) + }); -int ecs_ptr_to_expr_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *ptr, - ecs_strbuf_t *buf_out) -{ - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (ser == NULL) { - char *path = ecs_get_fullpath(world, type); - ecs_err("cannot serialize value for type '%s'", path); - ecs_os_free(path); - goto error; - } + ecs_set_hooks(world, EcsMetricOneOf, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsMetricOneOf), + .move = ecs_move(EcsMetricOneOf) + }); - if (expr_ser_type(world, ser->ops, ptr, buf_out)) { - goto error; - } + ecs_set_hooks(world, EcsMetricCountTargets, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsMetricCountTargets), + .move = ecs_move(EcsMetricCountTargets) + }); - return 0; -error: - return -1; -} + ecs_add_id(world, EcsMetric, EcsOneOf); -char* ecs_ptr_to_expr( - const ecs_world_t *world, - ecs_entity_t type, - const void* ptr) -{ - ecs_strbuf_t str = ECS_STRBUF_INIT; +#ifdef FLECS_DOC + ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, + Source); +#endif - if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { - ecs_strbuf_reset(&str); - return NULL; - } + ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore, + [in] Source); - return ecs_strbuf_get(&str); -} + ECS_SYSTEM(world, UpdateGaugeMemberInstance, EcsPreStore, + [out] Value, + [in] MemberInstance, + [none] (Metric, Gauge)); -int ecs_primitive_to_expr_buf( - const ecs_world_t *world, - ecs_primitive_kind_t kind, - const void *base, - ecs_strbuf_t *str) -{ - return expr_ser_primitive(world, kind, base, str); + ECS_SYSTEM(world, UpdateCounterMemberInstance, EcsPreStore, + [out] Value, + [in] MemberInstance, + [none] (Metric, Counter)); + + ECS_SYSTEM(world, UpdateCounterIncrementMemberInstance, EcsPreStore, + [out] Value, + [in] MemberInstance, + [none] (Metric, CounterIncrement)); + + ECS_SYSTEM(world, UpdateGaugeIdInstance, EcsPreStore, + [out] Value, + [in] IdInstance, + [none] (Metric, Gauge)); + + ECS_SYSTEM(world, UpdateCounterIdInstance, EcsPreStore, + [inout] Value, + [in] IdInstance, + [none] (Metric, Counter)); + + ECS_SYSTEM(world, UpdateGaugeOneOfInstance, EcsPreStore, + [none] (_, Value), + [in] OneOfInstance, + [none] (Metric, Gauge)); + + ECS_SYSTEM(world, UpdateCounterOneOfInstance, EcsPreStore, + [none] (_, Value), + [in] OneOfInstance, + [none] (Metric, Counter)); + + ECS_SYSTEM(world, UpdateCountIds, EcsPreStore, + [inout] CountIds, Value); + + ECS_SYSTEM(world, UpdateCountTargets, EcsPreStore, + [inout] CountTargets); } #endif +/** + * @file addons/module.c + * @brief Module addon. + */ + +#ifdef FLECS_MODULE -#ifdef FLECS_EXPR -char* ecs_chresc( - char *out, - char in, - char delimiter) +char* flecs_module_path_from_c( + const char *c_name) { - char *bptr = out; - switch(in) { - case '\a': - *bptr++ = '\\'; - *bptr = 'a'; - break; - case '\b': - *bptr++ = '\\'; - *bptr = 'b'; - break; - case '\f': - *bptr++ = '\\'; - *bptr = 'f'; - break; - case '\n': - *bptr++ = '\\'; - *bptr = 'n'; - break; - case '\r': - *bptr++ = '\\'; - *bptr = 'r'; - break; - case '\t': - *bptr++ = '\\'; - *bptr = 't'; - break; - case '\v': - *bptr++ = '\\'; - *bptr = 'v'; - break; - case '\\': - *bptr++ = '\\'; - *bptr = '\\'; - break; - default: - if (in == delimiter) { - *bptr++ = '\\'; - *bptr = delimiter; - } else { - *bptr = in; + ecs_strbuf_t str = ECS_STRBUF_INIT; + const char *ptr; + char ch; + + for (ptr = c_name; (ch = *ptr); ptr++) { + if (isupper(ch)) { + ch = flecs_ito(char, tolower(ch)); + if (ptr != c_name) { + ecs_strbuf_appendstrn(&str, ".", 1); + } } - break; - } - *(++bptr) = '\0'; + ecs_strbuf_appendstrn(&str, &ch, 1); + } - return bptr; + return ecs_strbuf_get(&str); } -const char* ecs_chrparse( - const char *in, - char *out) +ecs_entity_t ecs_import( + ecs_world_t *world, + ecs_module_action_t module, + const char *module_name) { - const char *result = in + 1; - char ch; + flecs_poly_assert(world, ecs_world_t); + ecs_check(!(world->flags & EcsWorldReadonly), + ECS_INVALID_WHILE_READONLY, NULL); - if (in[0] == '\\') { - result ++; + ecs_entity_t old_scope = ecs_set_scope(world, 0); + const char *old_name_prefix = world->info.name_prefix; - switch(in[1]) { - case 'a': - ch = '\a'; - break; - case 'b': - ch = '\b'; - break; - case 'f': - ch = '\f'; - break; - case 'n': - ch = '\n'; - break; - case 'r': - ch = '\r'; - break; - case 't': - ch = '\t'; - break; - case 'v': - ch = '\v'; - break; - case '\\': - ch = '\\'; - break; - case '"': - ch = '"'; - break; - case '0': - ch = '\0'; - break; - case ' ': - ch = ' '; - break; - case '$': - ch = '$'; - break; - default: - goto error; - } - } else { - ch = in[0]; - } + char *path = flecs_module_path_from_c(module_name); + ecs_entity_t e = ecs_lookup(world, path); + ecs_os_free(path); + + if (!e) { + ecs_trace("#[magenta]import#[reset] %s", module_name); + ecs_log_push(); - if (out) { - *out = ch; + /* Load module */ + module(world); + + /* Lookup module entity (must be registered by module) */ + e = ecs_lookup(world, module_name); + ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); + + ecs_log_pop(); } - return result; + /* Restore to previous state */ + ecs_set_scope(world, old_scope); + world->info.name_prefix = old_name_prefix; + + return e; error: - return NULL; + return 0; } -ecs_size_t ecs_stresc( - char *out, - ecs_size_t n, - char delimiter, - const char *in) +ecs_entity_t ecs_import_c( + ecs_world_t *world, + ecs_module_action_t module, + const char *c_name) { - const char *ptr = in; - char ch, *bptr = out, buff[3]; - ecs_size_t written = 0; - while ((ch = *ptr++)) { - if ((written += (ecs_size_t)(ecs_chresc( - buff, ch, delimiter) - buff)) <= n) - { - /* If size != 0, an out buffer must be provided. */ - ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL); - *bptr++ = buff[0]; - if ((ch = buff[1])) { - *bptr = ch; - bptr++; - } - } + char *name = flecs_module_path_from_c(c_name); + ecs_entity_t e = ecs_import(world, module, name); + ecs_os_free(name); + return e; +} + +ecs_entity_t ecs_import_from_library( + ecs_world_t *world, + const char *library_name, + const char *module_name) +{ + ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); + + char *import_func = ECS_CONST_CAST(char*, module_name); + char *module = ECS_CONST_CAST(char*, module_name); + + if (!ecs_os_has_modules() || !ecs_os_has_dl()) { + ecs_err( + "library loading not supported, set module_to_dl, dlopen, dlclose " + "and dlproc os API callbacks first"); + return 0; } - if (bptr) { - while (written < n) { - *bptr = '\0'; - bptr++; - written++; + /* If no module name is specified, try default naming convention for loading + * the main module from the library */ + if (!import_func) { + import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); + ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); + + const char *ptr; + char ch, *bptr = import_func; + bool capitalize = true; + for (ptr = library_name; (ch = *ptr); ptr ++) { + if (ch == '.') { + capitalize = true; + } else { + if (capitalize) { + *bptr = flecs_ito(char, toupper(ch)); + bptr ++; + capitalize = false; + } else { + *bptr = flecs_ito(char, tolower(ch)); + bptr ++; + } + } } + + *bptr = '\0'; + + module = ecs_os_strdup(import_func); + ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); + + ecs_os_strcat(bptr, "Import"); } - return written; -error: - return 0; -} -char* ecs_astresc( - char delimiter, - const char *in) -{ - if (!in) { - return NULL; + char *library_filename = ecs_os_module_to_dl(library_name); + if (!library_filename) { + ecs_err("failed to find library file for '%s'", library_name); + if (module != module_name) { + ecs_os_free(module); + } + return 0; + } else { + ecs_trace("found file '%s' for library '%s'", + library_filename, library_name); } - ecs_size_t len = ecs_stresc(NULL, 0, delimiter, in); - char *out = ecs_os_malloc_n(char, len + 1); - ecs_stresc(out, len, delimiter, in); - out[len] = '\0'; - return out; -} + ecs_os_dl_t dl = ecs_os_dlopen(library_filename); + if (!dl) { + ecs_err("failed to load library '%s' ('%s')", + library_name, library_filename); + + ecs_os_free(library_filename); -#endif + if (module != module_name) { + ecs_os_free(module); + } + return 0; + } else { + ecs_trace("library '%s' ('%s') loaded", + library_name, library_filename); + } + ecs_module_action_t action = (ecs_module_action_t) + ecs_os_dlproc(dl, import_func); + if (!action) { + ecs_err("failed to load import function %s from library %s", + import_func, library_name); + ecs_os_free(library_filename); + ecs_os_dlclose(dl); + return 0; + } else { + ecs_trace("found import function '%s' in library '%s' for module '%s'", + import_func, library_name, module); + } -#ifdef FLECS_EXPR + /* Do not free id, as it will be stored as the component identifier */ + ecs_entity_t result = ecs_import(world, action, module); -const char *ecs_parse_expr_token( - const char *name, - const char *expr, - const char *ptr, - char *token) -{ - const char *start = ptr; - char *token_ptr = token; + if (import_func != module_name) { + ecs_os_free(import_func); + } - while ((ptr = ecs_parse_token(name, expr, ptr, token_ptr))) { - if (ptr[0] == '|') { - token_ptr = &token_ptr[ptr - start]; - token_ptr[0] = '|'; - token_ptr[1] = '\0'; - token_ptr ++; - ptr ++; - start = ptr; - } else { - break; - } + if (module != module_name) { + ecs_os_free(module); } - return ptr; + ecs_os_free(library_filename); + + return result; +error: + return 0; } -const char* ecs_parse_expr( - const ecs_world_t *world, - const char *ptr, - ecs_entity_t type, - void *data_out, - const ecs_parse_expr_desc_t *desc) +ecs_entity_t ecs_module_init( + ecs_world_t *world, + const char *c_name, + const ecs_component_desc_t *desc) { - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - char token[ECS_MAX_TOKEN_SIZE]; - int depth = 0; - - const char *name = NULL; - const char *expr = NULL; + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_poly_assert(world, ecs_world_t); - ptr = ecs_parse_fluff(ptr, NULL); + ecs_entity_t old_scope = ecs_set_scope(world, 0); - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, data_out); - if (cur.valid == false) { - return NULL; + ecs_entity_t e = desc->entity; + if (!e) { + char *module_path = flecs_module_path_from_c(c_name); + e = ecs_entity(world, { .name = module_path }); + ecs_set_symbol(world, e, module_path); + ecs_os_free(module_path); + } else if (!ecs_exists(world, e)) { + char *module_path = flecs_module_path_from_c(c_name); + ecs_make_alive(world, e); + ecs_add_fullpath(world, e, module_path); + ecs_set_symbol(world, e, module_path); + ecs_os_free(module_path); } + + ecs_add_id(world, e, EcsModule); - if (desc) { - name = desc->name; - expr = desc->expr; - cur.lookup_action = desc->lookup_action; - cur.lookup_ctx = desc->lookup_ctx; - } + ecs_component_desc_t private_desc = *desc; + private_desc.entity = e; - while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { + if (desc->type.size) { + ecs_entity_t result = ecs_component_init(world, &private_desc); + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); + (void)result; + } - if (!ecs_os_strcmp(token, "{")) { - ecs_entity_t scope_type = ecs_meta_get_type(&cur); - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } + ecs_set_scope(world, old_scope); - if (ecs_meta_is_collection(&cur)) { - char *path = ecs_get_fullpath(world, scope_type); - ecs_parser_error(name, expr, ptr - expr, - "expected '[' for collection type '%s'", path); - ecs_os_free(path); - return NULL; - } - } + return e; +error: + return 0; +} - else if (!ecs_os_strcmp(token, "}")) { - depth --; +#endif - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected ']'"); - return NULL; - } +/** + * @file addons/rest.c + * @brief Rest addon. + */ - if (ecs_meta_pop(&cur) != 0) { - goto error; - } - } - else if (!ecs_os_strcmp(token, "[")) { - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } +/** + * @file addons/pipeline/pipeline.h + * @brief Internal functions/types for pipeline addon. + */ - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '{'"); - return NULL; - } - } +#ifndef FLECS_PIPELINE_PRIVATE_H +#define FLECS_PIPELINE_PRIVATE_H - else if (!ecs_os_strcmp(token, "]")) { - depth --; - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '}'"); - return NULL; - } +/** Instruction data for pipeline. + * This type is the element type in the "ops" vector of a pipeline. */ +typedef struct ecs_pipeline_op_t { + int32_t offset; /* Offset in systems vector */ + int32_t count; /* Number of systems to run before next op */ + double time_spent; /* Time spent merging commands for sync point */ + int64_t commands_enqueued; /* Number of commands enqueued for sync point */ + bool multi_threaded; /* Whether systems can be ran multi threaded */ + bool immediate; /* Whether systems are staged or not */ +} ecs_pipeline_op_t; - if (ecs_meta_pop(&cur) != 0) { - goto error; - } - } +struct ecs_pipeline_state_t { + ecs_query_t *query; /* Pipeline query */ + ecs_vec_t ops; /* Pipeline schedule */ + ecs_vec_t systems; /* Vector with system ids */ + + ecs_entity_t last_system; /* Last system ran by pipeline */ + int32_t match_count; /* Used to track of rebuild is necessary */ + int32_t rebuild_count; /* Number of pipeline rebuilds */ + ecs_iter_t *iters; /* Iterator for worker(s) */ + int32_t iter_count; + + /* Members for continuing pipeline iteration after pipeline rebuild */ + ecs_pipeline_op_t *cur_op; /* Current pipeline op */ + int32_t cur_i; /* Index in current result */ + int32_t ran_since_merge; /* Index in current op */ + bool immediate; /* Is pipeline in readonly mode */ +}; - else if (!ecs_os_strcmp(token, ",")) { - if (ecs_meta_next(&cur) != 0) { - goto error; - } - } +typedef struct EcsPipeline { + /* Stable ptr so threads can safely access while entity/components move */ + ecs_pipeline_state_t *state; +} EcsPipeline; - else if (!ecs_os_strcmp(token, "null")) { - if (ecs_meta_set_null(&cur) != 0) { - goto error; - } - } +//////////////////////////////////////////////////////////////////////////////// +//// Pipeline API +//////////////////////////////////////////////////////////////////////////////// - else if (token[0] == '\"') { - if (ecs_meta_set_string_literal(&cur, token) != 0) { - goto error; - } - } +bool flecs_pipeline_update( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + bool start_of_frame); - else { - ptr = ecs_parse_fluff(ptr, NULL); +void flecs_run_pipeline( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time); - if (ptr[0] == ':') { - /* Member assignment */ - ptr ++; - if (ecs_meta_member(&cur, token) != 0) { - goto error; - } - } else { - if (ecs_meta_set_string(&cur, token) != 0) { - goto error; - } - } - } +int32_t flecs_run_pipeline_ops( + ecs_world_t* world, + ecs_stage_t* stage, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time); - if (!depth) { - break; - } +//////////////////////////////////////////////////////////////////////////////// +//// Worker API +//////////////////////////////////////////////////////////////////////////////// - ptr = ecs_parse_fluff(ptr, NULL); - } +void flecs_workers_progress( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time); - return ptr; -error: - return NULL; -} +void flecs_create_worker_threads( + ecs_world_t *world); -#endif +void flecs_join_worker_threads( + ecs_world_t *world); +void flecs_signal_workers( + ecs_world_t *world); +void flecs_wait_for_sync( + ecs_world_t *world); -#ifdef FLECS_SYSTEM #endif -#ifdef FLECS_PIPELINE -#endif +/** + * @file addons/json/json.h + * @brief Internal functions for JSON addon. + */ -#ifdef FLECS_STATS +#ifndef FLECS_JSON_PRIVATE_H +#define FLECS_JSON_PRIVATE_H -#include -static -int32_t t_next( - int32_t t) -{ - return (t + 1) % ECS_STAT_WINDOW; -} +#ifdef FLECS_JSON -static -int32_t t_prev( - int32_t t) -{ - return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; -} +/* Deserialize from JSON */ +typedef enum ecs_json_token_t { + JsonObjectOpen, + JsonObjectClose, + JsonArrayOpen, + JsonArrayClose, + JsonColon, + JsonComma, + JsonNumber, + JsonString, + JsonBoolean, + JsonTrue, + JsonFalse, + JsonNull, + JsonLargeInt, + JsonLargeString, + JsonInvalid +} ecs_json_token_t; + +typedef struct ecs_json_value_ser_ctx_t { + ecs_entity_t type; + const EcsTypeSerializer *ser; + char *id_label; + bool initialized; +} ecs_json_value_ser_ctx_t; + +/* Cached data for serializer */ +typedef struct ecs_json_ser_ctx_t { + ecs_component_record_t *cr_doc_name; + ecs_component_record_t *cr_doc_color; + ecs_json_value_ser_ctx_t value_ctx[64]; + ecs_map_t serialized; +} ecs_json_ser_ctx_t; + +typedef struct ecs_json_this_data_t { + const ecs_table_t *table; + const ecs_entity_t *ids; + const EcsIdentifier *names; + const EcsParent *parents; + const EcsDocDescription *label; + const EcsDocDescription *brief; + const EcsDocDescription *detail; + const EcsDocDescription *color; + const EcsDocDescription *link; + bool has_alerts; +} ecs_json_this_data_t; + +const char* flecs_json_parse( + const char *json, + ecs_json_token_t *token_kind, + char *token); + +const char* flecs_json_parse_large_string( + const char *json, + ecs_strbuf_t *buf); -static -void _record_gauge( - ecs_gauge_t *m, - int32_t t, - float value) -{ - m->avg[t] = value; - m->min[t] = value; - m->max[t] = value; -} +const char* flecs_json_parse_next_member( + const char *json, + char *token, + ecs_json_token_t *token_kind, + const ecs_from_json_desc_t *desc); -static -float _record_counter( - ecs_counter_t *m, - int32_t t, - float value) -{ - int32_t tp = t_prev(t); - float prev = m->value[tp]; - m->value[t] = value; - _record_gauge((ecs_gauge_t*)m, t, value - prev); - return value - prev; -} +const char* flecs_json_expect( + const char *json, + ecs_json_token_t token_kind, + char *token, + const ecs_from_json_desc_t *desc); -/* Macro's to silence conversion warnings without adding casts everywhere */ -#define record_gauge(m, t, value)\ - _record_gauge(m, t, (float)value) +const char* flecs_json_expect_string( + const char *json, + char *token, + char **out, + const ecs_from_json_desc_t *desc); -#define record_counter(m, t, value)\ - _record_counter(m, t, (float)value) +const char* flecs_json_expect_member( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); -static -void print_value( - const char *name, - float value) -{ - ecs_size_t len = ecs_os_strlen(name); - ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); -} +const char* flecs_json_expect_next_member( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); -static -void print_gauge( - const char *name, - int32_t t, - const ecs_gauge_t *m) -{ - print_value(name, m->avg[t]); -} +const char* flecs_json_expect_member_name( + const char *json, + char *token, + const char *member_name, + const ecs_from_json_desc_t *desc); -static -void print_counter( - const char *name, - int32_t t, - const ecs_counter_t *m) -{ - print_value(name, m->rate.avg[t]); -} +const char* flecs_json_skip_object( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); -void ecs_gauge_reduce( - ecs_gauge_t *dst, - int32_t t_dst, - ecs_gauge_t *src, - int32_t t_src) -{ - ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); +const char* flecs_json_skip_array( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); - bool min_set = false; - dst->min[t_dst] = 0; - dst->avg[t_dst] = 0; - dst->max[t_dst] = 0; +/* Serialize to JSON */ +void flecs_json_next( + ecs_strbuf_t *buf); - int32_t i; - for (i = 0; i < ECS_STAT_WINDOW; i ++) { - int32_t t = (t_src + i) % ECS_STAT_WINDOW; - dst->avg[t_dst] += src->avg[t] / (float)ECS_STAT_WINDOW; - if (!min_set || (src->min[t] < dst->min[t_dst])) { - dst->min[t_dst] = src->min[t]; - min_set = true; - } - if ((src->max[t] > dst->max[t_dst])) { - dst->max[t_dst] = src->max[t]; - } - } -error: - return; -} +void flecs_json_number( + ecs_strbuf_t *buf, + double value); -void ecs_get_world_stats( - const ecs_world_t *world, - ecs_world_stats_t *s) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); +void flecs_json_u32( + ecs_strbuf_t *buf, + uint32_t value); - world = ecs_get_world(world); +void flecs_json_true( + ecs_strbuf_t *buf); - int32_t t = s->t = t_next(s->t); +void flecs_json_false( + ecs_strbuf_t *buf); - float delta_world_time = record_counter(&s->world_time_total_raw, t, world->info.world_time_total_raw); - record_counter(&s->world_time_total, t, world->info.world_time_total); - record_counter(&s->frame_time_total, t, world->info.frame_time_total); - record_counter(&s->system_time_total, t, world->info.system_time_total); - record_counter(&s->merge_time_total, t, world->info.merge_time_total); - - float delta_frame_count = record_counter(&s->frame_count_total, t, world->info.frame_count_total); - record_counter(&s->merge_count_total, t, world->info.merge_count_total); - record_counter(&s->pipeline_build_count_total, t, world->info.pipeline_build_count_total); - record_counter(&s->systems_ran_frame, t, world->info.systems_ran_frame); - - if (delta_world_time != 0.0f && delta_frame_count != 0.0f) { - record_gauge( - &s->fps, t, 1.0f / (delta_world_time / (float)delta_frame_count)); - } else { - record_gauge(&s->fps, t, 0); - } - - record_gauge(&s->entity_count, t, flecs_sparse_count(ecs_eis(world))); - record_gauge(&s->entity_not_alive_count, t, - flecs_sparse_not_alive_count(ecs_eis(world))); - - record_gauge(&s->id_count, t, world->info.id_count); - record_gauge(&s->tag_id_count, t, world->info.tag_id_count); - record_gauge(&s->component_id_count, t, world->info.component_id_count); - record_gauge(&s->pair_id_count, t, world->info.pair_id_count); - record_gauge(&s->wildcard_id_count, t, world->info.wildcard_id_count); - record_gauge(&s->component_count, t, ecs_sparse_count(world->type_info)); - - record_gauge(&s->query_count, t, flecs_sparse_count(world->queries)); - record_gauge(&s->trigger_count, t, ecs_count(world, EcsTrigger)); - record_gauge(&s->observer_count, t, ecs_count(world, EcsObserver)); - record_gauge(&s->system_count, t, ecs_count(world, EcsSystem)); - - record_counter(&s->id_create_count, t, world->info.id_create_total); - record_counter(&s->id_delete_count, t, world->info.id_delete_total); - record_counter(&s->table_create_count, t, world->info.table_create_total); - record_counter(&s->table_delete_count, t, world->info.table_delete_total); - - record_counter(&s->new_count, t, world->new_count); - record_counter(&s->bulk_new_count, t, world->bulk_new_count); - record_counter(&s->delete_count, t, world->delete_count); - record_counter(&s->clear_count, t, world->clear_count); - record_counter(&s->add_count, t, world->add_count); - record_counter(&s->remove_count, t, world->remove_count); - record_counter(&s->set_count, t, world->set_count); - record_counter(&s->discard_count, t, world->discard_count); - - /* Compute table statistics */ - int32_t empty_table_count = 0; - int32_t singleton_table_count = 0; +void flecs_json_bool( + ecs_strbuf_t *buf, + bool value); - int32_t i, count = flecs_sparse_count(&world->store.tables); - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense(&world->store.tables, - ecs_table_t, i); - int32_t entity_count = ecs_table_count(table); +void flecs_json_null( + ecs_strbuf_t *buf); - if (!entity_count) { - empty_table_count ++; - } +void flecs_json_array_push( + ecs_strbuf_t *buf); - /* Singleton tables are tables that have just one entity that also has - * itself in the table type. */ - if (entity_count == 1) { - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); - if (ecs_search(world, table, entities[0], 0)) { - singleton_table_count ++; - } - } - } +void flecs_json_array_pop( + ecs_strbuf_t *buf); - /* Correct for root table */ - count --; - empty_table_count --; +void flecs_json_object_push( + ecs_strbuf_t *buf); - if (count != world->info.table_count) { - ecs_warn("world::table_count (%d) is not equal to computed number (%d)", - world->info.table_count, count); - } - if (empty_table_count != world->info.empty_table_count) { - ecs_warn("world::empty_table_count (%d) is not equal to computed" - " number (%d)", - world->info.empty_table_count, empty_table_count); - } +void flecs_json_object_pop( + ecs_strbuf_t *buf); - record_gauge(&s->table_count, t, count); - record_gauge(&s->empty_table_count, t, empty_table_count); - record_gauge(&s->singleton_table_count, t, singleton_table_count); - record_gauge(&s->tag_table_count, t, world->info.tag_table_count); - record_gauge(&s->trivial_table_count, t, world->info.trivial_table_count); - record_gauge(&s->table_storage_count, t, world->info.table_storage_count); - record_gauge(&s->table_record_count, t, world->info.table_record_count); +void flecs_json_string( + ecs_strbuf_t *buf, + const char *value); -error: - return; -} +void flecs_json_string_escape( + ecs_strbuf_t *buf, + const char *value); -void ecs_get_query_stats( - const ecs_world_t *world, - const ecs_query_t *query, - ecs_query_stats_t *s) +void flecs_json_member( + ecs_strbuf_t *buf, + const char *name); + +void flecs_json_membern( + ecs_strbuf_t *buf, + const char *name, + int32_t name_len); + +#define flecs_json_memberl(buf, name)\ + flecs_json_membern(buf, name, sizeof(name) - 1) + +void flecs_json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); + +void flecs_json_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); + +void flecs_json_path_or_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e, + bool path); + +void flecs_json_color( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); + +void flecs_json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id); + +void flecs_json_id_member( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id, + bool fullpath); + +ecs_primitive_kind_t flecs_json_op_to_primitive_kind( + ecs_meta_op_kind_t kind); + +int flecs_json_serialize_iter_result( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx); + +void flecs_json_serialize_field( + const ecs_world_t *world, + const ecs_iter_t *it, + const ecs_query_t *q, + int field, + ecs_strbuf_t *buf, + ecs_json_ser_ctx_t *ctx); + +void flecs_json_serialize_query( + const ecs_world_t *world, + const ecs_query_t *q, + ecs_strbuf_t *buf); + +int flecs_json_ser_type( + const ecs_world_t *world, + const ecs_vec_t *ser, + const void *base, + ecs_strbuf_t *str); + +int flecs_json_serialize_iter_result_fields( + const ecs_world_t *world, + const ecs_iter_t *it, + int32_t i, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx); + +bool flecs_json_serialize_get_value_ctx( + const ecs_world_t *world, + ecs_id_t id, + ecs_json_value_ser_ctx_t *ctx, + const ecs_iter_to_json_desc_t *desc); + +int flecs_json_serialize_iter_result_table( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx, + int32_t count, + bool has_this, + const char *parent_path, + const ecs_json_this_data_t *this_data); + +int flecs_json_serialize_iter_result_query( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + ecs_json_ser_ctx_t *ser_ctx, + const ecs_iter_to_json_desc_t *desc, + int32_t count, + bool has_this, + const char *parent_path, + const ecs_json_this_data_t *this_data); + +bool flecs_json_serialize_iter_this( + const ecs_iter_t *it, + const char *parent_path, + const ecs_json_this_data_t *this_data, + int32_t row, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx); + +bool flecs_json_serialize_vars( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc); + +int flecs_json_serialize_matches( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity); + +int flecs_json_serialize_refs( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity, + ecs_entity_t relationship); + +int flecs_json_serialize_alerts( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity); + +bool flecs_json_is_builtin( + ecs_id_t id); + +int flecs_entity_to_json_buf( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx); + +bool flecs_json_should_serialize( + ecs_entity_t entity, + const ecs_iter_t *it, + const ecs_json_ser_ctx_t *ser_ctx); + +void flecs_json_mark_serialized( + ecs_entity_t entity, + ecs_json_ser_ctx_t *ser_ctx); + +#endif + +#endif /* FLECS_JSON_PRIVATE_H */ + + +#ifdef FLECS_REST + +/* Retain captured commands for one minute at 60 FPS */ +#define FLECS_REST_COMMAND_RETAIN_COUNT (60 * 60) + +typedef struct { + char *cmds; + ecs_time_t start_time; + ecs_strbuf_t buf; +} ecs_rest_cmd_sync_capture_t; + +typedef struct { + ecs_vec_t syncs; +} ecs_rest_cmd_capture_t; + +static ECS_COPY(EcsRest, dst, src, { + ecs_rest_ctx_t *impl = src->impl; + if (impl) { + impl->rc ++; + } + + ecs_os_strset(&dst->ipaddr, src->ipaddr); + dst->port = src->port; + dst->impl = impl; +}) + +static ECS_MOVE(EcsRest, dst, src, { + *dst = *src; + src->ipaddr = NULL; + src->impl = NULL; +}) + +static ECS_DTOR(EcsRest, ptr, { + ecs_rest_ctx_t *impl = ptr->impl; + if (impl) { + impl->rc --; + if (!impl->rc) { + ecs_rest_server_fini(impl->srv); + } + } + ecs_os_free(ptr->ipaddr); +}) + +static char *rest_last_err; +static ecs_os_api_log_t rest_prev_log; +static ecs_os_api_log_t rest_prev_fatal_log; + +static +void flecs_rest_set_prev_log( + ecs_os_api_log_t prev_log, + bool try) +{ + rest_prev_log = try ? NULL : prev_log; + rest_prev_fatal_log = prev_log; +} + +static +void flecs_rest_capture_log( + int32_t level, + const char *file, + int32_t line, + const char *msg) +{ + (void)file; (void)line; + + if (level <= -4) { + /* Make sure to always log fatal errors */ + if (rest_prev_fatal_log) { + ecs_log_enable_colors(true); + rest_prev_fatal_log(level, file, line, msg); + ecs_log_enable_colors(false); + return; + } else { + fprintf(stderr, "%s:%d: %s", file, line, msg); + } + } + +#ifdef FLECS_DEBUG + /* In debug mode, log unexpected errors to the console */ + if (level < 0) { + /* Also log to previous log function in debug mode */ + if (rest_prev_log) { + ecs_log_enable_colors(true); + rest_prev_log(level, file, line, msg); + ecs_log_enable_colors(false); + } + } +#endif + + if (!rest_last_err && level <= -3) { + rest_last_err = ecs_os_strdup(msg); + } +} + +static +char* flecs_rest_get_captured_log(void) { + char *result = rest_last_err; + rest_last_err = NULL; + return result; +} + +static +void flecs_reply_verror( + ecs_http_reply_t *reply, + const char *fmt, + va_list args) +{ + ecs_strbuf_appendlit(&reply->body, "{\"error\":\""); + ecs_strbuf_vappend(&reply->body, fmt, args); + ecs_strbuf_appendlit(&reply->body, "\"}"); +} + +static +void flecs_reply_error( + ecs_http_reply_t *reply, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + flecs_reply_verror(reply, fmt, args); + va_end(args); +} + +static +void flecs_rest_bool_param( + const ecs_http_request_t *req, + const char *name, + bool *value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + if (!ecs_os_strcmp(value, "true")) { + value_out[0] = true; + } else { + value_out[0] = false; + } + } +} + +static +void flecs_rest_int_param( + const ecs_http_request_t *req, + const char *name, + int32_t *value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + *value_out = atoi(value); + } +} + +static +void flecs_rest_string_param( + const ecs_http_request_t *req, + const char *name, + char **value_out) +{ + const char *value = ecs_http_get_param(req, name); + if (value) { + *value_out = ECS_CONST_CAST(char*, value); + } +} + +static +void flecs_rest_parse_json_ser_entity_params( + ecs_world_t *world, + ecs_entity_to_json_desc_t *desc, + const ecs_http_request_t *req) +{ + flecs_rest_bool_param(req, "entity_id", &desc->serialize_entity_id); + flecs_rest_bool_param(req, "doc", &desc->serialize_doc); + flecs_rest_bool_param(req, "full_paths", &desc->serialize_full_paths); + flecs_rest_bool_param(req, "inherited", &desc->serialize_inherited); + flecs_rest_bool_param(req, "values", &desc->serialize_values); + flecs_rest_bool_param(req, "builtin", &desc->serialize_builtin); + flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_rest_bool_param(req, "matches", &desc->serialize_matches); + flecs_rest_bool_param(req, "alerts", &desc->serialize_alerts); + + char *rel = NULL; + flecs_rest_string_param(req, "refs", &rel); + if (rel) { + desc->serialize_refs = ecs_lookup(world, rel); + } +} + +static +void flecs_rest_parse_json_ser_iter_params( + ecs_iter_to_json_desc_t *desc, + const ecs_http_request_t *req) +{ + flecs_rest_bool_param(req, "entity_ids", &desc->serialize_entity_ids); + flecs_rest_bool_param(req, "doc", &desc->serialize_doc); + flecs_rest_bool_param(req, "full_paths", &desc->serialize_full_paths); + flecs_rest_bool_param(req, "inherited", &desc->serialize_inherited); + flecs_rest_bool_param(req, "values", &desc->serialize_values); + flecs_rest_bool_param(req, "builtin", &desc->serialize_builtin); + flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); + flecs_rest_bool_param(req, "field_info", &desc->serialize_field_info); + flecs_rest_bool_param(req, "query_info", &desc->serialize_query_info); + flecs_rest_bool_param(req, "query_plan", &desc->serialize_query_plan); + flecs_rest_bool_param(req, "query_profile", &desc->serialize_query_profile); + flecs_rest_bool_param(req, "table", &desc->serialize_table); + flecs_rest_bool_param(req, "fields", &desc->serialize_fields); + + bool results = true; + flecs_rest_bool_param(req, "results", &results); + desc->dont_serialize_results = !results; +} + +static +bool flecs_rest_get_entity( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + char *path = &req->path[7]; + ecs_dbg_2("rest: request entity '%s'", path); + + ecs_entity_t e = ecs_lookup_path_w_sep( + world, 0, path, "/", NULL, false); + if (!e) { + ecs_dbg_2("rest: entity '%s' not found", path); + flecs_reply_error(reply, "entity '%s' not found", path); + reply->code = 404; + return true; + } + + ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; + flecs_rest_parse_json_ser_entity_params(world, &desc, req); + if (ecs_entity_to_json_buf(world, e, &reply->body, &desc) != 0) { + ecs_strbuf_reset(&reply->body); + reply->code = 500; + reply->status = "Internal server error"; + return true; + } + return true; +} + +static +bool flecs_rest_put_entity( + ecs_world_t *world, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_dbg_2("rest: create entity '%s'", path); + + ecs_entity_t result = ecs_entity(world, { + .name = path, + .sep = "/" + }); + + if (!result) { + ecs_dbg_2("rest: failed to create entity '%s'", path); + flecs_reply_error(reply, "failed to create entity '%s'", path); + reply->code = 500; + return true; + } + + ecs_strbuf_appendlit(&reply->body, "{\"id\":\""); + ecs_strbuf_appendint(&reply->body, (uint32_t)result); + ecs_strbuf_appendlit(&reply->body, "\"}"); + + return true; +} + +static +bool flecs_rest_get_world( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)req; + if (ecs_world_to_json_buf(world, &reply->body, NULL) != 0) { + ecs_strbuf_reset(&reply->body); + reply->code = 500; + reply->status = "Internal server error"; + return true; + } + return true; +} + +static +ecs_entity_t flecs_rest_entity_from_path( + ecs_world_t *world, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_entity_t e = ecs_lookup_path_w_sep( + world, 0, path, "/", NULL, false); + if (!e) { + flecs_reply_error(reply, "entity '%s' not found", path); + reply->code = 404; + } + return e; +} + +static +bool flecs_rest_get_component( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + return true; + } + + const char *component = ecs_http_get_param(req, "component"); + if (!component) { + flecs_reply_error(reply, "missing component for remove endpoint"); + reply->code = 400; + return true; + } + + ecs_entity_t id; + if (!flecs_id_parse(world, path, component, &id)) { + flecs_reply_error(reply, "unresolved component '%s'", component); + reply->code = 400; + return true; + } + + ecs_entity_t type = ecs_get_typeid(world, id); + if (!type) { + flecs_reply_error(reply, "component '%s' is not a type", component); + reply->code = 400; + return true; + } + + const void *ptr = ecs_get_id(world, e, id); + if (!ptr) { + flecs_reply_error(reply, "failed to get component '%s'", component); + reply->code = 500; + return true; + } + + ecs_ptr_to_json_buf(world, type, ptr, &reply->body); + + return true; +} + +static +bool flecs_rest_put_component( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + return true; + } + + const char *component = ecs_http_get_param(req, "component"); + if (!component) { + flecs_reply_error(reply, "missing component for remove endpoint"); + reply->code = 400; + return true; + } + + ecs_entity_t id; + if (!flecs_id_parse(world, path, component, &id)) { + flecs_reply_error(reply, "unresolved component '%s'", component); + reply->code = 400; + return true; + } + + const char *data = ecs_http_get_param(req, "value"); + if (!data) { + ecs_add_id(world, e, id); + return true; + } + + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (!ti) { + flecs_reply_error(reply, "component '%s' is not a type", component); + reply->code = 400; + return true; + } + + void *ptr = ecs_ensure_id(world, e, id, flecs_ito(size_t, ti->size)); + if (!ptr) { + flecs_reply_error(reply, "failed to create component '%s'", component); + reply->code = 500; + return true; + } + + ecs_entity_t type = ti->component; + if (!ecs_ptr_from_json(world, type, ptr, data, NULL)) { + flecs_reply_error(reply, "invalid value for component '%s'", component); + reply->code = 400; + return true; + } + + ecs_modified_id(world, e, id); + + return true; +} + +static +bool flecs_rest_delete_component( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + return true; + } + + const char *component = ecs_http_get_param(req, "component"); + if (!component) { + flecs_reply_error(reply, "missing component for remove endpoint"); + reply->code = 400; + return true; + } + + ecs_entity_t id; + if (!flecs_id_parse(world, path, component, &id)) { + flecs_reply_error(reply, "unresolved component '%s'", component); + reply->code = 400; + return true; + } + + ecs_remove_id(world, e, id); + + return true; +} + +static +bool flecs_rest_delete_entity( + ecs_world_t *world, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + return true; + } + + ecs_delete(world, e); + + return true; +} + +static +bool flecs_rest_toggle( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + return true; + } + + bool enable = true; + flecs_rest_bool_param(req, "enable", &enable); + + const char *component = ecs_http_get_param(req, "component"); + if (!component) { + ecs_enable(world, e, enable); + return true; + } + + ecs_entity_t id; + if (!flecs_id_parse(world, path, component, &id)) { + flecs_reply_error(reply, "unresolved component '%s'", component); + reply->code = 400; + return true; + } + + ecs_entity_t rel = 0; + if (ECS_IS_PAIR(id)) { + rel = ecs_pair_first(world, id); + } else { + rel = id & ECS_COMPONENT_MASK; + } + + if (!ecs_has_id(world, rel, EcsCanToggle)) { + flecs_reply_error(reply, "cannot toggle component '%s'", component); + reply->code = 400; + return true; + } + + ecs_enable_id(world, e, id, enable); + + return true; +} + +static +bool flecs_rest_script( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *path) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; + (void)req; + (void)reply; + (void)path; +#ifdef FLECS_SCRIPT + ecs_entity_t script = flecs_rest_entity_from_path(world, reply, path); + if (!script) { + script = ecs_entity(world, { .name = path }); + } + + /* If true, check if file changed */ + bool check_file = false; + flecs_rest_bool_param(req, "check_file", &check_file); + + /* If true, save code to file */ + bool save_file = false; + flecs_rest_bool_param(req, "save_file", &save_file); + + const char *code = ecs_http_get_param(req, "code"); + if (!code) { + code = req->body; + } + + bool try = false; + flecs_rest_bool_param(req, "try", &try); + + if (!code) { + code = ""; + } + + ecs_strbuf_appendlit(&reply->body, "{"); + + const EcsScript *s = ecs_get(world, script, EcsScript); + + if (s && s->filename && save_file) { + FILE *f; + ecs_os_fopen(&f, s->filename, "w"); + fwrite(code, strlen(code), 1, f); + fclose(f); + } + + if (s && check_file) { + ecs_strbuf_appendlit(&reply->body, "\"changed\": "); + if (s->filename) { + bool file_is_same; + char *file_code = flecs_load_from_file(s->filename); + if (!file_code) { + file_is_same = code[0] == '\0'; + } else { + file_is_same = !ecs_os_strcmp(code, file_code); + ecs_os_free(file_code); + } + + ecs_strbuf_appendstr(&reply->body, file_is_same ? "false" : "true"); + } else { + ecs_strbuf_appendstr(&reply->body, "false"); + } + } + + /* Update script code */ + ecs_script(world, { + .entity = script, + .code = code + }); + + /* Refetch in case it moved around */ + s = ecs_get(world, script, EcsScript); + + if (!s || s->error) { + if (check_file) { + ecs_strbuf_appendlit(&reply->body, ", "); + } + + char *escaped_err = flecs_astresc('"', s->error); + ecs_strbuf_append(&reply->body, + "\"error\": \"%s\"", escaped_err); + ecs_os_free(escaped_err); + + if (!try) { + reply->code = 400; + } + } + + ecs_strbuf_appendlit(&reply->body, "}"); + + return true; +#else + return false; +#endif +} + +static +void flecs_rest_shrink_memory( + ecs_world_t *world, + void *ctx) +{ + (void)ctx; + ecs_shrink(world); +} + +static +bool flecs_rest_action( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *path) +{ + (void)path; + + char *action = &req->path[7]; + ecs_dbg_2("rest: run action '%s'", action); + + if (ecs_os_strcmp(action, "shrink_memory") == 0) { + if (ecs_world_get_flags(world) & EcsWorldFrameInProgress) { + ecs_run_post_frame(world, flecs_rest_shrink_memory, NULL); + } else { + flecs_rest_shrink_memory(world, NULL); + } + } else { + flecs_reply_error(reply, "unknown action '%s'", action); + reply->code = 400; + } + + return true; +} + +static +void flecs_rest_reply_set_captured_log( + ecs_http_reply_t *reply) +{ + char *err = flecs_rest_get_captured_log(); + if (err) { + char *escaped_err = flecs_astresc('"', err); + flecs_reply_error(reply, "%s", escaped_err); + ecs_os_free(escaped_err); + ecs_os_free(err); + } + + reply->code = 400; +} + +static +void flecs_rest_iter_to_reply( + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + ecs_poly_t *query, + ecs_iter_t *it) +{ + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + flecs_rest_parse_json_ser_iter_params(&desc, req); + desc.query = query; + + int32_t offset = 0; + int32_t limit = 1000; + + flecs_rest_int_param(req, "offset", &offset); + flecs_rest_int_param(req, "limit", &limit); + + if (offset < 0 || limit < 0) { + flecs_reply_error(reply, "invalid offset/limit parameter"); + return; + } + + ecs_iter_t pit = ecs_page_iter(it, offset, limit); + if (ecs_iter_to_json_buf(&pit, &reply->body, &desc)) { + flecs_rest_reply_set_captured_log(reply); + } + + flecs_rest_int_param(req, "offset", &offset); +} + +static +bool flecs_rest_reply_existing_query( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *name) +{ + ecs_entity_t qe = ecs_lookup(world, name); + if (!qe) { + flecs_reply_error(reply, "unresolved identifier '%s'", name); + reply->code = 404; + return true; + } + + bool try = false; + flecs_rest_bool_param(req, "try", &try); + + ecs_query_t *q = NULL; + const EcsPoly *poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsQuery); + if (!poly_comp) { + poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsObserver); + if (poly_comp) { + q = ((ecs_observer_t*)poly_comp->poly)->query; + } else { + flecs_reply_error(reply, + "resolved identifier '%s' is not a query", name); + reply->code = 400; + return true; + } + } else { + q = poly_comp->poly; + } + + if (!q) { + flecs_reply_error(reply, "query '%s' is not initialized", name); + reply->code = 400; + return true; + } + + ecs_iter_t it = ecs_query_iter(world, q); + + ecs_dbg_2("rest: request query '%s'", name); + bool prev_color = ecs_log_enable_colors(false); + ecs_os_api_log_t prev_log = ecs_os_api.log_; + flecs_rest_set_prev_log(ecs_os_api.log_, try); + ecs_os_api.log_ = flecs_rest_capture_log; + + const char *vars = ecs_http_get_param(req, "vars"); + if (vars) { + #ifdef FLECS_QUERY_DSL + if (ecs_query_args_parse(q, &it, vars) == NULL) { + flecs_rest_reply_set_captured_log(reply); + return true; + } + #else + flecs_reply_error(reply, + "cannot parse query arg expression: script addon required"); + reply->code = 400; + return true; + #endif + } + + flecs_rest_iter_to_reply(req, reply, q, &it); + + ecs_os_api.log_ = prev_log; + ecs_log_enable_colors(prev_color); + + return true; +} + +static +bool flecs_rest_get_query( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + const char *q_name = ecs_http_get_param(req, "name"); + if (q_name) { + return flecs_rest_reply_existing_query(world, req, reply, q_name); + } + + const char *expr = ecs_http_get_param(req, "expr"); + if (!expr) { + ecs_strbuf_appendlit(&reply->body, "Missing parameter 'expr'"); + reply->code = 400; /* bad request */ + return true; + } + + bool try = false; + flecs_rest_bool_param(req, "try", &try); + + ecs_dbg_2("rest: request query '%s'", expr); + bool prev_color = ecs_log_enable_colors(false); + ecs_os_api_log_t prev_log = ecs_os_api.log_; + flecs_rest_set_prev_log(ecs_os_api.log_, try); + ecs_os_api.log_ = flecs_rest_capture_log; + + ecs_query_t *q = ecs_query(world, { .expr = expr }); + if (!q) { + flecs_rest_reply_set_captured_log(reply); + if (try) { + /* If client is trying queries, don't spam console with errors */ + reply->code = 200; + } + } else { + ecs_iter_t it = ecs_query_iter(world, q); + flecs_rest_iter_to_reply(req, reply, q, &it); + ecs_query_fini(q); + } + + ecs_os_api.log_ = prev_log; + ecs_log_enable_colors(prev_color); + + return true; +} + +#ifdef FLECS_STATS + +static +void flecs_rest_array_append_( + ecs_strbuf_t *reply, + const char *field, + int32_t field_len, + const ecs_float_t *values, + int32_t t) +{ + ecs_strbuf_list_appendch(reply, '"'); + ecs_strbuf_appendstrn(reply, field, field_len); + ecs_strbuf_appendlit(reply, "\":"); + ecs_strbuf_list_push(reply, "[", ","); + + int32_t i; + for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { + int32_t index = i % ECS_STAT_WINDOW; + ecs_strbuf_list_next(reply); + ecs_strbuf_appendflt(reply, (double)values[index], '"'); + } + + ecs_strbuf_list_pop(reply, "]"); +} + +#define flecs_rest_array_append(reply, field, values, t)\ + flecs_rest_array_append_(reply, field, sizeof(field) - 1, values, t) + +static +void flecs_rest_gauge_append( + ecs_strbuf_t *reply, + const ecs_metric_t *m, + const char *field, + int32_t field_len, + int32_t t, + const char *brief, + int32_t brief_len) +{ + ecs_strbuf_list_appendch(reply, '"'); + ecs_strbuf_appendstrn(reply, field, field_len); + ecs_strbuf_appendlit(reply, "\":"); + ecs_strbuf_list_push(reply, "{", ","); + + flecs_rest_array_append(reply, "avg", m->gauge.avg, t); + flecs_rest_array_append(reply, "min", m->gauge.min, t); + flecs_rest_array_append(reply, "max", m->gauge.max, t); + + if (brief) { + ecs_strbuf_list_appendlit(reply, "\"brief\":\""); + ecs_strbuf_appendstrn(reply, brief, brief_len); + ecs_strbuf_appendch(reply, '"'); + } + + ecs_strbuf_list_pop(reply, "}"); +} + +static +void flecs_rest_counter_append( + ecs_strbuf_t *reply, + const ecs_metric_t *m, + const char *field, + int32_t field_len, + int32_t t, + const char *brief, + int32_t brief_len) +{ + flecs_rest_gauge_append(reply, m, field, field_len, t, brief, brief_len); +} + +#define ECS_GAUGE_APPEND_T(reply, s, field, t, brief)\ + flecs_rest_gauge_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) + +#define ECS_COUNTER_APPEND_T(reply, s, field, t, brief)\ + flecs_rest_counter_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) + +#define ECS_GAUGE_APPEND(reply, s, field, brief)\ + ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief) + +#define ECS_COUNTER_APPEND(reply, s, field, brief)\ + ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief) + +static +void flecs_world_stats_to_json( + ecs_strbuf_t *reply, + const EcsWorldStats *monitor_stats) +{ + const ecs_world_stats_t *stats = monitor_stats->stats; + + ecs_strbuf_list_push(reply, "{", ","); + ECS_GAUGE_APPEND(reply, stats, entities.count, "Alive entity ids in the world"); + ECS_GAUGE_APPEND(reply, stats, entities.not_alive_count, "Not alive entity ids in the world"); + + ECS_GAUGE_APPEND(reply, stats, performance.fps, "Frames per second"); + ECS_COUNTER_APPEND(reply, stats, performance.frame_time, "Time spent in frame"); + ECS_COUNTER_APPEND(reply, stats, performance.system_time, "Time spent on running systems in frame"); + ECS_COUNTER_APPEND(reply, stats, performance.emit_time, "Time spent on notifying observers in frame"); + ECS_COUNTER_APPEND(reply, stats, performance.merge_time, "Time spent on merging commands in frame"); + ECS_COUNTER_APPEND(reply, stats, performance.rematch_time, "Time spent on revalidating query caches in frame"); + + ECS_COUNTER_APPEND(reply, stats, commands.add_count, "Add commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.remove_count, "Remove commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.delete_count, "Delete commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.clear_count, "Clear commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.set_count, "Set commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.ensure_count, "Get_mut commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.modified_count, "Modified commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.other_count, "Misc commands executed"); + ECS_COUNTER_APPEND(reply, stats, commands.discard_count, "Commands for already deleted entities"); + ECS_COUNTER_APPEND(reply, stats, commands.batched_entity_count, "Entities with batched commands"); + ECS_COUNTER_APPEND(reply, stats, commands.batched_count, "Number of commands batched"); + + ECS_COUNTER_APPEND(reply, stats, frame.merge_count, "Number of merges (sync points)"); + ECS_COUNTER_APPEND(reply, stats, frame.pipeline_build_count, "Pipeline rebuilds (happen when systems become active/enabled)"); + ECS_COUNTER_APPEND(reply, stats, frame.systems_ran, "Systems ran in frame"); + ECS_COUNTER_APPEND(reply, stats, frame.observers_ran, "Number of times an observer was invoked in frame"); + ECS_COUNTER_APPEND(reply, stats, frame.event_emit_count, "Events emitted in frame"); + ECS_COUNTER_APPEND(reply, stats, frame.rematch_count, "Number of query cache revalidations"); + + ECS_GAUGE_APPEND(reply, stats, tables.count, "Tables in the world (including empty)"); + ECS_GAUGE_APPEND(reply, stats, tables.empty_count, "Empty tables in the world"); + ECS_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created"); + ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted"); + + ECS_GAUGE_APPEND(reply, stats, components.tag_count, "Tag ids in use"); + ECS_GAUGE_APPEND(reply, stats, components.component_count, "Component ids in use"); + ECS_GAUGE_APPEND(reply, stats, components.pair_count, "Pair ids in use"); + ECS_GAUGE_APPEND(reply, stats, components.type_count, "Registered component types"); + ECS_COUNTER_APPEND(reply, stats, components.create_count, "Number of new component, tag and pair ids created"); + ECS_COUNTER_APPEND(reply, stats, components.delete_count, "Number of component, pair and tag ids deleted"); + + ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world"); + ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world"); + ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world"); + + ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API"); + ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API"); + ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API"); + ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API"); + ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators"); + ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators"); + ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations"); + ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators"); + ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators"); + ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations"); + + ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests"); + ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests"); + ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully"); + ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code"); + ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)"); + ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received"); + ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies"); + ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies"); + ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)"); + + ecs_strbuf_list_pop(reply, "}"); +} + +static +void flecs_system_stats_to_json( + ecs_world_t *world, + ecs_strbuf_t *reply, + ecs_entity_t system, + const ecs_system_stats_t *stats) +{ + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_appendlit(reply, "\"name\":\""); + ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply, true); + ecs_strbuf_appendch(reply, '"'); + + bool disabled = ecs_has_id(world, system, EcsDisabled); + ecs_strbuf_list_appendlit(reply, "\"disabled\":"); + ecs_strbuf_appendstr(reply, disabled ? "true" : "false"); + + if (!stats->task) { + ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, ""); + ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, ""); + } + + ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t, ""); + ecs_strbuf_list_pop(reply, "}"); +} + +static +void flecs_sync_stats_to_json( + ecs_http_reply_t *reply, + const ecs_pipeline_stats_t *pstats, + const ecs_sync_stats_t *stats) +{ + ecs_strbuf_list_push(&reply->body, "{", ","); + + ecs_strbuf_list_appendlit(&reply->body, "\"multi_threaded\":"); + ecs_strbuf_appendbool(&reply->body, stats->multi_threaded); + + ecs_strbuf_list_appendlit(&reply->body, "\"immediate\":"); + ecs_strbuf_appendbool(&reply->body, stats->immediate); + + ECS_GAUGE_APPEND_T(&reply->body, stats, time_spent, pstats->t, ""); + ECS_GAUGE_APPEND_T(&reply->body, stats, commands_enqueued, pstats->t, ""); + + ecs_strbuf_list_pop(&reply->body, "}"); +} + +static +void flecs_all_systems_stats_to_json( + ecs_world_t *world, + ecs_http_reply_t *reply, + ecs_entity_t period) +{ + const EcsSystemStats *stats = ecs_get_pair(world, EcsWorld, + EcsSystemStats, period); + + ecs_strbuf_list_push(&reply->body, "[", ","); + + if (stats) { + ecs_map_iter_t it = ecs_map_iter(&stats->stats); + while (ecs_map_next(&it)) { + ecs_entity_t id = ecs_map_key(&it); + ecs_system_stats_t *sys_stats = ecs_map_ptr(&it); + + if (!ecs_is_alive(world, id)) { + continue; + } + + ecs_strbuf_list_next(&reply->body); + flecs_system_stats_to_json(world, &reply->body, id, sys_stats); + } + } + + ecs_strbuf_list_pop(&reply->body, "]"); +} + +static +void flecs_pipeline_stats_to_json( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + ecs_entity_t period) +{ + char *pipeline_name = NULL; + flecs_rest_string_param(req, "name", &pipeline_name); + + if (!pipeline_name || !ecs_os_strcmp(pipeline_name, "all")) { + flecs_all_systems_stats_to_json(world, reply, period); + return; + } + + ecs_entity_t e = ecs_lookup(world, pipeline_name); + if (!e) { + flecs_reply_error(reply, "pipeline '%s' not found", pipeline_name); + reply->code = 404; + return; + } + + const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, + EcsPipelineStats, period); + const EcsSystemStats *system_stats = ecs_get_pair(world, EcsWorld, + EcsSystemStats, period); + if (!stats || !system_stats) { + goto noresults; + } + + ecs_pipeline_stats_t *pstats = ecs_map_get_deref( + &stats->stats, ecs_pipeline_stats_t, e); + if (!pstats) { + goto noresults; + } + + const EcsPipeline *p = ecs_get(world, e, EcsPipeline); + + ecs_strbuf_list_push(&reply->body, "[", ","); + + ecs_pipeline_op_t *ops = ecs_vec_first_t(&p->state->ops, ecs_pipeline_op_t); + ecs_system_t **systems = ecs_vec_first_t(&p->state->systems, ecs_system_t*); + ecs_sync_stats_t *syncs = ecs_vec_first_t( + &pstats->sync_points, ecs_sync_stats_t); + + int32_t s, o, op_count = ecs_vec_count(&p->state->ops); + + for (o = 0; o < op_count; o ++) { + ecs_pipeline_op_t *op = &ops[o]; + for (s = op->offset; s < (op->offset + op->count); s ++) { + ecs_system_t *system_data = systems[s]; + ecs_entity_t system = system_data->query->entity; + + if (!ecs_is_alive(world, system)) { + continue; + } + + ecs_system_stats_t *sys_stats = ecs_map_get_deref( + &system_stats->stats, ecs_system_stats_t, system); + ecs_strbuf_list_next(&reply->body); + flecs_system_stats_to_json(world, &reply->body, system, sys_stats); + } + + ecs_strbuf_list_next(&reply->body); + flecs_sync_stats_to_json(reply, pstats, &syncs[o]); + } + + ecs_strbuf_list_pop(&reply->body, "]"); + return; +noresults: + ecs_strbuf_appendlit(&reply->body, "[]"); +} + +static +bool flecs_rest_get_stats( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + char *period_str = NULL; + flecs_rest_string_param(req, "period", &period_str); + char *category = &req->path[6]; + + ecs_entity_t period = EcsPeriod1s; + if (period_str) { + char *period_name = flecs_asprintf("Period%s", period_str); + period = ecs_lookup_child(world, ecs_id(FlecsStats), period_name); + ecs_os_free(period_name); + if (!period) { + flecs_reply_error(reply, "bad request (invalid period string)"); + reply->code = 400; + return false; + } + } + + if (!ecs_os_strcmp(category, "world")) { + const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, + EcsWorldStats, period); + flecs_world_stats_to_json(&reply->body, stats); + return true; + + } else if (!ecs_os_strcmp(category, "pipeline")) { + flecs_pipeline_stats_to_json(world, req, reply, period); + return true; + + } else { + flecs_reply_error(reply, "bad request (unsupported category)"); + reply->code = 400; + return false; + } +} +#else +static +bool flecs_rest_get_stats( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)world; + (void)req; + (void)reply; + return false; +} +#endif + +static +void flecs_rest_append_type_hook( + ecs_strbuf_t *reply, + const char *name, + uint64_t flags, + uint64_t illegal_flag, + bool has_hook) +{ + ecs_strbuf_list_appendlit(reply, "\""); + ecs_strbuf_appendstr(reply, name); + ecs_strbuf_appendlit(reply, "\":"); + + if (flags & illegal_flag) { + ecs_strbuf_appendlit(reply, "null"); + } else { + ecs_strbuf_appendbool(reply, has_hook); + } +} + +static +void flecs_rest_append_component_memory( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_strbuf_t *reply, + int32_t storage_bytes) +{ + (void)world; + (void)cr; + (void)reply; + (void)storage_bytes; + +#ifdef FLECS_STATS + if (!ecs_id(ecs_component_index_memory_t)) { + return; + } + + ecs_component_index_memory_t component_index_memory = {0}; + ecs_component_record_memory_get(cr, &component_index_memory); + + ecs_strbuf_list_appendlit(reply, "\"memory\":"); + + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_appendlit(reply, "\"component_index\":"); + ecs_ptr_to_json_buf( + world, ecs_id(ecs_component_index_memory_t), &component_index_memory, reply); + + /* Only count storage for actual components, not wildcards */ + if (ecs_id_is_wildcard(cr->id)) { + storage_bytes = 0; + } + ecs_strbuf_list_appendlit(reply, "\"storage\":"); + ecs_strbuf_append(reply, "%d", storage_bytes); + ecs_strbuf_list_pop(reply, "}"); +#endif +} + +static +void flecs_rest_append_component_traits( + ecs_component_record_t *cr, + ecs_strbuf_t *reply) +{ + ecs_flags32_t flags = cr->flags; + ecs_strbuf_list_appendlit(reply, "\"traits\":"); + ecs_strbuf_list_push(reply, "[", ","); + + if (flags & EcsIdOnDeleteRemove) { + ecs_strbuf_list_appendlit(reply, "\"(OnDelete,Remove)\""); + } + if (flags & EcsIdOnDeleteDelete) { + ecs_strbuf_list_appendlit(reply, "\"(OnDelete,Delete)\""); + } + if (flags & EcsIdOnDeletePanic) { + ecs_strbuf_list_appendlit(reply, "\"(OnDelete,Panic)\""); + } + if (flags & EcsIdOnDeleteTargetRemove) { + ecs_strbuf_list_appendlit(reply, "\"(OnDeleteTarget,Remove)\""); + } + if (flags & EcsIdOnDeleteTargetDelete) { + ecs_strbuf_list_appendlit(reply, "\"(OnDeleteTarget,Delete)\""); + } + if (flags & EcsIdOnDeleteTargetPanic) { + ecs_strbuf_list_appendlit(reply, "\"(OnDeleteTarget,Panic)\""); + } + if (flags & EcsIdOnInstantiateOverride) { + ecs_strbuf_list_appendlit(reply, "\"(OnInstantiate,Override)\""); + } + if (flags & EcsIdOnInstantiateInherit) { + ecs_strbuf_list_appendlit(reply, "\"(OnInstantiate,Inherit)\""); + } + if (flags & EcsIdOnInstantiateDontInherit) { + ecs_strbuf_list_appendlit(reply, "\"(OnInstantiate,DontInherit)\""); + } + if (flags & EcsIdExclusive) { + ecs_strbuf_list_appendlit(reply, "\"Exclusive\""); + } + if (flags & EcsIdTraversable) { + ecs_strbuf_list_appendlit(reply, "\"Traversable\""); + } + if (flags & EcsPairIsTag) { + ecs_strbuf_list_appendlit(reply, "\"PairIsTag\""); + } + if (flags & EcsIdWith) { + ecs_strbuf_list_appendlit(reply, "\"With\""); + } + if (flags & EcsIdCanToggle) { + ecs_strbuf_list_appendlit(reply, "\"CanToggle\""); + } + if (flags & EcsIdIsTransitive) { + ecs_strbuf_list_appendlit(reply, "\"IsTransitive\""); + } + if (flags & EcsIdInheritable) { + ecs_strbuf_list_appendlit(reply, "\"Inheritable\""); + } + if (flags & EcsIdSparse) { + ecs_strbuf_list_appendlit(reply, "\"Sparse\""); + } + if (flags & EcsIdDontFragment) { + ecs_strbuf_list_appendlit(reply, "\"DontFragment\""); + } + if (flags & EcsIdOrderedChildren) { + ecs_strbuf_list_appendlit(reply, "\"OrderedChildren\""); + } + if (flags & EcsIdSingleton) { + ecs_strbuf_list_appendlit(reply, "\"Singleton\""); + } + + ecs_strbuf_list_pop(reply, "]"); +} + +static +void flecs_rest_append_component( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_strbuf_t *reply) +{ + ecs_strbuf_list_next(reply); + ecs_strbuf_appendlit(reply, "\n"); + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_appendlit(reply, "\"name\":"); + char *str = ecs_id_str(world, cr->id); + flecs_json_string_escape(reply, str); + ecs_os_free(str); + + ecs_strbuf_list_appendlit(reply, "\"tables\":"); + ecs_strbuf_list_push(reply, "[", ","); + ecs_table_cache_iter_t it; + int32_t entity_count = 0, entity_size = 0, storage_bytes = 0; + flecs_table_cache_iter(&cr->cache, &it); + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_strbuf_list_next(reply); + ecs_strbuf_appendint(reply, (int64_t)tr->hdr.table->id); + entity_count += ecs_table_count(tr->hdr.table); + entity_size += ecs_table_size(tr->hdr.table); + } + ecs_strbuf_list_pop(reply, "]"); + + ecs_strbuf_list_appendlit(reply, "\"entity_count\":"); + ecs_strbuf_appendint(reply, entity_count); + ecs_strbuf_list_appendlit(reply, "\"entity_size\":"); + ecs_strbuf_appendint(reply, entity_size); + + if (cr->type_info) { + ecs_strbuf_list_appendlit(reply, "\"type\":"); + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_appendlit(reply, "\"size\":"); + ecs_strbuf_appendint(reply, cr->type_info->size); + ecs_strbuf_list_appendlit(reply, "\"alignment\":"); + ecs_strbuf_appendint(reply, cr->type_info->alignment); + + ecs_type_hooks_t hooks = cr->type_info->hooks; + uint64_t flags = hooks.flags; + flecs_rest_append_type_hook( + reply, "ctor", flags, ECS_TYPE_HOOK_CTOR_ILLEGAL, + hooks.ctor != NULL); + flecs_rest_append_type_hook( + reply, "dtor", flags, ECS_TYPE_HOOK_DTOR_ILLEGAL, + hooks.dtor != NULL); + flecs_rest_append_type_hook( + reply, "copy", flags, ECS_TYPE_HOOK_COPY_ILLEGAL, + hooks.copy != NULL); + flecs_rest_append_type_hook( + reply, "move", flags, ECS_TYPE_HOOK_MOVE_ILLEGAL, + hooks.move != NULL); + flecs_rest_append_type_hook( + reply, "move_ctor", flags, ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL, + hooks.move_ctor != NULL); + flecs_rest_append_type_hook( + reply, "copy_ctor", flags, ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL, + hooks.copy_ctor != NULL); + ecs_strbuf_list_appendlit(reply, "\"on_add\":"); + ecs_strbuf_appendbool(reply, cr->type_info->hooks.on_add != NULL); + ecs_strbuf_list_appendlit(reply, "\"on_set\":"); + ecs_strbuf_appendbool(reply, cr->type_info->hooks.on_set != NULL); + ecs_strbuf_list_appendlit(reply, "\"on_remove\":"); + ecs_strbuf_appendbool(reply, cr->type_info->hooks.on_remove != NULL); + ecs_strbuf_list_appendlit(reply, "\"on_replace\":"); + ecs_strbuf_appendbool(reply, cr->type_info->hooks.on_replace != NULL); + ecs_strbuf_list_pop(reply, "}"); + + storage_bytes += entity_size * cr->type_info->size; + } + + if (cr->sparse) { + int32_t i, count = flecs_sparse_count(cr->sparse); + ecs_strbuf_list_appendlit(reply, "\"sparse\":"); + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_appendlit(reply, "\"count\":"); + ecs_strbuf_appendint(reply, count); + ecs_strbuf_list_appendlit(reply, "\"entities\":"); + ecs_strbuf_list_push(reply, "[", ","); + + for (i = 0; i < count; i ++) { + ecs_strbuf_list_next(reply); + ecs_strbuf_appendint(reply, (int64_t) + flecs_sparse_ids(cr->sparse)[i]); + } + + ecs_strbuf_list_pop(reply, "]"); + ecs_strbuf_list_pop(reply, "}"); + + if (cr->type_info) { + storage_bytes += count * cr->type_info->size; + } + } + + flecs_rest_append_component_memory(world, cr, reply, storage_bytes); + flecs_rest_append_component_traits(cr, reply); + + ecs_strbuf_list_pop(reply, "}"); +} + +static +bool flecs_rest_get_components( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)req; + + ecs_strbuf_list_push(&reply->body, "[", ","); + + FLECS_EACH_COMPONENT_RECORD(cr, { + flecs_rest_append_component(world, cr, &reply->body); + }) + + ecs_strbuf_list_pop(&reply->body, "]"); + + return true; +} + +static +void flecs_rest_append_query_memory( + ecs_world_t *world, + ecs_query_t *query, + ecs_strbuf_t *reply) +{ + (void)world; + (void)query; + (void)reply; + +#ifdef FLECS_STATS + if (!ecs_id(ecs_query_memory_t)) { + return; + } + + ecs_query_memory_t query_memory = {0}; + ecs_query_memory_get(query, &query_memory); + + ecs_strbuf_list_appendlit(reply, "\"memory\":"); + + ecs_ptr_to_json_buf( + world, ecs_id(ecs_query_memory_t), &query_memory, reply); +#endif +} + +static +void flecs_rest_append_query( + ecs_world_t *world, + ecs_query_t *query, + ecs_entity_t entity, + ecs_strbuf_t *reply) +{ + ecs_strbuf_list_next(reply); + ecs_strbuf_appendlit(reply, "\n"); + ecs_strbuf_list_push(reply, "{", ","); + + ecs_strbuf_list_appendlit(reply, "\"name\":"); + char *str = ecs_get_path(world, entity); + flecs_json_string_escape(reply, str); + ecs_os_free(str); + + ecs_strbuf_list_appendlit(reply, "\"kind\":"); + if (ecs_has_id(world, entity, EcsSystem)) { + ecs_strbuf_appendlit(reply, "\"System\""); + } else if (ecs_has_id(world, entity, EcsObserver)) { + ecs_strbuf_appendlit(reply, "\"Observer\""); + } else { + ecs_strbuf_appendlit(reply, "\"Query\""); + } + + ecs_strbuf_list_appendlit(reply, "\"batched\":"); + bool batched = false; + if ((query->flags & EcsQueryHasTableThisVar) && (query->flags & EcsQueryMatchThis)) { + batched = true; + ecs_strbuf_appendlit(reply, "true"); + } else { + ecs_strbuf_appendlit(reply, "false"); + } + + ecs_iter_t it = ecs_query_iter(world, query); + it.flags |= EcsIterMatchEmptyTables; + + ecs_strbuf_list_appendlit(reply, "\"eval_mode\":"); + if (it.flags & EcsIterTrivialCached) ecs_strbuf_appendlit(reply, "\"TrivialCached\""); + else if (it.flags & EcsIterCached) ecs_strbuf_appendlit(reply, "\"Cached\""); + else if (it.flags & EcsIterTrivialSearch) ecs_strbuf_appendlit(reply, "\"TrivialUncached\""); + else if (!query->term_count) ecs_strbuf_appendlit(reply, "\"Noop\""); + else ecs_strbuf_appendlit(reply, "\"Plan\""); + + ecs_strbuf_list_appendlit(reply, "\"cache_kind\":"); + switch(query->cache_kind) { + case EcsQueryCacheDefault: ecs_strbuf_appendlit(reply, "\"Default\""); break; + case EcsQueryCacheAuto: ecs_strbuf_appendlit(reply, "\"Auto\""); break; + case EcsQueryCacheAll: ecs_strbuf_appendlit(reply, "\"All\""); break; + case EcsQueryCacheNone: ecs_strbuf_appendlit(reply, "\"None\""); break; + default: ecs_strbuf_appendlit(reply, "\"!! Invalid !!\""); + } + + int32_t results = 0, count = 0, empty_tables = 0; + ecs_time_t t = {0}; ecs_time_measure(&t); + while (ecs_query_next(&it)) { + results ++; + count += it.count; + if (!count && batched) { + empty_tables ++; + } + } + + /* Don't count own iteration */ + query->eval_count --; + + double eval_time = ecs_time_measure(&t); + + ecs_strbuf_list_appendlit(reply, "\"eval_time\":"); + ecs_strbuf_appendflt(reply, eval_time, '"'); + + ecs_strbuf_list_appendlit(reply, "\"eval_count\":"); + ecs_strbuf_appendint(reply, query->eval_count); + + ecs_strbuf_list_appendlit(reply, "\"results\":"); + ecs_strbuf_appendint(reply, results); + ecs_strbuf_list_appendlit(reply, "\"count\":"); + ecs_strbuf_appendint(reply, count); + ecs_strbuf_list_appendlit(reply, "\"empty_tables\":"); + ecs_strbuf_appendint(reply, empty_tables); + + ecs_strbuf_list_appendlit(reply, "\"count\":"); + ecs_strbuf_appendint(reply, count); + + ecs_strbuf_list_appendlit(reply, "\"expr\":"); + char *expr = ecs_query_str(query); + flecs_json_string_escape(reply, expr); + ecs_os_free(expr); + + ecs_strbuf_list_appendlit(reply, "\"plan_size\":"); + ecs_strbuf_appendint(reply, flecs_query_impl(query)->op_count); + + char *plan = ecs_query_plan(query); + ecs_strbuf_list_appendlit(reply, "\"plan\":"); + flecs_json_string_escape(reply, plan); + ecs_os_free(plan); + + const ecs_query_t *cache_query = ecs_query_get_cache_query(query); + if (cache_query) { + ecs_strbuf_list_appendlit(reply, "\"cache_plan\":"); + char *cache_plan = ecs_query_plan(cache_query); + flecs_json_string_escape(reply, cache_plan); + ecs_os_free(cache_plan); + } + + flecs_rest_append_query_memory(world, query, reply); + + ecs_strbuf_list_pop(reply, "}"); +} + +static +bool flecs_rest_get_queries( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)req; + + ecs_strbuf_list_push(&reply->body, "[", ","); + + { + ecs_iter_t it = ecs_each_pair_t(world, EcsPoly, EcsQuery); + while (ecs_each_next(&it)) { + EcsPoly *queries = ecs_field(&it, EcsPoly, 0); + + for (int32_t i = 0; i < it.count; i++) { + ecs_query_t *query = queries[i].poly; + if (!query) { + continue; + } + + flecs_poly_assert(query, ecs_query_t); + flecs_rest_append_query( + world, query, it.entities[i], &reply->body); + } + } + } + + { + ecs_iter_t it = ecs_each_pair_t(world, EcsPoly, EcsObserver); + while (ecs_each_next(&it)) { + EcsPoly *observers = ecs_field(&it, EcsPoly, 0); + + for (int32_t i = 0; i < it.count; i++) { + ecs_observer_t *observer = observers[i].poly; + if (!observer || !observer->query) { + continue; + } + + flecs_poly_assert(observer, ecs_observer_t); + flecs_rest_append_query( + world, observer->query, it.entities[i], &reply->body); + } + } + } + + ecs_strbuf_list_pop(&reply->body, "]"); + + return true; +} + +static +void flecs_rest_reply_table_append_type( + ecs_world_t *world, + ecs_strbuf_t *reply, + const ecs_table_t *table) +{ + ecs_strbuf_list_push(reply, "[", ","); + int32_t i, count = table->type.count; + ecs_id_t *ids = table->type.array; + for (i = 0; i < count; i ++) { + ecs_strbuf_list_next(reply); + char *idstr = ecs_id_str(world, ids[i]); + flecs_json_string_escape(reply, idstr); + ecs_os_free(idstr); + } + ecs_strbuf_list_pop(reply, "]"); +} + +static +void flecs_rest_reply_table_append_memory( + const ecs_world_t *world, + ecs_strbuf_t *reply, + const ecs_table_t *table) +{ + (void)world; + (void)reply; + (void)table; + +#ifdef FLECS_STATS + if (!ecs_id(ecs_table_memory_t) || !ecs_id(ecs_component_memory_t)) { + return; + } + ecs_table_memory_t table_memory = {0}; + ecs_table_memory_get(table, &table_memory); + + ecs_component_memory_t component_memory = {0}; + ecs_table_component_memory_get(table, &component_memory); + + ecs_strbuf_list_appendlit(reply, "\"memory\":"); + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_appendlit(reply, "\"table\":"); + ecs_ptr_to_json_buf( + world, ecs_id(ecs_table_memory_t), &table_memory, reply); + + ecs_strbuf_list_appendlit(reply, "\"components\":"); + ecs_ptr_to_json_buf( + world, ecs_id(ecs_component_memory_t), &component_memory, reply); + ecs_strbuf_list_pop(reply, "}"); +#endif +} + +static +void flecs_rest_reply_table_append( + ecs_world_t *world, + ecs_strbuf_t *reply, + const ecs_table_t *table) +{ + ecs_strbuf_list_next(reply); + ecs_strbuf_appendlit(reply, "\n"); + ecs_strbuf_list_push(reply, "{", ","); + ecs_strbuf_list_appendlit(reply, "\"id\":"); + ecs_strbuf_appendint(reply, (uint32_t)table->id); + ecs_strbuf_list_appendlit(reply, "\"type\":"); + flecs_rest_reply_table_append_type(world, reply, table); + ecs_strbuf_list_appendlit(reply, "\"count\":"); + ecs_strbuf_appendint(reply, ecs_table_count(table)); + ecs_strbuf_list_appendlit(reply, "\"size\":"); + ecs_strbuf_appendint(reply, ecs_table_size(table)); + flecs_rest_reply_table_append_memory(world, reply, table); + ecs_strbuf_list_pop(reply, "}"); +} + +static +bool flecs_rest_get_tables( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)req; + + ecs_strbuf_list_push(&reply->body, "[", ","); + ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + flecs_rest_reply_table_append(world, &reply->body, table); + } + ecs_strbuf_list_pop(&reply->body, "]"); + + return true; +} + +static +const char* flecs_rest_cmd_kind_to_str( + ecs_cmd_kind_t kind) +{ + switch(kind) { + case EcsCmdClone: return "Clone"; + case EcsCmdBulkNew: return "BulkNew"; + case EcsCmdAdd: return "Add"; + case EcsCmdRemove: return "Remove"; + case EcsCmdSet: return "Set"; + case EcsCmdEmplace: return "Emplace"; + case EcsCmdEnsure: return "Ensure"; + case EcsCmdModified: return "Modified"; + case EcsCmdModifiedNoHook: return "ModifiedNoHook"; + case EcsCmdAddModified: return "AddModified"; + case EcsCmdPath: return "Path"; + case EcsCmdDelete: return "Delete"; + case EcsCmdClear: return "Clear"; + case EcsCmdOnDeleteAction: return "OnDeleteAction"; + case EcsCmdEnable: return "Enable"; + case EcsCmdDisable: return "Disable"; + case EcsCmdEvent: return "Event"; + case EcsCmdSkip: return "Skip"; + default: return "Unknown"; + } +} + +static +bool flecs_rest_cmd_has_id( + const ecs_cmd_t *cmd) +{ + switch(cmd->kind) { + case EcsCmdClear: + case EcsCmdDelete: + case EcsCmdClone: + case EcsCmdDisable: + case EcsCmdPath: + return false; + case EcsCmdBulkNew: + case EcsCmdAdd: + case EcsCmdRemove: + case EcsCmdSet: + case EcsCmdEmplace: + case EcsCmdEnsure: + case EcsCmdModified: + case EcsCmdModifiedNoHook: + case EcsCmdAddModified: + case EcsCmdOnDeleteAction: + case EcsCmdEnable: + case EcsCmdEvent: + case EcsCmdSkip: + default: + return true; + } +} + +static +void flecs_rest_server_garbage_collect_all( + ecs_rest_ctx_t *impl) +{ + ecs_map_iter_t it = ecs_map_iter(&impl->cmd_captures); + + while (ecs_map_next(&it)) { + ecs_rest_cmd_capture_t *capture = ecs_map_ptr(&it); + int32_t i, count = ecs_vec_count(&capture->syncs); + ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); + for (i = 0; i < count; i ++) { + ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; + ecs_os_free(sync->cmds); + } + ecs_vec_fini_t(NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); + ecs_os_free(capture); + } + + ecs_map_fini(&impl->cmd_captures); +} + +static +void flecs_rest_server_garbage_collect( + ecs_world_t *world, + ecs_rest_ctx_t *impl) +{ + const ecs_world_info_t *wi = ecs_get_world_info(world); + ecs_map_iter_t it = ecs_map_iter(&impl->cmd_captures); + ecs_vec_t removed_frames = {0}; + + while (ecs_map_next(&it)) { + int64_t frame = flecs_uto(int64_t, ecs_map_key(&it)); + if ((wi->frame_count_total - frame) > FLECS_REST_COMMAND_RETAIN_COUNT) { + ecs_rest_cmd_capture_t *capture = ecs_map_ptr(&it); + int32_t i, count = ecs_vec_count(&capture->syncs); + ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); + for (i = 0; i < count; i ++) { + ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; + ecs_os_free(sync->cmds); + } + ecs_vec_fini_t(NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); + ecs_os_free(capture); + + ecs_vec_init_if_t(&removed_frames, int64_t); + ecs_vec_append_t(NULL, &removed_frames, int64_t)[0] = frame; + } + } + + int32_t i, count = ecs_vec_count(&removed_frames); + if (count) { + int64_t *frames = ecs_vec_first(&removed_frames); + if (count) { + for (i = 0; i < count; i ++) { + ecs_map_remove(&impl->cmd_captures, + flecs_ito(uint64_t, frames[i])); + } + } + ecs_vec_fini_t(NULL, &removed_frames, int64_t); + } +} + +static +void flecs_rest_cmd_to_json( + ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_cmd_t *cmd) +{ + ecs_strbuf_list_push(buf, "{", ","); + + ecs_strbuf_list_appendlit(buf, "\"kind\":\""); + ecs_strbuf_appendstr(buf, flecs_rest_cmd_kind_to_str(cmd->kind)); + ecs_strbuf_appendlit(buf, "\""); + + if (flecs_rest_cmd_has_id(cmd)) { + ecs_strbuf_list_appendlit(buf, "\"id\":\""); + char *idstr = ecs_id_str(world, cmd->id); + ecs_strbuf_appendstr(buf, idstr); + ecs_strbuf_appendlit(buf, "\""); + ecs_os_free(idstr); + } + + if (cmd->system) { + ecs_strbuf_list_appendlit(buf, "\"system\":\""); + char *sysstr = ecs_get_path(world, cmd->system); + ecs_strbuf_appendstr(buf, sysstr); + ecs_strbuf_appendlit(buf, "\""); + ecs_os_free(sysstr); + } + + if (cmd->kind == EcsCmdBulkNew) { + /* Todo */ + } else if (cmd->kind == EcsCmdEvent) { + /* Todo */ + } else { + if (cmd->entity) { + ecs_strbuf_list_appendlit(buf, "\"entity\":\""); + char *path = ecs_get_path_w_sep(world, 0, cmd->entity, ".", ""); + ecs_strbuf_appendstr(buf, path); + ecs_strbuf_appendlit(buf, "\""); + ecs_os_free(path); + + ecs_strbuf_list_appendlit(buf, "\"is_alive\":\""); + if (ecs_is_alive(world, cmd->entity)) { + ecs_strbuf_appendlit(buf, "true"); + } else { + ecs_strbuf_appendlit(buf, "false"); + } + ecs_strbuf_appendlit(buf, "\""); + + ecs_strbuf_list_appendlit(buf, "\"next_for_entity\":"); + ecs_strbuf_appendint(buf, cmd->next_for_entity); + } + } + + ecs_strbuf_list_pop(buf, "}"); +} + +static +void flecs_rest_on_commands( + const ecs_stage_t *stage, + const ecs_vec_t *commands, + void *ctx) +{ + ecs_world_t *world = stage->world; + ecs_rest_cmd_capture_t *capture = ctx; + ecs_assert(capture != NULL, ECS_INTERNAL_ERROR, NULL); + + if (commands) { + ecs_vec_init_if_t(&capture->syncs, ecs_rest_cmd_sync_capture_t); + ecs_rest_cmd_sync_capture_t *sync = ecs_vec_append_t( + NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); + + int32_t i, count = ecs_vec_count(commands); + ecs_cmd_t *cmds = ecs_vec_first(commands); + sync->buf = ECS_STRBUF_INIT; + ecs_strbuf_list_push(&sync->buf, "{", ","); + ecs_strbuf_list_appendlit(&sync->buf, "\"commands\":"); + ecs_strbuf_list_push(&sync->buf, "[", ","); + for (i = 0; i < count; i ++) { + ecs_strbuf_list_next(&sync->buf); + flecs_rest_cmd_to_json(world, &sync->buf, &cmds[i]); + } + ecs_strbuf_list_pop(&sync->buf, "]"); + + /* Measure how long it takes to process queue */ + sync->start_time = (ecs_time_t){0}; + ecs_time_measure(&sync->start_time); + } else { + /* Finished processing queue, measure duration */ + ecs_rest_cmd_sync_capture_t *sync = ecs_vec_last_t( + &capture->syncs, ecs_rest_cmd_sync_capture_t); + double duration = ecs_time_measure(&sync->start_time); + + ecs_strbuf_list_appendlit(&sync->buf, "\"duration\":"); + ecs_strbuf_appendflt(&sync->buf, duration, '"'); + ecs_strbuf_list_pop(&sync->buf, "}"); + + sync->cmds = ecs_strbuf_get(&sync->buf); + } +} + +static +bool flecs_rest_get_commands_capture( + ecs_world_t *world, + ecs_rest_ctx_t *impl, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)req; + const ecs_world_info_t *wi = ecs_get_world_info(world); + ecs_strbuf_appendstr(&reply->body, "{"); + ecs_strbuf_appendlit(&reply->body, "\"frame\":"); + ecs_strbuf_appendint(&reply->body, wi->frame_count_total); + ecs_strbuf_appendstr(&reply->body, "}"); + + ecs_map_init_if(&impl->cmd_captures, &world->allocator); + ecs_rest_cmd_capture_t *capture = ecs_map_ensure_alloc_t( + &impl->cmd_captures, ecs_rest_cmd_capture_t, + flecs_ito(uint64_t, wi->frame_count_total)); + + world->on_commands = flecs_rest_on_commands; + world->on_commands_ctx = capture; + + /* Run garbage collection so that requests don't linger */ + flecs_rest_server_garbage_collect(world, impl); + + return true; +} + +static +bool flecs_rest_get_commands_request( + ecs_world_t *world, + ecs_rest_ctx_t *impl, + const ecs_http_request_t* req, + ecs_http_reply_t *reply) +{ + (void)world; + char *frame_str = &req->path[15]; + int32_t frame = atoi(frame_str); + + ecs_map_init_if(&impl->cmd_captures, &world->allocator); + const ecs_rest_cmd_capture_t *capture = ecs_map_get_deref( + &impl->cmd_captures, ecs_rest_cmd_capture_t, + flecs_ito(uint64_t, frame)); + + if (!capture) { + ecs_strbuf_appendstr(&reply->body, "{"); + ecs_strbuf_append(&reply->body, + "\"error\": \"no capture for frame %u\"", frame); + ecs_strbuf_appendstr(&reply->body, "}"); + reply->code = 404; + return true; + } + + ecs_strbuf_appendstr(&reply->body, "{"); + ecs_strbuf_list_append(&reply->body, "\"syncs\":"); + ecs_strbuf_list_push(&reply->body, "[", ","); + + int32_t i, count = ecs_vec_count(&capture->syncs); + ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); + + for (i = 0; i < count; i ++) { + ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; + ecs_strbuf_list_appendstr(&reply->body, sync->cmds); + } + + ecs_strbuf_list_pop(&reply->body, "]"); + ecs_strbuf_appendstr(&reply->body, "}"); + + return true; +} + +static +bool flecs_rest_reply( + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + void *ctx) +{ + ecs_rest_ctx_t *impl = ctx; + ecs_world_t *world = impl->world; + + if (req->path == NULL) { + ecs_dbg("rest: bad request (missing path)"); + flecs_reply_error(reply, "bad request (missing path)"); + reply->code = 400; + return false; + } + + if (req->method == EcsHttpGet) { + /* Entity endpoint */ + if (!ecs_os_strncmp(req->path, "entity/", 7)) { + return flecs_rest_get_entity(world, req, reply); + + /* Component GET endpoint */ + } else if (!ecs_os_strncmp(req->path, "component/", 10)) { + return flecs_rest_get_component(world, req, reply, &req->path[10]); + + /* Query endpoint */ + } else if (!ecs_os_strcmp(req->path, "query")) { + return flecs_rest_get_query(world, req, reply); + + /* World endpoint */ + } else if (!ecs_os_strcmp(req->path, "world")) { + return flecs_rest_get_world(world, req, reply); + + /* Stats endpoint */ + } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { + return flecs_rest_get_stats(world, req, reply); + + /* Components endpoint */ + } else if (!ecs_os_strncmp(req->path, "components", 10)) { + return flecs_rest_get_components(world, req, reply); + + /* Tables endpoint */ + } else if (!ecs_os_strncmp(req->path, "queries", 7)) { + return flecs_rest_get_queries(world, req, reply); + + /* Tables endpoint */ + } else if (!ecs_os_strncmp(req->path, "tables", 6)) { + return flecs_rest_get_tables(world, req, reply); + + /* Commands capture endpoint */ + } else if (!ecs_os_strncmp(req->path, "commands/capture", 16)) { + return flecs_rest_get_commands_capture(world, impl, req, reply); + + /* Commands request endpoint (request commands from specific frame) */ + } else if (!ecs_os_strncmp(req->path, "commands/frame/", 15)) { + return flecs_rest_get_commands_request(world, impl, req, reply); + } + + } else if (req->method == EcsHttpPut) { + /* Component PUT endpoint */ + if (!ecs_os_strncmp(req->path, "entity/", 7)) { + return flecs_rest_put_entity(world, reply, &req->path[7]); + + /* Component PUT endpoint */ + } else if (!ecs_os_strncmp(req->path, "component/", 10)) { + return flecs_rest_put_component(world, req, reply, &req->path[10]); + + /* Enable endpoint */ + } else if (!ecs_os_strncmp(req->path, "toggle/", 7)) { + return flecs_rest_toggle(world, req, reply, &req->path[7]); + + /* Script endpoint */ + } else if (!ecs_os_strncmp(req->path, "script/", 7)) { + return flecs_rest_script(world, req, reply, &req->path[7]); + + /* Action endpoint */ + } else if (!ecs_os_strncmp(req->path, "action/", 7)) { + return flecs_rest_action(world, req, reply, &req->path[7]); + } + } else if (req->method == EcsHttpDelete) { + /* Entity DELETE endpoint */ + if (!ecs_os_strncmp(req->path, "entity/", 7)) { + return flecs_rest_delete_entity(world, reply, &req->path[7]); + + /* Component DELETE endpoint */ + } else if (!ecs_os_strncmp(req->path, "component/", 10)) { + return flecs_rest_delete_component(world, req, reply, &req->path[10]); + } + } + + return false; +} + +ecs_http_server_t* ecs_rest_server_init( + ecs_world_t *world, + const ecs_http_server_desc_t *desc) +{ + ecs_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t); + ecs_http_server_desc_t private_desc = {0}; + if (desc) { + private_desc = *desc; + } + private_desc.callback = flecs_rest_reply; + private_desc.ctx = srv_ctx; + + ecs_http_server_t *srv = ecs_http_server_init(&private_desc); + if (!srv) { + ecs_os_free(srv_ctx); + return NULL; + } + + srv_ctx->world = world; + srv_ctx->srv = srv; + srv_ctx->rc = 1; + + /* Set build info on world so clients know which version they're using */ + ecs_id_t build_info = ecs_lookup(world, "flecs.core.BuildInfo"); + if (build_info) { + const ecs_build_info_t *bi = ecs_get_build_info(); + ecs_set_id(world, EcsWorld, build_info, sizeof(ecs_build_info_t), bi); + } + + return srv; +} + +void ecs_rest_server_fini( + ecs_http_server_t *srv) +{ + ecs_rest_ctx_t *impl = ecs_http_server_ctx(srv); + flecs_rest_server_garbage_collect_all(impl); + ecs_os_free(impl); + ecs_http_server_fini(srv); +} + +static +void flecs_on_set_rest(ecs_iter_t *it) { + EcsRest *rest = ecs_field(it, EcsRest, 0); + + int i; + for(i = 0; i < it->count; i ++) { + if (!rest[i].port) { + rest[i].port = ECS_REST_DEFAULT_PORT; + } + + ecs_http_server_t *srv = ecs_rest_server_init(it->real_world, + &(ecs_http_server_desc_t){ + .ipaddr = rest[i].ipaddr, + .port = rest[i].port, + .cache_timeout = 0.2 + }); + + if (!srv) { + const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; + ecs_err("failed to create REST server on %s:%u", + ipaddr, rest[i].port); + continue; + } + + rest[i].impl = ecs_http_server_ctx(srv); + + ecs_http_server_start(srv); + } +} + +static +void DequeueRest(ecs_iter_t *it) { + EcsRest *rest = ecs_field(it, EcsRest, 0); + + if (it->delta_system_time > (ecs_ftime_t)1.0) { + ecs_warn( + "detected large progress interval (%.2fs), REST request may timeout", + (double)it->delta_system_time); + } + + const ecs_world_info_t *wi = ecs_get_world_info(it->world); + + int32_t i; + for(i = 0; i < it->count; i ++) { + ecs_rest_ctx_t *ctx = rest[i].impl; + if (ctx) { + float elapsed = (float)(wi->world_time_total_raw - ctx->last_time); + ecs_http_server_dequeue(ctx->srv, (ecs_ftime_t)elapsed); + flecs_rest_server_garbage_collect(it->world, ctx); + ctx->last_time = wi->world_time_total_raw; + } + } +} + +static +void DisableRest(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + ecs_iter_t rit = ecs_each_id(world, ecs_id(EcsRest)); + + if (it->event == EcsOnAdd) { + /* REST module was disabled */ + while (ecs_each_next(&rit)) { + EcsRest *rest = ecs_field(&rit, EcsRest, 0); + int i; + for (i = 0; i < rit.count; i ++) { + ecs_rest_ctx_t *ctx = rest[i].impl; + ecs_http_server_stop(ctx->srv); + } + } + } else if (it->event == EcsOnRemove) { + /* REST module was enabled */ + while (ecs_each_next(&rit)) { + EcsRest *rest = ecs_field(&rit, EcsRest, 0); + int i; + for (i = 0; i < rit.count; i ++) { + ecs_rest_ctx_t *ctx = rest[i].impl; + ecs_http_server_start(ctx->srv); + } + } + } +} + +void FlecsRestImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsRest); + + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsMeta); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsRest); + + ecs_set_hooks(world, EcsRest, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsRest), + .copy = ecs_copy(EcsRest), + .dtor = ecs_dtor(EcsRest), + .on_set = flecs_on_set_rest + }); + + ecs_system(world, { + .entity = ecs_entity(world, { .name = "DequeueRest", .add = ecs_ids( ecs_dependson(EcsPostFrame)) }), + .query.terms = { + { .id = ecs_id(EcsRest) }, + }, + .callback = DequeueRest, + .immediate = true + }); + + ecs_observer(world, { + .query = { + .terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }} + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = DisableRest, + .global_observer = true + }); + + ecs_set_name_prefix(world, "EcsRest"); + + /* Enable frame time measurements so we're guaranteed to have a delta time + * value to pass into the HTTP server. */ + if (ecs_os_has_time()) { + ecs_measure_frame_time(world, true); + } +} + +#endif + +/** + * @file addons/timer.c + * @brief Timer addon. + */ + +/** + * @file addons/system/system.h + * @brief Internal types and functions for system addon. + */ + +#ifndef FLECS_SYSTEM_PRIVATE_H +#define FLECS_SYSTEM_PRIVATE_H + +#ifdef FLECS_SYSTEM + + +#define ecs_system_t_magic (0x65637383) +#define ecs_system_t_tag EcsSystem + +extern ecs_mixins_t ecs_system_t_mixins; + +/* Invoked when system becomes active / inactive */ +void ecs_system_activate( + ecs_world_t *world, + ecs_entity_t system, + bool activate, + const ecs_system_t *system_data); + +/* Internal function to run a system */ +ecs_entity_t flecs_run_system( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + ecs_system_t *system_data, + int32_t stage_current, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param); + +#endif + +#endif + + +#ifdef FLECS_TIMER + +static +void ProgressTimers(ecs_iter_t *it) { + EcsTimer *timer = ecs_field(it, EcsTimer, 0); + EcsTickSource *tick_source = ecs_field(it, EcsTickSource, 1); + + ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); + + int i; + for (i = 0; i < it->count; i ++) { + tick_source[i].tick = false; + + if (!timer[i].active) { + continue; + } + + const ecs_world_info_t *info = ecs_get_world_info(it->world); + ecs_ftime_t time_elapsed = timer[i].time + info->delta_time_raw; + ecs_ftime_t timeout = timer[i].timeout; + + if (time_elapsed >= timeout) { + ecs_ftime_t t = time_elapsed - timeout; + if (t > timeout) { + t = 0; + } + + timer[i].time = t; /* Initialize with remainder */ + tick_source[i].tick = true; + tick_source[i].time_elapsed = time_elapsed - timer[i].overshoot; + timer[i].overshoot = t; + + if (timer[i].single_shot) { + timer[i].active = false; + } + } else { + timer[i].time = time_elapsed; + } + } +} + +static +void ProgressRateFilters(ecs_iter_t *it) { + EcsRateFilter *filter = ecs_field(it, EcsRateFilter, 0); + EcsTickSource *tick_dst = ecs_field(it, EcsTickSource, 1); + + int i; + for (i = 0; i < it->count; i ++) { + ecs_entity_t src = filter[i].src; + bool inc = false; + + filter[i].time_elapsed += it->delta_time; + + if (src) { + const EcsTickSource *tick_src = ecs_get( + it->world, src, EcsTickSource); + if (tick_src) { + inc = tick_src->tick; + } else { + inc = true; + } + } else { + inc = true; + } + + if (inc) { + filter[i].tick_count ++; + bool triggered = !(filter[i].tick_count % filter[i].rate); + tick_dst[i].tick = triggered; + tick_dst[i].time_elapsed = filter[i].time_elapsed; + + if (triggered) { + filter[i].time_elapsed = 0; + } + } else { + tick_dst[i].tick = false; + } + } +} + +static +void ProgressTickSource(ecs_iter_t *it) { + EcsTickSource *tick_src = ecs_field(it, EcsTickSource, 0); + + /* If tick source has no filters, tick unconditionally */ + int i; + for (i = 0; i < it->count; i ++) { + tick_src[i].tick = true; + tick_src[i].time_elapsed = it->delta_time; + } +} + +ecs_entity_t ecs_set_timeout( + ecs_world_t *world, + ecs_entity_t timer, + ecs_ftime_t timeout) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!timer) { + timer = ecs_entity(world, {0}); + } + + ecs_set(world, timer, EcsTimer, { + .timeout = timeout, + .single_shot = true, + .active = true + }); + + ecs_system_t *system_data = flecs_poly_get(world, timer, ecs_system_t); + if (system_data) { + system_data->tick_source = timer; + } + +error: + return timer; +} + +ecs_ftime_t ecs_get_timeout( + const ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); + + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; + } +error: + return 0; +} + +ecs_entity_t ecs_set_interval( + ecs_world_t *world, + ecs_entity_t timer, + ecs_ftime_t interval) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!timer) { + timer = ecs_new_w(world, EcsTimer); + } + + EcsTimer *t = ecs_ensure(world, timer, EcsTimer); + ecs_check(t != NULL, ECS_INTERNAL_ERROR, NULL); + t->timeout = interval; + t->active = true; + ecs_modified(world, timer, EcsTimer); + + ecs_system_t *system_data = flecs_poly_get(world, timer, ecs_system_t); + if (system_data) { + system_data->tick_source = timer; + } +error: + return timer; +} + +ecs_ftime_t ecs_get_interval( + const ecs_world_t *world, + ecs_entity_t timer) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!timer) { + return 0; + } + + const EcsTimer *value = ecs_get(world, timer, EcsTimer); + if (value) { + return value->timeout; + } +error: + return 0; +} + +void ecs_start_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); + ecs_check(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ptr->active = true; + ptr->time = 0; +error: + return; +} + +void ecs_stop_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); + ecs_check(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ptr->active = false; +error: + return; +} + +void ecs_reset_timer( + ecs_world_t *world, + ecs_entity_t timer) +{ + EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); + ecs_check(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ptr->time = 0; +error: + return; +} + +ecs_entity_t ecs_set_rate( + ecs_world_t *world, + ecs_entity_t filter, + int32_t rate, + ecs_entity_t source) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + if (!filter) { + filter = ecs_entity(world, {0}); + } + + ecs_set(world, filter, EcsRateFilter, { + .rate = rate, + .src = source + }); + + ecs_system_t *system_data = flecs_poly_get(world, filter, ecs_system_t); + if (system_data) { + system_data->tick_source = filter; + } + +error: + return filter; +} + +void ecs_set_tick_source( + ecs_world_t *world, + ecs_entity_t system, + ecs_entity_t tick_source) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); + ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + + system_data->tick_source = tick_source; +error: + return; +} + +static +void RandomizeTimers(ecs_iter_t *it) { + EcsTimer *timer = ecs_field(it, EcsTimer, 0); + int32_t i; + for (i = 0; i < it->count; i ++) { + timer[i].time = + ((ecs_ftime_t)rand() / (ecs_ftime_t)RAND_MAX) * timer[i].timeout; + } +} + +void ecs_randomize_timers( + ecs_world_t *world) +{ + ecs_observer(world, { + .entity = ecs_entity(world, { .name = "flecs.timer.RandomizeTimers" }), + .query.terms = {{ + .id = ecs_id(EcsTimer) + }}, + .events = {EcsOnSet}, + .yield_existing = true, + .callback = RandomizeTimers + }); +} + +void FlecsTimerImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsTimer); + ECS_IMPORT(world, FlecsPipeline); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsTimer); + flecs_bootstrap_component(world, EcsRateFilter); + + ecs_set_hooks(world, EcsTimer, { + .ctor = flecs_default_ctor + }); + + ecs_add_pair(world, ecs_id(EcsTimer), EcsWith, ecs_id(EcsTickSource)); + ecs_add_pair(world, ecs_id(EcsRateFilter), EcsWith, ecs_id(EcsTickSource)); + + /* Timer handling */ + ecs_system(world, { + .entity = ecs_entity(world, {.name = "ProgressTimers", .add = ecs_ids( ecs_dependson(EcsPreFrame))}), + .query.terms = { + { .id = ecs_id(EcsTimer) }, + { .id = ecs_id(EcsTickSource) } + }, + .callback = ProgressTimers + }); + + /* Rate filter handling */ + ecs_system(world, { + .entity = ecs_entity(world, {.name = "ProgressRateFilters", .add = ecs_ids( ecs_dependson(EcsPreFrame))}), + .query.terms = { + { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, + { .id = ecs_id(EcsTickSource), .inout = EcsOut } + }, + .callback = ProgressRateFilters + }); + + /* TickSource without a timer or rate filter just increases each frame */ + ecs_system(world, { + .entity = ecs_entity(world, { .name = "ProgressTickSource", .add = ecs_ids( ecs_dependson(EcsPreFrame))}), + .query.terms = { + { .id = ecs_id(EcsTickSource), .inout = EcsOut }, + { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, + { .id = ecs_id(EcsTimer), .oper = EcsNot } + }, + .callback = ProgressTickSource + }); +} + +#endif + +/** + * @file addons/units.c + * @brief Units addon. + */ + + +#ifdef FLECS_UNITS + +void FlecsUnitsImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsUnits); + ECS_IMPORT(world, FlecsMeta); + + ecs_set_name_prefix(world, "Ecs"); + + EcsUnitPrefixes = ecs_entity(world, { + .name = "prefixes", + .add = ecs_ids( EcsModule ) + }); + + /* Initialize unit prefixes */ + + ecs_entity_t prev_scope = ecs_set_scope(world, EcsUnitPrefixes); + + EcsYocto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Yocto" }), + .symbol = "y", + .translation = { .factor = 10, .power = -24 } + }); + EcsZepto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Zepto" }), + .symbol = "z", + .translation = { .factor = 10, .power = -21 } + }); + EcsAtto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Atto" }), + .symbol = "a", + .translation = { .factor = 10, .power = -18 } + }); + EcsFemto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Femto" }), + .symbol = "a", + .translation = { .factor = 10, .power = -15 } + }); + EcsPico = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Pico" }), + .symbol = "p", + .translation = { .factor = 10, .power = -12 } + }); + EcsNano = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Nano" }), + .symbol = "n", + .translation = { .factor = 10, .power = -9 } + }); + EcsMicro = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Micro" }), + .symbol = "μ", + .translation = { .factor = 10, .power = -6 } + }); + EcsMilli = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Milli" }), + .symbol = "m", + .translation = { .factor = 10, .power = -3 } + }); + EcsCenti = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Centi" }), + .symbol = "c", + .translation = { .factor = 10, .power = -2 } + }); + EcsDeci = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Deci" }), + .symbol = "d", + .translation = { .factor = 10, .power = -1 } + }); + EcsDeca = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Deca" }), + .symbol = "da", + .translation = { .factor = 10, .power = 1 } + }); + EcsHecto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Hecto" }), + .symbol = "h", + .translation = { .factor = 10, .power = 2 } + }); + EcsKilo = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Kilo" }), + .symbol = "k", + .translation = { .factor = 10, .power = 3 } + }); + EcsMega = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Mega" }), + .symbol = "M", + .translation = { .factor = 10, .power = 6 } + }); + EcsGiga = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Giga" }), + .symbol = "G", + .translation = { .factor = 10, .power = 9 } + }); + EcsTera = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Tera" }), + .symbol = "T", + .translation = { .factor = 10, .power = 12 } + }); + EcsPeta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Peta" }), + .symbol = "P", + .translation = { .factor = 10, .power = 15 } + }); + EcsExa = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Exa" }), + .symbol = "E", + .translation = { .factor = 10, .power = 18 } + }); + EcsZetta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Zetta" }), + .symbol = "Z", + .translation = { .factor = 10, .power = 21 } + }); + EcsYotta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Yotta" }), + .symbol = "Y", + .translation = { .factor = 10, .power = 24 } + }); + + EcsKibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Kibi" }), + .symbol = "Ki", + .translation = { .factor = 1024, .power = 1 } + }); + EcsMebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Mebi" }), + .symbol = "Mi", + .translation = { .factor = 1024, .power = 2 } + }); + EcsGibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Gibi" }), + .symbol = "Gi", + .translation = { .factor = 1024, .power = 3 } + }); + EcsTebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Tebi" }), + .symbol = "Ti", + .translation = { .factor = 1024, .power = 4 } + }); + EcsPebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Pebi" }), + .symbol = "Pi", + .translation = { .factor = 1024, .power = 5 } + }); + EcsExbi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Exbi" }), + .symbol = "Ei", + .translation = { .factor = 1024, .power = 6 } + }); + EcsZebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Zebi" }), + .symbol = "Zi", + .translation = { .factor = 1024, .power = 7 } + }); + EcsYobi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ + .entity = ecs_entity(world, { .name = "Yobi" }), + .symbol = "Yi", + .translation = { .factor = 1024, .power = 8 } + }); + + ecs_set_scope(world, prev_scope); + + /* Duration units */ + + EcsDuration = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Duration" }); + prev_scope = ecs_set_scope(world, EcsDuration); + + EcsSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Seconds" }), + .quantity = EcsDuration, + .symbol = "s" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsSeconds, + .kind = EcsF32 + }); + EcsPicoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "PicoSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsPico }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPicoSeconds, + .kind = EcsF32 + }); + + + EcsNanoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "NanoSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsNano }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsNanoSeconds, + .kind = EcsF32 + }); + + EcsMicroSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MicroSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsMicro }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMicroSeconds, + .kind = EcsF32 + }); + + EcsMilliSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MilliSeconds" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .prefix = EcsMilli }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMilliSeconds, + .kind = EcsF32 + }); + + EcsMinutes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Minutes" }), + .quantity = EcsDuration, + .base = EcsSeconds, + .symbol = "min", + .translation = { .factor = 60, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMinutes, + .kind = EcsU32 + }); + + EcsHours = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Hours" }), + .quantity = EcsDuration, + .base = EcsMinutes, + .symbol = "h", + .translation = { .factor = 60, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsHours, + .kind = EcsU32 + }); + + EcsDays = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Days" }), + .quantity = EcsDuration, + .base = EcsHours, + .symbol = "d", + .translation = { .factor = 24, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDays, + .kind = EcsU32 + }); + ecs_set_scope(world, prev_scope); + + /* Time units */ + + EcsTime = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Time" }); + prev_scope = ecs_set_scope(world, EcsTime); + + EcsDate = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Date" }), + .quantity = EcsTime }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDate, + .kind = EcsU32 + }); + ecs_set_scope(world, prev_scope); + + /* Mass units */ + + EcsMass = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Mass" }); + prev_scope = ecs_set_scope(world, EcsMass); + EcsGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Grams" }), + .quantity = EcsMass, + .symbol = "g" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGrams, + .kind = EcsF32 + }); + EcsKiloGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloGrams" }), + .quantity = EcsMass, + .prefix = EcsKilo, + .base = EcsGrams }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloGrams, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Electric current units */ + + EcsElectricCurrent = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "ElectricCurrent" }); + prev_scope = ecs_set_scope(world, EcsElectricCurrent); + EcsAmpere = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Ampere" }), + .quantity = EcsElectricCurrent, + .symbol = "A" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsAmpere, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Amount of substance units */ + + EcsAmount = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Amount" }); + prev_scope = ecs_set_scope(world, EcsAmount); + EcsMole = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Mole" }), + .quantity = EcsAmount, + .symbol = "mol" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMole, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Luminous intensity units */ + + EcsLuminousIntensity = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "LuminousIntensity" }); + prev_scope = ecs_set_scope(world, EcsLuminousIntensity); + EcsCandela = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Candela" }), + .quantity = EcsLuminousIntensity, + .symbol = "cd" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsCandela, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Force units */ + + EcsForce = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Force" }); + prev_scope = ecs_set_scope(world, EcsForce); + EcsNewton = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Newton" }), + .quantity = EcsForce, + .symbol = "N" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsNewton, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Length units */ + + EcsLength = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Length" }); + prev_scope = ecs_set_scope(world, EcsLength); + EcsMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Meters" }), + .quantity = EcsLength, + .symbol = "m" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMeters, + .kind = EcsF32 + }); + + EcsPicoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "PicoMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsPico }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPicoMeters, + .kind = EcsF32 + }); + + EcsNanoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "NanoMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsNano }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsNanoMeters, + .kind = EcsF32 + }); + + EcsMicroMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MicroMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsMicro }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMicroMeters, + .kind = EcsF32 + }); + + EcsMilliMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MilliMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsMilli }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMilliMeters, + .kind = EcsF32 + }); + + EcsCentiMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "CentiMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsCenti }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsCentiMeters, + .kind = EcsF32 + }); + + EcsKiloMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloMeters" }), + .quantity = EcsLength, + .base = EcsMeters, + .prefix = EcsKilo }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloMeters, + .kind = EcsF32 + }); + + EcsMiles = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Miles" }), + .quantity = EcsLength, + .symbol = "mi" + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMiles, + .kind = EcsF32 + }); + + EcsPixels = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Pixels" }), + .quantity = EcsLength, + .symbol = "px" + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPixels, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Pressure units */ + + EcsPressure = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Pressure" }); + prev_scope = ecs_set_scope(world, EcsPressure); + EcsPascal = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Pascal" }), + .quantity = EcsPressure, + .symbol = "Pa" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPascal, + .kind = EcsF32 + }); + EcsBar = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bar" }), + .quantity = EcsPressure, + .symbol = "bar" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBar, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Speed units */ + + EcsSpeed = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Speed" }); + prev_scope = ecs_set_scope(world, EcsSpeed); + EcsMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MetersPerSecond" }), + .quantity = EcsSpeed, + .base = EcsMeters, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMetersPerSecond, + .kind = EcsF32 + }); + EcsKiloMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloMetersPerSecond" }), + .quantity = EcsSpeed, + .base = EcsKiloMeters, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloMetersPerSecond, + .kind = EcsF32 + }); + EcsKiloMetersPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloMetersPerHour" }), + .quantity = EcsSpeed, + .base = EcsKiloMeters, + .over = EcsHours }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloMetersPerHour, + .kind = EcsF32 + }); + EcsMilesPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MilesPerHour" }), + .quantity = EcsSpeed, + .base = EcsMiles, + .over = EcsHours }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMilesPerHour, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Acceleration */ + + EcsAcceleration = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Acceleration" }), + .base = EcsMetersPerSecond, + .over = EcsSeconds }); + ecs_quantity_init(world, &(ecs_entity_desc_t){ + .id = EcsAcceleration + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsAcceleration, + .kind = EcsF32 + }); + + /* Temperature units */ + + EcsTemperature = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Temperature" }); + prev_scope = ecs_set_scope(world, EcsTemperature); + EcsKelvin = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Kelvin" }), + .quantity = EcsTemperature, + .symbol = "K" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKelvin, + .kind = EcsF32 + }); + EcsCelsius = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Celsius" }), + .quantity = EcsTemperature, + .symbol = "°C" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsCelsius, + .kind = EcsF32 + }); + EcsFahrenheit = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Fahrenheit" }), + .quantity = EcsTemperature, + .symbol = "F" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsFahrenheit, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Data units */ + + EcsData = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Data" }); + prev_scope = ecs_set_scope(world, EcsData); + + EcsBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bits" }), + .quantity = EcsData, + .symbol = "bit" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBits, + .kind = EcsU64 + }); + + EcsKiloBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBits" }), + .quantity = EcsData, + .base = EcsBits, + .prefix = EcsKilo }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBits, + .kind = EcsU64 + }); + + EcsMegaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBits" }), + .quantity = EcsData, + .base = EcsBits, + .prefix = EcsMega }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBits, + .kind = EcsU64 + }); + + EcsGigaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBits" }), + .quantity = EcsData, + .base = EcsBits, + .prefix = EcsGiga }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBits, + .kind = EcsU64 + }); + + EcsBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bytes" }), + .quantity = EcsData, + .symbol = "B", + .base = EcsBits, + .translation = { .factor = 8, .power = 1 } }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBytes, + .kind = EcsU64 + }); + + EcsKiloBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsKilo }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBytes, + .kind = EcsU64 + }); + + EcsMegaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsMega }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBytes, + .kind = EcsU64 + }); + + EcsGigaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsGiga }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBytes, + .kind = EcsU64 + }); + + EcsKibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KibiBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsKibi }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKibiBytes, + .kind = EcsU64 + }); + + EcsMebiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MebiBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsMebi }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMebiBytes, + .kind = EcsU64 + }); + + EcsGibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GibiBytes" }), + .quantity = EcsData, + .base = EcsBytes, + .prefix = EcsGibi }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGibiBytes, + .kind = EcsU64 + }); + + ecs_set_scope(world, prev_scope); + + /* DataRate units */ + + EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "DataRate" }); + prev_scope = ecs_set_scope(world, EcsDataRate); + + EcsBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "BitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsBits, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBitsPerSecond, + .kind = EcsU64 + }); + + EcsKiloBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsKiloBits, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBitsPerSecond, + .kind = EcsU64 + }); + + EcsMegaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsMegaBits, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBitsPerSecond, + .kind = EcsU64 + }); + + EcsGigaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBitsPerSecond" }), + .quantity = EcsDataRate, + .base = EcsGigaBits, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBitsPerSecond, + .kind = EcsU64 + }); + + EcsBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "BytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsBytes, + .over = EcsSeconds }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBytesPerSecond, + .kind = EcsU64 + }); + + EcsKiloBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloBytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsKiloBytes, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloBytesPerSecond, + .kind = EcsU64 + }); + + EcsMegaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaBytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsMegaBytes, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaBytesPerSecond, + .kind = EcsU64 + }); + + EcsGigaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaBytesPerSecond" }), + .quantity = EcsDataRate, + .base = EcsGigaBytes, + .over = EcsSeconds + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaBytesPerSecond, + .kind = EcsU64 + }); + + ecs_set_scope(world, prev_scope); + + /* Percentage */ + + EcsPercentage = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Percentage" }); + ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = EcsPercentage, + .symbol = "%" + }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsPercentage, + .kind = EcsF32 + }); + + /* Angles */ + + EcsAngle = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Angle" }); + prev_scope = ecs_set_scope(world, EcsAngle); + EcsRadians = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Radians" }), + .quantity = EcsAngle, + .symbol = "rad" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsRadians, + .kind = EcsF32 + }); + + EcsDegrees = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Degrees" }), + .quantity = EcsAngle, + .symbol = "°" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDegrees, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + /* Color */ + + EcsColor = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Color" }); + prev_scope = ecs_set_scope(world, EcsColor); + EcsColorRgb = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Rgb" }), + .quantity = EcsColor }); + + EcsColorHsl = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Hsl" }), + .quantity = EcsColor }); + + EcsColorCss = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Css" }), + .quantity = EcsColor }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsColorCss, + .kind = EcsString + }); + + ecs_set_scope(world, prev_scope); + + /* DeciBel */ + + EcsBel = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Bel" }), + .symbol = "B" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsBel, + .kind = EcsF32 + }); + EcsDeciBel = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "DeciBel" }), + .prefix = EcsDeci, + .base = EcsBel }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsDeciBel, + .kind = EcsF32 + }); + + /* Frequency */ + + EcsFrequency = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Frequency" }); + prev_scope = ecs_set_scope(world, EcsFrequency); + + EcsHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Hertz" }), + .quantity = EcsFrequency, + .symbol = "Hz" }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsHertz, + .kind = EcsF32 + }); + + EcsKiloHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "KiloHertz" }), + .prefix = EcsKilo, + .base = EcsHertz }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsKiloHertz, + .kind = EcsF32 + }); + + EcsMegaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "MegaHertz" }), + .prefix = EcsMega, + .base = EcsHertz }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsMegaHertz, + .kind = EcsF32 + }); + + EcsGigaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "GigaHertz" }), + .prefix = EcsGiga, + .base = EcsHertz }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsGigaHertz, + .kind = EcsF32 + }); + ecs_set_scope(world, prev_scope); + + EcsUri = ecs_quantity_init(world, &(ecs_entity_desc_t){ + .name = "Uri" }); + prev_scope = ecs_set_scope(world, EcsUri); + + EcsUriHyperlink = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Hyperlink" }), + .quantity = EcsUri }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsUriHyperlink, + .kind = EcsString + }); + + EcsUriImage = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "Image" }), + .quantity = EcsUri }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsUriImage, + .kind = EcsString + }); + + EcsUriFile = ecs_unit_init(world, &(ecs_unit_desc_t){ + .entity = ecs_entity(world, { .name = "File" }), + .quantity = EcsUri }); + ecs_primitive_init(world, &(ecs_primitive_desc_t){ + .entity = EcsUriFile, + .kind = EcsString + }); + ecs_set_scope(world, prev_scope); + + /* Documentation */ +#ifdef FLECS_DOC + ECS_IMPORT(world, FlecsDoc); + + ecs_doc_set_brief(world, EcsDuration, + "Time amount (e.g. \"20 seconds\", \"2 hours\")"); + ecs_doc_set_brief(world, EcsSeconds, "Time amount in seconds"); + ecs_doc_set_brief(world, EcsMinutes, "60 seconds"); + ecs_doc_set_brief(world, EcsHours, "60 minutes"); + ecs_doc_set_brief(world, EcsDays, "24 hours"); + + ecs_doc_set_brief(world, EcsTime, + "Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")"); + ecs_doc_set_brief(world, EcsDate, + "Seconds passed since January 1st 1970"); + + ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); + + ecs_doc_set_brief(world, EcsElectricCurrent, + "Units of electrical current (e.g. \"2 ampere\")"); + + ecs_doc_set_brief(world, EcsAmount, + "Units of amount of substance (e.g. \"2 mole\")"); + + ecs_doc_set_brief(world, EcsLuminousIntensity, + "Units of luminous intensity (e.g. \"1 candela\")"); + + ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); + + ecs_doc_set_brief(world, EcsLength, + "Units of length (e.g. \"5 meters\", \"20 miles\")"); + + ecs_doc_set_brief(world, EcsPressure, + "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); + + ecs_doc_set_brief(world, EcsSpeed, + "Units of movement (e.g. \"5 meters/second\")"); + + ecs_doc_set_brief(world, EcsAcceleration, + "Unit of speed increase (e.g. \"5 meters/second/second\")"); + + ecs_doc_set_brief(world, EcsTemperature, + "Units of temperature (e.g. \"5 degrees Celsius\")"); + + ecs_doc_set_brief(world, EcsData, + "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); + + ecs_doc_set_brief(world, EcsDataRate, + "Units of data transmission (e.g. \"100 megabits/second\")"); + + ecs_doc_set_brief(world, EcsAngle, + "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); + + ecs_doc_set_brief(world, EcsFrequency, + "The number of occurrences of a repeating event per unit of time."); + + ecs_doc_set_brief(world, EcsUri, "Universal resource identifier."); +#endif +} + +#endif + +/** + * @file datastructures/allocator.c + * @brief Allocator for any size. + * + * Allocators create a block allocator for each requested size. + */ + + +#ifndef FLECS_USE_OS_ALLOC +static +ecs_size_t flecs_allocator_size( + ecs_size_t size) +{ + return ECS_ALIGN(size, 16); +} + +static +ecs_size_t flecs_allocator_size_hash( + ecs_size_t size) +{ + return size >> 4; +} +#endif + +void flecs_allocator_init( + ecs_allocator_t *a) +{ + (void)a; +#ifndef FLECS_USE_OS_ALLOC + flecs_ballocator_init_n(&a->chunks, ecs_block_allocator_t, + FLECS_SPARSE_PAGE_SIZE); + flecs_sparse_init_t(&a->sizes, NULL, &a->chunks, ecs_block_allocator_t); +#endif +} + +void flecs_allocator_fini( + ecs_allocator_t *a) +{ + (void)a; +#ifndef FLECS_USE_OS_ALLOC + ecs_assert(a != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t i = 0, count = flecs_sparse_count(&a->sizes); + for (i = 0; i < count; i ++) { + ecs_block_allocator_t *ba = flecs_sparse_get_dense_t( + &a->sizes, ecs_block_allocator_t, i); + flecs_ballocator_fini(ba); + } + flecs_sparse_fini(&a->sizes); + + flecs_ballocator_fini(&a->chunks); +#endif +} + +ecs_block_allocator_t* flecs_allocator_get( + ecs_allocator_t *a, + ecs_size_t size) +{ +#ifndef FLECS_USE_OS_ALLOC + ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); + if (!size) { + return NULL; + } + + ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size <= flecs_allocator_size(size), ECS_INTERNAL_ERROR, NULL); + size = flecs_allocator_size(size); + ecs_size_t hash = flecs_allocator_size_hash(size); + ecs_block_allocator_t *result = flecs_sparse_get_t(&a->sizes, + ecs_block_allocator_t, (uint32_t)hash); + + if (!result) { + result = flecs_sparse_ensure_fast_t(&a->sizes, + ecs_block_allocator_t, (uint32_t)hash); + flecs_ballocator_init(result, size); + } + + ecs_assert(result->data_size == size, ECS_INTERNAL_ERROR, NULL); + + return result; +#else + (void)a; + (void)size; + ecs_err("invalid call to flecs_allocator_get in FLECS_USE_OS_ALLOC build"); + return NULL; +#endif +} + +char* flecs_strdup( + ecs_allocator_t *a, + const char* str) +{ +#ifndef FLECS_USE_OS_ALLOC + ecs_size_t len = ecs_os_strlen(str); + char *result = flecs_alloc_n(a, char, len + 1); + ecs_os_memcpy(result, str, len + 1); + return result; +#else + (void)a; + return ecs_os_strdup(str); +#endif +} + +void flecs_strfree( + ecs_allocator_t *a, + char* str) +{ +#ifndef FLECS_USE_OS_ALLOC + ecs_size_t len = ecs_os_strlen(str); + flecs_free_n(a, char, len + 1, str); +#else + (void)a; + ecs_os_free(str); +#endif +} + +void* flecs_dup( + ecs_allocator_t *a, + ecs_size_t size, + const void *src) +{ + if (!size) { + return NULL; + } +#ifndef FLECS_USE_OS_ALLOC + ecs_block_allocator_t *ba = flecs_allocator_get(a, size); + void *dst = flecs_balloc(ba); + ecs_os_memcpy(dst, src, size); + return dst; +#else + (void)a; + return ecs_os_memdup(src, size); +#endif +} + +#ifdef FLECS_USE_OS_ALLOC + +void* flecs_alloc( + ecs_allocator_t *a, + ecs_size_t size) +{ + (void)a; + return ecs_os_malloc(size); +} + +void* flecs_calloc( + ecs_allocator_t *a, + ecs_size_t size) +{ + (void)a; + return ecs_os_calloc(size); +} + +void* flecs_realloc( + ecs_allocator_t *a, + ecs_size_t dst_size, + ecs_size_t src_size, + void *ptr) +{ + (void)a; + (void)src_size; + return ecs_os_realloc(ptr, dst_size); +} + +void flecs_free( + ecs_allocator_t *a, + ecs_size_t size, + void *ptr) +{ + (void)a; + (void)size; + ecs_os_free(ptr); +} + +#endif + +/** + * @file datastructures/bitset.c + * @brief Bitset data structure. + * + * Simple bitset implementation. The bitset allows for storage of arbitrary + * numbers of bits. + */ + + +static +void flecs_bitset_ensure_size( + ecs_bitset_t *bs, + ecs_size_t size) +{ + if (!bs->size) { + int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->size = ((size - 1) / 64 + 1) * 64; + bs->data = ecs_os_calloc(new_size); + } else if (size > bs->size) { + int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->size = ((size - 1) / 64 + 1) * 64; + int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->data = ecs_os_realloc(bs->data, new_size); + ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); + } +} + +void flecs_bitset_init( + ecs_bitset_t* bs) +{ + bs->size = 0; + bs->count = 0; + bs->data = NULL; +} + +void flecs_bitset_ensure( + ecs_bitset_t *bs, + int32_t count) +{ + if (count > bs->count) { + bs->count = count; + flecs_bitset_ensure_size(bs, count); + } +} + +void flecs_bitset_fini( + ecs_bitset_t *bs) +{ + ecs_os_free(bs->data); + bs->data = NULL; + bs->count = 0; +} + +void flecs_bitset_addn( + ecs_bitset_t *bs, + int32_t count) +{ + int32_t elem = bs->count += count; + flecs_bitset_ensure_size(bs, elem); +} + +void flecs_bitset_set( + ecs_bitset_t *bs, + int32_t elem, + bool value) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + uint32_t hi = ((uint32_t)elem) >> 6; + uint32_t lo = ((uint32_t)elem) & 0x3F; + uint64_t v = bs->data[hi]; + bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); +error: + return; +} + +bool flecs_bitset_get( + const ecs_bitset_t *bs, + int32_t elem) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); +error: + return false; +} + +int32_t flecs_bitset_count( + const ecs_bitset_t *bs) +{ + return bs->count; +} + +void flecs_bitset_remove( + ecs_bitset_t *bs, + int32_t elem) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t last = bs->count - 1; + bool last_value = flecs_bitset_get(bs, last); + flecs_bitset_set(bs, elem, last_value); + flecs_bitset_set(bs, last, 0); + bs->count --; +error: + return; +} + +void flecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b) +{ + ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); + ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); + + bool a = flecs_bitset_get(bs, elem_a); + bool b = flecs_bitset_get(bs, elem_b); + flecs_bitset_set(bs, elem_a, b); + flecs_bitset_set(bs, elem_b, a); +error: + return; +} + +/** + * @file datastructures/block_allocator.c + * @brief Block allocator. + * + * A block allocator is an allocator for a fixed size that allocates blocks of + * memory with N elements of the requested size. + */ + + +// #ifdef FLECS_SANITIZE +// #define FLECS_MEMSET_UNINITIALIZED +// #endif + +int64_t ecs_block_allocator_alloc_count = 0; +int64_t ecs_block_allocator_free_count = 0; + +#ifndef FLECS_USE_OS_ALLOC + +/* Bypass block allocator if chunks per block is lower than the configured + * value. This prevents holding on to large memory chunks when they're freed, + * which can add up especially in scenarios where an array is reallocated + * several times to a large size. + * A value of 1 seems to yield the best results. Higher values only impact lower + * allocation sizes, which are more likely to be reused. */ +#define FLECS_MIN_CHUNKS_PER_BLOCK 1 + +static +ecs_block_allocator_chunk_header_t* flecs_balloc_block( + ecs_block_allocator_t *allocator) +{ + if (!allocator->chunk_size) { + return NULL; + } + + ecs_block_allocator_block_t *block = + ecs_os_malloc(ECS_SIZEOF(ecs_block_allocator_block_t) + + allocator->block_size); + ecs_block_allocator_chunk_header_t *first_chunk = ECS_OFFSET(block, + ECS_SIZEOF(ecs_block_allocator_block_t)); + + block->memory = first_chunk; + block->next = NULL; + + if (allocator->block_head) { + block->next = allocator->block_head; + } + + allocator->block_head = block; + + ecs_block_allocator_chunk_header_t *chunk = first_chunk; + int32_t i, end; + for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) { + chunk->next = ECS_OFFSET(chunk, allocator->chunk_size); + chunk = chunk->next; + } + + ecs_os_linc(&ecs_block_allocator_alloc_count); + + chunk->next = NULL; + return first_chunk; +} + +#endif + +void flecs_ballocator_init( + ecs_block_allocator_t *ba, + ecs_size_t size) +{ + ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + ba->data_size = size; +#ifndef FLECS_USE_OS_ALLOC +#ifdef FLECS_SANITIZE + ba->alloc_count = 0; + if (size != 24) { /* Prevent stack overflow as map uses block allocator */ + ba->outstanding = ecs_os_malloc_t(ecs_map_t); + ecs_map_init(ba->outstanding, NULL); + } + size += ECS_SIZEOF(int64_t) * 2; /* 16 byte aligned */ +#endif + ba->chunk_size = ECS_ALIGN(size, 16); + ba->chunks_per_block = ECS_MAX(4096 / ba->chunk_size, 1); + ba->block_size = ba->chunks_per_block * ba->chunk_size; + ba->head = NULL; + ba->block_head = NULL; +#endif +} + +ecs_block_allocator_t* flecs_ballocator_new( + ecs_size_t size) +{ + ecs_block_allocator_t *result = ecs_os_calloc_t(ecs_block_allocator_t); + flecs_ballocator_init(result, size); + return result; +} + +void flecs_ballocator_fini( + ecs_block_allocator_t *ba) +{ + ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); + (void)ba; + +#ifndef FLECS_USE_OS_ALLOC +#ifdef FLECS_SANITIZE + if (ba->alloc_count != 0) { + ecs_err("Leak detected! (size %u, remaining = %d)", + (uint32_t)ba->data_size, ba->alloc_count); + if (ba->outstanding) { + ecs_map_iter_t it = ecs_map_iter(ba->outstanding); + while (ecs_map_next(&it)) { + uint64_t key = ecs_map_key(&it); + char *type_name = ecs_map_ptr(&it); + if (type_name) { + printf(" - %p (%s)\n", (void*)key, type_name); + } else { + printf(" - %p (unknown type)\n", (void*)key); + } + } + } + ecs_abort(ECS_LEAK_DETECTED, NULL); + } + if (ba->outstanding) { + ecs_map_fini(ba->outstanding); + ecs_os_free(ba->outstanding); + } +#endif + + ecs_block_allocator_block_t *block; + for (block = ba->block_head; block;) { + ecs_block_allocator_block_t *next = block->next; + ecs_os_free(block); + ecs_os_linc(&ecs_block_allocator_free_count); + block = next; + } + + ba->block_head = NULL; +#endif +} + +void flecs_ballocator_free( + ecs_block_allocator_t *ba) +{ + flecs_ballocator_fini(ba); + ecs_os_free(ba); +} + +void* flecs_balloc( + ecs_block_allocator_t *ba) +{ + return flecs_balloc_w_dbg_info(ba, NULL); +} + +void* flecs_balloc_w_dbg_info( + ecs_block_allocator_t *ba, + const char *type_name) +{ + (void)type_name; + void *result; + + if (!ba) return NULL; + +#ifdef FLECS_USE_OS_ALLOC + result = ecs_os_malloc(ba->data_size); +#else + + if (ba->chunks_per_block <= FLECS_MIN_CHUNKS_PER_BLOCK) { + return ecs_os_malloc(ba->data_size); + } + + if (!ba->head) { + ba->head = flecs_balloc_block(ba); + ecs_assert(ba->head != NULL, ECS_INTERNAL_ERROR, NULL); + } + + result = ba->head; + ba->head = ba->head->next; + +#ifdef FLECS_SANITIZE + ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); + if (ba->outstanding) { + uint64_t *v = ecs_map_ensure(ba->outstanding, (uintptr_t)result); + *(const char**)v = type_name; + } + ba->alloc_count ++; + *(int64_t*)result = (uintptr_t)ba; + result = ECS_OFFSET(result, ECS_SIZEOF(int64_t) * 2); +#endif +#endif + +#ifdef FLECS_MEMSET_UNINITIALIZED + ecs_os_memset(result, 0xAA, ba->data_size); +#endif + + return result; +} + +void* flecs_bcalloc( + ecs_block_allocator_t *ba) +{ + return flecs_bcalloc_w_dbg_info(ba, NULL); +} + +void* flecs_bcalloc_w_dbg_info( + ecs_block_allocator_t *ba, + const char *type_name) +{ + (void)type_name; + +#ifdef FLECS_USE_OS_ALLOC + ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_os_calloc(ba->data_size); +#else + if (!ba) return NULL; + void *result = flecs_balloc_w_dbg_info(ba, type_name); + ecs_os_memset(result, 0, ba->data_size); + return result; +#endif +} + +void flecs_bfree( + ecs_block_allocator_t *ba, + void *memory) +{ + flecs_bfree_w_dbg_info(ba, memory, NULL); +} + +void flecs_bfree_w_dbg_info( + ecs_block_allocator_t *ba, + void *memory, + const char *type_name) +{ + (void)type_name; + +#ifdef FLECS_USE_OS_ALLOC + (void)ba; + ecs_os_free(memory); + return; +#else + + if (!ba) { + ecs_assert(memory == NULL, ECS_INTERNAL_ERROR, NULL); + return; + } + + if (memory == NULL) { + return; + } + + if (ba->chunks_per_block <= FLECS_MIN_CHUNKS_PER_BLOCK) { + ecs_os_free(memory); + return; + } + +#ifdef FLECS_SANITIZE + memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t) * 2); + ecs_block_allocator_t *actual = *(ecs_block_allocator_t**)memory; + if (actual != ba) { + if (type_name) { + ecs_err("chunk %p returned to wrong allocator " + "(chunk = %ub, allocator = %ub, type = %s)", + memory, actual->data_size, ba->data_size, type_name); + } else { + ecs_err("chunk %p returned to wrong allocator " + "(chunk = %ub, allocator = %ub)", + memory, actual->data_size, ba->chunk_size); + } + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + if (ba->outstanding) { + ecs_map_remove(ba->outstanding, (uintptr_t)memory); + } + + ba->alloc_count --; + ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, + "corrupted allocator (size = %d)", ba->chunk_size); +#endif + + ecs_block_allocator_chunk_header_t *chunk = memory; + chunk->next = ba->head; + ba->head = chunk; +#endif +} + +void* flecs_brealloc( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory) +{ + return flecs_brealloc_w_dbg_info(dst, src, memory, NULL); +} + +void* flecs_brealloc_w_dbg_info( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory, + const char *type_name) +{ + (void)type_name; + + void *result; +#ifdef FLECS_USE_OS_ALLOC + (void)src; + result = ecs_os_realloc(memory, dst->data_size); +#else + if (dst == src) { + return memory; + } + + result = flecs_balloc_w_dbg_info(dst, type_name); + if (result && src) { + ecs_size_t size = src->data_size; + if (dst->data_size < size) { + size = dst->data_size; + } + ecs_os_memcpy(result, memory, size); + } + flecs_bfree_w_dbg_info(src, memory, type_name); +#endif +#ifdef FLECS_MEMSET_UNINITIALIZED + if (dst && src && (dst->data_size > src->data_size)) { + ecs_os_memset(ECS_OFFSET(result, src->data_size), 0xAA, + dst->data_size - src->data_size); + } else if (dst && !src) { + ecs_os_memset(result, 0xAA, dst->data_size); + } +#endif + + return result; +} + +void* flecs_bdup( + ecs_block_allocator_t *ba, + void *memory) +{ +#ifdef FLECS_USE_OS_ALLOC + if (memory && ba->data_size) { + return ecs_os_memdup(memory, ba->data_size); + } else { + return NULL; + } +#else + void *result = flecs_balloc(ba); + if (result) { + ecs_os_memcpy(result, memory, ba->data_size); + } + return result; +#endif +} + +// This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/) +// main repo: https://github.com/wangyi-fudan/wyhash +// author: 王一 Wang Yi +// contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, +// Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, +// Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, +// hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric + +/* quick example: + string s="fjsakfdsjkf"; + uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); +*/ + + +#ifndef WYHASH_CONDOM +//protections that produce different results: +//1: normal valid behavior +//2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" +#define WYHASH_CONDOM 1 +#endif + +#ifndef WYHASH_32BIT_MUM +//0: normal version, slow on 32 bit systems +//1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function +#define WYHASH_32BIT_MUM 0 +#endif + +//includes +#if defined(_MSC_VER) && defined(_M_X64) + #include + #pragma intrinsic(_umul128) +#endif + +//likely and unlikely macros +#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) + #define likely_(x) __builtin_expect(x,1) + #define unlikely_(x) __builtin_expect(x,0) +#else + #define likely_(x) (x) + #define unlikely_(x) (x) +#endif + +//128bit multiply function +static inline void wymum_(uint64_t *A, uint64_t *B){ +#if(WYHASH_32BIT_MUM) + uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B; + #if(WYHASH_CONDOM>1) + *A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; + #else + *A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; + #endif +#elif defined(__SIZEOF_INT128__) + __uint128_t r=*A; r*=*B; + #if(WYHASH_CONDOM>1) + *A^=(uint64_t)r; *B^=(uint64_t)(r>>64); + #else + *A=(uint64_t)r; *B=(uint64_t)(r>>64); + #endif +#elif defined(_MSC_VER) && defined(_M_X64) + #if(WYHASH_CONDOM>1) + uint64_t a, b; + a=_umul128(*A,*B,&b); + *A^=a; *B^=b; + #else + *A=_umul128(*A,*B,B); + #endif +#else + uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; + uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c; + #if(WYHASH_CONDOM>1) + *A^=lo; *B^=hi; + #else + *A=lo; *B=hi; + #endif +#endif +} + +//multiply and xor mix function, aka MUM +static inline uint64_t wymix_(uint64_t A, uint64_t B){ wymum_(&A,&B); return A^B; } + +//endian macros +#ifndef WYHASH_LITTLE_ENDIAN + #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + #define WYHASH_LITTLE_ENDIAN 1 + #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + #define WYHASH_LITTLE_ENDIAN 0 + #else + #warning could not determine endianness! Falling back to little endian. + #define WYHASH_LITTLE_ENDIAN 1 + #endif +#endif + +//read functions +#if (WYHASH_LITTLE_ENDIAN) +static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} +static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;} +#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) +static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} +static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);} +#elif defined(_MSC_VER) +static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} +static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);} +#else +static inline uint64_t wyr8_(const uint8_t *p) { + uint64_t v; memcpy(&v, p, 8); + return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000)); +} +static inline uint64_t wyr4_(const uint8_t *p) { + uint32_t v; memcpy(&v, p, 4); + return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000)); +} +#endif +static inline uint64_t wyr3_(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} + +//wyhash main function +static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){ + const uint8_t *p=(const uint8_t *)key; seed^=wymix_(seed^secret[0],secret[1]); uint64_t a, b; + if(likely_(len<=16)){ + if(likely_(len>=4)){ a=(wyr4_(p)<<32)|wyr4_(p+((len>>3)<<2)); b=(wyr4_(p+len-4)<<32)|wyr4_(p+len-4-((len>>3)<<2)); } + else if(likely_(len>0)){ a=wyr3_(p,len); b=0;} + else a=b=0; + } + else{ + size_t i=len; + if(unlikely_(i>48)){ + uint64_t see1=seed, see2=seed; + do{ + seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); + see1=wymix_(wyr8_(p+16)^secret[2],wyr8_(p+24)^see1); + see2=wymix_(wyr8_(p+32)^secret[3],wyr8_(p+40)^see2); + p+=48; i-=48; + }while(likely_(i>48)); + seed^=see1^see2; + } + while(unlikely_(i>16)){ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); i-=16; p+=16; } + a=wyr8_(p+i-16); b=wyr8_(p+i-8); + } + a^=secret[1]; b^=seed; wymum_(&a,&b); + return wymix_(a^secret[0]^len,b^secret[1]); +} + +//the default secret parameters +static const uint64_t wyp_[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; + +uint64_t flecs_hash( + const void *data, + ecs_size_t length) +{ + return wyhash(data, flecs_ito(size_t, length), 0, wyp_); +} + +/** + * @file datastructures/hashmap.c + * @brief Hashmap data structure. + * + * The hashmap data structure is built on top of the map data structure. Where + * the map data structure can only work with 64bit key values, the hashmap can + * hash keys of any size, and handles collisions between hashes. + */ + + +static +int32_t flecs_hashmap_find_key( + const ecs_hashmap_t *map, + ecs_vec_t *keys, + ecs_size_t key_size, + const void *key) +{ + int32_t i, count = ecs_vec_count(keys); + void *key_array = ecs_vec_first(keys); + for (i = 0; i < count; i ++) { + void *key_ptr = ECS_OFFSET(key_array, key_size * i); + if (map->compare(key_ptr, key) == 0) { + return i; + } + } + return -1; +} + +static +ecs_hm_bucket_t* flecs_hm_bucket_new( + ecs_hashmap_t *map) +{ + if (map->impl.allocator) { + return flecs_calloc_t(map->impl.allocator, ecs_hm_bucket_t); + } else { + return ecs_os_calloc_t(ecs_hm_bucket_t); + } +} + +static +void flecs_hm_bucket_free( + ecs_hashmap_t *map, + ecs_hm_bucket_t *bucket) +{ + if (map->impl.allocator) { + flecs_free_t(map->impl.allocator, ecs_hm_bucket_t, bucket); + } else { + ecs_os_free(bucket); + } +} + +void flecs_hashmap_init_( + ecs_hashmap_t *map, + ecs_size_t key_size, + ecs_size_t value_size, + ecs_hash_value_action_t hash, + ecs_compare_action_t compare, + ecs_allocator_t *allocator) +{ + map->key_size = key_size; + map->value_size = value_size; + map->hash = hash; + map->compare = compare; + ecs_map_init(&map->impl, allocator); +} + +void flecs_hashmap_fini( + ecs_hashmap_t *map) +{ + ecs_allocator_t *a = map->impl.allocator; + ecs_map_iter_t it = ecs_map_iter(&map->impl); + + while (ecs_map_next(&it)) { + ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); + ecs_vec_fini(a, &bucket->keys, map->key_size); + ecs_vec_fini(a, &bucket->values, map->value_size); + flecs_hm_bucket_free(map, bucket); + } + + ecs_map_fini(&map->impl); +} + +void flecs_hashmap_copy( + ecs_hashmap_t *dst, + const ecs_hashmap_t *src) +{ + ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL); + + flecs_hashmap_init_(dst, src->key_size, src->value_size, src->hash, + src->compare, src->impl.allocator); + ecs_map_copy(&dst->impl, &src->impl); + + ecs_allocator_t *a = dst->impl.allocator; + ecs_map_iter_t it = ecs_map_iter(&dst->impl); + while (ecs_map_next(&it)) { + ecs_hm_bucket_t **bucket_ptr = ecs_map_ref(&it, ecs_hm_bucket_t); + ecs_hm_bucket_t *src_bucket = bucket_ptr[0]; + ecs_hm_bucket_t *dst_bucket = flecs_hm_bucket_new(dst); + bucket_ptr[0] = dst_bucket; + dst_bucket->keys = ecs_vec_copy(a, &src_bucket->keys, dst->key_size); + dst_bucket->values = ecs_vec_copy(a, &src_bucket->values, dst->value_size); + } +} + +void* flecs_hashmap_get_( + const ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + + uint64_t hash = map->hash(key); + ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, + ecs_hm_bucket_t, hash); + if (!bucket) { + return NULL; + } + + int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); + if (index == -1) { + return NULL; + } + + return ecs_vec_get(&bucket->values, value_size, index); +} + +flecs_hashmap_result_t flecs_hashmap_ensure_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + + uint64_t hash = map->hash(key); + ecs_hm_bucket_t **r = ecs_map_ensure_ref(&map->impl, ecs_hm_bucket_t, hash); + ecs_hm_bucket_t *bucket = r[0]; + if (!bucket) { + bucket = r[0] = flecs_hm_bucket_new(map); + } + + ecs_allocator_t *a = map->impl.allocator; + void *value_ptr, *key_ptr; + ecs_vec_t *keys = &bucket->keys; + ecs_vec_t *values = &bucket->values; + if (!keys->array) { + ecs_vec_init(a, &bucket->keys, key_size, 1); + ecs_vec_init(a, &bucket->values, value_size, 1); + keys = &bucket->keys; + values = &bucket->values; + key_ptr = ecs_vec_append(a, keys, key_size); + value_ptr = ecs_vec_append(a, values, value_size); + ecs_os_memcpy(key_ptr, key, key_size); + ecs_os_memset(value_ptr, 0, value_size); + } else { + int32_t index = flecs_hashmap_find_key(map, keys, key_size, key); + if (index == -1) { + key_ptr = ecs_vec_append(a, keys, key_size); + value_ptr = ecs_vec_append(a, values, value_size); + ecs_os_memcpy(key_ptr, key, key_size); + ecs_os_memset(value_ptr, 0, value_size); + } else { + key_ptr = ecs_vec_get(keys, key_size, index); + value_ptr = ecs_vec_get(values, value_size, index); + } + } + + return (flecs_hashmap_result_t){ + .key = key_ptr, .value = value_ptr, .hash = hash + }; +} + +void flecs_hashmap_set_( + ecs_hashmap_t *map, + ecs_size_t key_size, + void *key, + ecs_size_t value_size, + const void *value) +{ + void *value_ptr = flecs_hashmap_ensure_(map, key_size, key, value_size).value; + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy(value_ptr, value, value_size); +} + +ecs_hm_bucket_t* flecs_hashmap_get_bucket( + const ecs_hashmap_t *map, + uint64_t hash) +{ + ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); +} + +void flecs_hm_bucket_remove( + ecs_hashmap_t *map, + ecs_hm_bucket_t *bucket, + uint64_t hash, + int32_t index) +{ + ecs_vec_remove(&bucket->keys, map->key_size, index); + ecs_vec_remove(&bucket->values, map->value_size, index); + + if (!ecs_vec_count(&bucket->keys)) { + ecs_allocator_t *a = map->impl.allocator; + ecs_vec_fini(a, &bucket->keys, map->key_size); + ecs_vec_fini(a, &bucket->values, map->value_size); + ecs_hm_bucket_t *b = ecs_map_remove_ptr(&map->impl, hash); + ecs_assert(bucket == b, ECS_INTERNAL_ERROR, NULL); (void)b; + flecs_hm_bucket_free(map, bucket); + } +} + +void flecs_hashmap_remove_w_hash_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size, + uint64_t hash) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + (void)value_size; + + ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, + ecs_hm_bucket_t, hash); + if (!bucket) { + return; + } + + int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); + if (index == -1) { + return; + } + + flecs_hm_bucket_remove(map, bucket, hash, index); +} + +void flecs_hashmap_remove_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + + uint64_t hash = map->hash(key); + flecs_hashmap_remove_w_hash_(map, key_size, key, value_size, hash); +} + +flecs_hashmap_iter_t flecs_hashmap_iter( + ecs_hashmap_t *map) +{ + return (flecs_hashmap_iter_t){ + .it = ecs_map_iter(&map->impl) + }; +} + +void* flecs_hashmap_next_( + flecs_hashmap_iter_t *it, + ecs_size_t key_size, + void *key_out, + ecs_size_t value_size) +{ + int32_t index = ++ it->index; + ecs_hm_bucket_t *bucket = it->bucket; + while (!bucket || it->index >= ecs_vec_count(&bucket->keys)) { + ecs_map_next(&it->it); + bucket = it->bucket = ecs_map_ptr(&it->it); + if (!bucket) { + return NULL; + } + index = it->index = 0; + } + + if (key_out) { + *(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); + } + + return ecs_vec_get(&bucket->values, value_size, index); +} + +/** + * @file datastructures/map.c + * @brief Map data structure. + * + * Map data structure for 64bit keys and dynamic payload size. + */ + + +/* The ratio used to determine whether the map should flecs_map_rehash. If + * (element_count * ECS_LOAD_FACTOR) > bucket_count, bucket count is increased. */ +#define ECS_LOAD_FACTOR (12) +#define ECS_BUCKET_END(b, c) ECS_ELEM_T(b, ecs_bucket_t, c) +#define ECS_MAP_ALLOC(a, T) a ? flecs_alloc_t(a, T) : ecs_os_malloc_t(T) +#define ECS_MAP_CALLOC_N(a, T, n) a ? flecs_calloc_n(a, T, n) : ecs_os_calloc_n(T, n) +#define ECS_MAP_FREE(a, T, ptr) a ? flecs_free_t(a, T, ptr) : ecs_os_free(ptr) +#define ECS_MAP_FREE_N(a, T, n, ptr) a ? flecs_free_n(a, T, n, ptr) : ecs_os_free(ptr) + +static +uint8_t flecs_log2(uint32_t v) { + static const uint8_t log2table[32] = + {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; + + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; +} + +/* Get bucket count for number of elements */ +static +int32_t flecs_map_get_bucket_count( + int32_t count) +{ + return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); +} + +/* Get bucket shift amount for a given bucket count */ +static +uint8_t flecs_map_get_bucket_shift( + int32_t bucket_count) +{ + return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); +} + +/* Get bucket index for provided map key */ +static +int32_t flecs_map_get_bucket_index( + uint16_t bucket_shift, + ecs_map_key_t key) +{ + ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); + return (int32_t)((11400714819323198485ull * key) >> bucket_shift); +} + +/* Get bucket for key */ +static +ecs_bucket_t* flecs_map_get_bucket( + const ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t bucket_id = flecs_map_get_bucket_index((uint16_t)map->bucket_shift, key); + ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); + return &map->buckets[bucket_id]; +} + +/* Add element to bucket */ +static +ecs_map_val_t* flecs_map_bucket_add( + ecs_allocator_t *a, + ecs_bucket_t *bucket, + ecs_map_key_t key) +{ + ecs_bucket_entry_t *new_entry = ECS_MAP_ALLOC(a, ecs_bucket_entry_t); + new_entry->key = key; + new_entry->next = bucket->first; + bucket->first = new_entry; + return &new_entry->value; +} + +/* Remove element from bucket */ +static +ecs_map_val_t flecs_map_bucket_remove( + ecs_map_t *map, + ecs_bucket_t *bucket, + ecs_map_key_t key) +{ + ecs_bucket_entry_t *entry; + for (entry = bucket->first; entry; entry = entry->next) { + if (entry->key == key) { + ecs_map_val_t value = entry->value; + ecs_bucket_entry_t **next_holder = &bucket->first; + while(*next_holder != entry) { + next_holder = &(*next_holder)->next; + } + *next_holder = entry->next; + ECS_MAP_FREE(map->allocator, ecs_bucket_entry_t, entry); + map->count --; + return value; + } + } + + return 0; +} + +/* Free contents of bucket */ +static +void flecs_map_bucket_clear( + ecs_allocator_t *allocator, + ecs_bucket_t *bucket) +{ + ecs_bucket_entry_t *entry = bucket->first; + while(entry) { + ecs_bucket_entry_t *next = entry->next; + ECS_MAP_FREE(allocator, ecs_bucket_entry_t, entry); + entry = next; + } +} + +/* Get payload pointer for key from bucket */ +static +ecs_map_val_t* flecs_map_bucket_get( + ecs_bucket_t *bucket, + ecs_map_key_t key) +{ + ecs_bucket_entry_t *entry; + for (entry = bucket->first; entry; entry = entry->next) { + if (entry->key == key) { + return &entry->value; + } + } + return NULL; +} + +/* Grow number of buckets */ +static +void flecs_map_rehash( + ecs_map_t *map, + int32_t count) +{ + count = flecs_next_pow_of_2(count); + if (count < 2) { + count = 2; + } + + int32_t old_count = map->bucket_count; + ecs_bucket_t *buckets = map->buckets, *b, *end = ECS_BUCKET_END(buckets, old_count); + + map->buckets = ECS_MAP_CALLOC_N(map->allocator, ecs_bucket_t, count); + map->bucket_count = count; + map->bucket_shift = flecs_map_get_bucket_shift(count) & 0x3fu; + + /* Remap old bucket entries to new buckets */ + for (b = buckets; b < end; b++) { + ecs_bucket_entry_t* entry; + for (entry = b->first; entry;) { + ecs_bucket_entry_t* next = entry->next; + int32_t bucket_index = flecs_map_get_bucket_index( + (uint16_t)map->bucket_shift, entry->key); + ecs_bucket_t *bucket = &map->buckets[bucket_index]; + entry->next = bucket->first; + bucket->first = entry; + entry = next; + } + } + + ECS_MAP_FREE_N(map->allocator, ecs_bucket_t, old_count, buckets); +} + +void ecs_map_init( + ecs_map_t *result, + ecs_allocator_t *allocator) +{ + ecs_os_zeromem(result); + + result->allocator = allocator; + + flecs_map_rehash(result, 0); +} + +void ecs_map_init_if( + ecs_map_t *result, + ecs_allocator_t *allocator) +{ + if (!ecs_map_is_init(result)) { + ecs_map_init(result, allocator); + } +} + +void ecs_map_fini( + ecs_map_t *map) +{ + if (!ecs_map_is_init(map)) { + return; + } + + ecs_allocator_t *a = map->allocator; + ecs_bucket_t *bucket = map->buckets, *end = &bucket[map->bucket_count]; + while (bucket != end) { + flecs_map_bucket_clear(a, bucket); + bucket ++; + } + + map->bucket_shift = 0; + + ECS_MAP_FREE_N(a, ecs_bucket_t, map->bucket_count, map->buckets); +} + +ecs_map_val_t* ecs_map_get( + const ecs_map_t *map, + ecs_map_key_t key) +{ + return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key); +} + +void* ecs_map_get_deref_( + const ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_map_val_t* ptr = flecs_map_bucket_get( + flecs_map_get_bucket(map, key), key); + if (ptr) { + return (void*)(uintptr_t)ptr[0]; + } + return NULL; +} + +void ecs_map_insert( + ecs_map_t *map, + ecs_map_key_t key, + ecs_map_val_t value) +{ + ecs_assert(ecs_map_get(map, key) == NULL, ECS_INVALID_PARAMETER, NULL); + int32_t map_count = ++map->count; + int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); + int32_t bucket_count = map->bucket_count; + if (tgt_bucket_count > bucket_count) { + flecs_map_rehash(map, tgt_bucket_count); + } + + ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); + flecs_map_bucket_add(map->allocator, bucket, key)[0] = value; + +#ifdef FLECS_DEBUG + ecs_os_linc(&map->change_count); +#endif +} + +void* ecs_map_insert_alloc( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + void *elem = ecs_os_calloc(elem_size); + ecs_map_insert_ptr(map, key, (uintptr_t)elem); + return elem; +} + +ecs_map_val_t* ecs_map_ensure( + ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); + ecs_map_val_t *result = flecs_map_bucket_get(bucket, key); + if (result) { + return result; + } + + int32_t map_count = ++map->count; + int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); + int32_t bucket_count = map->bucket_count; + if (tgt_bucket_count > bucket_count) { + flecs_map_rehash(map, tgt_bucket_count); + bucket = flecs_map_get_bucket(map, key); + } + + ecs_map_val_t* v = flecs_map_bucket_add(map->allocator, bucket, key); + *v = 0; + +#ifdef FLECS_DEBUG + ecs_os_linc(&map->change_count); +#endif + + return v; +} + +void* ecs_map_ensure_alloc( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + ecs_map_val_t *val = ecs_map_ensure(map, key); + if (!*val) { + void *elem = ecs_os_calloc(elem_size); + *val = (ecs_map_val_t)(uintptr_t)elem; + return elem; + } else { + return (void*)(uintptr_t)*val; + } +} + +ecs_map_val_t ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key) +{ +#ifdef FLECS_DEBUG + if (map->last_iterated != key) { + ecs_os_linc(&map->change_count); + } +#endif + return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); +} + +void ecs_map_reclaim( + ecs_map_t *map) +{ + int32_t tgt_bucket_count = flecs_map_get_bucket_count(map->count - 1); + if (tgt_bucket_count != map->bucket_count) { + flecs_map_rehash(map, tgt_bucket_count); + } +} + +void ecs_map_remove_free( + ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_map_val_t val = ecs_map_remove(map, key); + if (val) { + ecs_os_free((void*)(uintptr_t)val); + } +} + +void ecs_map_clear( + ecs_map_t *map) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t i, count = map->bucket_count; + for (i = 0; i < count; i ++) { + flecs_map_bucket_clear(map->allocator, &map->buckets[i]); + } + ECS_MAP_FREE_N(map->allocator, ecs_bucket_t, count, map->buckets); + map->buckets = NULL; + map->bucket_count = 0; + map->count = 0; + flecs_map_rehash(map, 2); + +#ifdef FLECS_DEBUG + ecs_os_linc(&map->change_count); +#endif +} + +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map) +{ + if (ecs_map_is_init(map)) { + return (ecs_map_iter_t){ + .map = map, + .bucket = NULL, + .entry = NULL, +#ifdef FLECS_DEBUG + .change_count = map->change_count +#endif + }; + } else { + return (ecs_map_iter_t){ 0 }; + } +} + +bool ecs_map_iter_valid( + ecs_map_iter_t *iter) +{ + const ecs_map_t *map = iter->map; + if (!map) { + return false; + } + +#ifdef FLECS_DEBUG + if (map->change_count != iter->change_count) { + return false; + } +#endif + + return true; +} + +bool ecs_map_next( + ecs_map_iter_t *iter) +{ + const ecs_map_t *map = iter->map; + if (!map) { + return false; + } + + ecs_dbg_assert(map->change_count == iter->change_count, ECS_INVALID_PARAMETER, + "map cannot be modified while it is being iterated"); + + ecs_bucket_t *end; + if (iter->bucket == (end = &map->buckets[map->bucket_count])) { + return false; + } + + ecs_bucket_entry_t *entry = NULL; + if (!iter->bucket) { + for (iter->bucket = map->buckets; + iter->bucket != end; + ++iter->bucket) + { + if (iter->bucket->first) { + entry = iter->bucket->first; + break; + } + } + if (iter->bucket == end) { + return false; + } + } else if ((entry = iter->entry) == NULL) { + do { + ++iter->bucket; + if (iter->bucket == end) { + return false; + } + } while(!iter->bucket->first); + entry = iter->bucket->first; + } + + ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); + iter->entry = entry->next; + iter->res = &entry->key; + +#ifdef FLECS_DEBUG + /* Safe, only used for detecting if an element got removed that's not the + * currently iterated element. */ + ECS_CONST_CAST(ecs_map_t*, map)->last_iterated = entry->key; +#endif + + return true; +} + +void ecs_map_copy( + ecs_map_t *dst, + const ecs_map_t *src) +{ + if (ecs_map_is_init(dst)) { + ecs_assert(ecs_map_count(dst) == 0, ECS_INVALID_PARAMETER, NULL); + ecs_map_fini(dst); + } + + if (!ecs_map_is_init(src)) { + return; + } + + ecs_map_init(dst, src->allocator); + + ecs_map_iter_t it = ecs_map_iter(src); + while (ecs_map_next(&it)) { + ecs_map_insert(dst, ecs_map_key(&it), ecs_map_value(&it)); + } +} + +/** + * @file datastructures/name_index.c + * @brief Data structure for resolving 64bit keys by string (name). + */ + + +static +uint64_t flecs_name_index_hash( + const void *ptr) +{ + const ecs_hashed_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); + return str->hash; +} + +static +int flecs_name_index_compare( + const void *ptr1, + const void *ptr2) +{ + const ecs_hashed_string_t *str1 = ptr1; + const ecs_hashed_string_t *str2 = ptr2; + ecs_size_t len1 = str1->length; + ecs_size_t len2 = str2->length; + if (len1 != len2) { + return (len1 > len2) - (len1 < len2); + } + + return ecs_os_memcmp(str1->value, str2->value, len1); +} + +void flecs_name_index_init( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator) +{ + flecs_hashmap_init_(hm, + ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t), + flecs_name_index_hash, + flecs_name_index_compare, + allocator); +} + +void flecs_name_index_init_if( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator) +{ + if (!hm->compare) { + flecs_name_index_init(hm, allocator); + } +} + +bool flecs_name_index_is_init( + const ecs_hashmap_t *hm) +{ + return hm->compare != NULL; +} + +ecs_hashmap_t* flecs_name_index_new( + ecs_allocator_t *allocator) +{ + ecs_hashmap_t *result = flecs_alloc_t(allocator, ecs_hashmap_t); + flecs_name_index_init(result, allocator); + return result; +} + +void flecs_name_index_fini( + ecs_hashmap_t *map) +{ + flecs_hashmap_fini(map); +} + +void flecs_name_index_free( + ecs_hashmap_t *map) +{ + if (map) { + ecs_allocator_t *a = map->impl.allocator; + flecs_name_index_fini(map); + flecs_free_t(a, ecs_hashmap_t, map); + } +} + +ecs_hashmap_t* flecs_name_index_copy( + ecs_hashmap_t *map) +{ + ecs_hashmap_t *result = flecs_alloc_t(map->impl.allocator, ecs_hashmap_t); + flecs_hashmap_copy(result, map); + return result; +} + +ecs_hashed_string_t flecs_get_hashed_string( + const char *name, + ecs_size_t length, + uint64_t hash) +{ + if (!length) { + length = ecs_os_strlen(name); + } else { + ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL); + } + + if (!hash) { + hash = flecs_hash(name, length); + } else { + ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); + } + + return (ecs_hashed_string_t) { + .value = ECS_CONST_CAST(char*, name), + .length = length, + .hash = hash + }; +} + +const uint64_t* flecs_name_index_find_ptr( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash) +{ + ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash); + ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); + if (!b) { + return NULL; + } + + ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); + int32_t i, count = ecs_vec_count(&b->keys); + + for (i = 0; i < count; i ++) { + ecs_hashed_string_t *key = &keys[i]; + ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); + + if (hs.length != key->length) { + continue; + } + + if (!ecs_os_strcmp(name, key->value)) { + uint64_t *e = ecs_vec_get_t(&b->values, uint64_t, i); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + return e; + } + } + + return NULL; +} + +uint64_t flecs_name_index_find( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash) +{ + const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash); + if (id) { + return id[0]; + } + return 0; +} + +void flecs_name_index_remove( + ecs_hashmap_t *map, + uint64_t e, + uint64_t hash) +{ + ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); + if (!b) { + return; + } + + uint64_t *ids = ecs_vec_first(&b->values); + int32_t i, count = ecs_vec_count(&b->values); + for (i = 0; i < count; i ++) { + if (ids[i] == e) { + flecs_hm_bucket_remove(map, b, hash, i); + break; + } + } +} + +void flecs_name_index_update_name( + ecs_hashmap_t *map, + uint64_t e, + uint64_t hash, + const char *name) +{ + ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); + if (!b) { + return; + } + + uint64_t *ids = ecs_vec_first(&b->values); + int32_t i, count = ecs_vec_count(&b->values); + for (i = 0; i < count; i ++) { + if (ids[i] == e) { + ecs_hashed_string_t *key = ecs_vec_get_t( + &b->keys, ecs_hashed_string_t, i); + key->value = ECS_CONST_CAST(char*, name); + ecs_assert(ecs_os_strlen(name) == key->length, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_hash(name, key->length) == key->hash, + ECS_INTERNAL_ERROR, NULL); + return; + } + } + + /* Record must already have been in the index */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); +} + +void flecs_name_index_ensure( + ecs_hashmap_t *map, + uint64_t id, + const char *name, + ecs_size_t length, + uint64_t hash) +{ + ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); + + uint64_t existing = flecs_name_index_find( + map, name, key.length, key.hash); + if (existing) { + if (existing != id) { + ecs_abort(ECS_ALREADY_DEFINED, + "conflicting entity registered with name '%s' " + "(existing = %u, new = %u)", + name, (uint32_t)existing, (uint32_t)id); + } + } + + flecs_hashmap_result_t hmr = flecs_hashmap_ensure( + map, &key, uint64_t); + *((uint64_t*)hmr.value) = id; +error: + return; +} + +/** + * @file datastructures/sparse.c + * @brief Sparse set data structure. + */ + + +/* Utility to get a pointer to the payload */ +#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) + +static +ecs_sparse_page_t* flecs_sparse_page_new( + ecs_sparse_t *sparse, + int32_t page_index) +{ + ecs_allocator_t *a = sparse->allocator; + ecs_block_allocator_t *ca = sparse->page_allocator; + int32_t count = ecs_vec_count(&sparse->pages); + ecs_sparse_page_t *pages; + + if (count <= page_index) { + ecs_vec_set_count_t(a, &sparse->pages, ecs_sparse_page_t, page_index + 1); + pages = ecs_vec_first_t(&sparse->pages, ecs_sparse_page_t); + ecs_os_memset_n(&pages[count], 0, ecs_sparse_page_t, (1 + page_index - count)); + } else { + pages = ecs_vec_first_t(&sparse->pages, ecs_sparse_page_t); + } + + ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_sparse_page_t *result = &pages[page_index]; + ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); + + /* Initialize sparse array with zero's, as zero is used to indicate that the + * sparse element has not been paired with a dense element. Use zero + * as this means we can take advantage of calloc having a possibly better + * performance than malloc + memset. */ + result->sparse = ca ? flecs_bcalloc(ca) + : ecs_os_calloc_n(int32_t, FLECS_SPARSE_PAGE_SIZE); + + /* Initialize the data array with zero's to guarantee that data is + * always initialized. When an entry is removed, data is reset back to + * zero. Initialize now, as this can take advantage of calloc. */ + if (sparse->size) { + result->data = a ? flecs_calloc(a, sparse->size * FLECS_SPARSE_PAGE_SIZE) + : ecs_os_calloc(sparse->size * FLECS_SPARSE_PAGE_SIZE); + ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + result->data = NULL; + } + + ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + + return result; +} + +static +void flecs_sparse_page_free( + ecs_sparse_t *sparse, + ecs_sparse_page_t *page) +{ + ecs_allocator_t *a = sparse->allocator; + ecs_block_allocator_t *ca = sparse->page_allocator; + + if (ca) { + flecs_bfree(ca, page->sparse); + } else { + ecs_os_free(page->sparse); + } + if (a) { + flecs_free(a, sparse->size * FLECS_SPARSE_PAGE_SIZE, page->data); + } else { + ecs_os_free(page->data); + } + + page->sparse = NULL; + page->data = NULL; +} + +static +ecs_sparse_page_t* flecs_sparse_get_page( + const ecs_sparse_t *sparse, + int32_t page_index) +{ + ecs_assert(page_index >= 0, ECS_INVALID_PARAMETER, NULL); + if (page_index >= ecs_vec_count(&sparse->pages)) { + return NULL; + } + return ecs_vec_get_t(&sparse->pages, ecs_sparse_page_t, page_index); +} + +static +ecs_sparse_page_t* flecs_sparse_get_or_create_page( + ecs_sparse_t *sparse, + int32_t page_index) +{ + ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, page_index); + if (page && page->sparse) { + ecs_assert(!sparse->size || page->data != NULL, + ECS_INTERNAL_ERROR, NULL); + return page; + } + + return flecs_sparse_page_new(sparse, page_index); +} + +static +void flecs_sparse_grow_dense( + ecs_sparse_t *sparse) +{ + ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); +} + +static +void flecs_sparse_assign_index( + ecs_sparse_page_t * page, + uint64_t * dense_array, + uint64_t id, + int32_t dense) +{ + /* Initialize sparse-dense pair. This assigns the dense index to the sparse + * array, and the sparse index to the dense array .*/ + page->sparse[FLECS_SPARSE_OFFSET(id)] = dense; + dense_array[dense] = id; +} + +static +uint64_t flecs_sparse_inc_id( + ecs_sparse_t *sparse) +{ + /* Generate a new id. The last issued id could be stored in an external + * variable, such as is the case with the last issued entity id, which is + * stored on the world. */ + return ++ sparse->max_id; +} + +static +uint64_t flecs_sparse_get_id( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + return sparse->max_id; +} + +static +void flecs_sparse_set_id( + ecs_sparse_t *sparse, + uint64_t value) +{ + /* Sometimes the max id needs to be assigned directly, which typically + * happens when the API calls get_or_create for an id that hasn't been + * issued before. */ + sparse->max_id = value; +} + +/* Pair dense id with new sparse id */ +static +uint64_t flecs_sparse_create_id( + ecs_sparse_t *sparse, + int32_t dense) +{ + uint64_t id = flecs_sparse_inc_id(sparse); + flecs_sparse_grow_dense(sparse); + + ecs_sparse_page_t *page = flecs_sparse_get_or_create_page( + sparse, FLECS_SPARSE_PAGE(id)); + ecs_assert(page->sparse[FLECS_SPARSE_OFFSET(id)] == 0, + ECS_INTERNAL_ERROR, NULL); + + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + flecs_sparse_assign_index(page, dense_array, id, dense); + + return id; +} + +/* Create new id */ +static +uint64_t flecs_sparse_new_index( + ecs_sparse_t *sparse) +{ + int32_t dense_count = ecs_vec_count(&sparse->dense); + int32_t count = sparse->count ++; + + ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); + if (count < dense_count) { + /* If there are unused elements in the dense array, return first */ + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + return dense_array[count]; + } else { + return flecs_sparse_create_id(sparse, count); + } +} + +/* Get value from sparse set when it is guaranteed that the value exists. This + * function is used when values are obtained using a dense index */ +static +void* flecs_sparse_get_sparse( + const ecs_sparse_t *sparse, + int32_t dense, + uint64_t id) +{ + uint64_t index = (uint32_t)id; + ecs_sparse_page_t *page = flecs_sparse_get_page( + sparse, FLECS_SPARSE_PAGE(index)); + if (!page || !page->sparse) { + return NULL; + } + + int32_t offset = FLECS_SPARSE_OFFSET(index); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + (void)dense; + + return DATA(page->data, sparse->size, offset); +} + +/* Swap dense elements. A swap occurs when an element is removed, or when a + * removed element is recycled. */ +static +void flecs_sparse_swap_dense( + ecs_sparse_t * sparse, + ecs_sparse_page_t * page_a, + int32_t a, + int32_t b) +{ + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + uint64_t id_a = dense_array[a]; + uint64_t id_b = dense_array[b]; + + ecs_sparse_page_t *page_b = flecs_sparse_get_or_create_page( + sparse, FLECS_SPARSE_PAGE(id_b)); + flecs_sparse_assign_index(page_a, dense_array, id_a, b); + flecs_sparse_assign_index(page_b, dense_array, id_b, a); +} + +void flecs_sparse_init( + ecs_sparse_t *result, + struct ecs_allocator_t *allocator, + ecs_block_allocator_t *page_allocator, + ecs_size_t size) +{ + ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + result->size = size; + result->max_id = UINT64_MAX; + result->allocator = allocator; + result->page_allocator = page_allocator; + + ecs_vec_init_t(allocator, &result->pages, ecs_sparse_page_t, 0); + ecs_vec_init_t(allocator, &result->dense, uint64_t, 1); + result->dense.count = 1; + + /* Consume first value in dense array as 0 is used in the sparse array to + * indicate that a sparse element hasn't been paired yet. */ + ecs_vec_first_t(&result->dense, uint64_t)[0] = 0; + + result->count = 1; +} + +void flecs_sparse_clear( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t i, count = ecs_vec_count(&sparse->pages); + ecs_sparse_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_sparse_page_t); + for (i = 0; i < count; i ++) { + int32_t *indices = pages[i].sparse; + if (indices) { + ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_PAGE_SIZE); + } + } + + ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); + + sparse->count = 1; + sparse->max_id = 0; +} + +void flecs_sparse_fini( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = ecs_vec_count(&sparse->pages); + ecs_sparse_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_sparse_page_t); + for (i = 0; i < count; i ++) { + flecs_sparse_page_free(sparse, &pages[i]); + } + + ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_sparse_page_t); + ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); +} + +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_sparse_new_index(sparse); +} + +void* flecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t size) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + uint64_t id = flecs_sparse_new_index(sparse); + ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(id)); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(page->data != NULL, ECS_INTERNAL_ERROR, NULL); + return DATA(page->data, size, FLECS_SPARSE_OFFSET(id)); +} + +uint64_t flecs_sparse_last_id( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + return dense_array[sparse->count - 1]; +} + +void* flecs_sparse_insert( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t id) +{ + bool is_new = true; + void *result = flecs_sparse_ensure(sparse, size, id, &is_new); + if (!is_new) { + result = NULL; + } + return result; +} + +void* flecs_sparse_ensure( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t id, + bool *is_new) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + /* Make sure is_new is initialized to true */ + ecs_assert(!is_new || *is_new, ECS_INVALID_PARAMETER, NULL); + (void)size; + + uint64_t index = (uint32_t)id; + ecs_sparse_page_t *page = flecs_sparse_get_or_create_page( + sparse, FLECS_SPARSE_PAGE(index)); + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; + + if (dense) { + /* Check if element is alive. If element is not alive, update indices so + * that the first unused dense element points to the sparse element. */ + int32_t count = sparse->count; + if (dense >= count) { + /* If dense is not alive, swap it with the first unused element. */ + flecs_sparse_swap_dense(sparse, page, dense, count); + dense = count; + + /* First unused element is now last used element */ + sparse->count ++; + + /* Set dense element to new generation */ + ecs_vec_first_t(&sparse->dense, uint64_t)[dense] = id; + } else { + if (is_new) *is_new = false; + } + } else { + /* Element is not paired yet. Must add a new element to dense array */ + flecs_sparse_grow_dense(sparse); + + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + int32_t dense_count = ecs_vec_count(&sparse->dense) - 1; + int32_t count = sparse->count ++; + + /* If index is larger than max id, update max id */ + if (index >= flecs_sparse_get_id(sparse)) { + flecs_sparse_set_id(sparse, index); + } + + if (count < dense_count) { + /* If there are unused elements in the list, move the first unused + * element to the end of the list */ + uint64_t unused = dense_array[count]; + ecs_sparse_page_t *unused_page = flecs_sparse_get_or_create_page( + sparse, FLECS_SPARSE_PAGE(unused)); + flecs_sparse_assign_index( + unused_page, dense_array, unused, dense_count); + } + + flecs_sparse_assign_index(page, dense_array, id, count); + } + + return DATA(page->data, sparse->size, offset); +} + +void* flecs_sparse_ensure_fast( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t id) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + (void)size; + + uint32_t index = (uint32_t)id; + ecs_sparse_page_t *page = flecs_sparse_get_or_create_page( + sparse, FLECS_SPARSE_PAGE(index)); + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; + int32_t count = sparse->count; + + if (!dense) { + /* Element is not paired yet. Must add a new element to dense array */ + sparse->count = count + 1; + if (count == ecs_vec_count(&sparse->dense)) { + flecs_sparse_grow_dense(sparse); + } + + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + flecs_sparse_assign_index(page, dense_array, index, count); + } + + return DATA(page->data, sparse->size, offset); +} + +bool flecs_sparse_remove( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t id) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + + ecs_sparse_page_t *page = flecs_sparse_get_page( + sparse, FLECS_SPARSE_PAGE(id)); + if (!page || !page->sparse) { + return false; + } + + uint64_t index = (uint32_t)id; + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; + + if (dense) { + int32_t count = sparse->count; + if (dense == (count - 1)) { + /* If dense is the last used element, simply decrease count */ + sparse->count --; + } else if (dense < count) { + /* If element is alive, move it to unused elements */ + flecs_sparse_swap_dense(sparse, page, dense, count - 1); + sparse->count --; + } + + /* Reset memory to zero on remove */ + if (sparse->size) { + void *ptr = DATA(page->data, sparse->size, offset); + ecs_os_memset(ptr, 0, size); + } + + /* Reset memory to zero on remove */ + return true; + } else { + /* Element is not paired and thus not alive, nothing to be done */ + return false; + } +} + +static +uint64_t flecs_sparse_inc_gen( + uint64_t index) +{ + /* When an index is deleted, its generation is increased so that we can do + * liveliness checking while recycling ids */ + return ECS_GENERATION_INC(index); +} + +bool flecs_sparse_remove_w_gen( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t id) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + + ecs_sparse_page_t *page = flecs_sparse_get_page( + sparse, FLECS_SPARSE_PAGE(id)); + if (!page || !page->sparse) { + return false; + } + + uint64_t index = (uint32_t)id; + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; + + if (dense) { + /* Increase generation */ + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + ecs_assert(dense_array[dense] == id, ECS_INVALID_PARAMETER, NULL); + dense_array[dense] = flecs_sparse_inc_gen(id); + + int32_t count = sparse->count; + if (dense == (count - 1)) { + /* If dense is the last used element, simply decrease count */ + sparse->count --; + } else if (dense < count) { + /* If element is alive, move it to unused elements */ + flecs_sparse_swap_dense(sparse, page, dense, count - 1); + sparse->count --; + } else { + return false; + } + + /* Reset memory to zero on remove */ + if (sparse->size) { + void *ptr = DATA(page->data, sparse->size, offset); + ecs_os_memset(ptr, 0, size); + } + + return true; + } else { + /* Element is not paired and thus not alive, nothing to be done */ + return false; + } +} + +void* flecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t size, + int32_t dense_index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); + (void)size; + + dense_index ++; + + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + return flecs_sparse_get_sparse( + sparse, dense_index, dense_array[dense_index]); +} + +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t id) +{ + ecs_sparse_page_t *page = flecs_sparse_get_page( + sparse, FLECS_SPARSE_PAGE(id)); + if (!page || !page->sparse) { + return false; + } + + int32_t offset = FLECS_SPARSE_OFFSET(id); + int32_t dense = page->sparse[offset]; + if (!dense || (dense >= sparse->count)) { + return false; + } + + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return true; +} + +void* flecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t id) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + + uint64_t index = (uint32_t)id; + ecs_sparse_page_t *page = flecs_sparse_get_page( + sparse, FLECS_SPARSE_PAGE(index)); + if (!page || !page->sparse) { + return NULL; + } + + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; + bool in_use = dense && (dense < sparse->count); + if (!in_use) { + return NULL; + } + + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(page->data, sparse->size, offset); +} + +bool flecs_sparse_has( + const ecs_sparse_t *sparse, + uint64_t id) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + + uint64_t index = (uint32_t)id; + ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, + FLECS_SPARSE_PAGE(index)); + if (!page || !page->sparse) { + return false; + } + + int32_t offset = FLECS_SPARSE_OFFSET(id); + int32_t dense = page->sparse[offset]; + return dense && (dense < sparse->count); +} + +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse) +{ + if (!sparse || !sparse->count) { + return 0; + } + + return sparse->count - 1; +} + +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + if (sparse->dense.array) { + return &(ecs_vec_first_t(&sparse->dense, uint64_t)[1]); + } else { + return NULL; + } +} + +void flecs_sparse_shrink( + ecs_sparse_t *sparse) +{ + int32_t i, e, max_page_index = -1, count = ecs_vec_count(&sparse->pages); + ecs_sparse_page_t *pages = ecs_vec_first_t(&sparse->pages, + ecs_sparse_page_t); + for (i = 0; i < count; i ++) { + ecs_sparse_page_t *page = &pages[i]; + if (!page->sparse) { + ecs_assert(page->data == NULL, ECS_INTERNAL_ERROR, NULL); + continue; + } + + bool has_alive = false; + for (e = 0; e < FLECS_SPARSE_PAGE_SIZE; e ++) { + uint64_t id = + flecs_ito(uint64_t, (i * FLECS_SPARSE_PAGE_SIZE) + e); + + if (flecs_sparse_is_alive(sparse, id)) { + has_alive = true; + break; + } + } + + if (!has_alive) { + flecs_sparse_page_free(sparse, page); + } else { + max_page_index = i; + } + } + + ecs_vec_set_count_t( + sparse->allocator, &sparse->pages, ecs_sparse_page_t, + max_page_index + 1); + ecs_vec_reclaim_t(sparse->allocator, &sparse->pages, ecs_sparse_page_t); + + ecs_vec_set_count_t( + sparse->allocator, &sparse->dense, uint64_t, sparse->count); + ecs_vec_reclaim_t(sparse->allocator, &sparse->dense, uint64_t); +} + +void ecs_sparse_init( + ecs_sparse_t *sparse, + ecs_size_t elem_size) +{ + flecs_sparse_init(sparse, NULL, NULL, elem_size); +} + +void* ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size) +{ + return flecs_sparse_add(sparse, elem_size); +} + +uint64_t ecs_sparse_last_id( + const ecs_sparse_t *sparse) +{ + return flecs_sparse_last_id(sparse); +} + +int32_t ecs_sparse_count( + const ecs_sparse_t *sparse) +{ + return flecs_sparse_count(sparse); +} + +void* ecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index) +{ + return flecs_sparse_get_dense(sparse, elem_size, index); +} + +void* ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id) +{ + return flecs_sparse_get(sparse, elem_size, id); +} + +/** + * @file datastructures/stack_allocator.c + * @brief Stack allocator. + * + * The stack allocator enables pushing and popping values to a stack, and has + * a lower overhead when compared to block allocators. A stack allocator is a + * good fit for small temporary allocations. + * + * The stack allocator allocates memory in pages. If the requested size of an + * allocation exceeds the page size, a regular allocator is used instead. + */ + + +int64_t ecs_stack_allocator_alloc_count = 0; +int64_t ecs_stack_allocator_free_count = 0; + +static +ecs_stack_page_t* flecs_stack_page_new(uint32_t page_id) { + ecs_stack_page_t *result = ecs_os_malloc( + FLECS_STACK_PAGE_OFFSET + FLECS_STACK_PAGE_SIZE); + result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); + result->next = NULL; + result->id = page_id + 1; + result->sp = 0; + ecs_os_linc(&ecs_stack_allocator_alloc_count); + return result; +} + +void* flecs_stack_alloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align) +{ + ecs_assert(size > 0, ECS_INTERNAL_ERROR, NULL); + void *result = NULL; + + if (size > FLECS_STACK_PAGE_SIZE) { + result = ecs_os_malloc(size); /* Too large for page */ + goto done; + } + + ecs_stack_page_t *page = stack->tail_page; + if (!page) { + page = stack->first = flecs_stack_page_new(0); + stack->tail_page = page; + } + + ecs_assert(page->data != NULL, ECS_INTERNAL_ERROR, NULL); + + int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); + int16_t next_sp = flecs_ito(int16_t, sp + size); + + if (next_sp > FLECS_STACK_PAGE_SIZE) { + if (page->next) { + page = page->next; + } else { + page = page->next = flecs_stack_page_new(page->id); + } + sp = 0; + next_sp = flecs_ito(int16_t, size); + stack->tail_page = page; + } + + page->sp = next_sp; + result = ECS_OFFSET(page->data, sp); + +done: + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); +#ifdef FLECS_SANITIZE + ecs_os_memset(result, 0xAA, size); +#endif + return result; +} + +void* flecs_stack_calloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align) +{ + void *ptr = flecs_stack_alloc(stack, size, align); + ecs_os_memset(ptr, 0, size); + return ptr; +} + +void flecs_stack_free( + void *ptr, + ecs_size_t size) +{ + if (size > FLECS_STACK_PAGE_SIZE) { + ecs_os_free(ptr); + } +} + +ecs_stack_cursor_t* flecs_stack_get_cursor( + ecs_stack_t *stack) +{ + ecs_assert(stack != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_stack_page_t *page = stack->tail_page; + if (!page) { + page = stack->first = flecs_stack_page_new(0); + stack->tail_page = page; + } + + int16_t sp = stack->tail_page->sp; + ecs_stack_cursor_t *result = flecs_stack_alloc_t(stack, ecs_stack_cursor_t); + result->page = page; + result->sp = sp; + result->is_free = false; + +#ifdef FLECS_DEBUG + ++ stack->cursor_count; + result->owner = stack; +#endif + + result->prev = stack->tail_cursor; + stack->tail_cursor = result; + return result; +} + +#define FLECS_STACK_LEAK_MSG \ + "a stack allocator leak is most likely due to an unterminated " \ + "iteration: call ecs_iter_fini to fix" + +void flecs_stack_restore_cursor( + ecs_stack_t *stack, + ecs_stack_cursor_t *cursor) +{ + if (!cursor) { + return; + } + + ecs_dbg_assert(stack == cursor->owner, ECS_INVALID_OPERATION, + "attempting to restore a cursor for the wrong stack"); + ecs_dbg_assert(stack->cursor_count > 0, ECS_DOUBLE_FREE, + "double free detected in stack allocator"); + ecs_assert(cursor->is_free == false, ECS_DOUBLE_FREE, + "double free detected in stack allocator"); + + cursor->is_free = true; + +#ifdef FLECS_DEBUG + -- stack->cursor_count; +#endif + + /* If cursor is not the last on the stack no memory should be freed */ + if (cursor != stack->tail_cursor) { + return; + } + + /* Iterate freed cursors to know how much memory we can free */ + do { + ecs_stack_cursor_t* prev = cursor->prev; + if (!prev || !prev->is_free) { + break; /* Found active cursor, free up until this point */ + } + cursor = prev; + } while (cursor); + + stack->tail_cursor = cursor->prev; + stack->tail_page = cursor->page; + stack->tail_page->sp = cursor->sp; + + /* If the cursor count is zero, stack should be empty + * if the cursor count is non-zero, stack should not be empty */ + ecs_dbg_assert((stack->cursor_count == 0) == + (stack->tail_page == stack->first && stack->tail_page->sp == 0), + ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); +} + +void flecs_stack_reset( + ecs_stack_t *stack) +{ + ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + stack->tail_page = stack->first; + if (stack->first) { + stack->first->sp = 0; + } + stack->tail_cursor = NULL; +} + +void flecs_stack_init( + ecs_stack_t *stack) +{ + ecs_os_zeromem(stack); + stack->first = NULL; + stack->tail_page = NULL; +} + +void flecs_stack_fini( + ecs_stack_t *stack) +{ + ecs_stack_page_t *next, *cur = stack->first; + ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + ecs_assert(stack->tail_page == stack->first, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + ecs_assert(!stack->tail_page || stack->tail_page->sp == 0, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + + if (cur) { + do { + next = cur->next; + ecs_os_linc(&ecs_stack_allocator_free_count); + ecs_os_free(cur); + } while ((cur = next)); + } +} + +/** + * @file datastructures/strbuf.c + * @brief Utility for constructing strings. + * + * A buffer builds up a list of elements which individually can be up to N bytes + * large. While appending, data is added to these elements. More elements are + * added on the fly when needed. When an application calls ecs_strbuf_get, all + * elements are combined in one string and the element administration is freed. + * + * This approach prevents reallocs of large blocks of memory, and therefore + * copying large blocks of memory when appending to a large buffer. A buffer + * preallocates some memory for the element overhead so that for small strings + * there is hardly any overhead, while for large strings the overhead is offset + * by the reduced time spent on copying memory. + * + * The functionality provided by strbuf is similar to std::stringstream. + */ + + +#include // isnan, isinf + +/** + * stm32tpl -- STM32 C++ Template Peripheral Library + * Visit https://github.com/antongus/stm32tpl for new versions + * + * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA + */ + +#define MAX_PRECISION (10) +#define EXP_THRESHOLD (3) +#define INT64_MAX_F ((double)INT64_MAX) + +static const double rounders[MAX_PRECISION + 1] = +{ + 0.5, // 0 + 0.05, // 1 + 0.005, // 2 + 0.0005, // 3 + 0.00005, // 4 + 0.000005, // 5 + 0.0000005, // 6 + 0.00000005, // 7 + 0.000000005, // 8 + 0.0000000005, // 9 + 0.00000000005 // 10 +}; + +static +char* flecs_strbuf_itoa( + char *buf, + int64_t v) +{ + char *ptr = buf; + char * p1; + char c; + + if (!v) { + *ptr++ = '0'; + } else { + if (v < 0) { + ptr[0] = '-'; + ptr ++; + v *= -1; + } + + char *p = ptr; + while (v) { + int64_t vdiv = v / 10; + int64_t vmod = v - (vdiv * 10); + p[0] = (char)('0' + vmod); + p ++; + v = vdiv; + } + + p1 = p; + + while (p > ptr) { + c = *--p; + *p = *ptr; + *ptr++ = c; + } + ptr = p1; + } + return ptr; +} + +static +void flecs_strbuf_ftoa( + ecs_strbuf_t *out, + double f, + int precision, + char nan_delim) +{ + char buf[64]; + char * ptr = buf; + char c; + int64_t intPart; + int64_t exp = 0; + + if (ecs_os_isnan(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendlit(out, "NaN"); + ecs_strbuf_appendch(out, nan_delim); + return; + } else { + ecs_strbuf_appendlit(out, "NaN"); + return; + } + } + if (ecs_os_isinf(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendlit(out, "Inf"); + ecs_strbuf_appendch(out, nan_delim); + return; + } else { + ecs_strbuf_appendlit(out, "Inf"); + return; + } + } + + if (precision > MAX_PRECISION) { + precision = MAX_PRECISION; + } + + if (f < 0) { + f = -f; + *ptr++ = '-'; + } + + if (precision < 0) { + if (f < 1.0) precision = 6; + else if (f < 10.0) precision = 5; + else if (f < 100.0) precision = 4; + else if (f < 1000.0) precision = 3; + else if (f < 10000.0) precision = 2; + else if (f < 100000.0) precision = 1; + else precision = 0; + } + + if (precision) { + f += rounders[precision]; + } + + /* Make sure that number can be represented as 64bit int, increase exp */ + while (f > INT64_MAX_F) { + f /= 1000 * 1000 * 1000; + exp += 9; + } + + intPart = (int64_t)f; + f -= (double)intPart; + + ptr = flecs_strbuf_itoa(ptr, intPart); + + if (precision) { + *ptr++ = '.'; + while (precision--) { + f *= 10.0; + c = (char)f; + *ptr++ = (char)('0' + c); + f -= c; + } + } + *ptr = 0; + + /* Remove trailing 0s */ + while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { + ptr[-1] = '\0'; + ptr --; + } + if (ptr != buf && ptr[-1] == '.') { + ptr[-1] = '\0'; + ptr --; + } + + /* If 0s before . exceed threshold, convert to exponent to save space + * without losing precision. */ + char *cur = ptr; + while ((&cur[-1] != buf) && (cur[-1] == '0')) { + cur --; + } + + if (exp || ((ptr - cur) > EXP_THRESHOLD)) { + cur[0] = '\0'; + exp += (ptr - cur); + ptr = cur; + } + + if (exp) { + char *p1 = &buf[1]; + if (nan_delim) { + ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); + buf[0] = nan_delim; + p1 ++; + } + + /* Make sure that exp starts after first character */ + c = p1[0]; + + if (c) { + p1[0] = '.'; + do { + char t = (++p1)[0]; + if (t == '.') { + exp ++; + p1 --; + break; + } + p1[0] = c; + c = t; + exp ++; + } while (c); + ptr = p1 + 1; + } else { + ptr = p1; + } + + ptr[0] = 'e'; + ptr = flecs_strbuf_itoa(ptr + 1, exp); + + if (nan_delim) { + ptr[0] = nan_delim; + ptr ++; + } + + ptr[0] = '\0'; + } + + ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); +} + +/* Add an extra element to the buffer */ +static +void flecs_strbuf_grow( + ecs_strbuf_t *b) +{ + if (!b->content) { + b->content = b->small_string; + b->size = ECS_STRBUF_SMALL_STRING_SIZE; + } else if (b->content == b->small_string) { + b->size *= 2; + b->content = ecs_os_malloc_n(char, b->size); + ecs_os_memcpy(b->content, b->small_string, b->length); + } else { + b->size *= 2; + if (b->size < 16) b->size = 16; + b->content = ecs_os_realloc_n(b->content, char, b->size); + } +} + +static +char* flecs_strbuf_ptr( + ecs_strbuf_t *b) +{ + ecs_assert(b->content != NULL, ECS_INTERNAL_ERROR, NULL); + return &b->content[b->length]; +} + +/* Append a format string to a buffer */ +static +void flecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* str, + va_list args) +{ + va_list arg_cpy; + + if (!str) { + return; + } + + /* Compute the memory required to add the string to the buffer. If user + * provided buffer, use space left in buffer, otherwise use space left in + * current element. */ + int32_t mem_left = b->size - b->length; + int32_t mem_required; + + va_copy(arg_cpy, args); + + if (b->content) { + mem_required = ecs_os_vsnprintf( + flecs_strbuf_ptr(b), + flecs_itosize(mem_left), str, args); + } else { + mem_required = ecs_os_vsnprintf(NULL, 0, str, args); + mem_left = 0; + } + + ecs_assert(mem_required != -1, ECS_INTERNAL_ERROR, NULL); + + if ((mem_required + 1) >= mem_left) { + while ((mem_required + 1) >= mem_left) { + flecs_strbuf_grow(b); + mem_left = b->size - b->length; + } + ecs_os_vsnprintf(flecs_strbuf_ptr(b), + flecs_itosize(mem_required + 1), str, arg_cpy); + } + + b->length += mem_required; + + va_end(arg_cpy); +} + +static +void flecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str, + int n) +{ + int32_t mem_left = b->size - b->length; + while (n >= mem_left) { + flecs_strbuf_grow(b); + mem_left = b->size - b->length; + } + + ecs_os_memcpy(flecs_strbuf_ptr(b), str, n); + b->length += n; +} + +static +void flecs_strbuf_appendch( + ecs_strbuf_t *b, + char ch) +{ + if (b->size == b->length) { + flecs_strbuf_grow(b); + } + + flecs_strbuf_ptr(b)[0] = ch; + b->length ++; +} + +void ecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* fmt, + va_list args) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_vappend(b, fmt, args); +} + +void ecs_strbuf_append( + ecs_strbuf_t *b, + const char* fmt, + ...) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + + va_list args; + va_start(args, fmt); + flecs_strbuf_vappend(b, fmt, args); + va_end(args); +} + +void ecs_strbuf_appendstrn( + ecs_strbuf_t *b, + const char* str, + int32_t len) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_appendstr(b, str, len); +} + +void ecs_strbuf_appendch( + ecs_strbuf_t *b, + char ch) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_appendch(b, ch); +} + +void ecs_strbuf_appendint( + ecs_strbuf_t *b, + int64_t v) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + char numbuf[32]; + char *ptr = flecs_strbuf_itoa(numbuf, v); + ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); +} + +void ecs_strbuf_appendflt( + ecs_strbuf_t *b, + double flt, + char nan_delim) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_ftoa(b, flt, 10, nan_delim); +} + +void ecs_strbuf_appendbool( + ecs_strbuf_t *buffer, + bool v) +{ + ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL); + if (v) { + ecs_strbuf_appendlit(buffer, "true"); + } else { + ecs_strbuf_appendlit(buffer, "false"); + } +} + +void ecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); +} + +void ecs_strbuf_mergebuff( + ecs_strbuf_t *b, + ecs_strbuf_t *src) +{ + if (src->content) { + ecs_strbuf_appendstr(b, src->content); + } + ecs_strbuf_reset(src); +} + +char* ecs_strbuf_get( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + char *result = b->content; + if (!result) { + return NULL; + } + + ecs_strbuf_appendch(b, '\0'); + result = b->content; + +#ifdef FLECS_SANITIZE + ecs_assert(ecs_os_strlen(result) <= (b->length - 1), + ECS_INTERNAL_ERROR, NULL); +#endif + + if (result == b->small_string) { + result = ecs_os_memdup_n(result, char, b->length); + } + + b->length = 0; + b->content = NULL; + b->size = 0; + b->list_sp = 0; + return result; +} + +char* ecs_strbuf_get_small( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + char *result = b->content; + result[b->length] = '\0'; + b->length = 0; + b->content = NULL; + b->size = 0; + return result; +} + +void ecs_strbuf_reset( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + if (b->content && b->content != b->small_string) { + ecs_os_free(b->content); + } + *b = ECS_STRBUF_INIT; +} + +void ecs_strbuf_list_push( + ecs_strbuf_t *b, + const char *list_open, + const char *separator) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(b->list_sp >= 0, ECS_INVALID_OPERATION, + "strbuf list is corrupt"); + b->list_sp ++; + ecs_assert(b->list_sp < ECS_STRBUF_MAX_LIST_DEPTH, + ECS_INVALID_OPERATION, "max depth for strbuf list stack exceeded"); + + b->list_stack[b->list_sp].count = 0; + b->list_stack[b->list_sp].separator = separator; + + if (list_open) { + char ch = list_open[0]; + if (ch && !list_open[1]) { + ecs_strbuf_appendch(b, ch); + } else { + ecs_strbuf_appendstr(b, list_open); + } + } +} + +void ecs_strbuf_list_pop( + ecs_strbuf_t *b, + const char *list_close) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(b->list_sp > 0, ECS_INVALID_OPERATION, + "pop called more often than push for strbuf list"); + + b->list_sp --; + + if (list_close) { + char ch = list_close[0]; + if (ch && !list_close[1]) { + ecs_strbuf_appendch(b, list_close[0]); + } else { + ecs_strbuf_appendstr(b, list_close); + } + } +} + +void ecs_strbuf_list_next( + ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t list_sp = b->list_sp; + if (b->list_stack[list_sp].count != 0) { + const char *sep = b->list_stack[list_sp].separator; + if (sep && !sep[1]) { + ecs_strbuf_appendch(b, sep[0]); + } else { + ecs_strbuf_appendstr(b, sep); + } + } + b->list_stack[list_sp].count ++; +} + +void ecs_strbuf_list_appendch( + ecs_strbuf_t *b, + char ch) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_list_next(b); + flecs_strbuf_appendch(b, ch); +} + +void ecs_strbuf_list_append( + ecs_strbuf_t *b, + const char *fmt, + ...) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_strbuf_list_next(b); + + va_list args; + va_start(args, fmt); + flecs_strbuf_vappend(b, fmt, args); + va_end(args); +} + +void ecs_strbuf_list_appendstr( + ecs_strbuf_t *b, + const char *str) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_strbuf_list_next(b); + ecs_strbuf_appendstr(b, str); +} + +void ecs_strbuf_list_appendstrn( + ecs_strbuf_t *b, + const char *str, + int32_t n) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_strbuf_list_next(b); + ecs_strbuf_appendstrn(b, str, n); +} + +int32_t ecs_strbuf_written( + const ecs_strbuf_t *b) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + return b->length; +} + +/** + * @file datastructures/vec.c + * @brief Vector with allocator support. + */ + + +void ecs_vec_init( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) +{ + ecs_vec_init_w_dbg_info(allocator, v, size, elem_count, NULL); +} + +static +void* flecs_vec_alloc( + struct ecs_allocator_t *allocator, + ecs_size_t size, + int32_t elem_count, + const char *type_name) +{ + (void)type_name; + if (elem_count) { + if (allocator) { + return flecs_alloc_w_dbg_info( + allocator, size * elem_count, type_name); + } else { + return ecs_os_malloc(size * elem_count); + } + } + return NULL; +} + +static +void flecs_vec_free( + struct ecs_allocator_t *allocator, + ecs_size_t elem_size, + int32_t size, + void *ptr) +{ + if (allocator) { + flecs_free(allocator, elem_size * size, ptr); + } else { + ecs_os_free(ptr); + } +} + +void ecs_vec_init_w_dbg_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count, + const char *type_name) +{ + (void)type_name; + ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + v->array = flecs_vec_alloc(allocator, size, elem_count, type_name); + v->count = 0; + v->size = elem_count; +#ifdef FLECS_SANITIZE + v->elem_size = size; + v->type_name = type_name; +#endif +} + +void ecs_vec_init_if( + ecs_vec_t *vec, + ecs_size_t size) +{ + ecs_san_assert(!vec->elem_size || vec->elem_size == size, + ECS_INVALID_PARAMETER, NULL); + (void)vec; + (void)size; +#ifdef FLECS_SANITIZE + if (!vec->elem_size) { + ecs_assert(vec->count == 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(vec->size == 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(vec->array == NULL, ECS_INTERNAL_ERROR, NULL); + vec->elem_size = size; + } +#endif +} + +void ecs_vec_fini( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) +{ + if (v->array) { + ecs_san_assert(!size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + if (allocator) { + flecs_free(allocator, size * v->size, v->array); + } else { + ecs_os_free(v->array); + } + v->array = NULL; + v->count = 0; + v->size = 0; + } +} + +ecs_vec_t* ecs_vec_reset( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) +{ + if (!v->size) { + ecs_vec_init(allocator, v, size, 0); + } else { + ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_vec_clear(v); + } + return v; +} + +void ecs_vec_clear( + ecs_vec_t *vec) +{ + vec->count = 0; +} + +ecs_vec_t ecs_vec_copy( + ecs_allocator_t *allocator, + const ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + void *array; + if (allocator) { + array = flecs_dup(allocator, size * v->size, v->array); + } else { + array = ecs_os_memdup(v->array, size * v->size); + } + return (ecs_vec_t) { + .count = v->count, + .size = v->size, + .array = array +#ifdef FLECS_SANITIZE + , .elem_size = size +#endif + }; +} + +ecs_vec_t ecs_vec_copy_shrink( + ecs_allocator_t *allocator, + const ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + int32_t count = v->count; + void *array = NULL; + if (count) { + if (allocator) { + array = flecs_dup(allocator, size * count, v->array); + } else { + array = ecs_os_memdup(v->array, size * count); + } + } + return (ecs_vec_t) { + .count = count, + .size = count, + .array = array +#ifdef FLECS_SANITIZE + , .elem_size = size +#endif + }; +} + +void ecs_vec_reclaim( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + int32_t count = v->count; + if (count < v->size) { + if (count) { + /* Don't use realloc as it can return the same size buffer when the + * new size is smaller than the existing size, which defeats the + * purpose of reclaim. */ + if (allocator) { + void *new_array = flecs_alloc(allocator, count * size); + ecs_os_memcpy(new_array, v->array, size * count); + flecs_free(allocator, v->size * size, v->array); + v->array = new_array; + } else { + void *new_array = ecs_os_malloc(size * count); + ecs_os_memcpy(new_array, v->array, size * count); + ecs_os_free(v->array); + v->array = new_array; + } + v->size = count; + } else { + ecs_vec_fini(allocator, v, size); + } + } +} + +void ecs_vec_set_size( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + if (v->size != elem_count) { + if (elem_count < v->count) { + elem_count = v->count; + } + + elem_count = flecs_next_pow_of_2(elem_count); + if (elem_count < 2) { + elem_count = 2; + } + if (elem_count != v->size) { + if (allocator) { +#ifdef FLECS_SANITIZE + v->array = flecs_realloc_w_dbg_info( + allocator, size * elem_count, size * v->size, v->array, + v->type_name); +#else + v->array = flecs_realloc( + allocator, size * elem_count, size * v->size, v->array); +#endif + } else { + v->array = ecs_os_realloc(v->array, size * elem_count); + } + v->size = elem_count; + } + } +} + +void ecs_vec_set_min_size( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count) +{ + if (elem_count > vec->size) { + ecs_vec_set_size(allocator, vec, size, elem_count); + } +} + +void ecs_vec_set_min_size_w_type_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count, + const ecs_type_info_t *ti) +{ + if (elem_count > vec->size) { + ecs_vec_set_min_size_w_type_info(allocator, vec, size, elem_count, ti); + } +} + +void ecs_vec_set_min_count( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count) +{ + ecs_vec_set_min_size(allocator, vec, size, elem_count); + if (vec->count < elem_count) { + vec->count = elem_count; + } +} + +void ecs_vec_set_min_count_zeromem( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count) +{ + int32_t count = vec->count; + if (count < elem_count) { + ecs_vec_set_min_count(allocator, vec, size, elem_count); + ecs_os_memset(ECS_ELEM(vec->array, size, count), 0, + size * (elem_count - count)); + } +} + +void ecs_vec_set_min_count_w_type_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count, + const ecs_type_info_t *ti) +{ + int32_t count = vec->count; + if (count < elem_count) { + ecs_vec_set_count_w_type_info(allocator, vec, size, elem_count, ti); + } +} + +void ecs_vec_set_count( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) +{ + ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_san_assert(v->elem_size != 0, ECS_INVALID_PARAMETER, + "vector is not initialized"); + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + if (v->count != elem_count) { + if (v->size < elem_count) { + ecs_vec_set_size(allocator, v, size, elem_count); + } + + v->count = elem_count; + } +} + +void ecs_vec_set_count_w_type_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count, + const ecs_type_info_t *ti) +{ + ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + ecs_san_assert(v->elem_size != 0, ECS_INVALID_PARAMETER, + "vector is not initialized"); + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + if (v->count == elem_count) { + return; + } + + ecs_xtor_t ctor, dtor; + ecs_move_t move = ti->hooks.ctor_move_dtor; + if (!move) { + /* Trivial type, use regular set_count */ + ecs_vec_set_count(allocator, v, size, elem_count); + return; + } + + /* If array is large enough, we don't need to realloc. */ + if (v->size > elem_count) { + if (elem_count > v->count) { + if ((ctor = ti->hooks.ctor)) { + void *ptr = ECS_ELEM(v->array, size, v->count); + ctor(ptr, elem_count - v->count, ti); + } + } + + if (elem_count < v->count) { + if ((dtor = ti->hooks.dtor)) { + void *ptr = ECS_ELEM(v->array, size, elem_count); + dtor(ptr, v->count - elem_count, ti); + } + } + + v->count = elem_count; + return; + } + + /* Resize array. We can't use realloc because we need to call the move hook + * from the old to the new memory. */ + + /* Round up to next power of 2 so we don't allocate for each new element */ + ecs_size_t new_size = flecs_next_pow_of_2(elem_count); + + void *array = NULL; + #ifdef FLECS_SANITIZE + array = flecs_vec_alloc(allocator, size, new_size, v->type_name); + #else + array = flecs_vec_alloc(allocator, size, new_size, NULL); + #endif + + int32_t move_count = elem_count; + if (move_count > v->count) { + move_count = v->count; + } + + /* Move elements over to new array */ + move(array, v->array, move_count, ti); + + /* Destruct remaining elements in old array, if any */ + if (move_count < v->count) { + if ((dtor = ti->hooks.dtor)) { + void *ptr = ECS_ELEM(v->array, size, move_count); + dtor(ptr, v->count - move_count, ti); + } + } + + /* Construct new elements, if any */ + if (move_count < elem_count) { + if ((ctor = ti->hooks.ctor)) { + void *ptr = ECS_ELEM(array, size, move_count); + ctor(ptr, elem_count - move_count, ti); + } + } + + flecs_vec_free(allocator, size, v->size, v->array); + + v->array = array; + v->size = new_size; + v->count = elem_count; +} + +void* ecs_vec_grow( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem_count >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t count = v->count; + ecs_vec_set_count(allocator, v, size, count + elem_count); + return ECS_ELEM(v->array, size, count); +} + +void* ecs_vec_append( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + int32_t count = v->count; + if (v->size == count) { + ecs_vec_set_size(allocator, v, size, count + 1); + } + v->count = count + 1; + return ECS_ELEM(v->array, size, count); +} + +void ecs_vec_remove( + ecs_vec_t *v, + ecs_size_t size, + int32_t index) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); + if (index == --v->count) { + return; + } + + ecs_os_memcpy( + ECS_ELEM(v->array, size, index), + ECS_ELEM(v->array, size, v->count), + size); +} + +void ecs_vec_remove_last( + ecs_vec_t *v) +{ + v->count --; +} + +void ecs_vec_remove_ordered( + ecs_vec_t *v, + ecs_size_t size, + int32_t index) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); + + int32_t new_count = --v->count; + if (index == new_count) { + return; + } + + ecs_os_memmove( + ECS_ELEM(v->array, size, index), + ECS_ELEM(v->array, size, index + 1), + size * (new_count - index)); +} + +int32_t ecs_vec_count( + const ecs_vec_t *v) +{ + return v->count; +} + +int32_t ecs_vec_size( + const ecs_vec_t *v) +{ + return v->size; +} + +void* ecs_vec_get( + const ecs_vec_t *v, + ecs_size_t size, + int32_t index) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index >= 0, ECS_OUT_OF_RANGE, NULL); + ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); + return ECS_ELEM(v->array, size, index); +} + +void* ecs_vec_last( + const ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(!v->elem_size || size == v->elem_size, + ECS_INVALID_PARAMETER, NULL); + return ECS_ELEM(v->array, size, v->count - 1); +} + +void* ecs_vec_first( + const ecs_vec_t *v) +{ + return v->array; +} + + /** + * @file queries/api.c + * @brief User facing API for rules. + */ + + +/* Placeholder arrays for queries that only have $this variable */ +ecs_query_var_t flecs_this_array = { + .kind = EcsVarTable, + .table_id = EcsVarNone +}; +char *flecs_this_name_array = NULL; + +ecs_mixins_t ecs_query_t_mixins = { + .type_name = "ecs_query_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_query_impl_t, pub.real_world), + [EcsMixinEntity] = offsetof(ecs_query_impl_t, pub.entity), + [EcsMixinDtor] = offsetof(ecs_query_impl_t, dtor) + } +}; + +int32_t ecs_query_find_var( + const ecs_query_t *q, + const char *name) +{ + flecs_poly_assert(q, ecs_query_t); + + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_var_id_t var_id = flecs_query_find_var_id(impl, name, EcsVarEntity); + if (var_id == EcsVarNone) { + if (q->flags & EcsQueryMatchThis) { + if (!ecs_os_strcmp(name, EcsThisName)) { + var_id = 0; + } + } + if (var_id == EcsVarNone) { + return -1; + } + } + return (int32_t)var_id; +} + +const char* ecs_query_var_name( + const ecs_query_t *q, + int32_t var_id) +{ + flecs_poly_assert(q, ecs_query_t); + + if (var_id) { + ecs_assert(var_id < flecs_query_impl(q)->var_count, + ECS_INVALID_PARAMETER, NULL); + return flecs_query_impl(q)->vars[var_id].name; + } else { + return EcsThisName; + } +} + +bool ecs_query_var_is_entity( + const ecs_query_t *q, + int32_t var_id) +{ + flecs_poly_assert(q, ecs_query_t); + + return flecs_query_impl(q)->vars[var_id].kind == EcsVarEntity; +} + +static +int flecs_query_set_caching_policy( + ecs_query_impl_t *impl, + const ecs_query_desc_t *desc) +{ + ecs_query_cache_kind_t kind = desc->cache_kind; + bool require_caching = desc->group_by || desc->group_by_callback || + desc->order_by || desc->order_by_callback || + (desc->flags & EcsQueryDetectChanges); + + /* If the query has a Cascade term it'll use group_by */ + int32_t i, term_count = impl->pub.term_count; + const ecs_term_t *terms = impl->pub.terms; + for (i = 0; i < term_count; i ++) { + const ecs_term_t *term = &terms[i]; + if (term->src.id & EcsCascade) { + require_caching = true; + break; + } + } + +#ifdef FLECS_DEFAULT_TO_UNCACHED_QUERIES + if (kind == EcsQueryCacheDefault && !require_caching) { + kind = EcsQueryCacheNone; + } +#endif + + /* If caching policy is default, try to pick a policy that does the right + * thing in most cases. */ + if (kind == EcsQueryCacheDefault) { + if (desc->entity || require_caching) { + /* If the query is created with an entity handle (typically + * indicating that the query is named or belongs to a system) the + * chance is very high that the query will be reused, so enable + * caching. + * Additionally, if the query uses features that require a cache + * such as group_by/order_by, also enable caching. */ + kind = EcsQueryCacheAuto; + } else { + /* Be conservative in other scenario's, as caching adds significant + * overhead to the cost of query creation which doesn't offset the + * benefit of faster iteration if it's only used once. */ + kind = EcsQueryCacheNone; + } + } + + /* Don't cache query, even if it has cacheable terms */ + if (kind == EcsQueryCacheNone) { + impl->pub.cache_kind = EcsQueryCacheNone; + if (require_caching && !(impl->pub.flags & EcsQueryNested)) { + ecs_err("cannot create uncached query with " + "group_by/order_by/change detection"); + return -1; + } + return 0; + } + + /* Entire query must be cached */ + if (desc->cache_kind == EcsQueryCacheAll) { + if (impl->pub.flags & EcsQueryIsCacheable) { + impl->pub.cache_kind = EcsQueryCacheAll; + return 0; + } else { + ecs_err("cannot enforce QueryCacheAll, " + "query contains uncacheable terms"); + return -1; + } + } + + /* Only cache terms that are cacheable */ + if (kind == EcsQueryCacheAuto) { + if (impl->pub.flags & EcsQueryIsCacheable) { + /* If all terms of the query are cacheable, just set the policy to + * All which simplifies work for the compiler. */ + if (!(impl->pub.flags & EcsQueryCacheWithFilter)) { + impl->pub.cache_kind = EcsQueryCacheAll; + } else { + impl->pub.cache_kind = EcsQueryCacheAuto; + } + } else if (!(impl->pub.flags & EcsQueryHasCacheable)) { + /* Same for when the query has no cacheable terms */ + impl->pub.cache_kind = EcsQueryCacheNone; + } else { + /* Part of the query is cacheable. Make sure to only create a cache + * if the cacheable part of the query contains not just not/optional + * terms, as this would build a cache that contains all tables. */ + int32_t not_optional_terms = 0, cacheable_terms = 0; + if (!require_caching) { + for (i = 0; i < term_count; i ++) { + const ecs_term_t *term = &terms[i]; + if (term->flags_ & EcsTermIsCacheable) { + cacheable_terms ++; + if (term->oper == EcsNot || term->oper == EcsOptional) { + not_optional_terms ++; + } + } + } + } + + if (require_caching || cacheable_terms != not_optional_terms) { + impl->pub.cache_kind = EcsQueryCacheAuto; + } else { + impl->pub.cache_kind = EcsQueryCacheNone; + } + } + } + + return 0; +} + +static +int flecs_query_create_cache( + ecs_query_impl_t *impl, + ecs_query_desc_t *desc) +{ + ecs_query_t *q = &impl->pub; + if (flecs_query_set_caching_policy(impl, desc)) { + return -1; + } + + if (q->cache_kind != EcsQueryCacheNone) { + flecs_check_exclusive_world_access_write(impl->pub.real_world); + } else { + flecs_check_exclusive_world_access_read(impl->pub.real_world); + } + + if ((q->cache_kind != EcsQueryCacheNone) && !q->entity) { + /* Cached queries need an entity handle for observer components */ + q->entity = ecs_new(q->world); + desc->entity = q->entity; + } + + if (q->cache_kind == EcsQueryCacheAll) { + /* Create query cache for all terms */ + if (!flecs_query_cache_init(impl, desc)) { + goto error; + } + } else if (q->cache_kind == EcsQueryCacheAuto) { + /* Query is partially cached */ + ecs_query_desc_t cache_desc = *desc; + ecs_os_memset_n(&cache_desc.terms, 0, ecs_term_t, FLECS_TERM_COUNT_MAX); + cache_desc.expr = NULL; + + /* Maps field indices from cache to query */ + int8_t field_map[FLECS_TERM_COUNT_MAX]; + + int32_t i, count = q->term_count, dst_count = 0, dst_field = 0; + ecs_term_t *terms = q->terms; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->flags_ & EcsTermIsCacheable) { + cache_desc.terms[dst_count] = *term; + field_map[dst_field] = flecs_ito(int8_t, term->field_index); + dst_count ++; + if (i) { + dst_field += term->field_index != term[-1].field_index; + } else { + dst_field ++; + } + } + } + + if (dst_count) { + if (!flecs_query_cache_init(impl, &cache_desc)) { + goto error; + } + + impl->cache->field_map = flecs_alloc_n(&impl->stage->allocator, + int8_t, FLECS_TERM_COUNT_MAX); + + ecs_os_memcpy_n(impl->cache->field_map, field_map, int8_t, dst_count); + } + } else { + /* Check if query has features that are unsupported for uncached */ + ecs_assert(q->cache_kind == EcsQueryCacheNone, ECS_INTERNAL_ERROR, NULL); + + if (!(q->flags & EcsQueryNested)) { + /* If uncached query is not create to populate a cached query, it + * should not have cascade modifiers */ + int32_t i, count = q->term_count; + ecs_term_t *terms = q->terms; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->src.id & EcsCascade) { + char *query_str = ecs_query_str(q); + ecs_err( + "cascade is unsupported for uncached query\n %s", + query_str); + ecs_os_free(query_str); + goto error; + } + } + } + } + + return 0; +error: + return -1; +} + +void flecs_query_copy_arrays( + ecs_query_t *q) +{ + ecs_allocator_t *a = &flecs_query_impl(q)->stage->allocator; + q->terms = flecs_dup_n(a, ecs_term_t, q->term_count, q->terms); + q->sizes = flecs_dup_n(a, ecs_size_t, q->term_count, q->sizes); + q->ids = flecs_dup_n(a, ecs_id_t, q->term_count, q->ids); +} + +void flecs_query_free_arrays( + ecs_query_t *q) +{ + ecs_allocator_t *a = &flecs_query_impl(q)->stage->allocator; + flecs_free_n(a, ecs_term_t, q->term_count, q->terms); + flecs_free_n(a, ecs_size_t, q->term_count, q->sizes); + flecs_free_n(a, ecs_id_t, q->term_count, q->ids); +} + +static +void flecs_query_fini( + ecs_query_impl_t *impl) +{ + ecs_stage_t *stage = impl->stage; + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &stage->allocator; + + if (impl->ctx_free) { + impl->ctx_free(impl->pub.ctx); + } + + if (impl->binding_ctx_free) { + impl->binding_ctx_free(impl->pub.binding_ctx); + } + + if (impl->vars != &flecs_this_array) { + flecs_free(a, (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * + impl->var_size, impl->vars); + flecs_name_index_fini(&impl->tvar_index); + flecs_name_index_fini(&impl->evar_index); + } + + flecs_free_n(a, ecs_query_op_t, impl->op_count, impl->ops); + flecs_free_n(a, ecs_var_id_t, impl->pub.field_count, impl->src_vars); + flecs_free_n(a, int32_t, impl->pub.field_count, impl->monitor); + + ecs_query_t *q = &impl->pub; + if (q->flags & EcsQueryValid) { + int i, count = q->term_count; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &q->terms[i]; + + if (!(ecs_world_get_flags(q->world) & EcsWorldQuit)) { + if (!ecs_term_match_0(term)) { + flecs_component_unlock(q->real_world, term->id); + } + } + } + } + + if (impl->tokens) { + flecs_free(&impl->stage->allocator, impl->tokens_len, impl->tokens); + } + + if (impl->cache) { + flecs_free_n(a, int8_t, FLECS_TERM_COUNT_MAX, impl->cache->field_map); + flecs_query_cache_fini(impl); + } + + flecs_query_free_arrays(q); + + flecs_poly_fini(impl, ecs_query_t); + flecs_bfree(&stage->allocators.query_impl, impl); +} + +static +void flecs_query_poly_fini(void *ptr) { + flecs_query_fini(ptr); +} + +static +void flecs_query_add_self_ref( + ecs_query_t *q) +{ + if (q->entity) { + int32_t t, term_count = q->term_count; + ecs_term_t *terms = q->terms; + + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (ECS_TERM_REF_ID(&term->src) == q->entity) { + ecs_add_id(q->world, q->entity, term->id); + } + } + } +} + +void ecs_query_fini( + ecs_query_t *q) +{ + flecs_poly_assert(q, ecs_query_t); + + if (q->entity) { + /* If query is associated with entity, use poly dtor path */ + ecs_delete(q->world, q->entity); + } else { + flecs_query_fini(flecs_query_impl(q)); + } +} + +ecs_query_t* ecs_query_init( + ecs_world_t *world, + const ecs_query_desc_t *const_desc) +{ + ecs_os_perf_trace_push("flecs.query_init"); + + ecs_world_t *world_arg = world; + ecs_stage_t *stage = flecs_stage_from_world(&world); + + ecs_query_impl_t *result = flecs_bcalloc(&stage->allocators.query_impl); + flecs_poly_init(result, ecs_query_t); + + ecs_query_desc_t desc = *const_desc; + ecs_entity_t entity = const_desc->entity; + + if (entity) { + flecs_check_exclusive_world_access_write(world); + + /* Remove existing query if entity has one */ + bool deferred = false; + if (ecs_is_deferred(world)) { + deferred = true; + /* Ensures that remove operation doesn't get applied after bind */ + ecs_defer_suspend(world); + } + ecs_remove_pair(world, entity, ecs_id(EcsPoly), EcsQuery); + if (deferred) { + ecs_defer_resume(world); + } + } + + /* Initialize the query */ + result->pub.entity = entity; + result->pub.real_world = world; + result->pub.world = world_arg; + result->stage = stage; + + ecs_assert(flecs_poly_is(result->pub.real_world, ecs_world_t), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_poly_is(result->stage, ecs_stage_t), + ECS_INTERNAL_ERROR, NULL); + + /* Validate input, translate input to canonical query representation */ + if (flecs_query_finalize_query(world, &result->pub, &desc)) { + goto error; + } + + /* If query terms have itself as source, add term ids to self. This makes it + * easy to attach components to queries, which is one of the ways + * applications can attach data to systems. */ + flecs_query_add_self_ref(&result->pub); + + /* Initialize static context */ + result->pub.ctx = const_desc->ctx; + result->pub.binding_ctx = const_desc->binding_ctx; + result->ctx_free = const_desc->ctx_free; + result->binding_ctx_free = const_desc->binding_ctx_free; + result->dtor = flecs_query_poly_fini; + result->cache = NULL; + + /* Initialize query cache if necessary */ + if (flecs_query_create_cache(result, &desc)) { + goto error; + } + + if (flecs_query_compile(world, stage, result)) { + goto error; + } + + /* Entity could've been set by finalize query if query is cached */ + entity = result->pub.entity; + if (entity) { + EcsPoly *poly = flecs_poly_bind(world, entity, ecs_query_t); + poly->poly = result; + flecs_poly_modified(world, entity, ecs_query_t); + } + + ecs_os_perf_trace_pop("flecs.query_init"); + + return &result->pub; +error: + result->pub.entity = 0; + ecs_query_fini(&result->pub); + + ecs_os_perf_trace_pop("flecs.query_init"); + + return NULL; +} + +bool ecs_query_has( + const ecs_query_t *q, + ecs_entity_t entity, + ecs_iter_t *it) +{ + flecs_poly_assert(q, ecs_query_t); + ecs_check(q->flags & EcsQueryMatchThis, ECS_INVALID_PARAMETER, NULL); + + *it = ecs_query_iter(q->world, q); + ecs_iter_set_var(it, 0, entity); + return ecs_query_next(it); +error: + return false; +} + +bool ecs_query_has_table( + const ecs_query_t *q, + ecs_table_t *table, + ecs_iter_t *it) +{ + flecs_poly_assert(q, ecs_query_t); + ecs_check(q->flags & EcsQueryMatchThis, ECS_INVALID_PARAMETER, NULL); + + if (!flecs_table_bloom_filter_test(table, q->bloom_filter)) { + /* Safe, only used for statistics */ + ECS_CONST_CAST(ecs_query_t*, q)->eval_count ++; + return false; + } + + *it = ecs_query_iter(q->world, q); + ecs_iter_set_var_as_table(it, 0, table); + return ecs_query_next(it); +error: + return false; +} + +bool ecs_query_has_range( + const ecs_query_t *q, + ecs_table_range_t *range, + ecs_iter_t *it) +{ + flecs_poly_assert(q, ecs_query_t); + + ecs_table_t *table = range->table; + + if (q->flags & EcsQueryMatchThis) { + if (table) { + if ((range->offset + range->count) > ecs_table_count(table)) { + return false; + } + } + } + + if (!flecs_table_bloom_filter_test(table, q->bloom_filter)) { + /* Safe, only used for statistics */ + ECS_CONST_CAST(ecs_query_t*, q)->eval_count ++; + return false; + } + + *it = ecs_query_iter(q->world, q); + + if (q->flags & EcsQueryMatchThis) { + ecs_iter_set_var_as_range(it, 0, range); + } + + return ecs_query_next(it); +} + +ecs_query_count_t ecs_query_count( + const ecs_query_t *q) +{ + flecs_poly_assert(q, ecs_query_t); + ecs_query_count_t result = {0}; + + ecs_iter_t it = flecs_query_iter(q->world, q); + it.flags |= EcsIterNoData; + + while (ecs_query_next(&it)) { + result.results ++; + result.entities += it.count; + ecs_iter_skip(&it); + } + + if ((q->flags & EcsQueryMatchOnlySelf) && + !(q->flags & EcsQueryMatchWildcards)) + { + result.tables = result.results; + } else if (q->flags & EcsQueryIsCacheable) { + ecs_query_impl_t *impl = flecs_query_impl(q); + if (impl->cache) { + result.tables = ecs_map_count(&impl->cache->tables); + } + } + + return result; +} + +bool ecs_query_is_true( + const ecs_query_t *q) +{ + flecs_poly_assert(q, ecs_query_t); + + ecs_iter_t it = flecs_query_iter(q->world, q); + return ecs_iter_is_true(&it); +} + +int32_t ecs_query_match_count( + const ecs_query_t *q) +{ + flecs_poly_assert(q, ecs_query_t); + + ecs_query_impl_t *impl = flecs_query_impl(q); + if (!impl->cache) { + return 0; + } else { + return impl->cache->match_count; + } +} + +const ecs_query_t* ecs_query_get_cache_query( + const ecs_query_t *q) +{ + ecs_query_impl_t *impl = flecs_query_impl(q); + if (!impl->cache) { + return NULL; + } else { + return impl->cache->query; + } +} + +const ecs_query_t* ecs_query_get( + const ecs_world_t *world, + ecs_entity_t query) +{ + const EcsPoly *poly_comp = ecs_get_pair(world, query, EcsPoly, EcsQuery); + if (!poly_comp) { + return NULL; + } else { + flecs_poly_assert(poly_comp->poly, ecs_query_t); + return poly_comp->poly; + } +} + +void ecs_iter_set_group( + ecs_iter_t *it, + uint64_t group_id) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, + "cannot set group during iteration"); + + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *q = flecs_query_impl(it->query); + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_poly_assert(q, ecs_query_t); + ecs_query_cache_t *cache = q->cache; + ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); + + static ecs_vec_t empty_table = {0}; + + ecs_query_cache_group_t *group = flecs_query_cache_get_group( + cache, group_id); + if (!group) { + qit->tables = &empty_table; /* Dummy table to indicate empty result */ + qit->all_tables = &empty_table; + qit->cur = 0; + qit->group = NULL; + qit->iter_single_group = true; + return; + } + + qit->tables = &group->tables; + qit->all_tables = &group->tables; + qit->cur = 0; + qit->group = group; + qit->iter_single_group = true; /* Prevent iterating next group */ + +error: + return; +} + +const ecs_query_group_info_t* ecs_query_get_group_info( + const ecs_query_t *query, + uint64_t group_id) +{ + flecs_poly_assert(query, ecs_query_t); + ecs_query_cache_group_t *node = flecs_query_cache_get_group( + flecs_query_impl(query)->cache, group_id); + if (!node) { + return NULL; + } + + return &node->info; +} + +void* ecs_query_get_group_ctx( + const ecs_query_t *query, + uint64_t group_id) +{ + flecs_poly_assert(query, ecs_query_t); + const ecs_query_group_info_t *info = ecs_query_get_group_info( + query, group_id); + if (!info) { + return NULL; + } else { + return info->ctx; + } +} + +const ecs_map_t* ecs_query_get_groups( + const ecs_query_t *query) +{ + flecs_poly_assert(query, ecs_query_t); + ecs_query_impl_t *q = flecs_query_impl(query); + ecs_check(q->cache != NULL, ECS_INVALID_PARAMETER, + "ecs_query_get_groups is not valid for queries that don't use group_by"); + + ecs_query_cache_t *cache = q->cache; + ecs_check(cache->group_by != 0, ECS_INVALID_PARAMETER, + "ecs_query_get_groups is not valid for queries that don't use group_by"); + + return &cache->groups; +error: + return NULL; +} + +/** + * @file query/util.c + * @brief Query utilities. + */ + + +const char* flecs_query_op_str( + uint16_t kind) +{ + switch(kind) { + case EcsQueryAll: return "all "; + case EcsQueryAnd: return "and "; + case EcsQueryAndAny: return "and_any "; + case EcsQueryAndWcTgt: return "and_wct "; + case EcsQueryTriv: return "triv "; + case EcsQueryCache: return "cache "; + case EcsQueryIsCache: return "xcache "; + case EcsQueryUp: return "up "; + case EcsQuerySelfUp: return "selfup "; + case EcsQueryWith: return "with "; + case EcsQueryWithWcTgt: return "with_wct "; + case EcsQueryTrav: return "trav "; + case EcsQueryAndFrom: return "andfrom "; + case EcsQueryOrFrom: return "orfrom "; + case EcsQueryNotFrom: return "notfrom "; + case EcsQueryIds: return "ids "; + case EcsQueryIdsRight: return "idsr "; + case EcsQueryIdsLeft: return "idsl "; + case EcsQueryEach: return "each "; + case EcsQueryStore: return "store "; + case EcsQueryReset: return "reset "; + case EcsQueryOr: return "or "; + case EcsQueryOptional: return "option "; + case EcsQueryIfVar: return "ifvar "; + case EcsQueryIfSet: return "ifset "; + case EcsQueryEnd: return "end "; + case EcsQueryNot: return "not "; + case EcsQueryPredEq: return "eq "; + case EcsQueryPredNeq: return "neq "; + case EcsQueryPredEqName: return "eq_nm "; + case EcsQueryPredNeqName: return "neq_nm "; + case EcsQueryPredEqMatch: return "eq_m "; + case EcsQueryPredNeqMatch: return "neq_m "; + case EcsQueryMemberEq: return "membereq "; + case EcsQueryMemberNeq: return "memberneq "; + case EcsQueryToggle: return "toggle "; + case EcsQueryToggleOption: return "togglopt "; + case EcsQuerySparse: return "sparse "; + case EcsQuerySparseWith: return "sparse_w "; + case EcsQuerySparseNot: return "sparse_not "; + case EcsQuerySparseSelfUp: return "sparse_sup "; + case EcsQuerySparseUp: return "sparse_up "; + case EcsQueryTree: return "tree "; + case EcsQueryTreeWildcard: return "tree_wc "; + case EcsQueryTreePre: return "tree_pre "; + case EcsQueryTreePost: return "tree_post "; + case EcsQueryTreeUpPre: return "treeup_pre "; + case EcsQueryTreeSelfUpPre: return "treesup_pre "; + case EcsQueryTreeUpPost: return "treeup_post "; + case EcsQueryTreeSelfUpPost: return "treesup_post"; + case EcsQueryTreeUp: return "tree_up "; + case EcsQueryTreeSelfUp: return "tree_selfup "; + case EcsQueryTreeWith: return "tree_w "; + case EcsQueryChildren: return "children "; + case EcsQueryChildrenWc: return "children_wc "; + case EcsQueryLookup: return "lookup "; + case EcsQuerySetVars: return "setvars "; + case EcsQuerySetThis: return "setthis "; + case EcsQuerySetFixed: return "setfix "; + case EcsQuerySetIds: return "setids "; + case EcsQuerySetId: return "setid "; + case EcsQueryContain: return "contain "; + case EcsQueryPairEq: return "pair_eq "; + case EcsQueryYield: return "yield "; + case EcsQueryNothing: return "nothing "; + default: return "!invalid "; + } +} + +ecs_query_lbl_t flecs_itolbl(int64_t val) { + return flecs_ito(int16_t, val); +} + +ecs_var_id_t flecs_itovar(int64_t val) { + return flecs_ito(uint8_t, val); +} + +ecs_var_id_t flecs_utovar(uint64_t val) { + return flecs_uto(uint8_t, val); +} + +bool flecs_term_is_builtin_pred( + ecs_term_t *term) +{ + if (term->first.id & EcsIsEntity) { + ecs_entity_t id = ECS_TERM_REF_ID(&term->first); + if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { + return true; + } + } + return false; +} + +const char* flecs_term_ref_var_name( + ecs_term_ref_t *ref) +{ + if (!(ref->id & EcsIsVariable)) { + return NULL; + } + + if (ECS_TERM_REF_ID(ref) == EcsThis) { + return EcsThisName; + } + + return ref->name; +} + +bool flecs_term_ref_is_wildcard( + ecs_term_ref_t *ref) +{ + if ((ref->id & EcsIsVariable) && + ((ECS_TERM_REF_ID(ref) == EcsWildcard) || (ECS_TERM_REF_ID(ref) == EcsAny))) + { + return true; + } + return false; +} + +bool flecs_term_is_fixed_id( + ecs_query_t *q, + ecs_term_t *term) +{ + /* Transitive/inherited terms have variable ids */ + if (term->flags_ & (EcsTermTransitive|EcsTermIdInherited)) { + return false; + } + + /* Or terms can match different ids */ + if (term->oper == EcsOr) { + return false; + } + if ((term != q->terms) && term[-1].oper == EcsOr) { + return false; + } + + /* Wildcards can assume different ids */ + if (ecs_id_is_wildcard(term->id)) { + return false; + } + + /* Any terms can have fixed ids, but they require special handling */ + if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { + return false; + } + + /* First terms that are Not or Optional require special handling */ + if (term->oper == EcsNot || term->oper == EcsOptional) { + if (term == q->terms) { + return false; + } + } + + return true; +} + +bool flecs_term_is_or( + const ecs_query_t *q, + const ecs_term_t *term) +{ + bool first_term = term == q->terms; + return (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); +} + +ecs_flags16_t flecs_query_ref_flags( + ecs_flags16_t flags, + ecs_flags16_t kind) +{ + return (flags >> kind) & (EcsQueryIsVar | EcsQueryIsEntity); +} + +bool flecs_query_is_written( + ecs_var_id_t var_id, + uint64_t written) +{ + if (var_id == EcsVarNone) { + return true; + } + + ecs_assert(var_id < EcsQueryMaxVarCount, ECS_INTERNAL_ERROR, NULL); + return (written & (1ull << var_id)) != 0; +} + +void flecs_query_write( + ecs_var_id_t var_id, + uint64_t *written) +{ + ecs_assert(var_id < EcsQueryMaxVarCount, ECS_INTERNAL_ERROR, NULL); + *written |= (1ull << var_id); +} + +void flecs_query_write_ctx( + ecs_var_id_t var_id, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + bool is_written = flecs_query_is_written(var_id, ctx->written); + flecs_query_write(var_id, &ctx->written); + if (!is_written) { + if (cond_write) { + flecs_query_write(var_id, &ctx->cond_written); + } + } +} + +bool flecs_ref_is_written( + const ecs_query_op_t *op, + const ecs_query_ref_t *ref, + ecs_flags16_t kind, + uint64_t written) +{ + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, kind); + if (flags & EcsQueryIsEntity) { + ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); + if (ref->entity) { + return true; + } + } else if (flags & EcsQueryIsVar) { + return flecs_query_is_written(ref->var, written); + } + return false; +} + +ecs_allocator_t* flecs_query_get_allocator( + const ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + if (flecs_poly_is(world, ecs_world_t)) { + return &world->allocator; + } else { + ecs_assert(flecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); + return &((ecs_stage_t*)world)->allocator; + } +} + +static +int32_t flecs_query_op_ref_str( + const ecs_query_impl_t *query, + ecs_query_ref_t *ref, + ecs_flags16_t flags, + ecs_strbuf_t *buf) +{ + int32_t color_chars = 0; + if (flags & EcsQueryIsVar) { + ecs_assert(ref->var < query->var_count, ECS_INTERNAL_ERROR, NULL); + ecs_query_var_t *var = &query->vars[ref->var]; + ecs_strbuf_appendlit(buf, "#[green]$#[reset]"); + if (var->kind == EcsVarTable) { + ecs_strbuf_appendch(buf, '['); + } + ecs_strbuf_appendlit(buf, "#[green]"); + if (var->name) { + ecs_strbuf_appendstr(buf, var->name); + } else { + if (var->id) { +#ifdef FLECS_DEBUG + if (var->label) { + ecs_strbuf_appendstr(buf, var->label); + ecs_strbuf_appendch(buf, '\''); + } +#endif + ecs_strbuf_append(buf, "%d", var->id); + } else { + ecs_strbuf_appendlit(buf, "this"); + } + } + ecs_strbuf_appendlit(buf, "#[reset]"); + if (var->kind == EcsVarTable) { + ecs_strbuf_appendch(buf, ']'); + } + color_chars = ecs_os_strlen("#[green]#[reset]#[green]#[reset]"); + } else if (flags & EcsQueryIsEntity) { + char *path = ecs_get_path(query->pub.world, ref->entity); + ecs_strbuf_appendlit(buf, "#[blue]"); + ecs_strbuf_appendstr(buf, path); + ecs_strbuf_appendlit(buf, "#[reset]"); + ecs_os_free(path); + color_chars = ecs_os_strlen("#[blue]#[reset]"); + } + return color_chars; +} + +static +void flecs_query_str_append_bitset( + ecs_strbuf_t *buf, + ecs_flags64_t bitset) +{ + ecs_strbuf_list_push(buf, "{", ","); + int8_t b; + for (b = 0; b < 64; b ++) { + if (bitset & (1llu << b)) { + ecs_strbuf_list_append(buf, "%d", b); + } + } + ecs_strbuf_list_pop(buf, "}"); +} + +static +void flecs_query_plan_w_profile( + const ecs_query_t *q, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_query_op_t *ops = impl->ops; + int32_t i, count = impl->op_count, indent = 0; + if (!count) { + ecs_strbuf_append(buf, ""); + return; /* No plan */ + } + + for (i = 0; i < count; i ++) { + ecs_query_op_t *op = &ops[i]; + ecs_flags16_t flags = op->flags; + ecs_flags16_t src_flags = flecs_query_ref_flags(flags, EcsQuerySrc); + ecs_flags16_t first_flags = flecs_query_ref_flags(flags, EcsQueryFirst); + ecs_flags16_t second_flags = flecs_query_ref_flags(flags, EcsQuerySecond); + + if (it) { +#ifdef FLECS_DEBUG + const ecs_query_iter_t *rit = &it->priv_.iter.query; + ecs_strbuf_append(buf, + "#[green]%4d -> #[red]%4d <- #[grey] | ", + rit->profile[i].count[0], + rit->profile[i].count[1]); +#endif + } + + ecs_strbuf_append(buf, + "#[normal]%2d. [#[grey]%2d#[reset], #[green]%2d#[reset]] ", + i, op->prev, op->next); + int32_t hidden_chars, start = ecs_strbuf_written(buf); + if (op->kind == EcsQueryEnd) { + indent --; + } + + ecs_strbuf_append(buf, "%*s", indent, ""); + ecs_strbuf_appendstr(buf, flecs_query_op_str(op->kind)); + ecs_strbuf_appendstr(buf, " "); + + int32_t written = ecs_strbuf_written(buf); + for (int32_t j = 0; j < (12 - (written - start)); j ++) { + ecs_strbuf_appendch(buf, ' '); + } + + hidden_chars = flecs_query_op_ref_str(impl, &op->src, src_flags, buf); + + if (op->kind == EcsQueryNot || + op->kind == EcsQueryOr || + op->kind == EcsQueryOptional || + op->kind == EcsQueryIfVar || + op->kind == EcsQueryIfSet) + { + indent ++; + } + + if (op->kind == EcsQueryTriv) { + flecs_query_str_append_bitset(buf, op->src.entity); + } + + if (op->kind == EcsQueryIfSet) { + ecs_strbuf_append(buf, "[%d]\n", op->other); + continue; + } + + bool is_toggle = op->kind == EcsQueryToggle || + op->kind == EcsQueryToggleOption; + + if (!first_flags && !second_flags && !is_toggle) { + ecs_strbuf_appendstr(buf, "\n"); + continue; + } + + written = ecs_strbuf_written(buf) - hidden_chars; + for (int32_t j = 0; j < (30 - (written - start)); j ++) { + ecs_strbuf_appendch(buf, ' '); + } + + if (is_toggle) { + if (op->first.entity) { + flecs_query_str_append_bitset(buf, op->first.entity); + } + if (op->second.entity) { + if (op->first.entity) { + ecs_strbuf_appendlit(buf, ", !"); + } + flecs_query_str_append_bitset(buf, op->second.entity); + } + ecs_strbuf_appendstr(buf, "\n"); + continue; + } + + ecs_strbuf_appendstr(buf, "("); + if (op->kind == EcsQueryMemberEq || op->kind == EcsQueryMemberNeq) { + uint32_t offset = (uint32_t)op->first.entity; + uint32_t size = (uint32_t)(op->first.entity >> 32); + ecs_strbuf_append(buf, "#[yellow]elem#[reset]([%d], 0x%x, 0x%x)", + op->field_index, size, offset); + } else { + flecs_query_op_ref_str(impl, &op->first, first_flags, buf); + } + + if (second_flags) { + ecs_strbuf_appendstr(buf, ", "); + flecs_query_op_ref_str(impl, &op->second, second_flags, buf); + } else { + switch (op->kind) { + case EcsQueryPredEqName: + case EcsQueryPredNeqName: + case EcsQueryPredEqMatch: + case EcsQueryPredNeqMatch: { + int8_t term_index = op->term_index; + ecs_strbuf_appendstr(buf, ", #[yellow]\""); + ecs_strbuf_appendstr(buf, q->terms[term_index].second.name); + ecs_strbuf_appendstr(buf, "\"#[reset]"); + break; + } + case EcsQueryLookup: { + ecs_var_id_t src_id = op->src.var; + ecs_strbuf_appendstr(buf, ", #[yellow]\""); + ecs_strbuf_appendstr(buf, impl->vars[src_id].lookup); + ecs_strbuf_appendstr(buf, "\"#[reset]"); + break; + } + default: + break; + } + } + + ecs_strbuf_appendch(buf, ')'); + + ecs_strbuf_appendch(buf, '\n'); + } +} + +char* ecs_query_plan_w_profile( + const ecs_query_t *q, + const ecs_iter_t *it) +{ + flecs_poly_assert(q, ecs_query_t); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + flecs_query_plan_w_profile(q, it, &buf); + +#ifdef FLECS_LOG + char *str = ecs_strbuf_get(&buf); + flecs_colorize_buf(str, ecs_os_api.flags_ & EcsOsApiLogWithColors, &buf); + ecs_os_free(str); +#endif + + return ecs_strbuf_get(&buf); +} + +char* ecs_query_plan( + const ecs_query_t *q) +{ + return ecs_query_plan_w_profile(q, NULL); +} + +char* ecs_query_plans( + const ecs_query_t *q) +{ + flecs_poly_assert(q, ecs_query_t); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + flecs_query_plan_w_profile(q, NULL, &buf); + + ecs_query_impl_t *impl = flecs_query_impl(q); + if (impl->cache) { + ecs_strbuf_appendstr(&buf, "---\n"); + flecs_query_plan_w_profile(impl->cache->query, NULL, &buf); + } + +#ifdef FLECS_LOG + char *str = ecs_strbuf_get(&buf); + flecs_colorize_buf(str, ecs_os_api.flags_ & EcsOsApiLogWithColors, &buf); + ecs_os_free(str); +#endif + + return ecs_strbuf_get(&buf); +} + +static +void flecs_query_str_add_id( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_term_t *term, + const ecs_term_ref_t *ref, + bool is_src) +{ + bool is_added = false; + ecs_entity_t ref_id = ECS_TERM_REF_ID(ref); + if (ref->id & EcsIsVariable && !ecs_id_is_wildcard(ref_id)){ + ecs_strbuf_appendlit(buf, "$"); + } + + if (ref_id) { + char *path = ecs_get_path(world, ref_id); + ecs_strbuf_appendstr(buf, path); + ecs_os_free(path); + } else if (ref->name) { + ecs_strbuf_appendstr(buf, ref->name); + } else { + ecs_strbuf_appendlit(buf, "#0"); + } + is_added = true; + + ecs_flags64_t flags = ECS_TERM_REF_FLAGS(ref); + if (!(flags & EcsTraverseFlags)) { + /* If flags haven't been set yet, initialize with defaults. This can + * happen if an error is thrown while the term is being finalized */ + flags |= EcsSelf; + } + + if ((flags & EcsTraverseFlags) != EcsSelf) { + if (is_added) { + ecs_strbuf_list_push(buf, "|", "|"); + } else { + ecs_strbuf_list_push(buf, "", "|"); + } + if (is_src) { + if (flags & EcsSelf) { + ecs_strbuf_list_appendstr(buf, "self"); + } + + if (flags & EcsCascade) { + ecs_strbuf_list_appendstr(buf, "cascade"); + } else if (flags & EcsUp) { + ecs_strbuf_list_appendstr(buf, "up"); + } + + if (flags & EcsDesc) { + ecs_strbuf_list_appendstr(buf, "desc"); + } + + if (term->trav) { + char *rel_path = ecs_get_path(world, term->trav); + ecs_strbuf_appendlit(buf, " "); + ecs_strbuf_appendstr(buf, rel_path); + ecs_os_free(rel_path); + } + } + + ecs_strbuf_list_pop(buf, ""); + } +} + +void flecs_term_to_buf( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_strbuf_t *buf, + int32_t t) +{ + const ecs_term_ref_t *first = &term->first; + const ecs_term_ref_t *second = &term->second; + + ecs_entity_t first_id = ECS_TERM_REF_ID(first); + + bool src_set = !ecs_term_match_0(term); + bool second_set = ecs_term_ref_is_set(second); + + if (first_id == EcsScopeOpen) { + ecs_strbuf_appendlit(buf, "{"); + return; + } else if (first_id == EcsScopeClose) { + ecs_strbuf_appendlit(buf, "}"); + return; + } + + if (((ECS_TERM_REF_ID(&term->first) == EcsPredEq) || + (ECS_TERM_REF_ID(&term->first) == EcsPredMatch)) && + (term->first.id & EcsIsEntity)) + { + ecs_strbuf_appendlit(buf, "$"); + if (ECS_TERM_REF_ID(&term->src) == EcsThis && + (term->src.id & EcsIsVariable)) + { + ecs_strbuf_appendlit(buf, "this"); + } else if (term->src.id & EcsIsVariable) { + if (term->src.name) { + ecs_strbuf_appendstr(buf, term->src.name); + } else { + ecs_strbuf_appendstr(buf, "<>"); + } + } else { + /* Shouldn't happen */ + } + + if (ECS_TERM_REF_ID(&term->first) == EcsPredEq) { + if (term->oper == EcsNot) { + ecs_strbuf_appendlit(buf, " != "); + } else { + ecs_strbuf_appendlit(buf, " == "); + } + } else if (ECS_TERM_REF_ID(&term->first) == EcsPredMatch) { + ecs_strbuf_appendlit(buf, " ~= "); + } + + if (term->second.id & EcsIsEntity) { + if (term->second.id != 0) { + ecs_get_path_w_sep_buf(world, 0, ECS_TERM_REF_ID(&term->second), + ".", NULL, buf, false); + } + } else { + if (term->second.id & EcsIsVariable) { + ecs_strbuf_appendlit(buf, "$"); + if (term->second.name) { + ecs_strbuf_appendstr(buf, term->second.name); + } else if (ECS_TERM_REF_ID(&term->second) == EcsThis) { + ecs_strbuf_appendlit(buf, "this"); + } + } else if (term->second.id & EcsIsName) { + ecs_strbuf_appendlit(buf, "\""); + if ((ECS_TERM_REF_ID(&term->first) == EcsPredMatch) && + (term->oper == EcsNot)) + { + ecs_strbuf_appendlit(buf, "!"); + } + ecs_strbuf_appendstr(buf, term->second.name); + ecs_strbuf_appendlit(buf, "\""); + } + } + + return; + } + + if (!t || !(term[-1].oper == EcsOr)) { + if (term->inout == EcsIn) { + ecs_strbuf_appendlit(buf, "[in] "); + } else if (term->inout == EcsInOut) { + ecs_strbuf_appendlit(buf, "[inout] "); + } else if (term->inout == EcsOut) { + ecs_strbuf_appendlit(buf, "[out] "); + } else if (term->inout == EcsInOutNone && term->oper != EcsNot) { + ecs_strbuf_appendlit(buf, "[none] "); + } + } + + if (term->oper == EcsNot) { + ecs_strbuf_appendlit(buf, "!"); + } else if (term->oper == EcsOptional) { + ecs_strbuf_appendlit(buf, "?"); + } + + if (!src_set) { + flecs_query_str_add_id(world, buf, term, &term->first, false); + if (!second_set) { + ecs_strbuf_appendlit(buf, "()"); + } else { + ecs_strbuf_appendlit(buf, "(#0,"); + flecs_query_str_add_id(world, buf, term, &term->second, false); + ecs_strbuf_appendlit(buf, ")"); + } + } else { + ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; + if (flags && !ECS_HAS_ID_FLAG(flags, PAIR)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(flags)); + ecs_strbuf_appendch(buf, '|'); + } + + flecs_query_str_add_id(world, buf, term, &term->first, false); + ecs_strbuf_appendlit(buf, "("); + flecs_query_str_add_id(world, buf, term, &term->src, true); + if (second_set) { + ecs_strbuf_appendlit(buf, ","); + flecs_query_str_add_id(world, buf, term, &term->second, false); + } + ecs_strbuf_appendlit(buf, ")"); + } +} + +char* ecs_term_str( + const ecs_world_t *world, + const ecs_term_t *term) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + flecs_term_to_buf(world, term, &buf, 0); + return ecs_strbuf_get(&buf); +} + +char* ecs_query_str( + const ecs_query_t *q) +{ + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_world_t *world = q->world; + + ecs_strbuf_t buf = ECS_STRBUF_INIT; + const ecs_term_t *terms = q->terms; + int32_t i, count = q->term_count; + + for (i = 0; i < count; i ++) { + const ecs_term_t *term = &terms[i]; + + flecs_term_to_buf(world, term, &buf, i); + + if (i != (count - 1)) { + if (term->oper == EcsOr) { + ecs_strbuf_appendlit(&buf, " || "); + } else { + if (ECS_TERM_REF_ID(&term->first) != EcsScopeOpen) { + if (ECS_TERM_REF_ID(&term[1].first) != EcsScopeClose) { + ecs_strbuf_appendlit(&buf, ", "); + } + } + } + } + } + + return ecs_strbuf_get(&buf); +error: + return NULL; +} + +void flecs_query_apply_iter_flags( + ecs_iter_t *it, + const ecs_query_t *query) +{ + ECS_BIT_COND(it->flags, EcsIterHasCondSet, + ECS_BIT_IS_SET(query->flags, EcsQueryHasCondSet)); + ECS_BIT_COND(it->flags, EcsIterNoData, query->data_fields == 0); +} + +void flecs_query_reclaim( + ecs_query_t *query) +{ + ecs_query_impl_t *impl = flecs_query_impl(query); + + ecs_query_cache_t *cache = impl->cache; + if (cache) { + ecs_map_reclaim(&cache->tables); + ecs_map_reclaim(&cache->groups); + } +} + +ecs_id_t flecs_query_iter_set_id( + ecs_iter_t *it, + int8_t field, + ecs_id_t id) +{ + ecs_assert(!(it->flags & EcsIterImmutableCacheData), + ECS_INTERNAL_ERROR, NULL); + it->ids[field] = id; + return id; +} + +/** + * @file query/validator.c + * @brief Validate and finalize queries. + */ + + +#ifdef FLECS_QUERY_DSL +#endif + +static +void flecs_query_validator_error( + const ecs_query_validator_ctx_t *ctx, + const char *fmt, + ...) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (ctx->desc && ctx->desc->expr) { + ecs_strbuf_appendlit(&buf, "expr: "); + ecs_strbuf_appendstr(&buf, ctx->desc->expr); + ecs_strbuf_appendlit(&buf, "\n"); + } + + if (ctx->query) { + ecs_query_t *query = ctx->query; + const ecs_term_t *terms = query->terms; + int32_t i, count = query->term_count; + + for (i = 0; i < count; i ++) { + const ecs_term_t *term = &terms[i]; + if (ctx->term_index == i) { + ecs_strbuf_appendlit(&buf, " > "); + } else { + ecs_strbuf_appendlit(&buf, " "); + } + flecs_term_to_buf(ctx->world, term, &buf, i); + if (term->oper == EcsOr) { + ecs_strbuf_appendlit(&buf, " ||"); + } else if (i != (count - 1)) { + ecs_strbuf_appendlit(&buf, ","); + } + ecs_strbuf_appendlit(&buf, "\n"); + } + } else { + ecs_strbuf_appendlit(&buf, " > "); + flecs_term_to_buf(ctx->world, ctx->term, &buf, 0); + ecs_strbuf_appendlit(&buf, "\n"); + } + + char *expr = ecs_strbuf_get(&buf); + const char *name = NULL; + if (ctx->query && ctx->query->entity) { + name = ecs_get_name(ctx->query->world, ctx->query->entity); + } + + va_list args; + va_start(args, fmt); + char *msg = flecs_vasprintf(fmt, args); + ecs_parser_error(name, NULL, 0, "%s\n%s", msg, expr); + ecs_os_free(msg); + ecs_os_free(expr); + + va_end(args); +} + +static +int flecs_term_ref_finalize_flags( + ecs_term_ref_t *ref, + ecs_query_validator_ctx_t *ctx, + const char* refname) +{ + (void)refname; + + if ((ref->id & EcsIsEntity) && (ref->id & EcsIsVariable)) { + flecs_query_validator_error(ctx, + "cannot set both EcsIsEntity and EcsIsVariable for term.%s", + refname); + return -1; + } + + if (ref->name && ref->name[0] == '$') { + if (!ref->name[1]) { + if (!(ref->id & EcsIsName)) { + flecs_query_validator_error(ctx, + "invalid variable name for term.%s" + " ('$' syntax support is removed, use new Singleton trait)", + refname); + return -1; + } + } else { + ref->name = &ref->name[1]; + ref->id |= EcsIsVariable; + } + } + + if (!(ref->id & (EcsIsEntity|EcsIsVariable|EcsIsName))) { + if (ECS_TERM_REF_ID(ref) || ref->name) { + if (ECS_TERM_REF_ID(ref) == EcsThis || + ECS_TERM_REF_ID(ref) == EcsWildcard || + ECS_TERM_REF_ID(ref) == EcsAny || + ECS_TERM_REF_ID(ref) == EcsVariable) + { + /* Builtin variable ids default to variable */ + ref->id |= EcsIsVariable; + } else { + ref->id |= EcsIsEntity; + } + } + } + + return 0; +} + +static +bool flecs_identifier_is_0( + const char *id) +{ + return id[0] == '#' && id[1] == '0' && !id[2]; +} + +static +int flecs_term_ref_lookup( + const ecs_world_t *world, + ecs_entity_t scope, + ecs_term_ref_t *ref, + ecs_query_validator_ctx_t *ctx) +{ + const char *name = ref->name; + if (!name) { + return 0; + } + + if (ref->id & EcsIsVariable) { + if (!ecs_os_strcmp(name, "this")) { + ref->id = EcsThis | ECS_TERM_REF_FLAGS(ref); + ref->name = NULL; + return 0; + } + return 0; + } else if (ref->id & EcsIsName) { + if (ref->name == NULL) { + flecs_query_validator_error(ctx, "IsName flag specified without name"); + return -1; + } + return 0; + } + + ecs_assert(ref->id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); + + if (flecs_identifier_is_0(name)) { + if (ECS_TERM_REF_ID(ref)) { + flecs_query_validator_error( + ctx, "name '0' does not match entity id"); + return -1; + } + ref->name = NULL; + return 0; + } + + ecs_entity_t e = 0; + if (scope) { + e = ecs_lookup_child(world, scope, name); + } + + if (!e) { + e = ecs_lookup(world, name); + } + + if (!e) { + if (ctx->query && (ctx->query->flags & EcsQueryAllowUnresolvedByName)) { + ref->id |= EcsIsName; + ref->id &= ~EcsIsEntity; + return 0; + } else { + flecs_query_validator_error(ctx, "unresolved identifier '%s'", name); + return -1; + } + } + + ecs_entity_t ref_id = ECS_TERM_REF_ID(ref); + if (ref_id && ref_id != e) { + char *e_str = ecs_get_path(world, ref_id); + flecs_query_validator_error(ctx, "name '%s' does not match term.id '%s'", + name, e_str); + ecs_os_free(e_str); + return -1; + } + + ref->id = e | ECS_TERM_REF_FLAGS(ref); + ref_id = ECS_TERM_REF_ID(ref); + + if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || + !ecs_os_strcmp(name, "$")) + { + ref->id &= ~EcsIsEntity; + ref->id |= EcsIsVariable; + } + + /* Check if looked up id is alive (relevant for numerical ids) */ + if (!(ref->id & EcsIsName) && ref_id) { + if (!ecs_is_alive(world, ref_id)) { + flecs_query_validator_error(ctx, "identifier '%s' is not alive", + ref->name); + return -1; + } + + ref->name = NULL; + return 0; + } + + return 0; +} + +static +int flecs_term_refs_finalize( + const ecs_world_t *world, + ecs_term_t *term, + ecs_query_validator_ctx_t *ctx) +{ + ecs_term_ref_t *src = &term->src; + ecs_term_ref_t *first = &term->first; + ecs_term_ref_t *second = &term->second; + + /* Include subsets for component by default, to support inheritance */ + if (!(first->id & EcsTraverseFlags)) { + first->id |= EcsSelf; + } + + /* Traverse Self by default for pair target */ + if (!(second->id & EcsTraverseFlags)) { + if (ECS_TERM_REF_ID(second) || second->name || (second->id & EcsIsEntity)) { + second->id |= EcsSelf; + } + } + + /* Source defaults to This */ + if (!ECS_TERM_REF_ID(src) && (src->name == NULL) && !(src->id & EcsIsEntity)) { + src->id = EcsThis | ECS_TERM_REF_FLAGS(src); + src->id |= EcsIsVariable; + } + + /* Initialize term identifier flags */ + if (flecs_term_ref_finalize_flags(src, ctx, "src")) { + return -1; + } + + if (flecs_term_ref_finalize_flags(first, ctx, "first")) { + return -1; + } + + if (flecs_term_ref_finalize_flags(second, ctx, "second")) { + return -1; + } + + /* Lookup term identifiers by name */ + if (flecs_term_ref_lookup(world, 0, src, ctx)) { + return -1; + } + if (flecs_term_ref_lookup(world, 0, first, ctx)) { + return -1; + } + + ecs_entity_t first_id = 0; + ecs_entity_t oneof = 0; + if (first->id & EcsIsEntity) { + first_id = ECS_TERM_REF_ID(first); + + if (!first_id) { + flecs_query_validator_error(ctx, "invalid \"0\" for first.name"); + return -1; + } + + /* If first element of pair has OneOf property, lookup second element of + * pair in the value of the OneOf property */ + oneof = flecs_get_oneof(world, first_id); + } + + if (flecs_term_ref_lookup(world, oneof, &term->second, ctx)) { + return -1; + } + + /* If source is 0, reset traversal flags */ + if (ECS_TERM_REF_ID(src) == 0 && src->id & EcsIsEntity) { + src->id &= ~EcsTraverseFlags; + term->trav = 0; + } + + /* If source is wildcard, term won't return any data */ + if ((src->id & EcsIsVariable) && ecs_id_is_wildcard(ECS_TERM_REF_ID(src))) { + term->inout = EcsInOutNone; + } + + return 0; +} + +static +ecs_entity_t flecs_term_ref_get_entity( + const ecs_term_ref_t *ref) +{ + if (ref->id & EcsIsEntity) { + return ECS_TERM_REF_ID(ref); /* Id is known */ + } else if (ref->id & EcsIsVariable) { + /* Return wildcard for variables, as they aren't known yet */ + if (ECS_TERM_REF_ID(ref) != EcsAny) { + /* Any variable should not use wildcard, as this would return all + * ids matching a wildcard, whereas Any returns the first match */ + return EcsWildcard; + } else { + return EcsAny; + } + } else { + return 0; /* Term id is uninitialized */ + } +} + +static +int flecs_term_populate_id( + ecs_term_t *term) +{ + ecs_entity_t first = flecs_term_ref_get_entity(&term->first); + ecs_entity_t second = flecs_term_ref_get_entity(&term->second); + ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; + + if (first & ECS_ID_FLAGS_MASK) { + return -1; + } + if (second & ECS_ID_FLAGS_MASK) { + return -1; + } + + if ((second || (term->second.id & EcsIsEntity))) { + flags |= ECS_PAIR; + } + + if (!second && !ECS_HAS_ID_FLAG(flags, PAIR)) { + term->id = first | flags; + } else { + term->id = ecs_pair(first, second) | flags; + } + + return 0; +} + +static +int flecs_term_populate_from_id( + const ecs_world_t *world, + ecs_term_t *term, + ecs_query_validator_ctx_t *ctx) +{ + ecs_entity_t first = 0; + ecs_entity_t second = 0; + bool pair_w_0_tgt = false; + + if (ECS_HAS_ID_FLAG(term->id, PAIR)) { + first = ECS_PAIR_FIRST(term->id); + second = ECS_PAIR_SECOND(term->id); + + if (!first) { + flecs_query_validator_error(ctx, "missing first element in term.id"); + return -1; + } + if (!second) { + if (first != EcsChildOf) { + flecs_query_validator_error(ctx, "missing second element in term.id"); + return -1; + } else { + /* Exception is made for ChildOf so we can use (ChildOf, 0) to match + * all entities in the root */ + pair_w_0_tgt = true; + } + } + } else { + first = term->id & ECS_COMPONENT_MASK; + if (!first) { + flecs_query_validator_error(ctx, "missing first element in term.id"); + return -1; + } + } + + ecs_entity_t term_first = flecs_term_ref_get_entity(&term->first); + if (term_first) { + if ((uint32_t)term_first != (uint32_t)first) { + flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + } else { + ecs_entity_t first_id = ecs_get_alive(world, first); + if (!first_id) { + term->first.id = first | ECS_TERM_REF_FLAGS(&term->first); + } else { + term->first.id = first_id | ECS_TERM_REF_FLAGS(&term->first); + } + } + + ecs_entity_t term_second = flecs_term_ref_get_entity(&term->second); + if (term_second) { + if ((uint32_t)term_second != second) { + flecs_query_validator_error(ctx, "mismatch between term.id and term.second"); + return -1; + } + } else if (second) { + ecs_entity_t second_id = ecs_get_alive(world, second); + if (!second_id) { + term->second.id = second | ECS_TERM_REF_FLAGS(&term->second); + } else { + term->second.id = second_id | ECS_TERM_REF_FLAGS(&term->second); + } + } else if (pair_w_0_tgt) { + term->second.id = EcsIsEntity; + } + + return 0; +} + +static +int flecs_term_verify_eq_pred( + const ecs_term_t *term, + ecs_query_validator_ctx_t *ctx) +{ + const ecs_term_ref_t *second = &term->second; + const ecs_term_ref_t *src = &term->src; + ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); + ecs_entity_t second_id = ECS_TERM_REF_ID(&term->second); + ecs_entity_t src_id = ECS_TERM_REF_ID(&term->src); + + if (term->oper != EcsAnd && term->oper != EcsNot && term->oper != EcsOr) { + flecs_query_validator_error(ctx, "invalid operator combination"); + goto error; + } + + if ((src->id & EcsIsName) && (second->id & EcsIsName)) { + flecs_query_validator_error(ctx, "both sides of operator cannot be a name"); + goto error; + } + + if ((src->id & EcsIsEntity) && (second->id & EcsIsEntity)) { + flecs_query_validator_error(ctx, "both sides of operator cannot be an entity"); + goto error; + } + + if (!(src->id & EcsIsVariable)) { + flecs_query_validator_error(ctx, "left-hand of operator must be a variable"); + goto error; + } + + if (first_id == EcsPredMatch && !(second->id & EcsIsName)) { + flecs_query_validator_error(ctx, "right-hand of match operator must be a string"); + goto error; + } + + if ((src->id & EcsIsVariable) && (second->id & EcsIsVariable)) { + if (src_id && src_id == second_id) { + flecs_query_validator_error(ctx, "both sides of operator are equal"); + goto error; + } + if (src->name && second->name && !ecs_os_strcmp(src->name, second->name)) { + flecs_query_validator_error(ctx, "both sides of operator are equal"); + goto error; + } + } + + if (first_id == EcsPredEq) { + if (second_id == EcsPredEq || second_id == EcsPredMatch) { + flecs_query_validator_error(ctx, + "invalid right-hand side for equality operator"); + goto error; + } + } + + return 0; +error: + return -1; +} + +static +int flecs_term_verify( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_query_validator_ctx_t *ctx) +{ + const ecs_term_ref_t *first = &term->first; + const ecs_term_ref_t *second = &term->second; + const ecs_term_ref_t *src = &term->src; + ecs_entity_t first_id = 0, second_id = 0; + ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; + ecs_id_t id = term->id; + + if ((src->id & EcsIsName) && (second->id & EcsIsName)) { + flecs_query_validator_error(ctx, "mismatch between term.cr_flags & term.id"); + return -1; + } + + ecs_entity_t src_id = ECS_TERM_REF_ID(src); + if (first->id & EcsIsEntity) { + first_id = ECS_TERM_REF_ID(first); + } + + if (second->id & EcsIsEntity) { + second_id = ECS_TERM_REF_ID(second); + } + + if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { + return flecs_term_verify_eq_pred(term, ctx); + } + + if (ecs_term_ref_is_set(second) && !ECS_HAS_ID_FLAG(flags, PAIR)) { + flecs_query_validator_error(ctx, "expected PAIR flag for term with pair"); + return -1; + } else if (!ecs_term_ref_is_set(second) && ECS_HAS_ID_FLAG(flags, PAIR)) { + if (first_id != EcsChildOf) { + flecs_query_validator_error(ctx, "unexpected PAIR flag for term without pair"); + return -1; + } else { + /* Exception is made for ChildOf so we can use (ChildOf, 0) to match + * all entities in the root */ + } + } + + if (!ecs_term_ref_is_set(src)) { + flecs_query_validator_error(ctx, "term.src is not initialized"); + return -1; + } + + if (!ecs_term_ref_is_set(first)) { + flecs_query_validator_error(ctx, "term.first is not initialized"); + return -1; + } + + if (ECS_HAS_ID_FLAG(flags, PAIR)) { + if (!ECS_PAIR_FIRST(id)) { + flecs_query_validator_error(ctx, "invalid 0 for first element in pair id"); + return -1; + } + if ((ECS_PAIR_FIRST(id) != EcsChildOf) && !ECS_PAIR_SECOND(id)) { + flecs_query_validator_error(ctx, "invalid 0 for second element in pair id"); + return -1; + } + + if ((first->id & EcsIsEntity) && + (ecs_entity_t_lo(first_id) != ECS_PAIR_FIRST(id))) + { + flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + if ((first->id & EcsIsVariable) && + !ecs_id_is_wildcard(ECS_PAIR_FIRST(id))) + { + char *id_str = ecs_id_str(world, id); + flecs_query_validator_error(ctx, + "expected wildcard for variable term.first (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } + + if ((second->id & EcsIsEntity) && + (ecs_entity_t_lo(second_id) != ECS_PAIR_SECOND(id))) + { + flecs_query_validator_error(ctx, "mismatch between term.id and term.second"); + return -1; + } + if ((second->id & EcsIsVariable) && + !ecs_id_is_wildcard(ECS_PAIR_SECOND(id))) + { + char *id_str = ecs_id_str(world, id); + flecs_query_validator_error(ctx, + "expected wildcard for variable term.second (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } + } else { + ecs_entity_t component = id & ECS_COMPONENT_MASK; + if (!component) { + flecs_query_validator_error(ctx, "missing component id"); + return -1; + } + if ((first->id & EcsIsEntity) && + (ecs_entity_t_lo(first_id) != ecs_entity_t_lo(component))) + { + flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + if ((first->id & EcsIsVariable) && !ecs_id_is_wildcard(component)) { + char *id_str = ecs_id_str(world, id); + flecs_query_validator_error(ctx, + "expected wildcard for variable term.first (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } + } + + if (first_id) { + if (ecs_term_ref_is_set(second)) { + ecs_flags64_t mask = EcsIsEntity | EcsIsVariable; + if ((src->id & mask) == (second->id & mask)) { + bool is_same = false; + if (src->id & EcsIsEntity) { + if (src_id) { + is_same = src_id == second_id; + } + } else if (src->name && second->name) { + is_same = !ecs_os_strcmp(src->name, second->name); + } + + if (is_same && ecs_has_id(world, first_id, EcsAcyclic) + && !(term->flags_ & EcsTermReflexive)) + { + flecs_query_validator_error(ctx, "term with acyclic " + "relationship cannot have the same source and target"); + return -1; + } + } + } + + if (second_id && !ecs_id_is_wildcard(second_id)) { + ecs_entity_t oneof = flecs_get_oneof(world, first_id); + if (oneof) { + if (!ecs_has_pair(world, second_id, EcsChildOf, oneof)) { + char *second_str = ecs_get_path(world, second_id); + char *oneof_str = ecs_get_path(world, oneof); + char *id_str = ecs_id_str(world, term->id); + flecs_query_validator_error(ctx, + "invalid target '%s' for %s: must be child of '%s'", + second_str, id_str, oneof_str); + ecs_os_free(second_str); + ecs_os_free(oneof_str); + ecs_os_free(id_str); + return -1; + } + } + } + } + + if (term->trav) { + if (!ecs_has_id(world, term->trav, EcsTraversable)) { + char *r_str = ecs_get_path(world, term->trav); + flecs_query_validator_error(ctx, + "cannot traverse non-traversable relationship '%s'", r_str); + ecs_os_free(r_str); + return -1; + } + } + + return 0; +} + +int flecs_term_finalize( + const ecs_world_t *world, + ecs_term_t *term, + ecs_query_validator_ctx_t *ctx) +{ + ctx->term = term; + + ecs_term_ref_t *src = &term->src; + ecs_term_ref_t *first = &term->first; + ecs_term_ref_t *second = &term->second; + ecs_flags64_t first_flags = ECS_TERM_REF_FLAGS(first); + ecs_flags64_t second_flags = ECS_TERM_REF_FLAGS(second); + + if (first->name && (first->id & ~EcsTermRefFlags)) { + flecs_query_validator_error(ctx, + "first.name (%s) and first.id have competing values", + first->name); + return -1; + } + if (src->name && (src->id & ~EcsTermRefFlags)) { + flecs_query_validator_error(ctx, + "src.name (%s) and src.id have competing values", + src->name); + return -1; + } + if (second->name && (second->id & ~EcsTermRefFlags)) { + flecs_query_validator_error(ctx, + "second.name (%s) and second.id have competing values", + second->name); + return -1; + } + + if (term->id & ~ECS_ID_FLAGS_MASK) { + if (flecs_term_populate_from_id(world, term, ctx)) { + return -1; + } + } + + if (flecs_term_refs_finalize(world, term, ctx)) { + return -1; + } + + ecs_entity_t first_id = ECS_TERM_REF_ID(first); + ecs_entity_t second_id = ECS_TERM_REF_ID(second); + ecs_entity_t src_id = ECS_TERM_REF_ID(src); + + if ((first->id & EcsIsVariable) && (first_id == EcsAny)) { + term->flags_ |= EcsTermMatchAny; + } + + if ((second->id & EcsIsVariable) && (second_id == EcsAny)) { + term->flags_ |= EcsTermMatchAny; + } + + if ((src->id & EcsIsVariable) && (src_id == EcsAny)) { + term->flags_ |= EcsTermMatchAnySrc; + } + + ecs_flags64_t ent_var_mask = EcsIsEntity | EcsIsVariable; + + /* If EcsVariable is used by itself, assign to predicate (singleton) */ + if ((ECS_TERM_REF_ID(src) == EcsVariable) && (src->id & EcsIsVariable)) { + src->id = first->id | ECS_TERM_REF_FLAGS(src); + src->id &= ~ent_var_mask; + src->id |= first->id & ent_var_mask; + src->name = first->name; + } + + if ((ECS_TERM_REF_ID(second) == EcsVariable) && (second->id & EcsIsVariable)) { + second->id = first->id | ECS_TERM_REF_FLAGS(second); + second->id &= ~ent_var_mask; + second->id |= first->id & ent_var_mask; + second->name = first->name; + } + + if (!(term->id & ~ECS_ID_FLAGS_MASK)) { + if (flecs_term_populate_id(term)) { + return -1; + } + } + + /* If term queries for !(ChildOf, _), translate it to the builtin + * (ChildOf, 0) index which is a cheaper way to find root entities */ + if (term->oper == EcsNot && ( + (term->id == ecs_pair(EcsChildOf, EcsAny)) || + (term->id == ecs_pair(EcsChildOf, EcsWildcard)) + )) + { + /* Only if the source is not EcsAny */ + if (!(ECS_TERM_REF_ID(&term->src) == EcsAny && (term->src.id & EcsIsVariable))) { + term->oper = EcsAnd; + term->id = ecs_pair(EcsChildOf, 0); + second->id = 0; + second->id |= EcsSelf|EcsIsEntity; + } + } + + if (term->id == ecs_pair(EcsChildOf, 0)) { + /* Ensure same behavior for (ChildOf, #0) and !(ChildOf, _) terms */ + term->flags_ |= EcsTermMatchAny; + } + + ecs_entity_t first_entity = 0; + if ((first->id & EcsIsEntity)) { + first_entity = first_id; + } + + ecs_flags32_t cr_flags = 0; + if (term->id) { + cr_flags = flecs_component_get_flags(world, term->id); + } + + if (src_id || src->name) { + if (!(term->src.id & EcsTraverseFlags)) { + if (cr_flags & EcsIdOnInstantiateInherit) { + term->src.id |= EcsSelf|EcsUp; + if (!term->trav) { + term->trav = EcsIsA; + } + } else { + term->src.id |= EcsSelf; + + if (term->trav) { + char *idstr = ecs_id_str(world, term->id); + flecs_query_validator_error(ctx, + "traversal relationship (term::trav) '%s' specified for " + "component '%s' which can't be inherited", + flecs_errstr(ecs_id_str(world, term->trav)), + idstr); + ecs_os_free(idstr); + return -1; + } + } + } + + if (term->first.id & EcsCascade) { + flecs_query_validator_error(ctx, + "cascade modifier invalid for term.first"); + return -1; + } + + if (term->second.id & EcsCascade) { + flecs_query_validator_error(ctx, + "cascade modifier invalid for term.second"); + return -1; + } + + if (term->first.id & EcsDesc) { + flecs_query_validator_error(ctx, + "desc modifier invalid for term.first"); + return -1; + } + + if (term->second.id & EcsDesc) { + flecs_query_validator_error(ctx, + "desc modifier invalid for term.second"); + return -1; + } + + if (term->src.id & EcsDesc && !(term->src.id & EcsCascade)) { + flecs_query_validator_error(ctx, + "desc modifier invalid without cascade"); + return -1; + } + + if (term->src.id & EcsCascade) { + /* Cascade always implies up traversal */ + term->src.id |= EcsUp; + } + + if ((src->id & EcsUp) && !term->trav) { + /* When traversal flags are specified but no traversal relationship, + * default to ChildOf, which is the most common kind of traversal. */ + term->trav = EcsChildOf; + } + } + + if (!(cr_flags & EcsIdOnInstantiateInherit) && (term->trav == EcsIsA)) { + if (src->id & EcsUp) { + char *idstr = ecs_id_str(world, term->id); + flecs_query_validator_error(ctx, "IsA traversal not allowed " + "for '%s' because the component doesn't have the " + "(OnInstantiate, Inherit) trait", idstr); + ecs_os_free(idstr); + return -1; + } + } + + if (first_entity && !ecs_term_match_0(term)) { + bool first_is_self = (first_flags & EcsTraverseFlags) == EcsSelf; + ecs_record_t *first_record = flecs_entities_get(world, first_entity); + ecs_table_t *first_table = first_record ? first_record->table : NULL; + + bool first_can_isa = false; + if (first_table) { + first_can_isa = (first_table->flags & EcsTableHasIsA) != 0; + if (first_can_isa) { + first_can_isa = !ecs_table_has_id(world, first_table, EcsFinal); + } + } + + /* Only enable inheritance for ids which are inherited from at the time + * of query creation. To force component inheritance to be evaluated, + * an application can explicitly set traversal flags. */ + if (flecs_components_get(world, ecs_pair(EcsIsA, first->id)) || + (cr_flags & EcsIdInheritable) || first_can_isa) + { + if (!first_is_self) { + term->flags_ |= EcsTermIdInherited; + } + } else { +#ifdef FLECS_DEBUG + if (!first_is_self) { + ecs_query_impl_t *q = flecs_query_impl(ctx->query); + if (q) { + ECS_TERMSET_SET(q->final_terms, 1u << ctx->term_index); + } + } +#endif + } + + if (first_table) { + if (ecs_term_ref_is_set(second)) { + /* Add traversal flags for transitive relationships */ + if (!((second_flags & EcsTraverseFlags) == EcsSelf)) { + if (!((src->id & EcsIsVariable) && (src_id == EcsAny))) { + if (!((second->id & EcsIsVariable) && (second_id == EcsAny))) { + if (!((second_id == EcsWildcard) && (term->oper == EcsNot))) { + if (ecs_table_has_id(world, first_table, EcsTransitive)) { + term->flags_ |= EcsTermTransitive; + } + + if (ecs_table_has_id(world, first_table, EcsReflexive)) { + term->flags_ |= EcsTermReflexive; + } + } + } + } + } + } + + if (ecs_table_has_id(world, first_table, EcsDontFragment)) { + term->flags_ |= EcsTermDontFragment; + } + } + + /* Check if term has toggleable component */ + if (cr_flags & EcsIdCanToggle) { + /* If the term isn't matched on a #0 source */ + if (term->src.id != EcsIsEntity) { + term->flags_ |= EcsTermIsToggle; + + } + } + + /* Check if this is a member query */ +#ifdef FLECS_META + if (ecs_id(EcsMember) != 0) { + if (first_entity) { + if (ecs_has(world, first_entity, EcsMember)) { + term->flags_ |= EcsTermIsMember; + } + } + } +#endif + } + + if (ECS_TERM_REF_ID(first) == EcsVariable) { + flecs_query_validator_error(ctx, "invalid $ for term.first"); + return -1; + } + + if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { + if (term->inout != EcsInOutDefault && term->inout != EcsInOutNone) { + flecs_query_validator_error(ctx, + "invalid inout value for AndFrom/OrFrom/NotFrom term"); + return -1; + } + } + + /* Is term trivial/cacheable */ + bool cacheable_term = true; + bool trivial_term = true; + if (term->oper != EcsAnd || term->flags_ & EcsTermIsOr) { + trivial_term = false; + } + + if (ECS_IS_PAIR(term->id) && (ECS_PAIR_FIRST(term->id) == EcsChildOf)) { + if (term->oper == EcsAnd) { + term->flags_ |= EcsTermNonFragmentingChildOf; + } + + if (ECS_PAIR_SECOND(term->id)) { + trivial_term = false; + } + } + + if (ecs_id_is_wildcard(term->id)) { + if (ECS_PAIR_FIRST(term->id) == EcsWildcard) { + cacheable_term = false; + trivial_term = false; + } + + if (!(cr_flags & EcsIdExclusive)) { + trivial_term = false; + } + + if (first->id & EcsIsVariable) { + if (!ecs_id_is_wildcard(first_id) || first_id == EcsAny) { + trivial_term = false; + cacheable_term = false; + } + } + + if (second->id & EcsIsVariable) { + if (!ecs_id_is_wildcard(second_id) || second_id == EcsAny) { + trivial_term = false; + if (second_id != EcsAny) { + cacheable_term = false; + } + } + } + } + + if (!ecs_term_match_this(term)) { + trivial_term = false; + } + + if (term->flags_ & EcsTermTransitive) { + trivial_term = false; + cacheable_term = false; + } + + if (term->flags_ & EcsTermIdInherited) { + trivial_term = false; + cacheable_term = false; + } + + if (term->flags_ & EcsTermReflexive) { + trivial_term = false; + cacheable_term = false; + } + + if (term->trav && term->trav != EcsIsA) { + trivial_term = false; + } + + if (!(src->id & EcsSelf)) { + trivial_term = false; + } + + if (((ECS_TERM_REF_ID(&term->first) == EcsPredEq) || + (ECS_TERM_REF_ID(&term->first) == EcsPredMatch) || + (ECS_TERM_REF_ID(&term->first) == EcsPredLookup)) && + (term->first.id & EcsIsEntity)) + { + trivial_term = false; + cacheable_term = false; + } + + if (ECS_TERM_REF_ID(src) != EcsThis) { + cacheable_term = false; + } + + if (term->id == ecs_childof(0)) { + cacheable_term = false; + } + + if (term->flags_ & EcsTermIsMember) { + trivial_term = false; + cacheable_term = false; + } + + if (term->flags_ & EcsTermIsToggle) { + trivial_term = false; + } + + if (term->flags_ & EcsTermDontFragment) { + trivial_term = false; + cacheable_term = false; + } + + ECS_BIT_COND16(term->flags_, EcsTermIsTrivial, trivial_term); + ECS_BIT_COND16(term->flags_, EcsTermIsCacheable, cacheable_term); + + if (flecs_term_verify(world, term, ctx)) { + return -1; + } + + return 0; +} + +bool ecs_term_ref_is_set( + const ecs_term_ref_t *ref) +{ + return ECS_TERM_REF_ID(ref) != 0 || ref->name != NULL || (ref->id & EcsIsEntity) != 0; +} + +bool ecs_term_is_initialized( + const ecs_term_t *term) +{ + return term->id != 0 || ecs_term_ref_is_set(&term->first); +} + +bool ecs_term_match_this( + const ecs_term_t *term) +{ + return (term->src.id & EcsIsVariable) && + (ECS_TERM_REF_ID(&term->src) == EcsThis); +} + +bool ecs_term_match_0( + const ecs_term_t *term) +{ + return (!ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)); +} + +static +ecs_term_t* flecs_query_or_other_type( + ecs_query_t *q, + int32_t t) +{ + ecs_term_t *term = &q->terms[t]; + ecs_term_t *first = NULL; + while (t--) { + if (q->terms[t].oper != EcsOr) { + break; + } + first = &q->terms[t]; + } + + if (first) { + ecs_world_t *world = q->world; + const ecs_type_info_t *first_type = ecs_get_type_info(world, first->id); + const ecs_type_info_t *term_type = ecs_get_type_info(world, term->id); + + if (first_type == term_type) { + return NULL; + } + return first; + } else { + return NULL; + } +} + +static +void flecs_normalize_term_name( + ecs_term_ref_t *ref) +{ + if (ref->name && ref->name[0] == '$' && ref->name[1]) { + ecs_assert(ref->id & EcsIsVariable, ECS_INTERNAL_ERROR, NULL); + const char *old = ref->name; + ref->name = &old[1]; + + if (!ecs_os_strcmp(ref->name, "this")) { + ref->name = NULL; + ref->id |= EcsThis; + } + } +} + +static +int flecs_query_finalize_terms( + ecs_world_t *world, + ecs_query_t *q, + const ecs_query_desc_t *desc) +{ + int8_t i, term_count = q->term_count, field_count = 0; + ecs_term_t *terms = q->terms; + int32_t scope_nesting = 0, cacheable_terms = 0; + bool cond_set = false; + + ecs_query_validator_ctx_t ctx = {0}; + ctx.world = world; + ctx.query = q; + ctx.desc = desc; + + q->flags |= EcsQueryMatchOnlyThis; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->oper == EcsOr) { + term->flags_ |= EcsTermIsOr; + if (i != (term_count - 1)) { + term[1].flags_ |= EcsTermIsOr; + } + } + } + + bool cacheable = true; + bool match_nothing = true; + + /* If a query has ChildOf terms we need to prevent it from being marked as + * IsCacheable (meaning the query can be evaluated by just iterating the + * cache). A ChildOf term must be marked as cacheable, but also needs an + * instruction to filter tables that use non-fragmenting relationships. */ + bool has_childof = false; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + bool prev_is_or = i && term[-1].oper == EcsOr; + bool nodata_term = false; + bool default_src = term->src.id == 0 && term->src.name == NULL; + ctx.term_index = i; + + if (flecs_term_finalize(world, term, &ctx)) { + return -1; + } + + if (term->flags_ & EcsTermNonFragmentingChildOf) { + if (!i) { + /* If the first term is a ChildOf pair, the query result should + * respect the order of parents with the OrderedChildren trait. + * This cannot be cached, so unset the IsCacheable bit. */ + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); + } + + if (ECS_PAIR_SECOND(term->id) != EcsAny) { + has_childof = true; + } + } + + if (term->trav == EcsChildOf && (term->oper == EcsAnd || term->oper == EcsOptional)) { + if (!(term->flags_ & EcsTermIsOr)) { + has_childof = true; + } + } + + if (term->src.id != EcsIsEntity) { + /* If term doesn't match 0 entity, query doesn't match nothing */ + match_nothing = false; + } + + if (scope_nesting) { + /* Terms inside a scope are not cacheable */ + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); + } + + /* If one of the terms in an OR chain isn't cacheable, none are */ + if (term->flags_ & EcsTermIsCacheable) { + /* Current term is marked as cacheable. Check if it is part of an OR + * chain, and if so, the previous term was also cacheable. */ + if (prev_is_or) { + if (term[-1].flags_ & EcsTermIsCacheable) { + cacheable_terms ++; + } else { + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); + } + } else { + cacheable_terms ++; + } + + /* Toggle terms may be cacheable for fetching the initial component, + * but require an additional toggle instruction for evaluation. */ + if (term->flags_ & EcsTermIsToggle) { + cacheable = false; + } + } else if (prev_is_or) { + /* Current term is not cacheable. If it is part of an OR chain, mark + * previous terms in the chain as also not cacheable. */ + int32_t j; + for (j = i - 1; j >= 0; j --) { + if (terms[j].oper != EcsOr) { + break; + } + if (terms[j].flags_ & EcsTermIsCacheable) { + cacheable_terms --; + ECS_BIT_CLEAR16(terms[j].flags_, EcsTermIsCacheable); + } + } + } + + if (!prev_is_or) { + field_count ++; + } + + term->field_index = flecs_ito(int8_t, field_count - 1); + + if (ecs_id_is_wildcard(term->id)) { + if (term->oper != EcsNot || ecs_id_is_any(term->id)) { + q->flags |= EcsQueryMatchWildcards; + } + } else if (!(term->flags_ & EcsTermIsOr)) { + ECS_TERMSET_SET(q->static_id_fields, 1u << term->field_index); + } + + if (ECS_TERM_REF_ID(term) == EcsPrefab) { + ECS_BIT_SET(q->flags, EcsQueryMatchPrefab); + } + if (ECS_TERM_REF_ID(term) == EcsDisabled && (term->src.id & EcsSelf)) { + ECS_BIT_SET(q->flags, EcsQueryMatchDisabled); + } + + if (term->oper == EcsNot && term->inout == EcsInOutDefault) { + term->inout = EcsInOutNone; + } + + if ((term->id == EcsWildcard) || (term->id == + ecs_pair(EcsWildcard, EcsWildcard))) + { + /* If term type is unknown beforehand, default the inout type to + * none. This prevents accidentally requesting lots of components, + * which can put stress on serializer code. */ + if (term->inout == EcsInOutDefault) { + term->inout = EcsInOutNone; + } + } + + if (term->src.id == EcsIsEntity) { + nodata_term = true; + } else if (term->inout == EcsInOutNone) { + nodata_term = true; + } else if (!ecs_get_type_info(world, term->id)) { + nodata_term = true; + } else if (term->flags_ & EcsTermIsMember) { + nodata_term = true; + } else if (scope_nesting) { + nodata_term = true; + } else { + if (ecs_id_is_tag(world, term->id)) { + nodata_term = true; + } else if ((ECS_PAIR_SECOND(term->id) == EcsWildcard) || + (ECS_PAIR_SECOND(term->id) == EcsAny)) + { + /* If the second element of a pair is a wildcard and the first + * element is not a type, we can't know in advance what the + * type of the term is, so it can't provide data. */ + if (!ecs_get_type_info(world, ecs_pair_first(world, term->id))) { + nodata_term = true; + } + } + } + + if (!nodata_term && term->inout != EcsIn && term->inout != EcsInOutNone) { + /* Non-this terms default to EcsIn */ + if (ecs_term_match_this(term) || term->inout != EcsInOutDefault) { + q->flags |= EcsQueryHasOutTerms; + } + + bool match_non_this = !ecs_term_match_this(term) || + (term->src.id & EcsUp); + if (match_non_this && term->inout != EcsInOutDefault) { + q->flags |= EcsQueryHasNonThisOutTerms; + } + } + + if (!nodata_term) { + /* If terms in an OR chain do not all return the same type, the + * field will not provide any data */ + if (term->flags_ & EcsTermIsOr) { + ecs_term_t *first = flecs_query_or_other_type(q, i); + if (first) { + nodata_term = true; + } + q->data_fields &= (ecs_termset_t)~(1llu << term->field_index); + } + } + + if (term->flags_ & EcsTermIsMember) { + nodata_term = false; + } + + if (!nodata_term && term->oper != EcsNot) { + ECS_TERMSET_SET(q->data_fields, 1u << term->field_index); + + if (term->inout != EcsIn) { + ECS_TERMSET_SET(q->write_fields, 1u << term->field_index); + } + if (term->inout != EcsOut) { + ECS_TERMSET_SET(q->read_fields, 1u << term->field_index); + } + if (term->inout == EcsInOutDefault) { + ECS_TERMSET_SET(q->shared_readonly_fields, + 1u << term->field_index); + } + } + + bool is_sparse = false; + + ecs_component_record_t *cr = flecs_components_get(world, term->id); + ecs_flags32_t cr_flags = 0; + + if (cr) { + cr_flags = cr->flags; + } else if (term->id) { + cr_flags = flecs_component_get_flags(world, term->id); + } + + if (cr_flags & EcsIdSparse) { + is_sparse = true; + } + + if (cr_flags & EcsIdSingleton) { + if (default_src) { + term->src.id = term->first.id|EcsSelf|EcsIsEntity; + + ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); + if (term->flags_ & EcsTermIsCacheable) { + cacheable_terms --; + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); + } + } + } + + /* If this is a static field, we need to assume that we might have + * to do change detection. */ + if (term->src.id & EcsIsEntity) { + if (term->id < FLECS_HI_COMPONENT_ID) { + world->non_trivial_set[term->id] = true; + } + } + + if (ecs_term_match_this(term)) { + ECS_BIT_SET(q->flags, EcsQueryMatchThis); + } else { + ECS_BIT_CLEAR(q->flags, EcsQueryMatchOnlyThis); + } + + if (ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)) { + ECS_TERMSET_SET(q->fixed_fields, 1u << term->field_index); + } + + if ((term->src.id & EcsIsVariable) && + (ECS_TERM_REF_ID(&term->src) != EcsThis)) + { + ECS_TERMSET_SET(q->var_fields, 1u << term->field_index); + } + + if (prev_is_or) { + if (ECS_TERM_REF_ID(&term[-1].src) != ECS_TERM_REF_ID(&term->src)) { + flecs_query_validator_error(&ctx, + "mismatching sources in OR expression (all terms in OR " + "expression must have the same source)"); + return -1; + } + if (term->oper != EcsOr && term->oper != EcsAnd) { + flecs_query_validator_error(&ctx, + "term after OR expression cannot use operators"); + return -1; + } + } + + if (is_sparse) { + term->flags_ |= EcsTermIsSparse; + ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); + if (term->flags_ & EcsTermIsCacheable) { + cacheable_terms --; + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); + } + + /* Sparse component fields must be accessed with ecs_field_at */ + if (!nodata_term) { + q->row_fields |= flecs_uto(uint32_t, 1llu << i); + } + } + + if (term->oper == EcsOptional || term->oper == EcsNot) { + cond_set = true; + } + + ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); + if (first_id == EcsPredEq || first_id == EcsPredMatch || + first_id == EcsPredLookup) + { + q->flags |= EcsQueryHasPred; + term->src.id = (term->src.id & ~EcsTraverseFlags) | EcsSelf; + term->inout = EcsInOutNone; + } else { + if (!ecs_term_match_0(term) && term->oper != EcsNot && + term->oper != EcsNotFrom) + { + ECS_TERMSET_SET(q->set_fields, 1u << term->field_index); + } + } + + if (first_id == EcsScopeOpen) { + q->flags |= EcsQueryHasScopes; + scope_nesting ++; + } + + if (scope_nesting) { + term->flags_ |= EcsTermIsScope; + ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); + cacheable_terms --; + } + + if (first_id == EcsScopeClose) { + if (i && ECS_TERM_REF_ID(&terms[i - 1].first) == EcsScopeOpen) { + flecs_query_validator_error(&ctx, "invalid empty scope"); + return -1; + } + + q->flags |= EcsQueryHasScopes; + scope_nesting --; + } + + if (scope_nesting < 0) { + flecs_query_validator_error(&ctx, "'}' without matching '{'"); + return -1; + } + } + + if (scope_nesting != 0) { + flecs_query_validator_error(&ctx, "missing '}'"); + return -1; + } + + if (term_count && (terms[term_count - 1].oper == EcsOr)) { + flecs_query_validator_error(&ctx, + "last term of query can't have OR operator"); + return -1; + } + + q->field_count = flecs_ito(int8_t, field_count); + + if (field_count) { + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + int32_t field = term->field_index; + q->ids[field] = term->id; + + if (!ecs_term_match_0(term)) { + flecs_component_lock(world, term->id); + } + + if (term->flags_ & EcsTermIsOr) { + if (flecs_query_or_other_type(q, i)) { + q->sizes[field] = 0; + q->ids[field] = 0; + continue; + } + } + + ecs_component_record_t *cr = flecs_components_get(world, term->id); + if (cr) { + if (!ECS_IS_PAIR(cr->id) || ECS_PAIR_FIRST(cr->id) != EcsWildcard) { + if (cr->type_info) { + q->sizes[field] = cr->type_info->size; + q->ids[field] = cr->id; + } + } + } else { + const ecs_type_info_t *ti = ecs_get_type_info( + world, term->id); + if (ti) { + q->sizes[field] = ti->size; + q->ids[field] = term->id; + } + } + } + } + + ECS_BIT_COND(q->flags, EcsQueryHasCondSet, cond_set); + + /* Check if this is a trivial query */ + if ((q->flags & EcsQueryMatchOnlyThis)) { + if (!(q->flags & + (EcsQueryHasPred|EcsQueryMatchDisabled|EcsQueryMatchPrefab))) + { + ECS_BIT_SET(q->flags, EcsQueryMatchOnlySelf); + + bool is_trivial = true; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *src = &term->src; + + if (src->id & EcsUp) { + ECS_BIT_CLEAR(q->flags, EcsQueryMatchOnlySelf); + } + + if (ECS_TERM_REF_ID(&term->first) == EcsChildOf) { + if (ECS_TERM_REF_ID(&term->second) != 0) { + is_trivial = false; + continue; + } + } + + if (!(term->flags_ & EcsTermIsTrivial)) { + is_trivial = false; + continue; + } + + if ((term->src.id & EcsTraverseFlags) == EcsSelf) { + if (!ecs_id_is_wildcard(term->id)) { + + q->bloom_filter = flecs_table_bloom_filter_add( + q->bloom_filter, term->id); + } + } + } + + if (term_count && is_trivial) { + ECS_BIT_SET(q->flags, EcsQueryIsTrivial); + } + } + } + + /* Set cacheable flags */ + ECS_BIT_COND(q->flags, EcsQueryHasCacheable, + cacheable_terms != 0); + + /* Exclude queries with order_by from setting the IsCacheable flag. This + * allows the routine that evaluates entirely cached queries to use more + * optimized logic as it doesn't have to deal with order_by edge cases */ + ECS_BIT_COND(q->flags, EcsQueryIsCacheable, + cacheable && (cacheable_terms == term_count) && + !desc->order_by_callback && + !has_childof); + + ECS_BIT_COND(q->flags, EcsQueryCacheWithFilter, has_childof); + + /* If none of the terms match a source, the query matches nothing */ + ECS_BIT_COND(q->flags, EcsQueryMatchNothing, match_nothing); + + for (i = 0; i < q->term_count; i ++) { + ecs_term_t *term = &q->terms[i]; + /* Post process term names in case they were used to create variables */ + flecs_normalize_term_name(&term->first); + flecs_normalize_term_name(&term->second); + flecs_normalize_term_name(&term->src); + } + + return 0; +} + +static +int flecs_query_query_populate_terms( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_query_t *q, + const ecs_query_desc_t *desc) +{ + /* Count number of initialized terms in desc->terms */ + int32_t i, term_count = 0; + for (i = 0; i < FLECS_TERM_COUNT_MAX; i ++) { + if (!ecs_term_is_initialized(&desc->terms[i])) { + break; + } + term_count ++; + } + + /* Copy terms from array to query */ + if (term_count) { + ecs_os_memcpy_n(q->terms, desc->terms, ecs_term_t, term_count); + } + + /* Parse query expression if set */ + const char *expr = desc->expr; + if (expr && expr[0]) { + #ifdef FLECS_QUERY_DSL + const char *name = desc->entity ? + ecs_get_name(world, desc->entity) : NULL; + + /* Allocate buffer that's large enough to tokenize the query string */ + ecs_size_t token_buffer_size = ecs_os_strlen(expr) * 2 + 1; + char *token_buffer = flecs_alloc( + &flecs_query_impl(q)->stage->allocator, token_buffer_size); + + if (flecs_terms_parse(world, name, expr, token_buffer, &q->terms[term_count], + &term_count)) + { + flecs_free(&stage->allocator, token_buffer_size, token_buffer); + goto error; + } + + /* Store on query object so we can free later */ + flecs_query_impl(q)->tokens = token_buffer; + flecs_query_impl(q)->tokens_len = flecs_ito(int16_t, token_buffer_size); + #else + (void)world; + (void)stage; + ecs_err("parsing query expressions requires the FLECS_QUERY_DSL addon"); + goto error; + #endif + } + + q->term_count = flecs_ito(int8_t, term_count); + + return 0; +error: + return -1; +} + +bool flecs_query_finalize_simple( + ecs_world_t *world, + ecs_query_t *q, + const ecs_query_desc_t *desc) +{ + ecs_assert(q->terms != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(q->sizes != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(q->ids != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Filter out queries that aren't simple enough */ + if (desc->expr) { + return false; + } + + if (desc->order_by_callback || desc->group_by_callback) { + return false; + } + + int8_t i, term_count; + for (i = 0; i < FLECS_TERM_COUNT_MAX; i ++) { + const ecs_term_t *term = &desc->terms[i]; + if (!ecs_term_is_initialized(&desc->terms[i])) { + break; + } + + ecs_id_t id = term->id; + if (ecs_id_is_wildcard(id)) { + return false; + } + + if (id == EcsThis || ECS_PAIR_FIRST(id) == EcsThis || + ECS_PAIR_SECOND(id) == EcsThis) + { + return false; + } + + if (id == EcsVariable || ECS_PAIR_FIRST(id) == EcsVariable || + ECS_PAIR_SECOND(id) == EcsVariable) + { + return false; + } + + if (id == EcsPrefab || id == EcsDisabled) { + return false; + } + + ecs_term_t cmp_term = { + .id = id, + .flags_ = term->flags_, + .field_index = term->field_index + }; + + if (term->src.id == (EcsThis|EcsSelf|EcsIsVariable)) { + cmp_term.src.id = EcsThis|EcsSelf|EcsIsVariable; + } else if (term->src.id == EcsSelf) { + cmp_term.src.id = EcsSelf; + } + + if (term->first.id == (term->id|EcsSelf|EcsIsEntity)) { + cmp_term.first.id = term->id|EcsSelf|EcsIsEntity; + } + + if (ecs_os_memcmp_t(&cmp_term, term, ecs_term_t)) { + return false; + } + } + + if (!i) { + return false; /* No terms */ + } + + term_count = i; + ecs_os_memcpy_n(q->terms, desc->terms, ecs_term_t, term_count); + + /* All fields are InOut */ + q->write_fields = (1u << term_count) - 1; + + /* Simple query that only queries for component ids */ + + /* Populate terms */ + bool has_this = false, has_only_this = true; + int8_t cacheable_count = 0, trivial_count = 0, up_count = 0; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &q->terms[i]; + ecs_id_t id = term->id; + + ecs_entity_t first = id; + if (ECS_IS_PAIR(id)) { + ecs_entity_t second = flecs_entities_get_alive(world, + ECS_PAIR_SECOND(id)); + first = flecs_entities_get_alive(world, ECS_PAIR_FIRST(id)); + term->second.id = second | EcsIsEntity | EcsSelf; + } + + bool is_self = term->src.id == EcsSelf; + bool default_src = term->src.id == 0; + + term->field_index = i; + term->first.id = first | EcsIsEntity | EcsSelf; + term->src.id = EcsThis | EcsIsVariable | EcsSelf; + + q->ids[i] = id; + + ecs_component_record_t *cr = NULL; +#ifndef FLECS_SANITIZE + cr = flecs_components_get(world, id); +#else + /* In sanitized mode, always compute the component flags on the spot + * instead of using the flags cached on the component record. This + * ensures that both code paths get the same kind of test coverage. */ +#endif + + const ecs_type_info_t *type_info = NULL; + ecs_flags32_t cr_flags = 0; + if (cr) { + cr_flags = cr->flags; + type_info = cr->type_info; + } else { + cr_flags = flecs_component_get_flags(world, id); + type_info = flecs_determine_type_info_for_component(world, id); + } + + bool cacheable = true, trivial = true; + + if (type_info) { + q->sizes[i] = type_info->size; + q->flags |= EcsQueryHasOutTerms; + q->data_fields |= (ecs_termset_t)(1llu << i); + } + + if (!is_self && cr_flags & EcsIdOnInstantiateInherit) { + term->src.id |= EcsUp; + term->trav = EcsIsA; + up_count ++; + } + + if (cr_flags & EcsIdCanToggle) { + term->flags_ |= EcsTermIsToggle; + trivial = false; + } + + if (cr_flags & EcsIdDontFragment) { + term->flags_ |= EcsTermDontFragment; + trivial = false; + } + + if (cr_flags & EcsIdSparse) { + term->flags_ |= EcsTermIsSparse; + cacheable = false; trivial = false; + q->row_fields |= flecs_uto(uint32_t, 1llu << i); + } + + if (cr_flags & EcsIdSingleton) { + if (default_src) { + term->src.id = term->first.id|EcsSelf|EcsIsEntity; + has_only_this = false; + cacheable = false; trivial = false; + } + } else { + has_this = true; + } + + if (ECS_IS_PAIR(id)) { + if (first == EcsChildOf) { + if (term->oper == EcsAnd) { + term->flags_ |= EcsTermNonFragmentingChildOf; + } + + ecs_entity_t second = ECS_PAIR_SECOND(id); + if (second) { + trivial = false; + if (ECS_PAIR_SECOND(id) != EcsAny) { + if (!i) { + /* If first query term is ChildOf, return children + * in order if possible, which can't be cached. */ + cacheable = false; + } + } + } + } + + if (ecs_has_id(world, first, EcsTransitive)) { + term->flags_ |= EcsTermTransitive; + trivial = false; + cacheable = false; + } + + if (ecs_has_id(world, first, EcsReflexive)) { + term->flags_ |= EcsTermReflexive; + trivial = false; + cacheable = false; + } + } + + if (flecs_components_get(world, ecs_pair(EcsIsA, first)) != NULL) { + term->flags_ |= EcsTermIdInherited; + cacheable = false; trivial = false; + } + + if (cacheable) { + term->flags_ |= EcsTermIsCacheable; + cacheable_count ++; + } + + if (trivial) { + term->flags_ |= EcsTermIsTrivial; + trivial_count ++; + + if ((term->src.id & EcsTraverseFlags) == EcsSelf) { + q->bloom_filter = flecs_table_bloom_filter_add( + q->bloom_filter, id); + } + } + + flecs_component_lock(world, id); + } + + /* Initialize static data */ + q->term_count = term_count; + q->field_count = term_count; + q->set_fields = (ecs_termset_t)((1llu << i) - 1); + q->static_id_fields = (ecs_termset_t)((1llu << i) - 1); + + if (has_this) { + q->flags |= EcsQueryHasTableThisVar|EcsQueryMatchThis; + } + + if (has_only_this) { + q->flags |= EcsQueryMatchOnlyThis; + } + + if (cacheable_count) { + q->flags |= EcsQueryHasCacheable; + } + + if (cacheable_count == term_count && trivial_count == term_count) { + q->flags |= EcsQueryIsCacheable|EcsQueryIsTrivial; + } + + if (!up_count) { + q->flags |= EcsQueryMatchOnlySelf; + } + + return true; +} + +static +char* flecs_query_append_token( + char *dst, + const char *src) +{ + int32_t len = ecs_os_strlen(src); + ecs_os_memcpy(dst, src, len + 1); + return dst + len + 1; +} + +static +void flecs_query_populate_tokens( + ecs_query_impl_t *impl) +{ + ecs_query_t *q = &impl->pub; + int32_t i, term_count = q->term_count; + + char *old_tokens = impl->tokens; + int32_t old_tokens_len = impl->tokens_len; + impl->tokens = NULL; + impl->tokens_len = 0; + + /* Step 1: determine size of token buffer */ + int32_t len = 0; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &q->terms[i]; + + if (term->first.name) { + len += ecs_os_strlen(term->first.name) + 1; + } + if (term->second.name) { + len += ecs_os_strlen(term->second.name) + 1; + } + if (term->src.name) { + len += ecs_os_strlen(term->src.name) + 1; + } + } + + /* Step 2: reassign term tokens to buffer */ + if (len) { + impl->tokens = flecs_alloc(&impl->stage->allocator, len); + impl->tokens_len = flecs_ito(int16_t, len); + char *token = impl->tokens, *next; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &q->terms[i]; + if (term->first.name) { + next = flecs_query_append_token(token, term->first.name); + term->first.name = token; + token = next; + } + if (term->second.name) { + next = flecs_query_append_token(token, term->second.name); + term->second.name = token; + token = next; + } + if (term->src.name) { + next = flecs_query_append_token(token, term->src.name); + term->src.name = token; + token = next; + } + } + } + + if (old_tokens) { + flecs_free(&impl->stage->allocator, old_tokens_len, old_tokens); + } +} + +int flecs_query_finalize_query( + ecs_world_t *world, + ecs_query_t *q, + const ecs_query_desc_t *desc) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_query_desc_t was not initialized to zero"); + ecs_stage_t *stage = flecs_stage_from_world(&world); + + q->flags |= desc->flags | world->default_query_flags; + + ecs_term_t terms[FLECS_TERM_COUNT_MAX] = {0}; + ecs_size_t sizes[FLECS_TERM_COUNT_MAX] = {0}; + ecs_id_t ids[FLECS_TERM_COUNT_MAX] = {0}; + + q->terms = terms; + q->sizes = sizes; + q->ids = ids; + + /* Fast routine that initializes simple queries and skips complex validation + * logic if it's not needed. When running in sanitized mode, always take the + * slow path. This in combination with the test suite ensures that the + * result of the fast & slow code is the same. */ + #ifndef FLECS_SANITIZE + if (flecs_query_finalize_simple(world, q, desc)) { + goto done; + } + #endif + + /* Populate term array from desc terms & DSL expression */ + if (flecs_query_query_populate_terms(world, stage, q, desc)) { + goto error; + } + + /* Ensure all fields are consistent and properly filled out */ + if (flecs_query_finalize_terms(world, q, desc)) { + goto error; + } + + /* Store remaining string tokens in terms (after entity lookups) in single + * token buffer which simplifies memory management & reduces allocations. */ + flecs_query_populate_tokens(flecs_query_impl(q)); + + #ifndef FLECS_SANITIZE +done: + #endif + + flecs_query_copy_arrays(q); + + q->flags |= EcsQueryValid; + + return 0; +error: + flecs_query_copy_arrays(q); + return -1; +} + +/** + * @file storage/component_index.c + * @brief Index for looking up tables by component id. + * + * An component record stores the administration for an in use (component) id, that is + * an id that has been used in tables. + * + * An component record contains a table cache, which stores the list of tables that + * have the id. Each entry in the cache (a table record) stores the first + * occurrence of the id in the table and the number of occurrences of the id in + * the table (in the case of wildcard ids). + * + * Id records are used in lots of scenarios, like uncached queries, or for + * getting a component array/component for an entity. + */ + + +static +ecs_id_record_elem_t* flecs_component_elem( + ecs_component_record_t *head, + ecs_id_record_elem_t *list, + ecs_component_record_t *cr) +{ + return ECS_OFFSET(cr->pair, (uintptr_t)list - (uintptr_t)head->pair); +} + +static +void flecs_component_elem_insert( + ecs_component_record_t *head, + ecs_component_record_t *cr, + ecs_id_record_elem_t *elem) +{ + ecs_id_record_elem_t *head_elem = flecs_component_elem(cr, elem, head); + ecs_component_record_t *cur = head_elem->next; + elem->next = cur; + elem->prev = head; + if (cur) { + ecs_id_record_elem_t *cur_elem = flecs_component_elem(cr, elem, cur); + cur_elem->prev = cr; + } + head_elem->next = cr; +} + +static +void flecs_component_elem_remove( + ecs_component_record_t *cr, + ecs_id_record_elem_t *elem) +{ + ecs_component_record_t *prev = elem->prev; + ecs_component_record_t *next = elem->next; + ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_record_elem_t *prev_elem = flecs_component_elem(cr, elem, prev); + prev_elem->next = next; + if (next) { + ecs_id_record_elem_t *next_elem = flecs_component_elem(cr, elem, next); + next_elem->prev = prev; + } +} + +static +void flecs_insert_id_elem( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_id_t wildcard, + ecs_component_record_t *wcr) +{ + ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); + if (!wcr) { + wcr = flecs_components_ensure(world, wildcard); + } + ecs_assert(wcr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_pair_record_t *pair = cr->pair; + ecs_assert(pair != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { + ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_component_elem_insert(wcr, cr, &pair->first); + } else { + ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_component_elem_insert(wcr, cr, &pair->second); + + if (cr->flags & EcsIdTraversable) { + flecs_component_elem_insert(wcr, cr, &pair->trav); + } + } +} + +static +void flecs_remove_id_elem( + ecs_component_record_t *cr, + ecs_id_t wildcard) +{ + ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); + + ecs_pair_record_t *pair = cr->pair; + ecs_assert(pair != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { + ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_component_elem_remove(cr, &pair->first); + } else { + ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + if (pair->second.prev) { + flecs_component_elem_remove(cr, &pair->second); + } + + if (cr->flags & EcsIdTraversable) { + flecs_component_elem_remove(cr, &pair->trav); + } + } +} + +static +ecs_id_t flecs_component_hash( + ecs_id_t id) +{ + id = ecs_strip_generation(id); + if (ECS_IS_PAIR(id)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + ecs_entity_t t = ECS_PAIR_SECOND(id); + + if (ECS_IS_VALUE_PAIR(id)) { + id = ecs_value_pair(r, t); + } else { + if (r == EcsAny) { + r = EcsWildcard; + } + if (t == EcsAny) { + t = EcsWildcard; + } + id = ecs_pair(r, t); + } + } + return id; +} + +void flecs_component_init_sparse( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + if (!ecs_id_is_wildcard(cr->id)) { + if (!cr->sparse) { + if (cr->flags & EcsIdSparse) { + cr->sparse = flecs_walloc_t(world, ecs_sparse_t); + if (cr->type_info) { + flecs_sparse_init(cr->sparse, NULL, NULL, cr->type_info->size); + } else { + flecs_sparse_init(cr->sparse, NULL, NULL, 0); + } + } + } + + if (cr->id < FLECS_HI_COMPONENT_ID) { + world->non_trivial_lookup[cr->id] |= EcsNonTrivialIdSparse; + } + } else if (ECS_IS_PAIR(cr->id)) { + if (cr->flags & EcsIdDontFragment) { + if (cr->sparse) { + ecs_assert(flecs_sparse_count(cr->sparse) == 0, + ECS_INTERNAL_ERROR, NULL); + flecs_sparse_fini(cr->sparse); + } else { + cr->sparse = flecs_walloc_t(world, ecs_sparse_t); + } + + if (cr->flags & EcsIdExclusive) { + flecs_sparse_init_t(cr->sparse, NULL, NULL, ecs_entity_t); + } else { + flecs_sparse_init_t(cr->sparse, NULL, NULL, ecs_type_t); + } + } + } +} + +void flecs_component_record_init_dont_fragment( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + if (world->cr_non_fragmenting_head) { + world->cr_non_fragmenting_head->non_fragmenting.prev = cr; + } + + cr->non_fragmenting.next = world->cr_non_fragmenting_head; + world->cr_non_fragmenting_head = cr; + + if (cr->id < FLECS_HI_COMPONENT_ID) { + world->non_trivial_lookup[cr->id] |= EcsNonTrivialIdNonFragmenting; + } + + flecs_component_init_sparse(world, cr); + + ecs_vec_init_t(&world->allocator, &cr->dont_fragment_tables, uint64_t, 0); +} + +static +void flecs_component_record_fini_dont_fragment( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + if (world->cr_non_fragmenting_head == cr) { + world->cr_non_fragmenting_head = cr->non_fragmenting.next; + } + + if (cr->non_fragmenting.prev) { + cr->non_fragmenting.prev->non_fragmenting.next = + cr->non_fragmenting.next; + } + + if (cr->non_fragmenting.next) { + cr->non_fragmenting.next->non_fragmenting.prev = + cr->non_fragmenting.prev; + } + + int32_t i, count = ecs_vec_count(&cr->dont_fragment_tables); + uint64_t *tables = ecs_vec_first(&cr->dont_fragment_tables); + for (i = 0; i < count; i ++) { + uint64_t table_id = tables[i]; + ecs_table_t *table = NULL; + if (table_id) { + table = flecs_sparse_get_t( + &world->store.tables, ecs_table_t, table_id); + } else { + table = &world->store.root; + } + + if (table) { + flecs_table_clear_edges_for_id(world, table, cr->id); + } else { + /* Table was deleted */ + } + } + + ecs_vec_fini_t(&world->allocator, &cr->dont_fragment_tables, uint64_t); +} + +void flecs_component_record_init_exclusive( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + flecs_component_init_sparse(world, cr); +} + +static +void flecs_component_fini_sparse( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + if (cr->sparse) { + ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); + if (cr->flags & EcsIdDontFragment) { + flecs_component_sparse_remove_all(world, cr); + } + + flecs_sparse_fini(cr->sparse); + flecs_wfree_t(world, ecs_sparse_t, cr->sparse); + } +} + +static +ecs_flags32_t flecs_component_event_flags( + const ecs_world_t *world, + ecs_id_t id) +{ + const ecs_observable_t *o = &world->observable; + ecs_flags32_t result = 0; + result |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; + result |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; + result |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; + result |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; + result |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; + result |= flecs_observers_exist(o, id, EcsWildcard) * ( + EcsIdHasOnAdd + |EcsIdHasOnRemove + |EcsIdHasOnSet + |EcsIdHasOnTableCreate + |EcsIdHasOnTableDelete); + + return result; +} + +void flecs_component_ordered_children_init( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + cr->flags |= EcsIdOrderedChildren; + flecs_ordered_children_init(world, cr); +} + +static +ecs_flags32_t flecs_component_get_flags_intern( + const ecs_world_t *world, + ecs_id_t id, + ecs_entity_t rel, + ecs_entity_t tgt, + const ecs_type_info_t *ti) +{ + ecs_flags32_t result = 0; + + if (id & ECS_ID_FLAGS_MASK) { + if ((id & ECS_ID_FLAGS_MASK) != ECS_PAIR) { + return 0; + } + } + + ecs_record_t *r = flecs_entities_get_any(world, rel); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + + result = table->trait_flags; + + if (tgt) { + if (ti && (ti->component == tgt)) { + /* Target determines the type, so unset storage flags */ + result &= ~EcsIdSparse; + result &= ~EcsIdDontFragment; + + if (ecs_owns_id(world, tgt, EcsSparse)) { + result |= EcsIdSparse; + } + + if (ecs_owns_id(world, tgt, EcsDontFragment)) { + result |= EcsIdDontFragment; + } + } + } else { + /* Disable flags that only apply to pairs */ + result &= ~(EcsIdExclusive|EcsIdTraversable|EcsIdPairIsTag|EcsIdIsTransitive); + } + + return result; +} + +ecs_flags32_t flecs_component_get_flags( + const ecs_world_t *world, + ecs_id_t id) +{ + const ecs_component_record_t *cr = flecs_components_get(world, id); + if (cr) { + return cr->flags; + } + + const ecs_type_info_t *ti = flecs_determine_type_info_for_component(world, id); + ecs_entity_t rel = 0, tgt = 0; + + if (ECS_IS_PAIR(id)) { + rel = ECS_PAIR_FIRST(id); + rel = flecs_entities_get_alive(world, rel); + + tgt = ECS_PAIR_SECOND(id); + tgt = flecs_entities_get_alive(world, tgt); + } else { + rel = id & ECS_COMPONENT_MASK; + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + } + + return flecs_component_get_flags_intern(world, id, rel, tgt, ti); +} + +static +void flecs_component_record_check_constraints( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_entity_t rel, + ecs_entity_t tgt) +{ + (void)world; + (void)cr; + (void)rel; + (void)tgt; + +#ifdef FLECS_DEBUG + if (ECS_IS_PAIR(cr->id)) { + if (tgt) { + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + /* Can't use relationship as target */ + if (ecs_has_id(world, tgt, EcsRelationship)) { + if (!ecs_id_is_wildcard(rel) && + !ecs_has_id(world, rel, EcsTrait)) + { + ecs_throw(ECS_CONSTRAINT_VIOLATED, "cannot use '%s' as target" + " in pair '%s': '%s' has the Relationship trait", + flecs_errstr(ecs_get_path(world, tgt)), + flecs_errstr_1(ecs_id_str(world, cr->id)), + flecs_errstr_2(ecs_get_path(world, tgt))); + } + } + } + + if (ecs_has_id(world, rel, EcsTarget)) { + ecs_throw(ECS_CONSTRAINT_VIOLATED, "cannot use '%s' as relationship " + "in pair '%s': '%s' has the Target trait", + flecs_errstr(ecs_get_path(world, rel)), + flecs_errstr_1(ecs_id_str(world, cr->id)), + flecs_errstr_2(ecs_get_path(world, rel))); + } + + if (tgt && !ecs_id_is_wildcard(tgt)) { + /* Check if target of relationship satisfies OneOf property */ + ecs_entity_t oneof = flecs_get_oneof(world, rel); + if (oneof) { + if (!ecs_has_pair(world, tgt, EcsChildOf, oneof)) { + if (oneof == rel) { + ecs_throw(ECS_CONSTRAINT_VIOLATED, + "cannot use '%s' as target in pair '%s': " + "relationship '%s' has the OneOf trait and '%s' " + "is not a child of '%s'", + flecs_errstr(ecs_get_path(world, tgt)), + flecs_errstr_1(ecs_id_str(world, cr->id)), + flecs_errstr_2(ecs_get_path(world, rel)), + flecs_errstr_3(ecs_get_path(world, tgt)), + flecs_errstr_4(ecs_get_path(world, rel))); + } else { + ecs_throw(ECS_CONSTRAINT_VIOLATED, + "cannot use '%s' as target in pair '%s': " + "relationship '%s' has (OneOf, %s) and '%s' " + "is not a child of '%s'", + flecs_errstr(ecs_get_path(world, tgt)), + flecs_errstr_1(ecs_id_str(world, cr->id)), + flecs_errstr_2(ecs_get_path(world, rel)), + flecs_errstr_3(ecs_get_path(world, oneof)), + flecs_errstr_4(ecs_get_path(world, tgt)), + flecs_errstr_5(ecs_get_path(world, oneof))); + } + } + } + + /* Check if we're not trying to inherit from a final target */ + if (rel == EcsIsA) { + if (ecs_has_id(world, tgt, EcsFinal)) { + ecs_throw(ECS_CONSTRAINT_VIOLATED, + "cannot add '(IsA, %s)': '%s' has the Final trait", + flecs_errstr(ecs_get_path(world, tgt)), + flecs_errstr_1(ecs_get_path(world, tgt))); + } + + if (flecs_component_is_trait_locked(world, tgt)) { + if (!ecs_has_id(world, tgt, EcsInheritable) && !ecs_has_pair(world, tgt, EcsIsA, EcsWildcard)) { + ecs_throw(ECS_INVALID_OPERATION, + "cannot add '(IsA, %s)': '%s' is already queried for", + flecs_errstr(ecs_get_path(world, tgt)), + flecs_errstr_1(ecs_get_path(world, tgt))); + } + } + } + } + } else { + bool is_tgt = false; + if (ecs_has_id(world, rel, EcsRelationship) || + (is_tgt = ecs_has_id(world, rel, EcsTarget))) + { + if (is_tgt) { + ecs_throw(ECS_CONSTRAINT_VIOLATED, + "cannot use '%s' by itself: it has the Target trait and " + "must be used in pair with relationship", + flecs_errstr(ecs_get_path(world, rel))); + } else { + ecs_throw(ECS_CONSTRAINT_VIOLATED, + "cannot use '%s' by itself: it has the Relationship trait " + "and must be used in pair with target", + flecs_errstr(ecs_get_path(world, rel))); + } + } + } +error: + return; +#endif +} + +static +ecs_component_record_t* flecs_component_new( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_component_record_t *cr, *cr_t = NULL; + ecs_id_t hash = flecs_component_hash(id); + cr = flecs_bcalloc_w_dbg_info( + &world->allocators.component_record, "ecs_component_record_t"); + + if (hash >= FLECS_HI_ID_RECORD_ID) { + ecs_map_insert_ptr(&world->id_index_hi, hash, cr); + } else { + world->id_index_lo[hash] = cr; + } + + ecs_table_cache_init(world, &cr->cache); + + cr->id = id; + cr->refcount = 1; + + bool is_wildcard = ecs_id_is_wildcard(id); + bool is_pair = ECS_IS_PAIR(id); + bool is_value_pair = ECS_IS_VALUE_PAIR(id); + + ecs_entity_t rel = 0, tgt = 0; + ecs_table_t *tgt_table = NULL; + ecs_record_t *tgt_r = NULL; + + if (is_pair) { + cr->pair = flecs_bcalloc_w_dbg_info( + &world->allocators.pair_record, "ecs_pair_record_t"); + cr->pair->reachable.current = -1; + + rel = ECS_PAIR_FIRST(id); + if (!is_value_pair) { + tgt = ECS_PAIR_SECOND(id); + } + + /* Link with (R, *) record so we can easily find all matching pairs. */ + if (!is_wildcard && (rel != EcsFlag)) { + ecs_id_t parent_id = ecs_pair(rel, EcsWildcard); + ecs_component_record_t *cr_r = flecs_components_ensure( + world, parent_id); + cr->pair->parent = cr_r; + cr->flags = cr_r->flags; + + /* If pair is not a wildcard, append it to wildcard lists. These + * allow for quickly enumerating all relationships for an object, + * or all objects for a relationship. */ + flecs_insert_id_elem(world, cr, parent_id, cr_r); + + if (tgt) { + ecs_id_t wc_tgt = ecs_pair(EcsWildcard, tgt); + cr_t = flecs_components_ensure(world, wc_tgt); + flecs_insert_id_elem(world, cr, wc_tgt, cr_t); + } + } + + /* Relationship object can be 0, as tables without a ChildOf + * relationship are added to the (ChildOf, 0) component record */ + if (tgt) { + ecs_entity_t alive_tgt = flecs_entities_get_alive(world, tgt); + ecs_assert(alive_tgt != 0, ECS_INVALID_PARAMETER, + "target '%s' of pair '%s' is not alive", + flecs_errstr(ecs_id_str(world, tgt)), + flecs_errstr_1(ecs_id_str(world, cr->id))); + + tgt = alive_tgt; + + if (tgt != EcsWildcard) { + tgt_r = flecs_entities_get(world, tgt); + ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); + tgt_table = tgt_r->table; + + /* Enable flag that indicates entity is a target. */ + flecs_record_add_flag(tgt_r, EcsEntityIsTarget); + + /* ChildOf records can be created often and since we already know + * the traits a ChildOf record will have, set them directly. */ + if (rel == EcsChildOf) { + /* Add flag to indicate that children are prefab children. + * This is used to determine whether to build the lookup map + * that allows for: + * instance_child = instance.target(prefab_child); + */ + if (tgt_table->flags & EcsTableIsPrefab) { + cr->flags |= EcsIdPrefabChildren; + } + + /* Check if we should keep a list of ordered children for + * parent */ + if (ecs_table_has_id( + world, tgt_table, EcsOrderedChildren)) + { + flecs_component_ordered_children_init(world, cr); + } + + flecs_component_update_childof_depth( + world, cr, tgt, tgt_r); + + cr->flags |= EcsIdOnDeleteTargetDelete | + EcsIdOnInstantiateDontInherit | EcsIdTraversable | + EcsIdPairIsTag | EcsIdExclusive; + + if (tgt && tgt != EcsWildcard) { + /* (ChildOf, tgt) pairs are always traversable. */ + ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); + } + } + } + } + } else { + rel = id & ECS_COMPONENT_MASK; + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + } + + /* Wildcards don't need trait flags, ChildOf is already initialized */ + if (rel != EcsWildcard && rel != EcsChildOf) { + rel = flecs_entities_get_alive(world, rel); + + cr->type_info = flecs_determine_type_info_for_component(world, id); + cr->flags |= flecs_component_get_flags_intern( + world, id, rel, tgt, cr->type_info); + + /* Set flag that indicates entity is used as component/relationship. */ + flecs_add_flag(world, rel, EcsEntityIsId); + + if (tgt && tgt != EcsWildcard) { + if (cr->flags & EcsIdTraversable) { + /* Flag used to determine if object should be traversed when + * propagating events or with super/subset queries */ + ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); + } + } + + /* Initialize Sparse storage */ + if (cr->flags & EcsIdSparse) { + flecs_component_init_sparse(world, cr); + } + + /* Initialize DontFragment storage */ + if (cr->flags & EcsIdDontFragment) { + flecs_component_record_init_dont_fragment(world, cr); + + if (cr_t) { + /* Mark (*, tgt) record with HasDontFragment so that queries + * can quickly detect if there are any non-fragmenting + * records to consider for a (*, tgt) query. */ + cr_t->flags |= EcsIdMatchDontFragment; + } + } + } + + /* Set flags that indicate whether there are observers for component */ + cr->flags |= flecs_component_event_flags(world, id); + + flecs_component_record_check_constraints(world, cr, rel, tgt); + + /* Mark entities that are used as component/pair ids. When a tracked + * entity is deleted, cleanup policies are applied so that the store + * won't contain any tables with deleted ids. */ + + if (ecs_should_log_1()) { + char *id_str = ecs_id_str(world, id); + ecs_dbg_1("#[green]component record#[normal] %s #[green]created", id_str); + ecs_os_free(id_str); + } + +#ifdef FLECS_DEBUG_INFO + cr->str = ecs_id_str(world, cr->id); +#endif + + /* Update counters */ + world->info.id_create_total ++; + world->info.component_id_count += cr->type_info != NULL; + world->info.tag_id_count += cr->type_info == NULL; + world->info.pair_id_count += is_pair; + + return cr; +} + +static +void flecs_component_assert_empty( + ecs_component_record_t *cr) +{ + (void)cr; + ecs_assert(flecs_table_cache_count(&cr->cache) == 0, + ECS_INTERNAL_ERROR, NULL); +} + +static +void flecs_component_free( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id = cr->id; + + flecs_component_assert_empty(cr); + + if (ECS_IS_PAIR(id)) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + if (!ecs_id_is_wildcard(id)) { + if (ECS_PAIR_FIRST(id) != EcsFlag) { + /* If id is not a wildcard, remove it from the wildcard lists */ + flecs_remove_id_elem(cr, ecs_pair(rel, EcsWildcard)); + if (!ECS_IS_VALUE_PAIR(id)) { + flecs_remove_id_elem(cr, ecs_pair(EcsWildcard, tgt)); + } + } + } else { + ecs_log_push_2(); + + /* If id is a wildcard, it means that all id records that match the + * wildcard are also empty, so release them */ + if (ECS_PAIR_FIRST(id) == EcsWildcard) { + /* Iterate (*, Target) list */ + ecs_component_record_t *cur, *next = cr->pair->second.next; + while ((cur = next)) { + flecs_component_assert_empty(cur); + next = cur->pair->second.next; + flecs_component_release(world, cur); + } + } else { + /* Iterate (Relationship, *) list */ + ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *cur, *next = cr->pair->first.next; + while ((cur = next)) { + flecs_component_assert_empty(cur); + next = cur->pair->first.next; + flecs_component_release(world, cur); + } + } + + ecs_log_pop_2(); + } + } + + /* Cleanup sparse storage */ + flecs_component_fini_sparse(world, cr); + + if (cr->flags & EcsIdDontFragment) { + flecs_component_record_fini_dont_fragment(world, cr); + } + + /* Update counters */ + world->info.id_delete_total ++; + world->info.pair_id_count -= ECS_IS_PAIR(id); + world->info.component_id_count -= cr->type_info != NULL; + world->info.tag_id_count -= cr->type_info == NULL; + + /* Unregister the component record from the world & free resources */ + ecs_table_cache_fini(&cr->cache); + + if (cr->pair) { + flecs_ordered_children_fini(world, cr); + flecs_name_index_free(cr->pair->name_index); + ecs_vec_fini_t(&world->allocator, &cr->pair->reachable.ids, + ecs_reachable_elem_t); + flecs_bfree_w_dbg_info(&world->allocators.pair_record, + cr->pair, "ecs_pair_record_t"); + } + + ecs_id_t hash = flecs_component_hash(id); + if (hash >= FLECS_HI_ID_RECORD_ID) { + ecs_map_remove(&world->id_index_hi, hash); + } else { + world->id_index_lo[hash] = NULL; + } + +#ifdef FLECS_DEBUG_INFO + ecs_os_free(cr->str); +#endif + + flecs_bfree_w_dbg_info(&world->allocators.component_record, + cr, "ecs_component_record_t"); + + if (ecs_should_log_1()) { + char *id_str = ecs_id_str(world, id); + ecs_dbg_1("#[green]component record#[normal] %s #[red]deleted", id_str); + ecs_os_free(id_str); + } +} + +ecs_component_record_t* flecs_components_ensure( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { + cr = flecs_component_new(world, id); + } + return cr; +} + +ecs_component_record_t* flecs_components_get( + const ecs_world_t *world, + ecs_id_t id) +{ + flecs_poly_assert(world, ecs_world_t); + if (id == ecs_pair(EcsIsA, EcsWildcard)) { + return world->cr_isa_wildcard; + } else if (id == ecs_pair(EcsChildOf, EcsWildcard)) { + return world->cr_childof_wildcard; + } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { + return world->cr_identifier_name; + } + + ecs_id_t hash = flecs_component_hash(id); + ecs_component_record_t *cr = NULL; + if (hash >= FLECS_HI_ID_RECORD_ID) { + cr = ecs_map_get_deref(&world->id_index_hi, ecs_component_record_t, hash); + } else { + cr = world->id_index_lo[hash]; + } + + return cr; +} + +ecs_component_record_t* flecs_components_try_ensure( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { + if (!(world->flags & EcsWorldMultiThreaded)) { + cr = flecs_component_new(world, id); + } + } + return cr; +} + +void flecs_component_claim( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + (void)world; + cr->refcount ++; +} + +int32_t flecs_component_release( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + int32_t rc = -- cr->refcount; + + ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, + flecs_errstr(ecs_id_str(world, cr->id))); + + if (!rc) { + flecs_component_free(world, cr); + } + + return rc; +} + +bool flecs_component_release_tables( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + bool remaining = false; + + ecs_table_cache_iter_t it; + if (flecs_table_cache_all_iter(&cr->cache, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + + if (table->keep) { + remaining = true; + continue; + } + + if (ecs_table_count(table)) { + remaining = true; + continue; + } + + /* Release current table */ + flecs_table_fini(world, tr->hdr.table); + } + } + + return remaining; +} + +bool flecs_component_set_type_info( + ecs_world_t *world, + ecs_component_record_t *cr, + const ecs_type_info_t *ti) +{ + bool is_wildcard = ecs_id_is_wildcard(cr->id); + if (!is_wildcard) { + if (ti) { + if (!cr->type_info) { + world->info.tag_id_count --; + world->info.component_id_count ++; + } + } else { + if (cr->type_info) { + world->info.tag_id_count ++; + world->info.component_id_count --; + } + } + } + + bool changed = cr->type_info != ti; + cr->type_info = ti; + + return changed; +} + +const ecs_type_info_t* flecs_component_get_type_info( + const ecs_component_record_t *cr) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + return cr->type_info; +} + +ecs_hashmap_t* flecs_component_name_index_ensure( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_hashmap_t *map = cr->pair->name_index; + if (!map) { + map = cr->pair->name_index = flecs_name_index_new(&world->allocator); + } + + return map; +} + +ecs_hashmap_t* flecs_component_name_index_get( + const ecs_world_t *world, + ecs_component_record_t *cr) +{ + flecs_poly_assert(world, ecs_world_t); + (void)world; + return cr->pair->name_index; +} + +const ecs_table_record_t* flecs_component_get_table( + const ecs_component_record_t *cr, + const ecs_table_t *table) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_table_cache_get(&cr->cache, table); +} + +void flecs_components_init( + ecs_world_t *world) +{ + /* Cache often used id records on world */ + world->cr_wildcard = flecs_components_ensure(world, EcsWildcard); + world->cr_wildcard_wildcard = flecs_components_ensure(world, + ecs_pair(EcsWildcard, EcsWildcard)); + world->cr_any = flecs_components_ensure(world, EcsAny); + world->cr_isa_wildcard = flecs_components_ensure(world, + ecs_pair(EcsIsA, EcsWildcard)); +} + +void flecs_components_fini( + ecs_world_t *world) +{ + /* Loop & delete first element until there are no elements left. Id records + * can recursively delete each other, this ensures we always have a + * valid iterator. */ + while (ecs_map_count(&world->id_index_hi) > 0) { + ecs_map_iter_t it = ecs_map_iter(&world->id_index_hi); + ecs_map_next(&it); + flecs_component_release(world, ecs_map_ptr(&it)); + } + + int32_t i; + for (i = 0; i < FLECS_HI_ID_RECORD_ID; i ++) { + ecs_component_record_t *cr = world->id_index_lo[i]; + if (cr) { + flecs_component_release(world, cr); + } + } + + ecs_assert(ecs_map_count(&world->id_index_hi) == 0, + ECS_INTERNAL_ERROR, NULL); + + ecs_map_fini(&world->id_index_hi); + ecs_os_free(world->id_index_lo); +} + +static +ecs_flags32_t flecs_id_flags( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_flags32_t cr_flags = flecs_component_get_flags(world, id); + ecs_flags32_t extra_flags = 0; + if (cr_flags & EcsIdOnInstantiateInherit) { + extra_flags |= EcsIdHasOnAdd|EcsIdHasOnRemove; + } + return cr_flags|extra_flags; +} + +ecs_flags32_t flecs_id_flags_get( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_flags32_t result = flecs_id_flags(world, id); + + if (id != EcsAny) { + result |= flecs_id_flags(world, EcsAny); + } + + if (ECS_IS_PAIR(id)) { + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_entity_t second = ECS_PAIR_SECOND(id); + + if (id != ecs_pair(first, EcsWildcard)) { + result |= flecs_id_flags(world, ecs_pair(first, EcsWildcard)); + } + if (id != ecs_pair(EcsWildcard, second)) { + result |= flecs_id_flags(world, ecs_pair(EcsWildcard, second)); + } + if (id != ecs_pair(EcsWildcard, EcsWildcard)) { + result |= flecs_id_flags(world, ecs_pair(EcsWildcard, EcsWildcard)); + } + + if (first == EcsIsA) { + result |= EcsIdHasOnAdd|EcsIdHasOnRemove; + } + } else if (id != EcsWildcard) { + result |= flecs_id_flags(world, EcsWildcard); + } + + return result; +} + +void flecs_component_shrink( + ecs_component_record_t *cr) +{ + ecs_map_reclaim(&cr->cache.index); + + ecs_pair_record_t *pr = cr->pair; + if (pr) { + if (pr->name_index) { + ecs_map_reclaim(&pr->name_index->impl); + } + } +} + +void flecs_component_delete_sparse( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); + int32_t i, count = flecs_sparse_count(cr->sparse); + const uint64_t *entities = flecs_sparse_ids(cr->sparse); + for (i = 0; i < count; i ++) { + ecs_delete(world, entities[i]); + } +} + +ecs_component_record_t* flecs_component_first_next( + ecs_component_record_t *cr) +{ + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + return cr->pair->first.next; +} + +ecs_component_record_t* flecs_component_second_next( + ecs_component_record_t *cr) +{ + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + return cr->pair->second.next; +} + +ecs_component_record_t* flecs_component_trav_next( + ecs_component_record_t *cr) +{ + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + return cr->pair->trav.next; +} + +bool flecs_component_iter( + const ecs_component_record_t *cr, + ecs_table_cache_iter_t *iter_out) +{ + return flecs_table_cache_all_iter(&cr->cache, iter_out); +} + +const ecs_table_record_t* flecs_component_next( + ecs_table_cache_iter_t *iter) +{ + return flecs_table_cache_next(iter, ecs_table_record_t); +} + +ecs_id_t flecs_component_get_id( + const ecs_component_record_t *cr) +{ + ecs_assert(cr != NULL, ECS_INVALID_PARAMETER, NULL); + return cr->id; +} + +ecs_parent_record_t* flecs_component_get_parent_record( + const ecs_component_record_t *cr, + const ecs_table_t *table) +{ + ecs_assert(cr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_pair_record_t *pair = cr->pair; + + if (!pair) { + return NULL; + } + + if (!ecs_map_is_init(&pair->children_tables)) { + return NULL; + } + + return (ecs_parent_record_t*)ecs_map_get(&pair->children_tables, table->id); +} + +int32_t flecs_component_get_childof_depth( + const ecs_component_record_t *cr) +{ + ecs_assert(ECS_IS_PAIR(cr->id), ECS_INVALID_PARAMETER, NULL); + ecs_assert(ECS_PAIR_FIRST(cr->id) == EcsChildOf, ECS_INVALID_PARAMETER, NULL); + ecs_assert(cr->pair != NULL, ECS_INVALID_PARAMETER, NULL); + return cr->pair->depth; +} + +static +void flecs_entities_update_childof_depth( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + if (cr->flags & EcsIdOrderedChildren) { + ecs_entity_t *entities = ecs_vec_first(&cr->pair->ordered_children); + int32_t i, count = ecs_vec_count(&cr->pair->ordered_children); + for (i = 0; i < count; i ++) { + ecs_entity_t tgt = entities[i]; + ecs_record_t *r = flecs_entities_get(world, tgt); + ecs_table_t *table = r->table; + + if (table->flags & EcsTableHasParent) { + ecs_add_id(world, tgt, + ecs_value_pair(EcsParentDepth, cr->pair->depth)); + } + + if (!(r->row & EcsEntityIsTraversable)) { + continue; + } + + ecs_component_record_t *tgt_cr = flecs_components_get( + world, ecs_childof(tgt)); + if (!tgt_cr) { + return; + } + + flecs_component_update_childof_depth(world, tgt_cr, tgt, r); + } + return; + } + + /* If the component record doesn't have a children vector, iterate tables. */ + ecs_table_cache_iter_t it; flecs_component_iter(cr, &it); + const ecs_table_record_t *tr; + while ((tr = flecs_component_next(&it))) { + ecs_table_t *table = tr->hdr.table; + if (!(table->flags & EcsTableHasTraversable)) { + continue; + } + + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_entity_t tgt = entities[i]; + ecs_component_record_t *tgt_cr = flecs_components_get( + world, ecs_childof(tgt)); + if (!tgt_cr) { + return; + } + + ecs_record_t *r = flecs_entities_get(world, tgt); + flecs_component_update_childof_depth(world, tgt_cr, tgt, r); + } + } +} + +void flecs_component_update_childof_w_depth( + ecs_world_t *world, + ecs_component_record_t *cr, + int32_t depth) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_pair_record_t *pair = cr->pair; + ecs_assert(pair != NULL, ECS_INTERNAL_ERROR, NULL); + + if (cr->flags & EcsIdMarkedForDelete) { + return; + } + + /* If depth changed, propagate downwards */ + if (depth != pair->depth) { + pair->depth = depth; + flecs_entities_update_childof_depth(world, cr); + } +} + +void flecs_component_update_childof_depth( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_entity_t tgt, + const ecs_record_t *tgt_r) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_PAIR_SECOND(cr->id) == (uint32_t)tgt, + ECS_INTERNAL_ERROR, NULL); + + if (cr->flags & EcsIdMarkedForDelete) { + return; + } + + int32_t new_depth; + if (tgt) { + ecs_table_t *tgt_table = tgt_r->table; + if (tgt_table->flags & EcsTableHasChildOf) { + ecs_pair_record_t *tgt_childof_pr = flecs_table_get_childof_pr( + world, tgt_table); + new_depth = tgt_childof_pr->depth + 1; + } else if (tgt_table->flags & EcsTableHasParent) { + int32_t column = tgt_table->component_map[ecs_id(EcsParent)]; + ecs_assert(column > 0, ECS_INTERNAL_ERROR, NULL); + + EcsParent *data = tgt_table->data.columns[column - 1].data; + ecs_entity_t parent = data[ECS_RECORD_TO_ROW(tgt_r->row)].value; + ecs_assert(parent != 0, ECS_CYCLE_DETECTED, + "possible cycle detected in Parent hierarchy"); + + ecs_component_record_t *cr_parent = flecs_components_get(world, + ecs_childof(parent)); + ecs_assert(cr_parent != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr_parent->pair != NULL, ECS_INTERNAL_ERROR, NULL); + + new_depth = cr_parent->pair->depth + 1; + } else { + new_depth = 1; + } + } else { + new_depth = 0; + } + + flecs_component_update_childof_w_depth(world, cr, new_depth); +} + +#include + +static +ecs_entity_index_page_t* flecs_entity_index_ensure_page( + ecs_entity_index_t *index, + uint32_t id) +{ + int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); + if (page_index >= ecs_vec_count(&index->pages)) { + ecs_vec_set_min_count_zeromem_t(index->allocator, &index->pages, + ecs_entity_index_page_t*, page_index + 1); + } + + ecs_entity_index_page_t **page_ptr = ecs_vec_get_t(&index->pages, + ecs_entity_index_page_t*, page_index); + ecs_entity_index_page_t *page = *page_ptr; + if (!page) { + page = *page_ptr = ecs_os_calloc_t(ecs_entity_index_page_t); + ecs_assert(page != NULL, ECS_OUT_OF_MEMORY, NULL); + } + + return page; +} + +void flecs_entity_index_init( + ecs_allocator_t *allocator, + ecs_entity_index_t *index) +{ + index->allocator = allocator; + index->alive_count = 1; + ecs_vec_init_t(allocator, &index->dense, uint64_t, 1); + ecs_vec_set_count_t(allocator, &index->dense, uint64_t, 1); + ecs_vec_init_t(allocator, &index->pages, ecs_entity_index_page_t*, 0); +} + +void flecs_entity_index_fini( + ecs_entity_index_t *index) +{ + ecs_vec_fini_t(index->allocator, &index->dense, uint64_t); + int32_t i, count = ecs_vec_count(&index->pages); + ecs_entity_index_page_t **pages = ecs_vec_first(&index->pages); + for (i = 0; i < count; i ++) { + ecs_os_free(pages[i]); + } + ecs_vec_fini_t(index->allocator, &index->pages, ecs_entity_index_page_t*); +} + +ecs_record_t* flecs_entity_index_get_any( + const ecs_entity_index_t *index, + uint64_t entity) +{ + uint32_t id = (uint32_t)entity; + int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); + ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, + ecs_entity_index_page_t*, page_index)[0]; + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + ecs_assert(r->dense != 0, ECS_INVALID_PARAMETER, + "entity %u does not exist", (uint32_t)entity); + return r; +} + +ecs_record_t* flecs_entity_index_get( + const ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_get_any(index, entity); + ecs_assert(r->dense < index->alive_count, ECS_INVALID_PARAMETER, + "entity is not alive"); + ecs_assert(ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] == entity, + ECS_INVALID_PARAMETER, "mismatching liveliness generation for entity"); + return r; +} + +ecs_record_t* flecs_entity_index_try_get_any( + const ecs_entity_index_t *index, + uint64_t entity) +{ + uint32_t id = (uint32_t)entity; + int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); + if (page_index >= ecs_vec_count(&index->pages)) { + return NULL; + } + + ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, + ecs_entity_index_page_t*, page_index)[0]; + if (!page) { + return NULL; + } + + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + if (!r->dense) { + return NULL; + } + + return r; +} + +ecs_record_t* flecs_entity_index_try_get( + const ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); + if (r) { + if (r->dense >= index->alive_count) { + return NULL; + } + if (ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] != entity) { + return NULL; + } + } + return r; +} + +ecs_record_t* flecs_entity_index_ensure( + ecs_entity_index_t *index, + uint64_t entity) +{ + uint32_t id = (uint32_t)entity; + ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + + int32_t dense = r->dense; + if (dense) { + /* Entity is already alive, nothing to be done */ + if (dense < index->alive_count) { + ecs_assert( + ecs_vec_get_t(&index->dense, uint64_t, dense)[0] == entity, + ECS_INTERNAL_ERROR, NULL); + return r; + } + } else { + /* Entity doesn't have a dense index yet */ + ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = entity; + r->dense = dense = ecs_vec_count(&index->dense) - 1; + index->max_id = id > index->max_id ? id : index->max_id; + } + + ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); + + /* Entity is not alive, swap with first not alive element */ + uint64_t *ids = ecs_vec_first(&index->dense); + uint64_t e_swap = ids[index->alive_count]; + ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); + ecs_assert(r_swap->dense == index->alive_count, + ECS_INTERNAL_ERROR, NULL); + + r_swap->dense = dense; + r->dense = index->alive_count; + ids[dense] = e_swap; + ids[index->alive_count ++] = entity; + + ecs_assert(flecs_entity_index_is_alive(index, entity), + ECS_INTERNAL_ERROR, NULL); + + return r; +} + +void flecs_entity_index_remove( + ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_try_get(index, entity); + if (!r) { + /* Entity is not alive or doesn't exist, nothing to be done */ + return; + } + + int32_t dense = r->dense; + int32_t i_swap = -- index->alive_count; + uint64_t *e_swap_ptr = ecs_vec_get_t(&index->dense, uint64_t, i_swap); + uint64_t e_swap = e_swap_ptr[0]; + ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); + ecs_assert(r_swap->dense == i_swap, ECS_INTERNAL_ERROR, NULL); + + r_swap->dense = dense; + r->table = NULL; + r->row = 0; + r->dense = i_swap; + ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = e_swap; + e_swap_ptr[0] = ECS_GENERATION_INC(entity); + ecs_assert(!flecs_entity_index_is_alive(index, entity), + ECS_INTERNAL_ERROR, NULL); +} + +void flecs_entity_index_make_alive( + ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); + if (r) { + ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] = entity; + } +} + +uint64_t flecs_entity_index_get_alive( + const ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); + if (r) { + if (r->dense < index->alive_count) { + return ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0]; + } + } + return 0; +} + +bool flecs_entity_index_is_alive( + const ecs_entity_index_t *index, + uint64_t entity) +{ + return flecs_entity_index_try_get(index, entity) != NULL; +} + +bool flecs_entity_index_is_valid( + const ecs_entity_index_t *index, + uint64_t entity) +{ + uint32_t id = (uint32_t)entity; + ecs_record_t *r = flecs_entity_index_try_get_any(index, id); + if (!r || !r->dense) { + /* Doesn't exist yet, so is valid */ + return true; + } + + /* If the id exists, it must be alive */ + return r->dense < index->alive_count; +} + +bool flecs_entity_index_exists( + const ecs_entity_index_t *index, + uint64_t entity) +{ + return flecs_entity_index_try_get_any(index, entity) != NULL; +} + +uint64_t flecs_entity_index_new_id( + ecs_entity_index_t *index) +{ + if (index->alive_count != ecs_vec_count(&index->dense)) { + /* Recycle id */ + return ecs_vec_get_t(&index->dense, uint64_t, index->alive_count ++)[0]; + } + + /* Create new id */ + uint32_t id = (uint32_t)++ index->max_id; + + ecs_assert(index->max_id <= UINT32_MAX, ECS_INVALID_OPERATION, + "entity id overflow after creating new entity " + "(value is %" PRIu64 ", cannot exceed %u)", + index->max_id, UINT32_MAX); + + /* Make sure id hasn't been issued before */ + ecs_assert(!flecs_entity_index_exists(index, id), ECS_INVALID_OPERATION, + "new entity %u id already in use (likely due to overlapping ranges)", + (uint32_t)id); + + ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; + + ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + r->dense = index->alive_count ++; + ecs_assert(index->alive_count == ecs_vec_count(&index->dense), + ECS_INTERNAL_ERROR, NULL); + + return id; +} + +uint64_t* flecs_entity_index_new_ids( + ecs_entity_index_t *index, + int32_t count) +{ + int32_t alive_count = index->alive_count; + int32_t new_count = alive_count + count; + int32_t dense_count = ecs_vec_count(&index->dense); + + if (new_count < dense_count) { + /* Recycle ids */ + index->alive_count = new_count; + return ecs_vec_get_t(&index->dense, uint64_t, alive_count); + } + + /* Allocate new ids */ + ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, new_count); + int32_t i, to_add = new_count - dense_count; + for (i = 0; i < to_add; i ++) { + uint32_t id = (uint32_t)++ index->max_id; + + ecs_assert(index->max_id <= UINT32_MAX, ECS_INVALID_OPERATION, + "entity id overflow after creating new entity " + "(value is %" PRIu64 ", cannot exceed %u)", + index->max_id, UINT32_MAX); + + /* Make sure id hasn't been issued before */ + ecs_assert(!flecs_entity_index_exists(index, id), ECS_INVALID_OPERATION, + "new entity %u id already in use (likely due to overlapping ranges)", + (uint32_t)id); + + int32_t dense = dense_count + i; + ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = id; + ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + r->dense = dense; + } + + index->alive_count = new_count; + return ecs_vec_get_t(&index->dense, uint64_t, alive_count); +} + +void flecs_entity_index_set_size( + ecs_entity_index_t *index, + int32_t size) +{ + ecs_vec_set_size_t(index->allocator, &index->dense, uint64_t, size); +} + +int32_t flecs_entity_index_count( + const ecs_entity_index_t *index) +{ + return index->alive_count - 1; +} + +int32_t flecs_entity_index_size( + const ecs_entity_index_t *index) +{ + return ecs_vec_count(&index->dense) - 1; +} + +int32_t flecs_entity_index_not_alive_count( + const ecs_entity_index_t *index) +{ + return ecs_vec_count(&index->dense) - index->alive_count; +} + +void flecs_entity_index_clear( + ecs_entity_index_t *index) +{ + int32_t i, count = ecs_vec_count(&index->pages); + ecs_entity_index_page_t **pages = ecs_vec_first_t(&index->pages, + ecs_entity_index_page_t*); + for (i = 0; i < count; i ++) { + ecs_entity_index_page_t *page = pages[i]; + if (page) { + ecs_os_zeromem(page); + } + } + + ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1); + + index->alive_count = 1; + index->max_id = 0; +} + +void flecs_entity_index_shrink( + ecs_entity_index_t *index) +{ + ecs_vec_set_count_t( + index->allocator, &index->dense, uint64_t, index->alive_count); + ecs_vec_reclaim_t(index->allocator, &index->dense, uint64_t); + + int32_t i, e, max_page_index = 0, count = ecs_vec_count(&index->pages); + ecs_entity_index_page_t **pages = ecs_vec_first_t(&index->pages, + ecs_entity_index_page_t*); + for (i = 0; i < count; i ++) { + ecs_entity_index_page_t *page = pages[i]; + if (!page) { + continue; + } + + bool has_alive = false; + for (e = 0; e < FLECS_ENTITY_PAGE_SIZE; e ++) { + ecs_record_t *r = &page->records[e]; + ecs_entity_t entity = flecs_ito(uint64_t, (i * FLECS_ENTITY_PAGE_SIZE) + e); + + if (r->dense) { + ecs_assert(flecs_entity_index_get_any(index, entity) == r, + ECS_INTERNAL_ERROR, NULL); + + if (flecs_entity_index_is_alive(index, entity)) { + ecs_assert(flecs_entity_index_is_alive(index, entity), + ECS_INTERNAL_ERROR, NULL); + has_alive = true; + break; + } + } + } + + if (!has_alive) { + ecs_os_free(pages[i]); + pages[i] = NULL; + } else { + max_page_index = i; + } + } + + ecs_vec_set_count_t( + index->allocator, &index->pages, ecs_entity_index_page_t*, + max_page_index + 1); + ecs_vec_reclaim_t(index->allocator, &index->pages, ecs_entity_index_page_t*); +} + +const uint64_t* flecs_entity_index_ids( + const ecs_entity_index_t *index) +{ + return ecs_vec_get_t(&index->dense, uint64_t, 1); +} + + +static +void flecs_add_non_fragmenting_child_to_table( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_entity_t entity, + const ecs_table_t *table) +{ + ecs_map_init_if(&cr->pair->children_tables, &world->allocator); + ecs_parent_record_t *elem = (ecs_parent_record_t*) + ecs_map_ensure(&cr->pair->children_tables, table->id); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Encode id of first entity in table + the total number of entities in the + * table for this parent in a single uint64 so everything fits in a map + * element without having to allocate. */ + if (!elem->count) { + elem->entity = (uint32_t)entity; + + if (table->flags & EcsTableIsDisabled) { + cr->pair->disabled_tables ++; + } + if (table->flags & EcsTableIsPrefab) { + cr->pair->prefab_tables ++; + } + } else { + elem->entity = 0; + } + + elem->count ++; +} + +static +void flecs_remove_non_fragmenting_child_from_table( + ecs_component_record_t *cr, + const ecs_table_t *table) +{ + ecs_parent_record_t *elem = flecs_component_get_parent_record(cr, table); + if (!elem) { + return; + } + + elem->count --; + + if (!elem->count) { + ecs_map_remove(&cr->pair->children_tables, table->id); + if (table->flags & EcsTableIsDisabled) { + cr->pair->disabled_tables --; + ecs_assert(cr->pair->disabled_tables >= 0, + ECS_INTERNAL_ERROR, NULL); + } + if (table->flags & EcsTableIsPrefab) { + cr->pair->prefab_tables --; + ecs_assert(cr->pair->prefab_tables >= 0, + ECS_INTERNAL_ERROR, NULL); + } + } +} + +int flecs_add_non_fragmenting_child_w_records( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t entity, + ecs_component_record_t *cr, + const ecs_record_t *r) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + + (void)parent; + + if (!(cr->flags & EcsIdOrderedChildren)) { + flecs_component_ordered_children_init(world, cr); + flecs_ordered_children_populate(world, cr); + } + + ecs_check(parent != 0, ECS_INVALID_OPERATION, + "cannot set Parent component with 0 entity"); + ecs_check(ecs_is_alive(world, parent), ECS_INVALID_OPERATION, + "cannot set Parent component to entity that is not alive"); + + flecs_ordered_entities_append(world, cr, entity); + + flecs_add_non_fragmenting_child_to_table(world, cr, entity, r->table); + + ecs_record_t *r_parent = flecs_entities_get(world, parent); + if (r_parent->table->flags & EcsTableIsPrefab) { + ecs_add_id(world, entity, EcsPrefab); + } + + return 0; +error: + return -1; +} + +static +ecs_component_record_t* flecs_add_non_fragmenting_child( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t entity) +{ + ecs_component_record_t *cr = flecs_components_ensure(world, + ecs_pair(EcsChildOf, parent)); + ecs_record_t *r = flecs_entities_get(world, entity); + + if (flecs_add_non_fragmenting_child_w_records(world, parent, entity, cr, r)) { + return NULL; + } + + return cr; +} + +static +void flecs_remove_non_fragmenting_child( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t entity) +{ + if (!parent) { + return; + } + + ecs_component_record_t *cr = flecs_components_get(world, + ecs_pair(EcsChildOf, parent)); + if (!cr || (cr->flags & EcsIdMarkedForDelete)) { + return; + } + + flecs_ordered_entities_remove(world, cr, entity); + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_remove_non_fragmenting_child_from_table(cr, table); +} + +static +void flecs_on_reparent_update_name( + ecs_world_t *world, + ecs_entity_t e, + EcsIdentifier *name, + ecs_entity_t parent_old, + ecs_component_record_t *cr_parent_new) +{ + if (name->value && name->index_hash) { + /* Remove entity from name index of old parent */ + ecs_component_record_t *old_cr = flecs_components_get( + world, ecs_childof(parent_old)); + if (old_cr) { + ecs_hashmap_t *old_index = + flecs_component_name_index_get(world, old_cr); + if (old_index) { + flecs_name_index_remove(old_index, e, name->index_hash); + } + } + + if (cr_parent_new) { + /* Add entity to name index of new parent */ + ecs_hashmap_t *new_index = + flecs_component_name_index_ensure(world, cr_parent_new); + flecs_name_index_ensure( + new_index, e, name->value, name->length, name->hash); + name->index = new_index; + } else { + name->index = NULL; + } + } +} + +static +void flecs_on_replace_parent(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsParent *old = ecs_field(it, EcsParent, 0); + EcsParent *new = ecs_field(it, EcsParent, 1); + + ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(new != NULL, ECS_INTERNAL_ERROR, NULL); + + EcsIdentifier *names = NULL; + if (it->table->flags & EcsTableHasName) { + names = ecs_table_get_pair( + world, it->table, EcsIdentifier, EcsName, it->offset); + ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); + } + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t old_parent = old[i].value; + ecs_entity_t new_parent = new[i].value; + + /* This can happen when a child is parented to a parent that is deleted + * in the same command queue. */ + if (!flecs_entities_is_alive(world, new_parent)) { + /* So cleanup code can see this is child of deleted parent */ + old[i].value = new_parent; + ecs_delete(world, e); + continue; + } + + flecs_journal_begin(world, EcsJournalSetParent, e, &(ecs_type_t){ + .count = 1, .array = &new_parent + }, NULL); + + flecs_remove_non_fragmenting_child(world, old_parent, e); + + ecs_component_record_t *cr_parent = + flecs_add_non_fragmenting_child(world, new_parent, e); + if (!cr_parent) { + continue; + } + + if (names) { + flecs_on_reparent_update_name( + world, e, &names[i], old_parent, cr_parent); + } + + int32_t depth = cr_parent->pair->depth; + ecs_add_id(world, e, ecs_value_pair(EcsParentDepth, depth)); + + ecs_component_record_t *cr = flecs_components_get(world, ecs_childof(e)); + if (cr) { + flecs_component_update_childof_w_depth(world, cr, depth + 1); + } + + ecs_record_t *r = flecs_entities_get(world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + if (r->row & EcsEntityIsTraversable) { + ecs_id_t added = ecs_childof(new_parent); + ecs_id_t removed = ecs_childof(old_parent); + + flecs_update_component_monitors(world, + &(ecs_type_t){ .count = 1, .array = &added }, + &(ecs_type_t) { .count = 1, .array = &removed }); + } + + flecs_journal_end(); + } +} + +void flecs_on_non_fragmenting_child_move_add( + ecs_world_t *world, + const ecs_table_t *dst, + const ecs_table_t *src, + int32_t row, + int32_t count) +{ + ecs_assert(dst->flags & EcsTableHasParent, ECS_INTERNAL_ERROR, NULL); + + EcsParent *parents = ecs_table_get(world, dst, EcsParent, 0); + int32_t i = row, end = i + count; + for (; i < end; i ++) { + ecs_entity_t e = ecs_table_entities(dst)[i]; + ecs_entity_t p = parents[i].value; + + ecs_component_record_t *cr = flecs_components_get( + world, ecs_childof(p)); + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (src && (src->flags & EcsTableHasParent)) { + flecs_remove_non_fragmenting_child_from_table(cr, src); + } + + flecs_add_non_fragmenting_child_to_table(world, cr, e, dst); + } +} + +void flecs_on_non_fragmenting_child_move_remove( + ecs_world_t *world, + const ecs_table_t *dst, + const ecs_table_t *src, + int32_t row, + int32_t count, + bool update_parent_records) +{ + EcsIdentifier *names = NULL; + if (src->flags & EcsTableHasName) { + names = ecs_table_get_pair(world, src, EcsIdentifier, EcsName, 0); + } + + EcsParent *parents = ecs_table_get(world, src, EcsParent, 0); + int32_t i = row, end = i + count; + for (; i < end; i ++) { + ecs_entity_t e = ecs_table_entities(src)[i]; + ecs_entity_t p = parents[i].value; + if (!ecs_is_alive(world, p)) { + continue; + } + + ecs_component_record_t *cr = flecs_components_ensure( + world, ecs_childof(p)); + + if (update_parent_records) { + flecs_remove_non_fragmenting_child_from_table(cr, src); + } + + if (dst && (dst->flags & EcsTableHasParent)) { + if (update_parent_records) { + flecs_add_non_fragmenting_child_to_table(world, cr, e, dst); + } + } else { + flecs_ordered_entities_remove(world, cr, e); + + if (names) { + flecs_on_reparent_update_name(world, e, &names[i], p, NULL); + } + + ecs_component_record_t *cr_e = flecs_components_get( + world, ecs_childof(e)); + if (cr_e) { + flecs_component_update_childof_w_depth(world, cr_e, 1); + } + } + } +} + +void flecs_non_fragmenting_childof_reparent( + ecs_world_t *world, + const ecs_table_t *dst, + const ecs_table_t *src, + int32_t row, + int32_t count) +{ + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!src) { + return; + } + + ecs_pair_record_t *dst_pair = flecs_table_get_childof_pr(world, dst); + ecs_assert(dst_pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_pair_record_t *src_pair = flecs_table_get_childof_pr(world, src); + + int32_t dst_depth = dst_pair->depth; + int32_t src_depth = 0; + if (src_pair) { + src_depth = src_pair->depth; + } + + if (dst_depth == src_depth) { + /* If src depth is dst depth, no need to update cached depth values */ + return; + } + + if (!ecs_table_has_traversable(src)) { + /* If table doesn't contain any traversable entities (meaning there + * can't be any parents in the table) there can't be any cached depth + * values to update. */ + return; + } + + const ecs_entity_t *entities = ecs_table_entities(dst); + int32_t i = row, end = i + count; + for (i = 0; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_component_record_t *cr = flecs_components_get( + world, ecs_childof(e)); + if (!cr) { + continue; + } + + ecs_record_t *r = flecs_entities_get(world, e); + flecs_component_update_childof_depth(world, cr, e, r); + } +} + +void flecs_non_fragmenting_childof_unparent( + ecs_world_t *world, + const ecs_table_t *dst, + const ecs_table_t *src, + int32_t row, + int32_t count) +{ + ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_pair_record_t *dst_pair = flecs_table_get_childof_pr(world, dst); + ecs_pair_record_t *src_pair = flecs_table_get_childof_pr(world, src); + + ecs_assert(src_pair != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t dst_depth = dst_pair ? dst_pair->depth : 0; + int32_t src_depth = src_pair->depth; + + /* If unparent is called there has to be a difference in depth, since the + * parent is removed from an entity. */ + ecs_assert(src_depth != dst_depth, ECS_INTERNAL_ERROR, NULL); + (void)src_depth; + (void)dst_depth; + + if (!ecs_table_has_traversable(src)) { + /* If table doesn't contain any traversable entities (meaning there + * aren't any parents in the table) there can't be any cached depth + * values to update. */ + return; + } + + const ecs_entity_t *entities = ecs_table_entities(src); + int32_t i = row, end = i + count; + for (i = 0; i < end; i ++) { + ecs_entity_t e = entities[i]; + ecs_component_record_t *cr = flecs_components_get( + world, ecs_childof(e)); + if (!cr || (cr->flags & EcsIdMarkedForDelete)) { + continue; + } else { + /* Entity is a parent */ + } + + /* Update depth to 1 if parent is removed */ + flecs_component_update_childof_w_depth(world, cr, 1); + } +} + +bool flecs_component_has_non_fragmenting_childof( + ecs_component_record_t *cr) +{ + if (cr->flags & EcsIdOrderedChildren) { + return ecs_map_count(&cr->pair->children_tables) != 0; + } + return false; +} + +void flecs_bootstrap_parent_component( + ecs_world_t *world) +{ + flecs_type_info_init(world, EcsParent, { + .ctor = flecs_default_ctor, + .on_replace = flecs_on_replace_parent + }); + + ecs_add_pair(world, ecs_id(EcsParent), EcsOnInstantiate, EcsDontInherit); +} + + +void flecs_ordered_children_init( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + ecs_vec_init_t( + &world->allocator, &cr->pair->ordered_children, ecs_entity_t, 0); +} + +void flecs_ordered_children_fini( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + ecs_vec_fini_t( + &world->allocator, &cr->pair->ordered_children, ecs_entity_t); + ecs_map_fini(&cr->pair->children_tables); +} + +void flecs_ordered_children_populate( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + ecs_assert(ecs_vec_count(&cr->pair->ordered_children) == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_IS_PAIR(cr->id), ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_PAIR_FIRST(cr->id) == EcsChildOf, + ECS_INTERNAL_ERROR, NULL); + + ecs_iter_t it = ecs_each_id(world, cr->id); + while (ecs_each_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + flecs_ordered_entities_append(world, cr, it.entities[i]); + } + } +} + +void flecs_ordered_children_clear( + ecs_component_record_t *cr) +{ + ecs_vec_t *v = &cr->pair->ordered_children; + ecs_assert(ECS_IS_PAIR(cr->id), ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_PAIR_FIRST(cr->id) == EcsChildOf, + ECS_INTERNAL_ERROR, NULL); + + if (!(cr->flags & EcsIdMarkedForDelete)) { + ecs_assert(!ecs_map_count(&cr->pair->children_tables), + ECS_UNSUPPORTED, + "cannot remove OrderedChildren trait from parent that has " + "children which use the Parent component"); + + ecs_vec_clear(v); + } +} + +void flecs_ordered_entities_append( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_entity_t e) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_pair_record_t *pr = cr->pair; + ecs_assert(pr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_vec_append_t( + &world->allocator, &pr->ordered_children, ecs_entity_t)[0] = e; + + if (cr->flags & EcsIdPrefabChildren) { + /* Register index of prefab child so that it can be used to lookup + * corresponding instance child. */ + ecs_map_ensure(&world->prefab_child_indices, e)[0] = + flecs_ito(uint64_t, ecs_vec_count(&pr->ordered_children) - 1); + } else { + ecs_assert( + !ecs_owns_id(world, ecs_pair_second(world, cr->id), EcsPrefab), + ECS_INTERNAL_ERROR, NULL); + } +} + +void flecs_ordered_entities_remove( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_entity_t e) +{ + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_vec_t *vec = &cr->pair->ordered_children; + int32_t i, count = ecs_vec_count(vec); + ecs_entity_t *entities = ecs_vec_first_t(vec, ecs_entity_t); + + for (i = 0; i < count; i ++) { + if (entities[i] == e) { + ecs_vec_remove_ordered_t(vec, ecs_entity_t, i); + if (cr->flags & EcsIdPrefabChildren) { + ecs_map_remove(&world->prefab_child_indices, e); + } + break; + } + } +} + +static +void flecs_ordered_entities_unparent_internal( + ecs_world_t *world, + const ecs_table_t *entities_table, + const ecs_table_t *table, + int32_t row, + int32_t count) +{ + if (table && (table->flags & EcsTableHasOrderedChildren)) { + ecs_component_record_t *cr = flecs_table_get_childof_cr(world, table); + const ecs_entity_t *entities = ecs_table_entities(entities_table); + int32_t i = row, end = row + count; + for (; i < end; i ++) { + ecs_entity_t e = entities[i]; + flecs_ordered_entities_remove(world, cr, e); + } + } +} + +void flecs_ordered_children_reparent( + ecs_world_t *world, + const ecs_table_t *dst, + const ecs_table_t *src, + int32_t row, + int32_t count) +{ + flecs_ordered_entities_unparent_internal(world, dst, src, row, count); + + if (dst->flags & EcsTableHasOrderedChildren) { + ecs_component_record_t *cr = flecs_table_get_childof_cr(world, dst); + const ecs_entity_t *entities = ecs_table_entities(dst); + int32_t i = row, end = row + count; + for (; i < end; i ++) { + ecs_entity_t e = entities[i]; + flecs_ordered_entities_append(world, cr, e); + } + } +} + +void flecs_ordered_children_unparent( + ecs_world_t *world, + const ecs_table_t *src, + int32_t row, + int32_t count) +{ + (void)world; + flecs_ordered_entities_unparent_internal(world, src, src, row, count); +} + +void flecs_ordered_children_reorder( + ecs_world_t *world, + ecs_entity_t parent, + const ecs_entity_t *children, + int32_t child_count) +{ + (void)world; + + ecs_component_record_t *cr = flecs_components_get( + world, ecs_childof(parent)); + + ecs_check(cr != NULL, ECS_INVALID_PARAMETER, + "ecs_set_child_order() is called for parent '%s' which does not have " + "the OrderedChildren trait", + flecs_errstr(ecs_get_path(world, parent))); + + ecs_check(cr->flags & EcsIdOrderedChildren, ECS_INVALID_PARAMETER, + "ecs_set_child_order() is called for parent '%s' which does not have " + "the OrderedChildren trait", + flecs_errstr(ecs_get_path(world, parent))); + + ecs_vec_t *vec = &cr->pair->ordered_children; + ecs_entity_t *parent_children = ecs_vec_first_t(vec, ecs_entity_t); + int32_t parent_child_count = ecs_vec_count(vec); + ecs_check(parent_child_count == child_count, ECS_INVALID_PARAMETER, + "children provided to set_child_order() for parent '%s' do not match " + "existing children", + flecs_errstr(ecs_get_path(world, parent))); + + (void)parent_child_count; + + if (parent_children == children) { + return; /* Application is passing the existing children array. */ + } + + ecs_check(children != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(parent_children != NULL, ECS_INTERNAL_ERROR, NULL); + + #ifdef FLECS_DEBUG + /* Make sure that the provided child ids equal the existing children */ + int i, j; + for (i = 0; i < child_count; i ++) { + ecs_entity_t child = parent_children[i]; + for (j = 0; j < child_count; j ++) { + ecs_entity_t child_arg = children[j]; + if (child == child_arg) { + break; + } + } + + if (j == child_count) { + ecs_throw(ECS_INVALID_PARAMETER, + "children provided to set_child_order() for parent '%s' do not " + "match existing children (child '%s' is missing in provided " + "children vector)", + flecs_errstr(ecs_get_path(world, parent)), + flecs_errstr_2(ecs_get_path(world, child))); + } + } + #endif + + /* The actual operation. */ + ecs_os_memcpy_n(parent_children, children, ecs_entity_t, child_count); +error: + return; +} + + +bool flecs_component_sparse_has( + ecs_component_record_t *cr, + ecs_entity_t entity) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_id_t id = cr->id; + if (ecs_id_is_wildcard(id)) { + if (ECS_IS_PAIR(id)) { + if ((ECS_PAIR_SECOND(id) == EcsWildcard) && + (cr->flags & EcsIdDontFragment)) + { + ecs_component_record_t *cur = cr; + while ((cur = flecs_component_first_next(cur))) { + if (!cur->sparse) { + continue; + } + + if (flecs_sparse_has(cur->sparse, entity)) { + return true; + } + } + } + + if ((ECS_PAIR_FIRST(id) == EcsWildcard) && + (cr->flags & EcsIdMatchDontFragment)) + { + ecs_component_record_t *cur = cr; + while ((cur = flecs_component_second_next(cur))) { + if (!cur->sparse) { + continue; + } + + if (flecs_sparse_has(cur->sparse, entity)) { + return true; + } + } + } + + return false; + } + + return false; + } else { + ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_sparse_has(cr->sparse, entity); + } +} + +void* flecs_component_sparse_get( + const ecs_world_t *world, + ecs_component_record_t *cr, + ecs_table_t *table, + ecs_entity_t entity) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + + if (!ecs_id_is_wildcard(cr->id)) { + return flecs_sparse_get(cr->sparse, 0, entity); + } + + /* Table should always be provided from context where wildcard is allowed */ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!(cr->flags & EcsIdDontFragment)) { + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get the non-wildcard record */ + const ecs_table_record_t *ttr = &table->_->records[tr->index]; + cr = ttr->hdr.cr; + } else { + /* Find the target entity to replace the wildcard with */ + ecs_entity_t tgt = 0; + if (cr->flags & EcsIdExclusive) { + ecs_entity_t *tgt_ptr = + flecs_sparse_get_t(cr->sparse, ecs_entity_t, entity); + if (!tgt_ptr) { + return NULL; + } + + tgt = *tgt_ptr; + } else { + ecs_type_t *type = flecs_sparse_get_t( + cr->sparse, ecs_type_t, entity); + if (!type) { + return NULL; + } + + tgt = type->array[0]; + } + + /* Find component record for the non-wildcard component */ + if (ECS_PAIR_FIRST(cr->id) == EcsWildcard) { + cr = flecs_components_get(world, + ecs_pair(tgt, ECS_PAIR_SECOND(cr->id))); + } else { + /* Component record for (*, *) doesn't exist. */ + ecs_assert(ECS_PAIR_SECOND(cr->id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + cr = flecs_components_get(world, + ecs_pair(ECS_PAIR_FIRST(cr->id), tgt)); + } + } + + return flecs_sparse_get(cr->sparse, 0, entity); +} + +static +ecs_entity_t flecs_component_sparse_remove_intern( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_table_t *table, + int32_t row) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t entity = ecs_table_entities(table)[row]; + const ecs_type_info_t *ti = cr->type_info; + + if (!ti) { + if (flecs_sparse_remove(cr->sparse, 0, entity)) { + return entity; + } + return 0; + } + + void *ptr = flecs_sparse_get(cr->sparse, 0, entity); + if (!ptr) { + return 0; + } + + ecs_iter_action_t on_remove = ti->hooks.on_remove; + if (on_remove) { + const ecs_table_record_t *tr = NULL; + if (!(cr->flags & EcsIdDontFragment)) { + tr = flecs_component_get_table(cr, table); + } + + flecs_invoke_hook(world, table, cr, tr, 1, row, + &entity, cr->id, ti, EcsOnRemove, on_remove); + } + + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + dtor(ptr, 1, ti); + } + + flecs_sparse_remove(cr->sparse, 0, entity); + + return entity; +} + +static +void flecs_component_sparse_dont_fragment_pair_remove( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_entity_t entity) +{ + ecs_component_record_t *parent = cr->pair->parent; + ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); + if (!parent->sparse) { + /* It's not the relationship that's non-fragmenting, but the target */ + return; + } + + ecs_entity_t tgt = ecs_pair_second(world, cr->id); + if (!tgt) { + /* It's possible that the target entity is cleaned up as part of the + * same entity that holds the relationship. If that's the case, the + * relationship will get cleaned up later anyway so we can exit here. */ + if (!(world->flags & EcsWorldFini)) { + ecs_assert(cr->flags & EcsIdMarkedForDelete, + ECS_INTERNAL_ERROR, NULL); + } + return; + } + + ecs_type_t *type = flecs_sparse_get_t( + parent->sparse, ecs_type_t, entity); + if (!type) { + return; + } + + ecs_assert(type->count > 0, ECS_INTERNAL_ERROR, NULL); + + int32_t old_type_count = type->count; + flecs_type_remove(world, type, tgt); + ecs_assert(type->count != old_type_count, ECS_INTERNAL_ERROR, NULL); + (void)old_type_count; + + if (!type->count) { + flecs_sparse_remove(parent->sparse, 0, entity); + } +} + +static +void flecs_component_sparse_dont_fragment_exclusive_remove( + ecs_component_record_t *cr, + ecs_entity_t entity) +{ + ecs_component_record_t *parent = cr->pair->parent; + ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(parent->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_sparse_remove( + parent->sparse, ECS_SIZEOF(ecs_entity_t), entity); +} + +void flecs_component_sparse_remove( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_table_t *table, + int32_t row) +{ + ecs_id_t id = cr->id; + ecs_flags32_t flags = cr->flags; + bool dont_fragment = flags & EcsIdDontFragment; + + /* If id is a wildcard, remove entity from all matching ids. */ + if (dont_fragment && ecs_id_is_wildcard(cr->id)) { + /* A wildcard by itself can't be marked sparse so must be a pair. */ + ecs_assert(ECS_IS_PAIR(id), ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, + ECS_UNSUPPORTED, + "remove(*, T) not supported for DontFragment targets"); + ecs_entity_t entity = ecs_table_entities(table)[row]; + ecs_component_record_t *cur = cr; + while ((cur = flecs_component_first_next(cur))) { + if (flecs_component_sparse_has(cur, entity)) { + ecs_type_t type = { + .array = &cur->id, + .count = 1 + }; + + flecs_emit(world, world, &(ecs_event_desc_t) { + .event = EcsOnRemove, + .ids = &type, + .table = table, + .other_table = NULL, + .offset = row, + .count = 1, + .observable = world + }); + + flecs_component_sparse_remove(world, cur, table, row); + } + } + + return; + } + + ecs_entity_t entity = + flecs_component_sparse_remove_intern(world, cr, table, row); + + if (entity) { + if (dont_fragment) { + if (ECS_IS_PAIR(id)) { + if (flags & EcsIdExclusive) { + flecs_component_sparse_dont_fragment_exclusive_remove( + cr, entity); + } else { + flecs_component_sparse_dont_fragment_pair_remove( + world, cr, entity); + } + } + } + } +} + +static +void flecs_component_sparse_remove_all_id( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + ecs_id_t component_id = cr->id; + + ecs_sparse_t *sparse = cr->sparse; + const ecs_entity_t *entities = flecs_sparse_ids(sparse); + int32_t i, count = flecs_sparse_count(sparse); + + const ecs_type_info_t *ti = cr->type_info; + if (ti) { + ecs_iter_action_t on_remove = ti->hooks.on_remove; + ecs_xtor_t dtor = ti->hooks.dtor; + + if (on_remove) { + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + ecs_record_t *r = flecs_entities_get(world, e); + flecs_invoke_hook(world, r->table, cr, NULL, 1, + ECS_RECORD_TO_ROW(r->row), &entities[i], component_id, ti, + EcsOnRemove, on_remove); + } + } + + if (dtor) { + for (i = 0; i < count; i ++) { + void *ptr = flecs_sparse_get_dense(sparse, 0, i); + dtor(ptr, 1, ti); + } + } + } + + if (ECS_IS_PAIR(component_id)) { + if (cr->flags & EcsIdExclusive) { + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + flecs_component_sparse_dont_fragment_exclusive_remove(cr, e); + } + } else { + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + flecs_component_sparse_dont_fragment_pair_remove(world, cr, e); + } + } + } +} + +static +void flecs_component_sparse_remove_all_wildcard( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + ecs_id_t component_id = cr->id; + + if (!ECS_IS_PAIR(component_id)) { + return; + } + + if (cr->flags & EcsIdExclusive) { + return; + } + + ecs_sparse_t *sparse = cr->sparse; + int32_t i, count = flecs_sparse_count(sparse); + for (i = 0; i < count; i ++) { + ecs_type_t *type = flecs_sparse_get_dense_t(sparse, ecs_type_t, i); + flecs_type_free(world, type); + } +} + +void flecs_component_sparse_remove_all( + ecs_world_t *world, + ecs_component_record_t *cr) +{ + ecs_assert(cr->flags & EcsIdDontFragment, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_t component_id = cr->id; + if (ecs_id_is_wildcard(component_id)) { + flecs_component_sparse_remove_all_wildcard(world, cr); + } else { + flecs_component_sparse_remove_all_id(world, cr); + } +} + +static +void flecs_component_sparse_dont_fragment_pair_insert( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_entity_t entity) +{ + ecs_component_record_t *parent = cr->pair->parent; + + ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); + if (!parent->sparse) { + /* It's not the relationship that's non-fragmenting, but the target */ + return; + } + + ecs_type_t *type = flecs_sparse_ensure_t( + parent->sparse, ecs_type_t, entity, NULL); + flecs_type_add(world, type, ecs_pair_second(world, cr->id)); +} + +static +void flecs_component_sparse_dont_fragment_exclusive_insert( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_table_t *table, + int32_t row, + ecs_entity_t entity) +{ + ecs_component_record_t *parent = cr->pair->parent; + ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(parent->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_t component_id = cr->id; + ecs_entity_t tgt, *tgt_ptr = flecs_sparse_ensure_t( + parent->sparse, ecs_entity_t, entity, NULL); + ecs_assert(tgt_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(flecs_sparse_has(parent->sparse, entity), + ECS_INTERNAL_ERROR, NULL); + + if ((tgt = *tgt_ptr)) { + ecs_component_record_t *other = flecs_components_get(world, + ecs_pair(ECS_PAIR_FIRST(component_id), tgt)); + ecs_assert(other != NULL, ECS_INTERNAL_ERROR, NULL); + if (other != cr) { + ecs_type_t type = { + .array = &other->id, + .count = 1 + }; + + flecs_emit(world, world, &(ecs_event_desc_t) { + .event = EcsOnRemove, + .ids = &type, + .table = table, + .other_table = NULL, + .offset = row, + .count = 1, + .observable = world + }); + + flecs_component_sparse_remove_intern(world, other, table, row); + } + } + + ecs_assert(flecs_sparse_has(parent->sparse, entity), + ECS_INTERNAL_ERROR, NULL); + + *tgt_ptr = flecs_entities_get_alive(world, ECS_PAIR_SECOND(component_id)); +} + +static +void flecs_component_sparse_override( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t component_id, + void *ptr, + const ecs_type_info_t *ti) +{ + const void *override_ptr = NULL; + if (table->flags & EcsTableHasIsA) { + ecs_entity_t base = 0; + if (ecs_search_relation(world, table, 0, component_id, EcsIsA, EcsUp, + &base, NULL, NULL) != -1) + { + override_ptr = ecs_get_id(world, base, component_id); + ecs_assert(override_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + } + } + + ecs_xtor_t ctor = ti->hooks.ctor; + if (!override_ptr) { + if (ctor) { + ctor(ptr, 1, ti); + } + } else { + ecs_copy_t copy = ti->hooks.copy_ctor; + if (copy) { + copy(ptr, override_ptr, 1, ti); + } else { + if (ctor) { + ctor(ptr, 1, ti); + } + ecs_os_memcpy(ptr, override_ptr, ti->size); + } + } +} + +void* flecs_component_sparse_insert( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_table_t *table, + int32_t row) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t entity = ecs_table_entities(table)[row]; + + bool is_new = true; + void *ptr = flecs_sparse_ensure(cr->sparse, 0, entity, &is_new); + + ecs_id_t component_id = cr->id; + if (ECS_IS_PAIR(component_id)) { + ecs_flags32_t flags = cr->flags; + if (flags & EcsIdDontFragment) { + if (flags & EcsIdExclusive) { + flecs_component_sparse_dont_fragment_exclusive_insert( + world, cr, table, row, entity); + } else { + flecs_component_sparse_dont_fragment_pair_insert( + world, cr, entity); + } + } + } + + if (!ptr || !is_new) { + return ptr; + } + + const ecs_type_info_t *ti = cr->type_info; + if (!ti) { + return ptr; + } + + flecs_component_sparse_override(world, table, component_id, ptr, ti); + + ecs_iter_action_t on_add = ti->hooks.on_add; + if (!on_add) { + return ptr; + } + + const ecs_table_record_t *tr = NULL; + if (!(cr->flags & EcsIdDontFragment)) { + tr = flecs_component_get_table(cr, table); + } + + flecs_invoke_hook(world, table, cr, tr, 1, row, + &entity, component_id, ti, EcsOnAdd, on_add); + + return ptr; +} + +void* flecs_component_sparse_emplace( + ecs_world_t *world, + ecs_component_record_t *cr, + ecs_table_t *table, + int32_t row) +{ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t entity = ecs_table_entities(table)[row]; + void *ptr = flecs_sparse_ensure(cr->sparse, 0, entity, NULL); + if (!ptr) { + return NULL; + } + + const ecs_type_info_t *ti = cr->type_info; + if (!ti) { + return ptr; + } + + ecs_iter_action_t on_add = ti->hooks.on_add; + if (!on_add) { + return ptr; + } + + const ecs_table_record_t *tr = NULL; + if (!(cr->flags & EcsIdDontFragment)) { + tr = flecs_component_get_table(cr, table); + } + + flecs_invoke_hook(world, table, cr, tr, 1, row, + &entity, cr->id, ti, EcsOnAdd, on_add); + + return ptr; +} + +/** + * @file storage/table.c + * @brief Table storage implementation. + * + * Tables are the data structure that store the component data. Tables have + * columns for each component in the table, and rows for each entity stored in + * the table. Once created, the component list for a table doesn't change, but + * entities can move from one table to another. + * + * Each table has a type, which is a vector with the (component) ids in the + * table. The vector is sorted by id, which ensures that there can be only one + * table for each unique combination of components. + * + * Not all ids in a table have to be components. Tags are ids that have no + * data type associated with them, and as a result don't need to be explicitly + * stored beyond an element in the table type. To save space and speed up table + * creation, each table has a reference to a "storage table", which is a table + * that only includes component ids (so excluding tags). + * + * Note that the actual data is not stored on the storage table. The storage + * table is only used for sharing administration. A column_map member maps + * between column indices of the table and its storage table. Tables are + * refcounted, which ensures that storage tables won't be deleted if other + * tables have references to it. + */ + + +/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as + * this can severely slow down many ECS operations. */ +#ifdef FLECS_SANITIZE +static +void flecs_table_check_sanity( + ecs_table_t *table) +{ + int32_t i, count = ecs_table_count(table); + int32_t size = ecs_table_size(table); + ecs_assert(count <= size, ECS_INTERNAL_ERROR, NULL); + + int32_t bs_offset = table->_ ? table->_->bs_offset : 0; + int32_t bs_count = table->_ ? table->_->bs_count : 0; + int32_t type_count = table->type.count; + ecs_id_t *ids = table->type.array; + + ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); + + if (size) { + ecs_assert(table->data.entities != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_assert(table->data.entities == NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (table->column_count) { + int32_t column_count = table->column_count; + ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); + + int16_t *column_map = table->column_map; + ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); + + for (i = 0; i < column_count; i ++) { + int32_t column_map_id = column_map[i + type_count]; + ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data.columns[i].ti != NULL, + ECS_INTERNAL_ERROR, NULL); + if (size) { + ecs_assert(table->data.columns[i].data != NULL, + ECS_INTERNAL_ERROR, NULL); + } else { + ecs_assert(table->data.columns[i].data == NULL, + ECS_INTERNAL_ERROR, NULL); + } + } + } else { + ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (bs_count) { + ecs_assert(table->_->bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &table->_->bs_columns[i]; + ecs_assert(flecs_bitset_count(bs) == count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), + ECS_INTERNAL_ERROR, NULL); + } + } + + ecs_assert((table->_->traversable_count == 0) || + (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); +} +#else +#define flecs_table_check_sanity(table) +#endif + +/* Set flags for type hooks so table operations can quickly check whether a + * fast or complex operation that invokes hooks is required. */ +static +ecs_flags32_t flecs_type_info_flags( + const ecs_type_info_t *ti) +{ + ecs_flags32_t flags = 0; + + if (ti->hooks.ctor) { + flags |= EcsTableHasCtors; + } + if (ti->hooks.on_add) { + flags |= EcsTableHasCtors; + } + if (ti->hooks.dtor) { + flags |= EcsTableHasDtors; + } + if (ti->hooks.on_remove) { + flags |= EcsTableHasDtors; + } + if (ti->hooks.copy) { + flags |= EcsTableHasCopy; + } + if (ti->hooks.move) { + flags |= EcsTableHasMove; + } + + return flags; +} + +static +void flecs_table_init_columns( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_count) +{ + int16_t i, cur = 0, ids_count = flecs_ito(int16_t, table->type.count); + + for (i = 0; i < ids_count; i ++) { + ecs_id_t id = table->type.array[i]; + if (id < FLECS_HI_COMPONENT_ID) { + table->component_map[id] = flecs_ito(int16_t, -(i + 1)); + } + } + + if (!column_count) { + return; + } + + ecs_column_t *columns = flecs_wcalloc_n(world, ecs_column_t, column_count); + table->data.columns = columns; + + ecs_id_t *ids = table->type.array; + ecs_table_record_t *records = table->_->records; + int16_t *t2s = table->column_map; + int16_t *s2t = &table->column_map[ids_count]; + + for (i = 0; i < ids_count; i ++) { + ecs_id_t id = ids[i]; + ecs_table_record_t *tr = &records[i]; + ecs_component_record_t *cr = tr->hdr.cr; + const ecs_type_info_t *ti = cr->type_info; + + if (!ti || (cr->flags & EcsIdSparse)) { + t2s[i] = -1; + continue; + } + + t2s[i] = cur; + s2t[cur] = i; + tr->column = flecs_ito(int16_t, cur); + + columns[cur].ti = ECS_CONST_CAST(ecs_type_info_t*, ti); + + if (id < FLECS_HI_COMPONENT_ID) { + table->component_map[id] = flecs_ito(int16_t, cur + 1); + } + + table->flags |= flecs_type_info_flags(ti); + cur ++; + } + + int32_t record_count = table->_->record_count; + for (; i < record_count; i ++) { + ecs_table_record_t *tr = &records[i]; + ecs_component_record_t *cr = tr->hdr.cr; + ecs_id_t id = cr->id; + + if (ecs_id_is_wildcard(id)) { + ecs_table_record_t *first_tr = &records[tr->index]; + tr->column = first_tr->column; + } + } + + /* For debug visualization */ +#ifdef FLECS_DEBUG_INFO + if (table->_->name_column != -1) { + table->_->name_column = table->column_map[table->_->name_column]; + } + if (table->_->doc_name_column != -1) { + table->_->doc_name_column = table->column_map[table->_->doc_name_column]; + } +#endif +} + +/* Initialize table storage */ +void flecs_table_init_data( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_table_init_columns(world, table, table->column_count); + + ecs_table__t *meta = table->_; + int32_t i, bs_count = meta->bs_count; + + if (bs_count) { + meta->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); + for (i = 0; i < bs_count; i ++) { + flecs_bitset_init(&meta->bs_columns[i]); + } + } +} + +/* Initialize table flags. Table flags are used in lots of scenarios to quickly + * check the features of a table without having to inspect the table type. Table + * flags are typically used to early-out of potentially expensive operations. */ +static +void flecs_table_init_flags( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_id_t *ids = table->type.array; + int32_t count = table->type.count; + +#ifdef FLECS_DEBUG_INFO + /* For debug visualization */ + table->_->name_column = -1; + table->_->doc_name_column = -1; + table->_->parent.world = world; + table->_->parent.id = 0; +#endif + + table->childof_index = -1; + + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + + if (id <= EcsLastInternalComponentId) { + table->flags |= EcsTableHasModule; + } + + if (id == EcsModule) { + table->flags |= EcsTableHasModule; + } else if (id == EcsPrefab) { + table->flags |= EcsTableIsPrefab; + } else if (id == EcsDisabled) { + table->flags |= EcsTableIsDisabled; + } else if (id == EcsNotQueryable) { + table->flags |= EcsTableNotQueryable; + } else if (id == ecs_id(EcsParent)) { + table->flags |= EcsTableHasParent; + table->trait_flags |= EcsIdTraversable; + } else if (ECS_PAIR_FIRST(id) == EcsWith) { + table->trait_flags |= EcsIdWith; + } else if (ECS_PAIR_FIRST(id) == EcsOnDelete) { + ecs_entity_t on_delete_kind = ECS_PAIR_SECOND(id); + if (on_delete_kind == EcsRemove) { + table->trait_flags |= EcsIdOnDeleteRemove; + } else if (on_delete_kind == EcsDelete) { + table->trait_flags |= EcsIdOnDeleteDelete; + } else if (on_delete_kind == EcsPanic) { + table->trait_flags |= EcsIdOnDeletePanic; + } + } else if (ECS_PAIR_FIRST(id) == EcsOnDeleteTarget) { + ecs_entity_t on_delete_kind = ECS_PAIR_SECOND(id); + if (on_delete_kind == EcsRemove) { + table->trait_flags |= EcsIdOnDeleteTargetRemove; + } else if (on_delete_kind == EcsDelete) { + table->trait_flags |= EcsIdOnDeleteTargetDelete; + } else if (on_delete_kind == EcsPanic) { + table->trait_flags |= EcsIdOnDeleteTargetPanic; + } + } else if (ECS_PAIR_FIRST(id) == EcsOnInstantiate) { + ecs_entity_t inherit_kind = ECS_PAIR_SECOND(id); + if (inherit_kind == EcsInherit) { + table->trait_flags |= EcsIdOnInstantiateInherit; + } else if (inherit_kind == EcsDontInherit) { + table->trait_flags |= EcsIdOnInstantiateDontInherit; + } else if (inherit_kind == EcsOverride) { + table->trait_flags |= EcsIdOnInstantiateOverride; + } + } else if (id == EcsCanToggle) { + table->trait_flags |= EcsIdCanToggle; + } else if (id == EcsInheritable) { + table->trait_flags |= EcsIdInheritable; + } else if (id == EcsSingleton) { + table->trait_flags |= EcsIdSingleton; + } else if (id == EcsSparse) { + table->trait_flags |= EcsIdSparse; + } else if (id == EcsDontFragment) { + table->trait_flags |= EcsIdDontFragment; + } else if (id == EcsExclusive) { + table->trait_flags |= EcsIdExclusive; + } else if (id == EcsTraversable) { + table->trait_flags |= EcsIdTraversable; + } else if (id == EcsPairIsTag) { + table->trait_flags |= EcsIdPairIsTag; + } else if (id == EcsTransitive) { + table->trait_flags |= EcsIdIsTransitive; + } else if (ECS_IS_PAIR(id)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + + table->flags |= EcsTableHasPairs; + + if (r == EcsIsA) { + table->flags |= EcsTableHasIsA; + } else if (r == EcsChildOf) { + table->flags |= EcsTableHasChildOf; + table->childof_index = flecs_ito(int16_t, i); + + ecs_entity_t tgt = ecs_pair_second(world, id); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + /* If table contains entities that are inside one of the + * builtin modules, it contains builtin entities */ + + if (tgt == EcsFlecsCore) { + table->flags |= EcsTableHasBuiltins; + } + + if (ecs_has_id(world, tgt, EcsModule)) { + table->flags |= EcsTableHasModule; + } + +#ifdef FLECS_DEBUG_INFO + table->_->parent.id = tgt; +#endif + } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { + table->flags |= EcsTableHasName; +#ifdef FLECS_DEBUG_INFO + table->_->name_column = flecs_ito(int16_t, i); +#endif + } else if (r == ecs_id(EcsPoly)) { + table->flags |= EcsTableHasModule; + } +#if defined(FLECS_DEBUG_INFO) && defined(FLECS_DOC) + else if (id == ecs_pair_t(EcsDocDescription, EcsName)) { + table->_->doc_name_column = flecs_ito(int16_t, i); + } +#endif + } else { + if (ECS_HAS_ID_FLAG(id, TOGGLE)) { + ecs_table__t *meta = table->_; + table->flags |= EcsTableHasToggle; + + if (!meta->bs_count) { + meta->bs_offset = flecs_ito(int16_t, i); + } + meta->bs_count ++; + } + if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { + table->flags |= EcsTableHasOverrides; + } + } + } +} + +/* Utility function that appends an element to the table record array */ +static +void flecs_table_append_to_records( + ecs_world_t *world, + ecs_table_t *table, + ecs_vec_t *records, + ecs_id_t id, + int32_t column) +{ + /* To avoid a quadratic search, use the O(1) lookup that the index + * already provides. */ + ecs_component_record_t *cr = flecs_components_ensure(world, id); + + /* Safe, record is owned by table. */ + ecs_table_record_t *tr = ECS_CONST_CAST(ecs_table_record_t*, + flecs_component_get_table(cr, table)); + if (!tr) { + tr = ecs_vec_append_t(&world->allocator, records, ecs_table_record_t); + tr->index = flecs_ito(int16_t, column); + tr->count = 1; + + ecs_table_cache_insert(&cr->cache, table, &tr->hdr); + } else { + tr->count ++; + } + + ecs_assert(tr->hdr.cr != NULL, ECS_INTERNAL_ERROR, NULL); +} + +static +void flecs_table_init_overrides( + ecs_world_t *world, + ecs_table_t *table, + const ecs_table_record_t *tr) +{ + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!table->column_count) { + return; + } + + ecs_table_overrides_t *o = flecs_walloc_t(world, ecs_table_overrides_t); + if (tr->count > 1) { + table->flags |= EcsTableHasMultiIsA; + o->is._n.tr = tr; + o->is._n.generations = flecs_wcalloc_n(world, int32_t, tr->count); + + int32_t i; + for (i = 0; i < tr->count; i ++) { + o->is._n.generations[i] = -1; + } + } else { + const ecs_table_record_t *first = &table->_->records[tr->index]; + const ecs_component_record_t *cr = first->hdr.cr; + ecs_assert(ECS_IS_PAIR(cr->id), ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_PAIR_FIRST(cr->id) == EcsIsA, ECS_INTERNAL_ERROR, NULL); + ecs_assert(first->count == 1, ECS_INTERNAL_ERROR, NULL); + o->is._1.generation = -1; + o->is._1.pair = cr->pair; + } + + o->refs = flecs_wcalloc_n(world, ecs_ref_t, table->column_count); + + table->data.overrides = o; +} + +static +void flecs_table_fini_overrides( + ecs_world_t *world, + ecs_table_t *table) +{ + if (!table->column_count) { + return; + } + + ecs_table_overrides_t *o = table->data.overrides; + if (!o) { + return; + } + + if (table->flags & EcsTableHasMultiIsA) { + const ecs_table_record_t *tr = o->is._n.tr; + flecs_wfree_n(world, int32_t, tr->count, o->is._n.generations); + } + + flecs_wfree_n(world, ecs_ref_t, table->column_count, o->refs); + flecs_wfree_t(world, ecs_table_overrides_t, o); +} + +static +void flecs_table_update_overrides( + ecs_world_t *world, + ecs_table_t *table) +{ + if (!(table->flags & EcsTableHasIsA)) { + return; + } + + ecs_table_overrides_t *o = table->data.overrides; + if (!o) { + return; + } + + if (table->flags & EcsTableHasMultiIsA) { + const ecs_table_record_t *tr = o->is._n.tr; + const ecs_table_record_t *records = table->_->records; + + int32_t *generations = o->is._n.generations; + int32_t i = tr->index, end = i + tr->count; + for (; i < end; i ++) { + ecs_component_record_t *cr = records[i].hdr.cr; + if (cr->pair->reachable.generation != *generations) { + break; + } + generations ++; + } + + if (i == end) { + /* Cache is up to date */ + return; + } + + generations = o->is._n.generations; + i = tr->index; end = i + tr->count; + for (; i < end; i ++) { + ecs_component_record_t *cr = records[i].hdr.cr; + generations[0] = cr->pair->reachable.generation; + generations ++; + } + } else { + /* Fast cache validation for tables with single IsA pair */ + int32_t generation = o->is._1.pair->reachable.generation; + if (o->is._1.generation == generation) { + /* Cache is up to date */ + return; + } + + o->is._1.generation = generation; + } + + int16_t *map = &table->column_map[table->type.count]; + int32_t i, count = table->column_count; + for (i = 0; i < count; i ++) { + ecs_id_t id = table->type.array[map[i]]; + ecs_entity_t base = 0; + + if (ecs_search_relation( + world, table, 0, id, EcsIsA, EcsUp, &base, NULL, NULL) != -1) + { + o->refs[i] = ecs_ref_init_id(world, base, id); + } else { + ecs_os_zeromem(&o->refs[i]); + } + } +} + +void flecs_table_emit( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t event) +{ + ecs_defer_begin(world); + flecs_emit(world, world, &(ecs_event_desc_t) { + .ids = &table->type, + .event = event, + .table = table, + .flags = EcsEventTableOnly, + .observable = world + }); + ecs_defer_end(world); +} + +/* Main table initialization function */ +void flecs_table_init( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *from) +{ + /* Make sure table->flags is initialized */ + flecs_table_init_flags(world, table); + + /* The following code walks the table type to discover which id records the + * table needs to register table records with. + * + * In addition to registering itself with id records for each id in the + * table type, a table also registers itself with wildcard id records. For + * example, if a table contains (Eats, Apples), it will register itself with + * wildcard id records (Eats, *), (*, Apples) and (*, *). This makes it + * easier for wildcard queries to find the relevant tables. */ + + int32_t dst_i = 0, dst_count = table->type.count; + int32_t src_i = 0, src_count = 0; + ecs_id_t *dst_ids = table->type.array; + ecs_id_t *src_ids = NULL; + ecs_table_record_t *tr = NULL, *src_tr = NULL; + if (from) { + src_count = from->type.count; + src_ids = from->type.array; + src_tr = from->_->records; + } + + /* We don't know in advance how large the records array will be, so use + * cached vector. This eliminates unnecessary allocations, and/or expensive + * iterations to determine how many records we need. */ + ecs_allocator_t *a = &world->allocator; + ecs_vec_t *records = &world->store.records; + ecs_vec_reset_t(a, records, ecs_table_record_t); + ecs_component_record_t *cr, *childof_cr = NULL; + + int32_t last_id = -1; /* Track last regular (non-pair) id */ + int32_t first_pair = -1; /* Track the first pair in the table */ + int32_t first_role = -1; /* Track first id with role */ + + /* Scan to find boundaries of regular ids, pairs and roles */ + for (dst_i = 0; dst_i < dst_count; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + if (first_pair == -1 && ECS_IS_PAIR(dst_id)) { + first_pair = dst_i; + } + if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { + last_id = dst_i; + } else if (first_role == -1 && !ECS_IS_PAIR(dst_id)) { + first_role = dst_i; + } + + /* Build bloom filter for table */ + table->bloom_filter = + flecs_table_bloom_filter_add(table->bloom_filter, dst_id); + } + + /* The easy part: initialize a record for every id in the type */ + for (dst_i = 0; (dst_i < dst_count) && (src_i < src_count); ) { + ecs_id_t dst_id = dst_ids[dst_i]; + ecs_id_t src_id = src_ids[src_i]; + + cr = NULL; + + if (dst_id == src_id) { + ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL); + cr = (ecs_component_record_t*)src_tr[src_i].hdr.cr; + } else if (dst_id < src_id) { + cr = flecs_components_ensure(world, dst_id); + } + if (cr) { + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cr = cr; + tr->index = flecs_ito(int16_t, dst_i); + tr->count = 1; + } + + dst_i += dst_id <= src_id; + src_i += dst_id >= src_id; + } + + /* Add remaining ids that the "from" table didn't have */ + for (; (dst_i < dst_count); dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + cr = flecs_components_ensure(world, dst_id); + tr->hdr.cr = cr; + ecs_assert(tr->hdr.cr != NULL, ECS_INTERNAL_ERROR, NULL); + tr->index = flecs_ito(int16_t, dst_i); + tr->count = 1; + } + + /* We're going to insert records from the vector into the index that + * will get patched up later. To ensure the record pointers don't get + * invalidated we need to grow the vector so that it won't realloc as + * we're adding the next set of records */ + if (first_role != -1 || first_pair != -1) { + int32_t start = first_role; + if (first_pair != -1 && (start == -1 || first_pair < start)) { + start = first_pair; + } + + /* Total number of records can never be higher than + * - number of regular (non-pair) ids + + * - three records for pairs: (R,T), (R,*), (*,T) + * - one wildcard (*), one any (_) and one pair wildcard (*,*) record + * - one record for (ChildOf, 0) + */ + int32_t flag_id_count = dst_count - start; + int32_t record_count = start + 3 * flag_id_count + 3 + 1; + ecs_vec_set_min_size_t(a, records, ecs_table_record_t, record_count); + } + + /* Get records size now so we can check that array did not resize */ + int32_t records_size = ecs_vec_size(records); + (void)records_size; + + /* Add records for ids with roles (used by cleanup logic) */ + if (first_role != -1) { + for (dst_i = first_role; dst_i < dst_count; dst_i ++) { + ecs_id_t id = dst_ids[dst_i]; + if (!ECS_IS_PAIR(id)) { + ecs_entity_t first = 0; + ecs_entity_t second = 0; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + first = ECS_PAIR_FIRST(id); + second = ECS_PAIR_SECOND(id); + } else { + first = id & ECS_COMPONENT_MASK; + } + if (first) { + flecs_table_append_to_records(world, table, records, + ecs_pair(EcsFlag, first), dst_i); + } + if (second) { + flecs_table_append_to_records(world, table, records, + ecs_pair(EcsFlag, second), dst_i); + } + } + } + } + + int32_t last_pair = -1; + bool has_childof = !!(table->flags & (EcsTableHasChildOf|EcsTableHasParent)); + if (first_pair != -1) { + /* Add a (Relationship, *) record for each relationship. */ + ecs_entity_t r = 0; + for (dst_i = first_pair; dst_i < dst_count; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + if (!ECS_IS_PAIR(dst_id)) { + break; /* no more pairs */ + } + if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ + tr = ecs_vec_get_t(records, ecs_table_record_t, dst_i); + + ecs_component_record_t *p_cr = tr->hdr.cr; + r = ECS_PAIR_FIRST(dst_id); + if (r == EcsChildOf) { + childof_cr = p_cr; + ecs_assert(childof_cr->pair != NULL, + ECS_INTERNAL_ERROR, NULL); + } + + ecs_assert(p_cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + cr = p_cr->pair->parent; /* (R, *) */ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cr = cr; + tr->index = flecs_ito(int16_t, dst_i); + tr->count = 0; + } + + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + tr->count ++; + } + + last_pair = dst_i; + + /* Add a (*, Target) record for each relationship target. Type + * ids are sorted relationship-first, so we can't simply do a single + * linear scan to find all occurrences for a target. */ + for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { + ecs_id_t dst_id = dst_ids[dst_i]; + + if (ECS_IS_VALUE_PAIR(dst_id)) { + continue; + } + + ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); + + flecs_table_append_to_records( + world, table, records, tgt_id, dst_i); + } + } + + /* Lastly, add records for all-wildcard ids */ + if (last_id >= 0) { + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cr = world->cr_wildcard; + tr->index = 0; + tr->count = flecs_ito(int16_t, last_id + 1); + } + if (last_pair - first_pair) { + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cr = world->cr_wildcard_wildcard; + tr->index = flecs_ito(int16_t, first_pair); + tr->count = flecs_ito(int16_t, last_pair - first_pair); + } + if (!has_childof) { + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + childof_cr = world->cr_childof_0; + tr->hdr.cr = childof_cr; + tr->index = -1; /* The table doesn't have a (ChildOf, 0) component */ + tr->count = 0; + + table->bloom_filter = flecs_table_bloom_filter_add( + table->bloom_filter, ecs_pair(EcsChildOf, 0)); + } + + /* Now that all records have been added, copy them to array */ + int32_t i, dst_record_count = ecs_vec_count(records); + ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t, + dst_record_count, ecs_vec_first_t(records, ecs_table_record_t)); + table->_->record_count = flecs_ito(int16_t, dst_record_count); + table->_->records = dst_tr; + int32_t column_count = 0; + + ecs_table_record_t *isa_tr = NULL; + + /* Register & patch up records */ + for (i = 0; i < dst_record_count; i ++) { + tr = &dst_tr[i]; + cr = dst_tr[i].hdr.cr; + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_table_cache_get(&cr->cache, table)) { + /* If this is a target wildcard record it has already been + * registered, but the record is now at a different location in + * memory. Patch up the linked list with the new address */ + + /* Ensure that record array hasn't been reallocated */ + ecs_assert(records_size == ecs_vec_size(records), + ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_replace(&cr->cache, table, &tr->hdr); + } else { + /* Other records are not registered yet */ + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_cache_insert(&cr->cache, table, &tr->hdr); + } + + /* Claim component record so it stays alive as long as the table exists */ + flecs_component_claim(world, cr); + + /* Initialize event flags */ + table->flags |= cr->flags & EcsIdEventMask; + + /* Initialize column index (will be overwritten by init_data) */ + tr->column = -1; + + if (ECS_ID_ON_INSTANTIATE(cr->flags) == EcsOverride) { + table->flags |= EcsTableHasOverrides; + } + + if ((i < table->type.count) && (cr->type_info != NULL)) { + if (!(cr->flags & EcsIdSparse)) { + column_count ++; + } + } + + if (cr->id == ecs_pair(EcsIsA, EcsWildcard)) { + isa_tr = tr; + } + } + + /* Initialize event flags for any record */ + table->flags |= world->cr_any->flags & EcsIdEventMask; + + table->component_map = ecs_os_calloc_n(int16_t, FLECS_HI_COMPONENT_ID); + + if (column_count) { + table->column_map = ecs_os_calloc_n(int16_t, dst_count + column_count); + } + + table->column_count = flecs_ito(int16_t, column_count); + table->version = 1; + flecs_table_init_data(world, table); + + if (childof_cr) { + if (table->flags & EcsTableHasName) { + flecs_component_name_index_ensure(world, childof_cr); + ecs_assert(childof_cr->pair->name_index != NULL, + ECS_INTERNAL_ERROR, NULL); + } + } + + /* If table has IsA pairs, create overrides cache */ + if (isa_tr) { + flecs_table_init_overrides(world, table, isa_tr); + } + + if (table->flags & EcsTableHasOnTableCreate) { + flecs_table_emit(world, table, EcsOnTableCreate); + } +} + +/* Unregister table from id records */ +static +void flecs_table_records_unregister( + ecs_world_t *world, + ecs_table_t *table) +{ + uint64_t table_id = table->id; + int32_t i, count = table->_->record_count; + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &table->_->records[i]; + ecs_component_record_t *cr = tr->hdr.cr; + ecs_id_t id = cr->id; + + ecs_assert(tr->hdr.cr == cr, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_components_get(world, id) == cr, + ECS_INTERNAL_ERROR, NULL); + (void)id; + + ecs_table_cache_remove(&cr->cache, table_id, &tr->hdr); + flecs_component_release(world, cr); + } + + flecs_wfree_n(world, ecs_table_record_t, count, table->_->records); +} + +/* Keep track for what kind of builtin events observers are registered that can + * potentially match the table. This allows code to early out of calling the + * emit function that notifies observers. */ +static +void flecs_table_add_trigger_flags( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_entity_t event) +{ + (void)world; + + ecs_flags32_t flags = 0; + + if (event == EcsOnAdd) { + flags = EcsTableHasOnAdd; + } else if (event == EcsOnRemove) { + flags = EcsTableHasOnRemove; + } else if (event == EcsOnSet) { + flags = EcsTableHasOnSet; + } else if (event == EcsOnTableCreate) { + flags = EcsTableHasOnTableCreate; + } else if (event == EcsOnTableDelete) { + flags = EcsTableHasOnTableDelete; + } else if (event == EcsWildcard) { + flags = EcsTableHasOnAdd|EcsTableHasOnRemove|EcsTableHasOnSet| + EcsTableHasOnTableCreate|EcsTableHasOnTableDelete; + } + + table->flags |= flags; + + /* Add observer flags to incoming edges for id */ + if (id && ((flags == EcsTableHasOnAdd) || (flags == EcsTableHasOnRemove))) { + flecs_table_edges_add_flags(world, table, id, flags); + } +} + +/* Invoke OnRemove observers for all entities in table. Useful during table + * deletion or when clearing entities from a table. */ +static +void flecs_table_notify_on_remove( + ecs_world_t *world, + ecs_table_t *table) +{ + int32_t count = ecs_table_count(table); + if (count) { + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + diff.removed = table->type; + diff.removed_flags = table->flags & EcsTableRemoveEdgeFlags; + flecs_actions_move_remove(world, table, &world->store.root, 0, count, &diff); + } +} + +/* Invoke type hook for entities in table */ +static +void flecs_table_invoke_hook( + ecs_world_t *world, + ecs_table_t *table, + ecs_iter_action_t callback, + ecs_entity_t event, + ecs_column_t *column, + const ecs_entity_t *entities, + int32_t row, + int32_t count) +{ + int32_t column_index = flecs_ito(int32_t, column - table->data.columns); + ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); + int32_t type_index = table->column_map[table->type.count + column_index]; + ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = &table->_->records[type_index]; + ecs_component_record_t *cr = tr->hdr.cr; + flecs_invoke_hook(world, table, cr, tr, count, row, entities, + table->type.array[type_index], column->ti, event, callback); +} + +static +void flecs_table_invoke_ctor_for_array( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_index, + void *array, + int32_t row, + int32_t count, + const ecs_type_info_t *ti) +{ + void *ptr = ECS_ELEM(array, ti->size, row); + if (table) { + ecs_table_overrides_t *o = table->data.overrides; + if (o) { + ecs_ref_t *r = &o->refs[column_index]; + if (r->entity) { + void *base_ptr = ecs_ref_get_id(world, r, r->id); + ecs_assert(base_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_copy_t copy = ti->hooks.copy_ctor; + ecs_iter_action_t on_set = ti->hooks.on_set; + ecs_size_t size = ti->size; + + int32_t i; + for (i = 0; i < count; i ++) { + if (copy) { + copy(ptr, base_ptr, 1, ti); + } else { + ecs_os_memcpy(ptr, base_ptr, size); + } + + ptr = ECS_OFFSET(ptr, size); + } + + if (on_set) { + table->data.columns[column_index].data = array; + + int32_t record_index = table->column_map[table->type.count + column_index]; + const ecs_table_record_t *tr = &table->_->records[record_index]; + const ecs_entity_t *entities = &ecs_table_entities(table)[row]; + flecs_invoke_hook(world, table, tr->hdr.cr, tr, + count, row, entities, ti->component, ti, EcsOnSet, on_set); + } + + return; + } + } + } + + ecs_xtor_t ctor = ti->hooks.ctor; + if (ctor) { + ctor(ptr, count, ti); + } +} + +static +void flecs_table_invoke_ctor( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_index, + int32_t row, + int32_t count) +{ + ecs_column_t *column = &table->data.columns[column_index]; + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_table_invoke_ctor_for_array(world, table, column_index, + column->data, row, count, ti); +} + +/* Destruct components */ +static +void flecs_table_invoke_dtor( + ecs_column_t *column, + int32_t row, + int32_t count) +{ + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + void *ptr = ECS_ELEM(column->data, ti->size, row); + dtor(ptr, count, ti); + } +} + +/* Run hooks that get invoked when component is added to entity */ +static +void flecs_table_invoke_add_hooks( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_index, + ecs_entity_t *entities, + int32_t row, + int32_t count, + bool construct) +{ + ecs_column_t *column = &table->data.columns[column_index]; + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + if (construct) { + flecs_table_invoke_ctor(world, table, column_index, row, count); + } + + ecs_iter_action_t on_add = ti->hooks.on_add; + if (on_add) { + flecs_table_invoke_hook(world, table, on_add, EcsOnAdd, column, + entities, row, count); + } +} + +/* Run hooks that get invoked when component is removed from entity */ +static +void flecs_table_invoke_remove_hooks( + ecs_world_t *world, + ecs_table_t *table, + ecs_column_t *column, + ecs_entity_t *entities, + int32_t row, + int32_t count, + bool dtor) +{ + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_iter_action_t on_remove = ti->hooks.on_remove; + if (on_remove) { + flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, + entities, row, count); + } + + if (dtor) { + flecs_table_invoke_dtor(column, row, count); + } +} + +static +void flecs_table_remove_dont_fragment( + ecs_world_t *world, + ecs_entity_t e) +{ + ecs_record_t *r = flecs_entities_get(world, e); + flecs_entity_remove_non_fragmenting(world, e, r); +} + +/* Destruct all components and/or delete all entities in table */ +static +void flecs_table_dtor_all( + ecs_world_t *world, + ecs_table_t *table) +{ + int32_t i, c, count = ecs_table_count(table); + if (!count) { + return; + } + + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t column_count = table->column_count; + + ecs_assert(!column_count || table->data.columns != NULL, + ECS_INTERNAL_ERROR, NULL); + + if (table->_->traversable_count) { + /* If table contains monitored entities with traversable relationships, + * make sure to invalidate observer cache */ + flecs_emit_propagate_invalidate(world, table, 0, count); + } + + /* If table has components with destructors, iterate component columns */ + if (table->flags & EcsTableHasDtors) { + /* Run on_remove callbacks first before destructing components */ + for (c = 0; c < column_count; c++) { + ecs_column_t *column = &table->data.columns[c]; + ecs_iter_action_t on_remove = column->ti->hooks.on_remove; + if (on_remove) { + flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, + column, &entities[0], 0, count); + } + } + + /* Destruct components */ + for (c = 0; c < column_count; c++) { + flecs_table_invoke_dtor(&table->data.columns[c], 0, count); + } + + /* Iterate entities first, then components. This ensures that only one + * entity is invalidated at a time, which ensures that destructors can + * safely access other entities. */ + for (i = 0; i < count; i ++) { + /* Update entity index after invoking destructors so that entity can + * be safely used in destructor callbacks. */ + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), + ECS_INTERNAL_ERROR, NULL); + + flecs_table_remove_dont_fragment(world, e); + flecs_entities_remove(world, e); + ecs_assert(ecs_is_valid(world, e) == false, + ECS_INTERNAL_ERROR, NULL); + } + + /* If table does not have destructors, just update entity index */ + } else { + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + flecs_table_remove_dont_fragment(world, e); + flecs_entities_remove(world, e); + ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + } + } +} + +#define FLECS_LOCKED_STORAGE_MSG(operation) \ + "a " #operation " operation failed because the table is locked, fix by surrounding the operation with defer_begin()/defer_end()" + +/* Cleanup table storage */ +static +void flecs_table_fini_data( + ecs_world_t *world, + ecs_table_t *table, + bool do_on_remove, + bool deallocate) +{ + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, + FLECS_LOCKED_STORAGE_MSG("table data cleanup")); + + if (do_on_remove) { + flecs_table_notify_on_remove(world, table); + } + + flecs_table_dtor_all(world, table); + + if (deallocate) { + ecs_column_t *columns = table->data.columns; + if (columns) { + int32_t c, column_count = table->column_count; + for (c = 0; c < column_count; c ++) { + ecs_column_t *column = &columns[c]; + ecs_vec_t v = ecs_vec_from_column(column, table, column->ti->size); + ecs_vec_fini(NULL, &v, column->ti->size); + column->data = NULL; + } + + flecs_wfree_n(world, ecs_column_t, table->column_count, columns); + table->data.columns = NULL; + } + } + + ecs_table__t *meta = table->_; + ecs_bitset_t *bs_columns = meta->bs_columns; + if (bs_columns) { + int32_t c, column_count = meta->bs_count; + if (deallocate) { + for (c = 0; c < column_count; c ++) { + flecs_bitset_fini(&bs_columns[c]); + } + } + else { + for (c = 0; c < column_count; c++) { + bs_columns[c].count = 0; + } + } + + if (deallocate) { + flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); + meta->bs_columns = NULL; + } + } + + if (deallocate) { + ecs_vec_t v = ecs_vec_from_entities(table); + ecs_vec_fini_t(NULL, &v, ecs_entity_t); + table->data.entities = NULL; + table->data.size = 0; + } + + table->data.count = 0; + table->_->traversable_count = 0; + table->flags &= ~EcsTableHasTraversable; +} + +const ecs_entity_t* ecs_table_entities( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return table->data.entities; +} + +void ecs_table_clear_entities( + ecs_world_t* world, + ecs_table_t* table) +{ + flecs_table_fini_data(world, table, true, false); +} + +void flecs_table_delete_entities( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_table_fini_data(world, table, true, true); +} + +/* Unset all components in table. This function is called before a table is + * deleted, and invokes all OnRemove handlers, if any */ +void flecs_table_remove_actions( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_table_notify_on_remove(world, table); +} + +/* Free table resources. */ +void flecs_table_fini( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_poly_assert(world, ecs_world_t); + + flecs_increment_table_version(world, table); + + bool is_root = table == &world->store.root; + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table deletion")); + ecs_assert((world->flags & EcsWorldFini) || !table->keep, ECS_INVALID_OPERATION, + "cannot delete table (still in use): '[%s]'", + flecs_errstr(ecs_table_str(world, table))); + ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), + ECS_INTERNAL_ERROR, NULL); + (void)world; + + ecs_os_perf_trace_push("flecs.table.free"); + + if (!is_root && !(world->flags & EcsWorldQuit)) { + if (table->flags & EcsTableHasOnTableDelete) { + flecs_table_emit(world, table, EcsOnTableDelete); + } + } + + if (ecs_should_log_2()) { + char *expr = ecs_type_str(world, &table->type); + ecs_dbg_2( + "#[green]table#[normal] [%s] #[red]deleted#[reset] with id %d", + expr, table->id); + ecs_os_free(expr); + ecs_log_push_2(); + } + + /* Cleanup data, no OnRemove, free allocations */ + flecs_table_fini_data(world, table, false, true); + flecs_table_clear_edges(world, table); + + if (!is_root) { + ecs_type_t ids = { + .array = table->type.array, + .count = table->type.count + }; + + flecs_hashmap_remove_w_hash( + &world->store.table_map, &ids, ecs_table_t*, table->_->hash); + } + + flecs_table_fini_overrides(world, table); + flecs_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); + ecs_os_free(table->column_map); + ecs_os_free(table->component_map); + flecs_table_records_unregister(world, table); + + /* Update counters */ + world->info.table_count --; + world->info.table_delete_total ++; + + flecs_free_t(&world->allocator, ecs_table__t, table->_); + + if (!(world->flags & EcsWorldFini)) { + ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); + flecs_table_free_type(world, table); + flecs_sparse_remove_w_gen_t( + &world->store.tables, ecs_table_t, table->id); + } + + ecs_log_pop_2(); + + ecs_os_perf_trace_pop("flecs.table.free"); +} + +/* Free table type. Do this separately from freeing the table as types can be + * in use by application destructors. */ +void flecs_table_free_type( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); +} + +/* Reset a table to its initial state. */ +void flecs_table_reset( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, + FLECS_LOCKED_STORAGE_MSG("table reset")); + flecs_table_clear_edges(world, table); +} + +/* Keep track of number of traversable entities in table. A traversable entity + * is an entity used as target in a pair with a traversable relationship. The + * traversable count and flag are used by code to early out of mechanisms like + * event propagation and recursive cleanup. */ +void flecs_table_traversable_add( + ecs_table_t *table, + int32_t value) +{ + int32_t result = table->_->traversable_count += value; + ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); + if (result == 0) { + table->flags &= ~EcsTableHasTraversable; + } else if (result == value) { + table->flags |= EcsTableHasTraversable; + } +} + +/* Mark table column dirty. This usually happens as the result of a set + * operation, or iteration of a query with [out] fields. */ +static +void flecs_table_mark_table_dirty( + ecs_world_t *world, + ecs_table_t *table, + int32_t index) +{ + if (table->dirty_state) { + table->dirty_state[index] ++; + } + if (!index) { + flecs_increment_table_version(world, table); + } +} + +/* Mark table component dirty */ +void flecs_table_mark_dirty( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t component) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (table->dirty_state) { + int32_t column; + if (component < FLECS_HI_COMPONENT_ID) { + column = table->component_map[component]; + if (column <= 0) { + return; + } + } else { + ecs_component_record_t *cr = flecs_components_get(world, component); + if (!cr) { + return; + } + + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr || tr->column == -1) { + return; + } + + column = tr->column + 1; + } + + /* Column is offset by 1, 0 is reserved for entity column. */ + + table->dirty_state[column] ++; + + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, + FLECS_LOCKED_STORAGE_MSG("dirty marking")); + } +} + +/* Get (or create) dirty state of table. Used by queries for change tracking */ +int32_t* flecs_table_get_dirty_state( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (!table->dirty_state) { + int32_t column_count = table->column_count; + table->dirty_state = flecs_alloc_n(&world->allocator, + int32_t, column_count + 1); + ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + for (int i = 0; i < column_count + 1; i ++) { + table->dirty_state[i] = 1; + } + } + return table->dirty_state; +} + +/* Table move logic for bitset (toggle component) column */ +static +void flecs_table_move_bitset_columns( + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index, + int32_t count, + bool clear) +{ + ecs_table__t *dst_meta = dst_table->_; + ecs_table__t *src_meta = src_table->_; + if (!dst_meta && !src_meta) { + return; + } + + int32_t i_old = 0, src_column_count = src_meta ? src_meta->bs_count : 0; + int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->bs_count : 0; + + if (!src_column_count && !dst_column_count) { + return; + } + + ecs_bitset_t *src_columns = src_meta ? src_meta->bs_columns : NULL; + ecs_bitset_t *dst_columns = dst_meta ? dst_meta->bs_columns : NULL; + + ecs_type_t dst_type = dst_table->type; + ecs_type_t src_type = src_table->type; + + int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; + int32_t offset_old = src_meta ? src_meta->bs_offset : 0; + + ecs_id_t *dst_ids = dst_type.array; + ecs_id_t *src_ids = src_type.array; + + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_id_t dst_id = dst_ids[i_new + offset_new]; + ecs_id_t src_id = src_ids[i_old + offset_old]; + + if (dst_id == src_id) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + ecs_bitset_t *dst_bs = &dst_columns[i_new]; + + flecs_bitset_ensure(dst_bs, dst_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_bitset_get(src_bs, src_index + i); + flecs_bitset_set(dst_bs, dst_index + i, value); + } + + if (clear) { + ecs_assert(count == flecs_bitset_count(src_bs), + ECS_INTERNAL_ERROR, NULL); + flecs_bitset_fini(src_bs); + } + } else if (dst_id > src_id) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + flecs_bitset_fini(src_bs); + } + + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } + + /* Clear remaining columns */ + if (clear) { + for (; (i_old < src_column_count); i_old ++) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + ecs_assert(count == flecs_bitset_count(src_bs), + ECS_INTERNAL_ERROR, NULL); + flecs_bitset_fini(src_bs); + } + } +} + +/* Grow table column. When a column needs to be reallocated this function takes + * care of correctly invoking ctor/move/dtor hooks. */ +static +void flecs_table_grow_column( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_index, + ecs_vec_t *column, + const ecs_type_info_t *ti, + int32_t to_add, + int32_t dst_size, + bool construct) +{ + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t count = ecs_vec_count(column); + int32_t size = ecs_vec_size(column); + int32_t elem_size = ti->size; + int32_t dst_count = count + to_add; + bool can_realloc = dst_size != size; + + ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); + + /* If the array could possibly realloc and the component has a move action + * defined, move old elements manually */ + ecs_move_t move_ctor; + if (count && can_realloc && (move_ctor = ti->hooks.ctor_move_dtor)) { + ecs_assert(ti->hooks.ctor != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Create vector */ + ecs_vec_t dst; + ecs_vec_init(NULL, &dst, elem_size, dst_size); + dst.count = dst_count; + + void *src_buffer = column->array; + void *dst_buffer = dst.array; + + /* Move (and construct) existing elements to new vector */ + move_ctor(dst_buffer, src_buffer, count, ti); + + if (construct) { + /* Construct new element(s) */ + flecs_table_invoke_ctor_for_array( + world, table, column_index, dst_buffer, count, to_add, ti); + } + + /* Free old vector */ + ecs_vec_fini(NULL, column, elem_size); + + *column = dst; + } else { + /* If array won't realloc or has no move, simply add new elements */ + if (can_realloc) { + ecs_vec_set_size(NULL, column, elem_size, dst_size); + } + + ecs_vec_grow(NULL, column, elem_size, to_add); + + if (construct) { + flecs_table_invoke_ctor_for_array( + world, table, column_index, column->array, count, to_add, ti); + } + } + + ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); +} + +/* Grow all data structures in a table */ +static +int32_t flecs_table_grow_data( + ecs_world_t *world, + ecs_table_t *table, + int32_t to_add, + int32_t size, + const ecs_entity_t *ids) +{ + flecs_poly_assert(world, ecs_world_t); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data.count + to_add == size, ECS_INTERNAL_ERROR, NULL); + + int32_t count = ecs_table_count(table); + int32_t column_count = table->column_count; + + /* Add entity to column with entity ids */ + ecs_vec_t v_entities = ecs_vec_from_entities(table); + ecs_vec_set_size_t(NULL, &v_entities, ecs_entity_t, size); + + ecs_entity_t *e = NULL; + + if (size) { + e = ECS_ELEM_T(v_entities.array, ecs_entity_t, v_entities.count); + } + + v_entities.count += to_add; + if (v_entities.size > size) { + size = v_entities.size; + } + + /* Update table entities/count/size */ + int32_t prev_count = table->data.count, prev_size = table->data.size; + table->data.entities = v_entities.array; + table->data.count = v_entities.count; + table->data.size = v_entities.size; + + /* Initialize entity ids and record ptrs */ + int32_t i; + if (e) { + if (ids) { + ecs_os_memcpy_n(e, ids, ecs_entity_t, to_add); + } else { + ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); + } + } + + flecs_table_update_overrides(world, table); + + /* Add elements to each column array */ + ecs_column_t *columns = table->data.columns; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + const ecs_type_info_t *ti = column->ti; + ecs_vec_t v_column = ecs_vec_from_column_ext(column, prev_count, prev_size, ti->size); + flecs_table_grow_column(world, table, i, &v_column, ti, to_add, size, true); + ecs_assert(v_column.size == size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v_column.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v_column.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); + column->data = v_column.array; + + if (to_add) { + flecs_table_invoke_add_hooks( + world, table, i, e, count, to_add, false); + } + } + + ecs_table__t *meta = table->_; + int32_t bs_count = meta->bs_count; + ecs_bitset_t *bs_columns = meta->bs_columns; + + /* Add elements to each bitset column */ + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &bs_columns[i]; + flecs_bitset_addn(bs, to_add); + } + + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); + + /* Mark columns as potentially reallocated */ + flecs_increment_table_column_version(world, table); + + /* Return index of first added entity */ + return count; +} + +/* Append operation for tables that don't have any complex logic */ +static +void flecs_table_fast_append( + ecs_table_t *table) +{ + /* Add elements to each column array */ + ecs_column_t *columns = table->data.columns; + int32_t i, count = table->column_count; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &columns[i]; + const ecs_type_info_t *ti = column->ti; + ecs_vec_t v = ecs_vec_from_column(column, table, ti->size); + ecs_vec_append(NULL, &v, ti->size); + column->data = v.array; + } +} + +/* Append entity to table */ +int32_t flecs_table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + bool construct, + bool on_add) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, + FLECS_LOCKED_STORAGE_MSG("table append")); + + flecs_table_check_sanity(table); + + /* Get count & size before growing entities array. This tells us whether the + * arrays will realloc */ + int32_t count = ecs_table_count(table); + int32_t column_count = table->column_count; + ecs_column_t *columns = table->data.columns; + + /* Grow buffer with entity ids, set new element to new entity */ + ecs_vec_t v_entities = ecs_vec_from_entities(table); + ecs_entity_t *e = ecs_vec_append_t(NULL, &v_entities, ecs_entity_t); + + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *entities = table->data.entities = v_entities.array; + *e = entity; + + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); + flecs_increment_table_column_version(world, table); + ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Fast path: no switch columns, no lifecycle actions */ + if (!(table->flags & (EcsTableIsComplex|EcsTableHasIsA))) { + flecs_table_fast_append(table); + table->data.count = v_entities.count; + table->data.size = v_entities.size; + return count; + } + + flecs_table_update_overrides(world, table); + + int32_t prev_count = table->data.count; + int32_t prev_size = table->data.size; + + ecs_assert(table->data.count == v_entities.count - 1, + ECS_INTERNAL_ERROR, NULL); + table->data.count = v_entities.count; + table->data.size = v_entities.size; + + /* Reobtain size to ensure that the columns have the same size as the + * entities and record vectors. This keeps reasoning about when allocations + * occur easier. */ + int32_t size = v_entities.size; + + /* Grow component arrays with 1 element */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + const ecs_type_info_t *ti = column->ti; + ecs_vec_t v_column = ecs_vec_from_column_ext(column, prev_count, prev_size, ti->size); + flecs_table_grow_column(world, table, i, &v_column, ti, 1, size, construct); + column->data = v_column.array; + + ecs_iter_action_t on_add_hook; + if (on_add && (on_add_hook = column->ti->hooks.on_add)) { + flecs_table_invoke_hook(world, table, on_add_hook, EcsOnAdd, column, + &entities[count], count, 1); + } + + ecs_assert(v_column.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v_column.count == v_entities.count, + ECS_INTERNAL_ERROR, NULL); + } + + ecs_table__t *meta = table->_; + int32_t bs_count = meta->bs_count; + ecs_bitset_t *bs_columns = meta->bs_columns; + + /* Add element to each bitset column */ + for (i = 0; i < bs_count; i ++) { + ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_bitset_t *bs = &bs_columns[i]; + flecs_bitset_addn(bs, 1); + } + + flecs_table_check_sanity(table); + + return count; +} + +/* Delete operation for tables that don't have any complex logic */ +static +void flecs_table_fast_delete( + ecs_table_t *table, + int32_t row) +{ + ecs_column_t *columns = table->data.columns; + int32_t i, count = table->column_count; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &columns[i]; + const ecs_type_info_t *ti = column->ti; + ecs_vec_t v = ecs_vec_from_column(column, table, ti->size); + ecs_vec_remove(&v, ti->size, row); + column->data = v.array; + } +} + +/* Delete entity from table */ +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + bool destruct) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, + FLECS_LOCKED_STORAGE_MSG("table delete")); + + flecs_table_check_sanity(table); + + int32_t count = ecs_table_count(table); + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + count --; + ecs_assert(row <= count, ECS_INTERNAL_ERROR, NULL); + + /* Move last entity id to row */ + ecs_entity_t *entities = table->data.entities; + ecs_entity_t entity_to_move = entities[count]; + ecs_entity_t entity_to_delete = entities[row]; + entities[row] = entity_to_move; + + /* Update record of moved entity in entity index */ + if (row != count) { + ecs_record_t *record_to_move = flecs_entities_get(world, entity_to_move); + if (record_to_move) { + uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; + record_to_move->row = ECS_ROW_TO_RECORD(row, row_flags); + ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); + } + } + + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); + + /* Destruct component data */ + ecs_column_t *columns = table->data.columns; + int32_t column_count = table->column_count; + int32_t i; + + /* If this is a table without lifecycle callbacks or special columns, take + * fast path that just remove an element from the array(s) */ + if (!(table->flags & EcsTableIsComplex)) { + if (row != count) { + flecs_table_fast_delete(table, row); + } + + table->data.count --; + + flecs_table_check_sanity(table); + return; + } + + /* Last element, destruct & remove */ + if (row == count) { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & EcsTableHasDtors)) { + for (i = 0; i < column_count; i ++) { + flecs_table_invoke_remove_hooks(world, table, &columns[i], + &entity_to_delete, row, 1, true); + } + } + + /* Not last element, move last element to deleted element & destruct */ + } else { + /* If table has component destructors, invoke */ + if ((table->flags & (EcsTableHasDtors | EcsTableHasMove))) { + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_type_info_t *ti = column->ti; + ecs_size_t size = ti->size; + void *dst = ECS_ELEM(column->data, size, row); + void *src = ECS_ELEM(column->data, size, count); + + ecs_iter_action_t on_remove = ti->hooks.on_remove; + if (destruct && on_remove) { + flecs_table_invoke_hook(world, table, on_remove, + EcsOnRemove, column, &entity_to_delete, row, 1); + } + + ecs_move_t move_dtor = ti->hooks.move_dtor; + + /* If neither move nor move_ctor are set, this indicates that + * non-destructive move semantics are not supported for this + * type. In such cases, we set the move_dtor as ctor_move_dtor, + * which indicates a destructive move operation. This adjustment + * ensures compatibility with different language bindings. */ + if (!ti->hooks.move_ctor && ti->hooks.ctor_move_dtor) { + move_dtor = ti->hooks.ctor_move_dtor; + } + + if (move_dtor) { + move_dtor(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, size); + } + } + } else { + flecs_table_fast_delete(table, row); + } + } + + /* Remove elements from bitset columns */ + ecs_table__t *meta = table->_; + ecs_bitset_t *bs_columns = meta->bs_columns; + int32_t bs_count = meta->bs_count; + for (i = 0; i < bs_count; i ++) { + flecs_bitset_remove(&bs_columns[i], row); + } + + table->data.count --; + + flecs_table_check_sanity(table); +} + +/* Move operation for tables that don't have any complex logic */ +static +void flecs_table_fast_move( + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index) +{ + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; + + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; + + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_column_t *dst_column = &dst_columns[i_new]; + ecs_column_t *src_column = &src_columns[i_old]; + ecs_id_t dst_id = flecs_column_id(dst_table, i_new); + ecs_id_t src_id = flecs_column_id(src_table, i_old); + + if (dst_id == src_id) { + int32_t size = dst_column->ti->size; + void *dst = ECS_ELEM(dst_column->data, size, dst_index); + void *src = ECS_ELEM(src_column->data, size, src_index); + ecs_os_memcpy(dst, src, size); + } + + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } +} + +/* Move entity from src to dst table */ +void flecs_table_move( + ecs_world_t *world, + ecs_entity_t dst_entity, + ecs_entity_t src_entity, + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index, + bool construct) +{ + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("move")); + ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("move")); + + ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); + + flecs_table_check_sanity(dst_table); + flecs_table_check_sanity(src_table); + + if (!((dst_table->flags | src_table->flags) & (EcsTableIsComplex|EcsTableHasIsA))) { + flecs_table_fast_move(dst_table, dst_index, src_table, src_index); + flecs_table_check_sanity(dst_table); + flecs_table_check_sanity(src_table); + return; + } + + flecs_table_update_overrides(world, dst_table); + + flecs_table_move_bitset_columns( + dst_table, dst_index, src_table, src_index, 1, false); + + /* Call move_dtor for moved away from storage only if the entity is at the + * last index in the source table. If it isn't the last entity, the last + * entity in the table will be moved to the src storage, which will take + * care of cleaning up resources. */ + bool use_move_dtor = ecs_table_count(src_table) == (src_index + 1); + + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; + + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; + + for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { + ecs_column_t *dst_column = &dst_columns[i_new]; + ecs_column_t *src_column = &src_columns[i_old]; + ecs_id_t dst_id = flecs_column_id(dst_table, i_new); + ecs_id_t src_id = flecs_column_id(src_table, i_old); + + if (dst_id == src_id) { + ecs_type_info_t *ti = dst_column->ti; + int32_t size = ti->size; + + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + void *dst = ECS_ELEM(dst_column->data, size, dst_index); + void *src = ECS_ELEM(src_column->data, size, src_index); + + ecs_move_t move = ti->hooks.move_ctor; + if (use_move_dtor || !move) { + /* Also use move_dtor if component doesn't have a move_ctor + * registered, to ensure that the dtor gets called to + * cleanup resources. */ + move = ti->hooks.ctor_move_dtor; + } + + if (move) { + move(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, size); + } + } else { + if (dst_id < src_id) { + flecs_table_invoke_add_hooks(world, dst_table, + i_new, &dst_entity, dst_index, 1, construct); + } else { + flecs_table_invoke_remove_hooks(world, src_table, + src_column, &src_entity, src_index, 1, use_move_dtor); + } + } + + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } + + for (; (i_new < dst_column_count); i_new ++) { + flecs_table_invoke_add_hooks(world, dst_table, i_new, + &dst_entity, dst_index, 1, construct); + } + + for (; (i_old < src_column_count); i_old ++) { + flecs_table_invoke_remove_hooks(world, src_table, &src_columns[i_old], + &src_entity, src_index, 1, use_move_dtor); + } + + flecs_table_check_sanity(dst_table); + flecs_table_check_sanity(src_table); +} + +/* Append n entities to table */ +int32_t flecs_table_appendn( + ecs_world_t *world, + ecs_table_t *table, + int32_t to_add, + const ecs_entity_t *ids) +{ + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, + FLECS_LOCKED_STORAGE_MSG("table bulk append")); + + /* Update entity index before calling hooks. */ + int32_t i; + for (i = 0; i < to_add; i ++) { + ecs_record_t *r = flecs_entities_get(world, ids[i]); + r->table = table; + r->row = ECS_ROW_TO_RECORD(ecs_table_count(table) + i, 0); + } + + flecs_table_check_sanity(table); + int32_t cur_count = ecs_table_count(table); + int32_t result = flecs_table_grow_data( + world, table, to_add, cur_count + to_add, ids); + flecs_table_check_sanity(table); + + return result; +} + +/* Shrink table storage to fit number of entities */ +bool flecs_table_shrink( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, + FLECS_LOCKED_STORAGE_MSG("table shrink")); + (void)world; + + flecs_table_check_sanity(table); + + bool has_payload = table->data.entities != NULL; + + int32_t count = table->data.count; + if (count == table->data.size) { + return has_payload; + } + + ecs_column_t *columns = table->data.columns; + ecs_entity_t *entities = table->data.entities; + + if (count) { + ecs_assert(table->data.entities != NULL, ECS_INTERNAL_ERROR, NULL); + table->data.entities = ecs_os_malloc_n(ecs_entity_t, count); + ecs_os_memcpy_n(table->data.entities, entities, ecs_entity_t, count); + } else { + table->data.entities = NULL; + } + + ecs_os_free(entities); + + int32_t i, column_count = table->column_count; + for (i = 0; i < column_count; i ++) { + const ecs_type_info_t *ti = columns[i].ti; + ecs_size_t component_size = ti->size; + ecs_move_t move = ti->hooks.ctor_move_dtor; + void *data = columns[i].data; + + if (count) { + columns[i].data = ecs_os_malloc(component_size * count); + + if (move) { + move(columns[i].data, data, count, ti); + } else { + ecs_assert(columns[i].data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy(columns[i].data, data, component_size * count); + } + } else { + columns[i].data = NULL; + } + + ecs_os_free(data); + } + + table->data.size = count; + + flecs_increment_table_column_version(world, table); + + flecs_table_mark_table_dirty(world, table, 0); + + flecs_table_check_sanity(table); + + return has_payload; +} + +/* Swap operation for bitset (toggle component) columns */ +static +void flecs_table_swap_bitset_columns( + ecs_table_t *table, + int32_t row_1, + int32_t row_2) +{ + int32_t i = 0, column_count = table->_->bs_count; + if (!column_count) { + return; + } + + ecs_bitset_t *columns = table->_->bs_columns; + + for (i = 0; i < column_count; i ++) { + ecs_bitset_t *bs = &columns[i]; + flecs_bitset_swap(bs, row_1, row_2); + } +} + +/* Swap two rows in a table. Used for table sorting. */ +void flecs_table_swap( + ecs_world_t *world, + ecs_table_t *table, + int32_t row_1, + int32_t row_2) +{ + (void)world; + + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, + FLECS_LOCKED_STORAGE_MSG("table swap")); + ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); + + flecs_table_check_sanity(table); + + if (row_1 == row_2) { + return; + } + + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); + + ecs_entity_t *entities = table->data.entities; + ecs_entity_t e1 = entities[row_1]; + ecs_entity_t e2 = entities[row_2]; + + ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); + ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); + + ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Keep track of whether entity is watched */ + uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); + uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); + + /* Swap entities & records */ + entities[row_1] = e2; + entities[row_2] = e1; + record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); + record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); + + flecs_table_swap_bitset_columns(table, row_1, row_2); + + ecs_column_t *columns = table->data.columns; + if (!columns) { + flecs_table_check_sanity(table); + return; + } + + /* Find the maximum size of column elements + * and allocate a temporary buffer for swapping */ + int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->column_count; + for (i = 0; i < column_count; i++) { + temp_buffer_size = ECS_MAX(temp_buffer_size, columns[i].ti->size); + } + + void* tmp = ecs_os_alloca(temp_buffer_size); + + /* Swap columns */ + for (i = 0; i < column_count; i ++) { + int32_t size = columns[i].ti->size; + ecs_column_t *column = &columns[i]; + void *ptr = column->data; + const ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + void *el_1 = ECS_ELEM(ptr, size, row_1); + void *el_2 = ECS_ELEM(ptr, size, row_2); + + ecs_move_t move = ti->hooks.move; + if (!move) { + ecs_os_memcpy(tmp, el_1, size); + ecs_os_memcpy(el_1, el_2, size); + ecs_os_memcpy(el_2, tmp, size); + } else { + ecs_move_t move_ctor = ti->hooks.move_ctor; + ecs_move_t move_dtor = ti->hooks.move_dtor; + ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(move_dtor != NULL, ECS_INTERNAL_ERROR, NULL); + move_ctor(tmp, el_1, 1, ti); + move(el_1, el_2, 1, ti); + move_dtor(el_2, tmp, 1, ti); + } + } + + flecs_table_check_sanity(table); +} + +static +void flecs_table_merge_vec( + ecs_vec_t *dst, + ecs_vec_t *src, + int32_t size, + int32_t elem_size) +{ + int32_t dst_count = dst->count; + + if (!dst_count) { + ecs_vec_fini(NULL, dst, size); + *dst = *src; + src->array = NULL; + src->count = 0; + src->size = 0; + } else { + int32_t src_count = src->count; + + if (elem_size) { + ecs_vec_set_size(NULL, dst, size, elem_size); + } + ecs_vec_set_count(NULL, dst, size, dst_count + src_count); + + void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); + void *src_ptr = src->array; + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + + ecs_vec_fini(NULL, src, size); + } +} + +/* Merge data from one table column into other table column */ +static +void flecs_table_merge_column( + ecs_world_t *world, + ecs_vec_t *dst_vec, + ecs_vec_t *src_vec, + ecs_column_t *dst, + ecs_column_t *src, + int32_t column_size) +{ + const ecs_type_info_t *ti = dst->ti; + ecs_assert(ti == src->ti, ECS_INTERNAL_ERROR, NULL); + ecs_size_t elem_size = ti->size; + int32_t dst_count = ecs_vec_count(dst_vec); + + if (!dst_count) { + ecs_vec_fini(NULL, dst_vec, elem_size); + *dst_vec = *src_vec; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = src_vec->count; + + flecs_table_grow_column(world, NULL, -1, dst_vec, ti, src_count, column_size, false); + void *dst_ptr = ECS_ELEM(dst_vec->array, elem_size, dst_count); + void *src_ptr = src_vec->array; + + /* Move values into column */ + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_move_t move = ti->hooks.ctor_move_dtor; + if (move) { + move(dst_ptr, src_ptr, src_count, ti); + } else { + ecs_os_memcpy(dst_ptr, src_ptr, elem_size * src_count); + } + + ecs_vec_fini(NULL, src_vec, elem_size); + } + + dst->data = dst_vec->array; + src->data = NULL; +} + +/* Merge storage of two tables. */ +static +void flecs_table_merge_data( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_table_t *src_table, + int32_t dst_count, + int32_t src_count) +{ + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; + + ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); + + if (!src_count) { + return; + } + + /* Merge entities */ + ecs_vec_t dst_entities = ecs_vec_from_entities(dst_table); + ecs_vec_t src_entities = ecs_vec_from_entities(src_table); + flecs_table_merge_vec( &dst_entities, &src_entities, + ECS_SIZEOF(ecs_entity_t), 0); + ecs_assert(dst_entities.count == src_count + dst_count, + ECS_INTERNAL_ERROR, NULL); + int32_t column_size = dst_entities.size; + + for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { + ecs_column_t *dst_column = &dst_columns[i_new]; + ecs_column_t *src_column = &src_columns[i_old]; + ecs_id_t dst_id = flecs_column_id(dst_table, i_new); + ecs_id_t src_id = flecs_column_id(src_table, i_old); + ecs_size_t dst_elem_size = dst_column->ti->size; + ecs_size_t src_elem_size = src_column->ti->size; + + ecs_vec_t dst_vec = ecs_vec_from_column( + dst_column, dst_table, dst_elem_size); + ecs_vec_t src_vec = ecs_vec_from_column( + src_column, src_table, src_elem_size); + + if (dst_id == src_id) { + flecs_table_merge_column(world, &dst_vec, &src_vec, dst_column, + src_column, column_size); + flecs_table_mark_table_dirty(world, dst_table, i_new + 1); + i_new ++; + i_old ++; + } else if (dst_id < src_id) { + /* New column, make sure vector is large enough. */ + ecs_vec_set_size(NULL, &dst_vec, dst_elem_size, column_size); + dst_column->data = dst_vec.array; + flecs_table_invoke_ctor(world, dst_table, i_new, dst_count, src_count); + i_new ++; + } else if (dst_id > src_id) { + /* Old column does not occur in new table, destruct */ + flecs_table_invoke_dtor(src_column, 0, src_count); + ecs_vec_fini(NULL, &src_vec, src_elem_size); + src_column->data = NULL; + i_old ++; + } + } + + flecs_table_move_bitset_columns( + dst_table, dst_count, src_table, 0, src_count, true); + + /* Initialize remaining columns */ + for (; i_new < dst_column_count; i_new ++) { + ecs_column_t *column = &dst_columns[i_new]; + int32_t elem_size = column->ti->size; + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + ecs_vec_t vec = ecs_vec_from_column(column, dst_table, elem_size); + ecs_vec_set_size(NULL, &vec, elem_size, column_size); + column->data = vec.array; + flecs_table_invoke_ctor(world, dst_table, i_new, dst_count, src_count); + } + + /* Destruct remaining columns */ + for (; i_old < src_column_count; i_old ++) { + ecs_column_t *column = &src_columns[i_old]; + int32_t elem_size = column->ti->size; + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + flecs_table_invoke_dtor(column, 0, src_count); + ecs_vec_t vec = ecs_vec_from_column(column, src_table, elem_size); + ecs_vec_fini(NULL, &vec, elem_size); + column->data = vec.array; + } + + /* Mark entity column as dirty */ + flecs_table_mark_table_dirty(world, dst_table, 0); + + /* Mark columns as potentially reallocated */ + flecs_increment_table_column_version(world, dst_table); + + dst_table->data.entities = dst_entities.array; + dst_table->data.count = dst_entities.count; + dst_table->data.size = dst_entities.size; + + src_table->data.entities = src_entities.array; + src_table->data.count = src_entities.count; + src_table->data.size = src_entities.size; +} + +/* Merge source table into destination table. This typically happens as result + * of a bulk operation, like when a component is removed from all entities in + * the source table (like for the Remove OnDelete policy). */ +void flecs_table_merge( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_table_t *src_table) +{ + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, + FLECS_LOCKED_STORAGE_MSG("table merge")); + ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, + FLECS_LOCKED_STORAGE_MSG("table merge")); + + flecs_table_check_sanity(src_table); + flecs_table_check_sanity(dst_table); + + const ecs_entity_t *src_entities = ecs_table_entities(src_table); + int32_t src_count = ecs_table_count(src_table); + int32_t dst_count = ecs_table_count(dst_table); + + /* First, update entity index so old entities point to new type */ + int32_t i; + for(i = 0; i < src_count; i ++) { + ecs_record_t *record = flecs_entities_ensure(world, src_entities[i]); + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); + record->table = dst_table; + } + + /* Merge table columns */ + flecs_table_merge_data(world, dst_table, src_table, dst_count, src_count); + + if (src_count) { + flecs_table_traversable_add(dst_table, src_table->_->traversable_count); + flecs_table_traversable_add(src_table, -src_table->_->traversable_count); + ecs_assert(src_table->_->traversable_count == 0, ECS_INTERNAL_ERROR, NULL); + } + + flecs_table_check_sanity(src_table); + flecs_table_check_sanity(dst_table); +} + +/* Internal mechanism for propagating information to tables */ +void flecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_table_event_t *event) +{ + flecs_poly_assert(world, ecs_world_t); + + if (world->flags & EcsWorldFini) { + return; + } + + switch(event->kind) { + case EcsTableTriggersForId: + flecs_table_add_trigger_flags(world, table, id, event->event); + break; + case EcsTableNoTriggersForId: + break; /* TODO */ + } +} + +int32_t flecs_table_get_toggle_column( + ecs_table_t *table, + ecs_id_t id) +{ + ecs_id_t *ids = table->type.array; + int32_t i = table->_->bs_offset, end = i + table->_->bs_count; + + for (; i < end; i ++) { + if (ids[i] == (ECS_TOGGLE | id)) { + return i; + } + } + + return -1; +} + +ecs_bitset_t* flecs_table_get_toggle( + ecs_table_t *table, + ecs_id_t id) +{ + int32_t toggle_column = flecs_table_get_toggle_column(table, id); + if (toggle_column == -1) { + return NULL; + } + + toggle_column -= table->_->bs_offset; + ecs_assert(toggle_column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(toggle_column < table->_->bs_count, ECS_INTERNAL_ERROR, NULL); + return &table->_->bs_columns[toggle_column]; +} + +ecs_id_t flecs_column_id( + ecs_table_t *table, + int32_t column_index) +{ + int32_t type_index = table->column_map[table->type.count + column_index]; + return table->type.array[type_index]; +} + +int32_t flecs_table_observed_count( + const ecs_table_t *table) +{ + return table->_->traversable_count; +} + +uint64_t flecs_table_bloom_filter_add( + uint64_t filter, + uint64_t value) +{ + filter |= 1llu << (value % 64); + return filter; +} + +bool flecs_table_bloom_filter_test( + const ecs_table_t *table, + uint64_t filter) +{ + return (table->bloom_filter & filter) == filter; +} + + +ecs_table_records_t flecs_table_records( + ecs_table_t* table) +{ + return (ecs_table_records_t){ + .array = table->_->records, + .count = table->_->record_count + }; +} + +ecs_component_record_t* flecs_table_record_get_component( + const ecs_table_record_t *tr) +{ + return tr->hdr.cr; +} + +uint64_t flecs_table_id( + ecs_table_t* table) +{ + return table->id; +} + +const ecs_ref_t* flecs_table_get_override( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + const ecs_component_record_t *cr, + ecs_ref_t *storage) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + if (!(table->flags & EcsTableHasIsA)) { + return NULL; + } + + if (cr->flags & EcsIdSparse) { + ecs_entity_t base = 0; + if (ecs_search_relation(world, table, 0, id, EcsIsA, EcsUp, + &base, NULL, NULL) != -1) + { + *storage = ecs_ref_init_id(world, base, id); + return storage; + } + } + + ecs_table_overrides_t *o = table->data.overrides; + if (!o) { + return NULL; + } + + int32_t column_index = ecs_table_get_column_index(world, table, id); + if (column_index == -1) { + return NULL; + } + + flecs_table_update_overrides(world, table); + + ecs_ref_t *r = &o->refs[column_index]; + if (!r->entity) { + return NULL; + } + + return r; +} + +void flecs_table_keep( + ecs_table_t *table) +{ + table->keep ++; +} + +void flecs_table_release( + ecs_table_t *table) +{ + table->keep --; + ecs_assert(table->keep >= 0, ECS_INTERNAL_ERROR, NULL); +} + +ecs_component_record_t* flecs_table_get_childof_cr( + const ecs_world_t *world, + const ecs_table_t *table) +{ + if (!table) { + return NULL; + } + + int16_t index = table->childof_index; + if (index == -1) { + return world->cr_childof_0; + } else { + return table->_->records[index].hdr.cr; + } +} + +ecs_pair_record_t* flecs_table_get_childof_pr( + const ecs_world_t *world, + const ecs_table_t *table) +{ + if (!table) { + return NULL; + } + + ecs_component_record_t *cr = flecs_table_get_childof_cr(world, table); + if (cr) { + return cr->pair; + } + return NULL; +} + +ecs_hashmap_t* flecs_table_get_name_index( + const ecs_world_t *world, + const ecs_table_t *table) +{ + if (!table) { + return NULL; + } + + ecs_pair_record_t *pr = flecs_table_get_childof_pr(world, table); + if (pr) { + return pr->name_index; + } + return NULL; +} + + +/* -- Public API -- */ + +void ecs_table_lock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (table) { + if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { + table->_->lock ++; + } + } +} + +void ecs_table_unlock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (table) { + if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { + table->_->lock --; + ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, + "table_unlock called more often than table_lock"); + } + } +} + +const ecs_type_t* ecs_table_get_type( + const ecs_table_t *table) +{ + if (table) { + return &table->type; + } else { + return NULL; + } +} + +int32_t ecs_table_get_type_index( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + + if (id < FLECS_HI_COMPONENT_ID) { + int16_t res = table->component_map[id]; + if (res > 0) { + return table->column_map[table->type.count + (res - 1)]; + } + + return -res - 1; + } + + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { + return -1; + } + + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + return -1; + } + + return tr->index; +error: + return -1; +} + +int32_t ecs_table_get_column_index( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + if (id < FLECS_HI_COMPONENT_ID) { + int16_t res = table->component_map[id]; + if (res > 0) { + return res - 1; + } + return -1; + } + + ecs_component_record_t *cr = flecs_components_get(world, id); + if (!cr) { + return -1; + } + + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + return -1; + } + + return tr->column; +error: + return -1; +} + +int32_t ecs_table_column_count( + const ecs_table_t *table) +{ + return table->column_count; +} + +int32_t ecs_table_type_to_column_index( + const ecs_table_t *table, + int32_t index) +{ + ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); + int16_t *column_map = table->column_map; + if (column_map) { + return column_map[index]; + } +error: + return -1; +} + +int32_t ecs_table_column_to_type_index( + const ecs_table_t *table, + int32_t index) +{ + ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t offset = table->type.count; + return table->column_map[offset + index]; +error: + return -1; +} + +void* ecs_table_get_column( + const ecs_table_t *table, + int32_t index, + int32_t offset) +{ + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, + "column index %d out of range for table", index); + + ecs_column_t *column = &table->data.columns[index]; + void *result = column->data; + if (offset) { + result = ECS_ELEM(result, column->ti->size, offset); + } + + return result; +error: + return NULL; +} + +void* ecs_table_get_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + int32_t offset) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + int32_t index = ecs_table_get_column_index(world, table, id); + if (index == -1) { + return NULL; + } + + return ecs_table_get_column(table, index, offset); +error: + return NULL; +} + +size_t ecs_table_get_column_size( + const ecs_table_t *table, + int32_t column) +{ + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(column < table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); + + return flecs_ito(size_t, table->data.columns[column].ti->size); +error: + return 0; +} + +int32_t ecs_table_count( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return table->data.count; +} + +int32_t ecs_table_size( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return table->data.size; +} + +bool ecs_table_has_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + return ecs_table_get_type_index(world, table, id) != -1; +} + +ecs_entity_t ecs_table_get_target( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t relationship, + int32_t index) +{ + flecs_poly_assert(world, ecs_world_t); + + ecs_component_record_t *cr = flecs_components_get(world, + ecs_pair(relationship, EcsWildcard)); + if (!cr) { + return 0; + } + + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + return 0; + } + + if (index > tr->count) { + return 0; + } + + ecs_id_t id = table->type.array[tr->index + index]; + ecs_assert(ECS_IS_PAIR(id), ECS_INTERNAL_ERROR, NULL); + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + return flecs_entities_get_alive(world, tgt); +} + +int32_t ecs_table_get_depth( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t rel) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, + "cannot safely determine depth for relationship that is not acyclic " + "(add Acyclic property to relationship)"); + + world = ecs_get_world(world); + + return flecs_relation_depth(world, rel, table); +error: + return -1; +} + +bool ecs_table_has_flags( + ecs_table_t *table, + ecs_flags32_t flags) +{ + return (table->flags & flags) == flags; +} + +bool ecs_table_has_traversable( + const ecs_table_t *table) +{ + return table->_->traversable_count != 0; +} + +void ecs_table_swap_rows( + ecs_world_t* world, + ecs_table_t* table, + int32_t row_1, + int32_t row_2) +{ + flecs_table_swap(world, table, row_1, row_2); +} + +void* ecs_record_get_by_column( + const ecs_record_t *r, + int32_t index, + size_t c_size) +{ + (void)c_size; + ecs_table_t *table = r->table; + + ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_column_t *column = &table->data.columns[index]; + ecs_size_t size = column->ti->size; + + ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, + ECS_INVALID_PARAMETER, NULL); + + return ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); +error: + return NULL; +} + +ecs_record_t* ecs_record_find( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_record_t *r = flecs_entities_get(world, entity); + if (r) { + return r; + } +error: + return NULL; +} + +char* ecs_table_str( + const ecs_world_t *world, + const ecs_table_t *table) +{ + if (table) { + return ecs_type_str(world, &table->type); + } else { + return NULL; + } +} + +/** + * @file storage/table_cache.c + * @brief Data structure for fast table iteration/lookups. + * + * A table cache is a data structure that provides constant time operations for + * insertion and removal of tables, and to testing whether a table is registered + * with the cache. A table cache also provides functions to iterate the tables + * in a cache. + * + * The world stores a table cache per (component) id inside the component record + * administration. Cached queries store a table cache with matched tables. + * + * A table cache has separate lists for non-empty tables and empty tables. This + * improves performance as applications don't waste time iterating empty tables. + */ + + +static +void flecs_table_cache_list_remove( + ecs_table_cache_t *cache, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t *next = elem->next; + ecs_table_cache_hdr_t *prev = elem->prev; + + if (next) { + next->prev = prev; + } + if (prev) { + prev->next = next; + } + + cache->tables.count --; + + if (cache->tables.first == elem) { + cache->tables.first = next; + } + if (cache->tables.last == elem) { + cache->tables.last = prev; + } + + ecs_assert(cache->tables.first == NULL || cache->tables.count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->tables.first == NULL || cache->tables.last != NULL, + ECS_INTERNAL_ERROR, NULL); +} + +static +void flecs_table_cache_list_insert( + ecs_table_cache_t *cache, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t *last = cache->tables.last; + cache->tables.last = elem; + if ((++ cache->tables.count) == 1) { + cache->tables.first = elem; + } + + elem->next = NULL; + elem->prev = last; + + if (last) { + last->next = elem; + } + + ecs_assert( + cache->tables.count != 1 || cache->tables.first == cache->tables.last, + ECS_INTERNAL_ERROR, NULL); +} + +void ecs_table_cache_init( + ecs_world_t *world, + ecs_table_cache_t *cache) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_init(&cache->index, &world->allocator); +} + +void ecs_table_cache_fini( + ecs_table_cache_t *cache) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_fini(&cache->index); +} + +void ecs_table_cache_insert( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *result) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_table_cache_get(cache, table) == NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + + result->cr = (ecs_component_record_t*)cache; + result->table = ECS_CONST_CAST(ecs_table_t*, table); + + flecs_table_cache_list_insert(cache, result); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_insert_ptr(&cache->index, table->id, result); + + ecs_assert(cache->tables.first != NULL, ECS_INTERNAL_ERROR, NULL); +} + +void ecs_table_cache_replace( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t **r = ecs_map_get_ref( + &cache->index, ecs_table_cache_hdr_t, table->id); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *old = *r; + ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *prev = old->prev, *next = old->next; + if (prev) { + ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL); + prev->next = elem; + } + if (next) { + ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL); + next->prev = elem; + } + + if (cache->tables.first == old) { + cache->tables.first = elem; + } + if (cache->tables.last == old) { + cache->tables.last = elem; + } + + *r = elem; + elem->prev = prev; + elem->next = next; +} + +void* ecs_table_cache_get( + const ecs_table_cache_t *cache, + const ecs_table_t *table) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_map_is_init(&cache->index), ECS_INTERNAL_ERROR, NULL); + return ecs_map_get_deref(&cache->index, void**, table->id); +} + +void* ecs_table_cache_remove( + ecs_table_cache_t *cache, + uint64_t table_id, + ecs_table_cache_hdr_t *elem) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(elem->cr == (ecs_component_record_t*)cache, + ECS_INTERNAL_ERROR, NULL); + + flecs_table_cache_list_remove(cache, elem); + ecs_map_remove(&cache->index, table_id); + + return elem; +} + +bool flecs_table_cache_iter( + const ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->tables.first; + out->cur = NULL; + out->iter_fill = true; + out->iter_empty = false; + return out->next != NULL; +} + +bool flecs_table_cache_empty_iter( + const ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->tables.first; + out->cur = NULL; + out->iter_fill = false; + out->iter_empty = true; + return out->next != NULL; +} + +bool flecs_table_cache_all_iter( + const ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->tables.first; + out->cur = NULL; + out->iter_fill = true; + out->iter_empty = true; + return out->next != NULL; +} + +const ecs_table_cache_hdr_t* flecs_table_cache_next_( + ecs_table_cache_iter_t *it) +{ + const ecs_table_cache_hdr_t *next; + +repeat: + next = it->next; + it->cur = next; + + if (next) { + it->next = next->next; + + if (ecs_table_count(next->table)) { + if (!it->iter_fill) { + goto repeat; + } + } else { + if (!it->iter_empty) { + goto repeat; + } + } + } + + return next; +} + +/** + * @file storage/table_graph.c + * @brief Data structure to speed up table transitions. + * + * The table graph is used to speed up finding tables in add/remove operations. + * For example, if component C is added to an entity in table [A, B], the entity + * must be moved to table [A, B, C]. The graph speeds this process up with an + * edge for component C that connects [A, B] to [A, B, C]. + */ + + +/* Id sequence (type) utilities */ + +static +uint64_t flecs_type_hash(const void *ptr) { + const ecs_type_t *type = ptr; + ecs_id_t *ids = type->array; + int32_t count = type->count; + return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); +} + +static +int flecs_type_compare(const void *ptr_1, const void *ptr_2) { + const ecs_type_t *type_1 = ptr_1; + const ecs_type_t *type_2 = ptr_2; + + int32_t count_1 = type_1->count; + int32_t count_2 = type_2->count; + + if (count_1 != count_2) { + return (count_1 > count_2) - (count_1 < count_2); + } + + const ecs_id_t *ids_1 = type_1->array; + const ecs_id_t *ids_2 = type_2->array; + int result = 0; + + int32_t i; + for (i = 0; !result && (i < count_1); i ++) { + ecs_id_t id_1 = ids_1[i]; + ecs_id_t id_2 = ids_2[i]; + result = (id_1 > id_2) - (id_1 < id_2); + } + + return result; +} + +void flecs_table_hashmap_init( + ecs_world_t *world, + ecs_hashmap_t *hm) +{ + flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*, + flecs_type_hash, flecs_type_compare, &world->allocator); +} + +/* Find location where to insert id into type */ +static +int flecs_type_find_insert( + const ecs_type_t *type, + int32_t offset, + ecs_id_t to_add) +{ + ecs_id_t *array = type->array; + int32_t i, count = type->count; + + for (i = offset; i < count; i ++) { + ecs_id_t id = array[i]; + if (id == to_add) { + return -1; + } + if (id > to_add) { + return i; + } + } + return i; +} + +/* Find location of id in type */ +static +int flecs_type_find( + const ecs_type_t *type, + ecs_id_t id) +{ + ecs_id_t *array = type->array; + int32_t i, count = type->count; + + for (i = 0; i < count; i ++) { + ecs_id_t cur = array[i]; + if (ecs_id_match(cur, id)) { + return i; + } + if (!ECS_IS_PAIR(id) && (cur > id)) { + return -1; + } + } + + return -1; +} + +/* Count number of matching ids */ +static +int flecs_type_count_matches( + const ecs_type_t *type, + ecs_id_t wildcard, + int32_t offset) +{ + ecs_id_t *array = type->array; + int32_t i = offset, count = type->count; + + for (; i < count; i ++) { + ecs_id_t cur = array[i]; + if (!ecs_id_match(cur, wildcard)) { + break; + } + } + + return i - offset; +} + +/* Create type from source type with id */ +static +int flecs_type_new_with( + ecs_world_t *world, + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t with) +{ + ecs_id_t *src_array = src->array; + int32_t at = flecs_type_find_insert(src, 0, with); + if (at == -1) { + return -1; + } + + int32_t dst_count = src->count + 1; + ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); + dst->count = dst_count; + dst->array = dst_array; + + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } + + int32_t remain = src->count - at; + if (remain) { + ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); + } + + dst_array[at] = with; + + return 0; +} + +/* Create type from source type without ids matching wildcard */ +static +int flecs_type_new_filtered( + ecs_world_t *world, + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t wildcard, + int32_t at) +{ + *dst = flecs_type_copy(world, src); + ecs_id_t *dst_array = dst->array; + ecs_id_t *src_array = src->array; + if (at) { + ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } + + int32_t i = at + 1, w = at, count = src->count; + for (; i < count; i ++) { + ecs_id_t id = src_array[i]; + if (!ecs_id_match(id, wildcard)) { + dst_array[w] = id; + w ++; + } + } + + dst->count = w; + + if (w != count) { + if (w) { + dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); + } else { + flecs_wfree_n(world, ecs_id_t, count, dst->array); + dst->array = NULL; + } + } + + return 0; +} + +/* Create type from source type without id */ +static +int flecs_type_new_without( + ecs_world_t *world, + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t without) +{ + ecs_id_t *src_array = src->array; + int32_t count = 1, at = flecs_type_find(src, without); + if (at == -1) { + return -1; + } + + int32_t src_count = src->count; + if (src_count == 1) { + dst->array = NULL; + dst->count = 0; + return 0; + } + + if (ecs_id_is_wildcard(without)) { + if (ECS_IS_PAIR(without)) { + ecs_entity_t r = ECS_PAIR_FIRST(without); + ecs_entity_t o = ECS_PAIR_SECOND(without); + if (r == EcsWildcard && o != EcsWildcard) { + return flecs_type_new_filtered(world, dst, src, without, at); + } + } + count += flecs_type_count_matches(src, without, at + 1); + } + + int32_t dst_count = src_count - count; + dst->count = dst_count; + if (!dst_count) { + dst->array = NULL; + return 0; + } + + ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); + dst->array = dst_array; + + ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src_array != NULL, ECS_INTERNAL_ERROR, NULL); + + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } + + int32_t remain = dst_count - at; + if (remain) { + ecs_os_memcpy_n( + &dst_array[at], &src_array[at + count], ecs_id_t, remain); + } + + return 0; +} + +/* Copy type */ +ecs_type_t flecs_type_copy( + ecs_world_t *world, + const ecs_type_t *src) +{ + int32_t src_count = src->count; + if (!src_count) { + return (ecs_type_t){ 0 }; + } + + ecs_id_t *ids = flecs_walloc_n(world, ecs_id_t, src_count); + ecs_os_memcpy_n(ids, src->array, ecs_id_t, src_count); + return (ecs_type_t) { + .array = ids, + .count = src_count + }; +} + +/* Free type */ +void flecs_type_free( + ecs_world_t *world, + ecs_type_t *type) +{ + int32_t count = type->count; + if (count) { + flecs_wfree_n(world, ecs_id_t, type->count, type->array); + } +} + +/* Add to type */ +void flecs_type_add( + ecs_world_t *world, + ecs_type_t *type, + ecs_id_t add) +{ + ecs_type_t new_type; + int res = flecs_type_new_with(world, &new_type, type, add); + if (res != -1) { + flecs_type_free(world, type); + type->array = new_type.array; + type->count = new_type.count; + } +} + +/* Remove from type */ +void flecs_type_remove( + ecs_world_t *world, + ecs_type_t *type, + ecs_id_t remove) +{ + ecs_type_t new_type; + int res = flecs_type_new_without(world, &new_type, type, remove); + if (res != -1) { + flecs_type_free(world, type); + type->array = new_type.array; + type->count = new_type.count; + } +} + +/* Graph edge utilities */ + +void flecs_table_diff_builder_init( + ecs_world_t *world, + ecs_table_diff_builder_t *builder) +{ + ecs_allocator_t *a = &world->allocator; + ecs_vec_init_t(a, &builder->added, ecs_id_t, 32); + ecs_vec_init_t(a, &builder->removed, ecs_id_t, 32); + builder->added_flags = 0; + builder->removed_flags = 0; +} + +void flecs_table_diff_builder_fini( + ecs_world_t *world, + ecs_table_diff_builder_t *builder) +{ + ecs_allocator_t *a = &world->allocator; + ecs_vec_fini_t(a, &builder->added, ecs_id_t); + ecs_vec_fini_t(a, &builder->removed, ecs_id_t); +} + +void flecs_table_diff_builder_clear( + ecs_table_diff_builder_t *builder) +{ + ecs_vec_clear(&builder->added); + ecs_vec_clear(&builder->removed); +} + +static +void flecs_table_diff_build_type( + ecs_world_t *world, + ecs_vec_t *vec, + ecs_type_t *type, + int32_t offset) +{ + int32_t count = vec->count - offset; + ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + if (count) { + type->array = flecs_wdup_n(world, ecs_id_t, count, + ECS_ELEM_T(vec->array, ecs_id_t, offset)); + type->count = count; + ecs_vec_set_count_t(&world->allocator, vec, ecs_id_t, offset); + } +} + +void flecs_table_diff_build( + ecs_world_t *world, + ecs_table_diff_builder_t *builder, + ecs_table_diff_t *diff, + int32_t added_offset, + int32_t removed_offset) +{ + flecs_table_diff_build_type(world, &builder->added, &diff->added, + added_offset); + flecs_table_diff_build_type(world, &builder->removed, &diff->removed, + removed_offset); + diff->added_flags = builder->added_flags; + diff->removed_flags = builder->removed_flags; +} + +void flecs_table_diff_build_noalloc( + ecs_table_diff_builder_t *builder, + ecs_table_diff_t *diff) +{ + diff->added = (ecs_type_t){ + .array = builder->added.array, .count = builder->added.count }; + diff->removed = (ecs_type_t){ + .array = builder->removed.array, .count = builder->removed.count }; + diff->added_flags = builder->added_flags; + diff->removed_flags = builder->removed_flags; +} + +static +void flecs_table_diff_build_add_type_to_vec( + ecs_world_t *world, + ecs_vec_t *vec, + ecs_type_t *add) +{ + if (!add || !add->count) { + return; + } + + int32_t offset = vec->count; + ecs_vec_grow_t(&world->allocator, vec, ecs_id_t, add->count); + ecs_os_memcpy_n(ecs_vec_get_t(vec, ecs_id_t, offset), + add->array, ecs_id_t, add->count); +} + +void flecs_table_diff_build_append_table( + ecs_world_t *world, + ecs_table_diff_builder_t *dst, + ecs_table_diff_t *src) +{ + flecs_table_diff_build_add_type_to_vec(world, &dst->added, &src->added); + flecs_table_diff_build_add_type_to_vec(world, &dst->removed, &src->removed); + dst->added_flags |= src->added_flags; + dst->removed_flags |= src->removed_flags; +} + +static +void flecs_table_diff_free( + ecs_world_t *world, + ecs_table_diff_t *diff) +{ + flecs_wfree_n(world, ecs_id_t, diff->added.count, diff->added.array); + flecs_wfree_n(world, ecs_id_t, diff->removed.count, diff->removed.array); + flecs_bfree(&world->allocators.table_diff, diff); +} + +static +ecs_graph_edge_t* flecs_table_ensure_hi_edge( + ecs_world_t *world, + ecs_graph_edges_t *edges, + ecs_id_t id) +{ + if (!edges->hi) { + edges->hi = flecs_alloc_t(&world->allocator, ecs_map_t); + ecs_map_init(edges->hi, &world->allocator); + } + + ecs_graph_edge_t **r = ecs_map_ensure_ref(edges->hi, ecs_graph_edge_t, id); + ecs_graph_edge_t *edge = r[0]; + if (edge) { + return edge; + } + + if (id < FLECS_HI_COMPONENT_ID) { + edge = &edges->lo[id]; + } else { + edge = flecs_bcalloc(&world->allocators.graph_edge); + } + + r[0] = edge; + return edge; +} + +static +ecs_graph_edge_t* flecs_table_ensure_edge( + ecs_world_t *world, + ecs_graph_edges_t *edges, + ecs_id_t id) +{ + ecs_graph_edge_t *edge; + + if (id < FLECS_HI_COMPONENT_ID) { + if (!edges->lo) { + edges->lo = flecs_bcalloc(&world->allocators.graph_edge_lo); + } + edge = &edges->lo[id]; + } else { + edge = flecs_table_ensure_hi_edge(world, edges, id); + } + + return edge; +} + +static +void flecs_table_disconnect_edge( + ecs_world_t *world, + ecs_id_t id, + ecs_graph_edge_t *edge) +{ + ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); + (void)id; + + /* Remove backref from destination table */ + ecs_graph_edge_hdr_t *next = edge->hdr.next; + ecs_graph_edge_hdr_t *prev = edge->hdr.prev; + + if (next) { + next->prev = prev; + } + if (prev) { + prev->next = next; + } + + /* Remove data associated with edge */ + ecs_table_diff_t *diff = edge->diff; + if (diff) { + flecs_table_diff_free(world, diff); + } + + /* If edge id is low, clear it from fast lookup array */ + if (id < FLECS_HI_COMPONENT_ID) { + ecs_os_memset_t(edge, 0, ecs_graph_edge_t); + } else { + flecs_bfree(&world->allocators.graph_edge, edge); + } +} + +static +void flecs_table_remove_edge( + ecs_world_t *world, + ecs_graph_edges_t *edges, + ecs_id_t id, + ecs_graph_edge_t *edge) +{ + ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edges->hi != NULL, ECS_INTERNAL_ERROR, NULL); + if (!edge->id) { + return; + } + flecs_table_disconnect_edge(world, id, edge); + ecs_map_remove(edges->hi, id); +} + +static +void flecs_table_init_edges( + ecs_graph_edges_t *edges) +{ + edges->lo = NULL; + edges->hi = NULL; +} + +static +void flecs_table_init_node( + ecs_graph_node_t *node) +{ + flecs_table_init_edges(&node->add); + flecs_table_init_edges(&node->remove); +} + +static +void flecs_init_table( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *prev) +{ + table->flags = 0; + table->dirty_state = NULL; + table->_->lock = 0; + table->_->generation = 0; + + flecs_table_init_node(&table->node); + + flecs_table_init(world, table, prev); +} + +static +ecs_table_t *flecs_table_new( + ecs_world_t *world, + ecs_type_t *type, + flecs_hashmap_result_t table_elem, + ecs_table_t *prev) +{ + flecs_check_exclusive_world_access_write(world); + + ecs_os_perf_trace_push("flecs.table.create"); + + ecs_table_t *result = flecs_sparse_add_t(&world->store.tables, ecs_table_t); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + result->_ = flecs_calloc_t(&world->allocator, ecs_table__t); + ecs_assert(result->_ != NULL, ECS_INTERNAL_ERROR, NULL); + +#ifdef FLECS_SANITIZE + int32_t i, j, count = type->count; + for (i = 0; i < count - 1; i ++) { + if (type->array[i] >= type->array[i + 1]) { + for (j = 0; j < count; j ++) { + char *str = ecs_id_str(world, type->array[j]); + if (i == j) { + ecs_err(" > %d: %s", j, str); + } else { + ecs_err(" %d: %s", j, str); + } + ecs_os_free(str); + } + ecs_abort(ECS_CONSTRAINT_VIOLATED, "table type is not ordered"); + } + } +#endif + + result->id = flecs_sparse_last_id(&world->store.tables); + result->type = *type; + + if (ecs_should_log_2()) { + char *expr = ecs_type_str(world, &result->type); + ecs_dbg_2( + "#[green]table#[normal] [%s] #[green]created#[reset] with id %d", + expr, result->id); + ecs_os_free(expr); + } + + ecs_log_push_2(); + + /* Store table in table hashmap */ + *(ecs_table_t**)table_elem.value = result; + + /* Set keyvalue to one that has the same lifecycle as the table */ + *(ecs_type_t*)table_elem.key = result->type; + result->_->hash = table_elem.hash; + + flecs_init_table(world, result, prev); + + /* Update counters */ + world->info.table_count ++; + world->info.table_create_total ++; + + ecs_log_pop_2(); + + ecs_os_perf_trace_pop("flecs.table.create"); + + return result; +} + +static +ecs_table_t* flecs_table_ensure( + ecs_world_t *world, + ecs_type_t *type, + bool own_type, + ecs_table_t *prev) +{ + flecs_poly_assert(world, ecs_world_t); + + int32_t id_count = type->count; + if (!id_count) { + return &world->store.root; + } + + ecs_table_t *table; + flecs_hashmap_result_t elem = flecs_hashmap_ensure( + &world->store.table_map, type, ecs_table_t*); + if ((table = *(ecs_table_t**)elem.value)) { + if (own_type) { + flecs_type_free(world, type); + } + return table; + } + + /* If we get here, table needs to be created which is only allowed when the + * application is not currently in progress */ + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); + + /* If we get here, the table has not been found, so create it. */ + if (own_type) { + return flecs_table_new(world, type, elem, prev); + } + + ecs_type_t copy = flecs_type_copy(world, type); + return flecs_table_new(world, ©, elem, prev); +} + +static +void flecs_diff_insert_added( + ecs_world_t *world, + ecs_table_diff_builder_t *diff, + ecs_id_t id) +{ + ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id; +} + +static +void flecs_diff_insert_removed( + ecs_world_t *world, + ecs_table_diff_builder_t *diff, + ecs_id_t id) +{ + ecs_allocator_t *a = &world->allocator; + ecs_vec_append_t(a, &diff->removed, ecs_id_t)[0] = id; +} + +static +bool flecs_id_is_alive( + ecs_world_t *world, + ecs_id_t id) +{ + if (ECS_IS_PAIR(id)) { + if (!flecs_entities_get_alive(world, ECS_PAIR_FIRST(id))) { + return false; + } + if (!flecs_entities_get_alive(world, ECS_PAIR_SECOND(id))) { + return false; + } + return true; + } else { + return flecs_entities_get_alive(world, id & ECS_COMPONENT_MASK) != 0; + } +} + +static +void flecs_compute_table_diff( + ecs_world_t *world, + ecs_table_t *node, + ecs_table_t *next, + ecs_graph_edge_t *edge, + ecs_id_t id, + bool is_remove) +{ + ecs_type_t node_type = node->type; + ecs_type_t next_type = next->type; + bool childof = false; + + if (ECS_IS_PAIR(id)) { + childof = ECS_PAIR_FIRST(id) == EcsChildOf; + } + + ecs_component_record_t *cr = NULL; + + if (!flecs_id_is_alive(world, id)) { + return; + } + + bool dont_fragment = false; + if (id < FLECS_HI_COMPONENT_ID) { + dont_fragment = (world->non_trivial_lookup[id] & + EcsNonTrivialIdNonFragmenting) != 0; + if (dont_fragment) { + cr = flecs_components_ensure(world, id); + } + } else { + cr = flecs_components_ensure(world, id); + dont_fragment = cr->flags & EcsIdDontFragment; + } + + if (dont_fragment) { + ecs_table_diff_t *diff = flecs_bcalloc( + &world->allocators.table_diff); + if (is_remove) { + diff->removed.count = 1; + diff->removed.array = flecs_wdup_n(world, ecs_id_t, 1, &id); + diff->removed_flags = EcsTableHasDontFragment|EcsTableHasSparse; + } else { + diff->added.count = 1; + diff->added.array = flecs_wdup_n(world, ecs_id_t, 1, &id); + diff->added_flags = EcsTableHasDontFragment|EcsTableHasSparse; + } + edge->diff = diff; + + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_append_t(&world->allocator, + &cr->dont_fragment_tables, uint64_t)[0] = node->id; + return; + } + + ecs_id_t *ids_node = node_type.array; + ecs_id_t *ids_next = next_type.array; + int32_t i_node = 0, node_count = node_type.count; + int32_t i_next = 0, next_count = next_type.count; + int32_t added_count = 0; + int32_t removed_count = 0; + ecs_flags32_t added_flags = 0, removed_flags = 0; + bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA) && !childof; + + /* First do a scan to see how big the diff is, so we don't have to realloc + * or alloc more memory than required. */ + for (; i_node < node_count && i_next < next_count; ) { + ecs_id_t id_node = ids_node[i_node]; + ecs_id_t id_next = ids_next[i_next]; + + bool added = id_next < id_node; + bool removed = id_node < id_next; + + trivial_edge &= !added || id_next == id; + trivial_edge &= !removed || id_node == id; + + if (added) { + added_flags |= flecs_id_flags_get(world, id_next) & + EcsTableAddEdgeFlags; + added_count ++; + } + + if (removed) { + removed_flags |= flecs_id_flags_get(world, id_node) & + EcsTableRemoveEdgeFlags; + removed_count ++; + } + + i_node += id_node <= id_next; + i_next += id_next <= id_node; + } + + for (; i_next < next_count; i_next ++) { + added_flags |= flecs_id_flags_get(world, ids_next[i_next]) & + EcsTableAddEdgeFlags; + added_count ++; + } + + for (; i_node < node_count; i_node ++) { + removed_flags |= flecs_id_flags_get(world, ids_node[i_node]) & + EcsTableRemoveEdgeFlags; + removed_count ++; + } + + trivial_edge &= (added_count + removed_count) <= 1 && + !ecs_id_is_wildcard(id) && !(added_flags|removed_flags); + + if (trivial_edge) { + /* If edge is trivial there's no need to create a diff element for it */ + return; + } + + ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; + int32_t added_offset = builder->added.count; + int32_t removed_offset = builder->removed.count; + + for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { + ecs_id_t id_node = ids_node[i_node]; + ecs_id_t id_next = ids_next[i_next]; + + if (id_next < id_node) { + flecs_diff_insert_added(world, builder, id_next); + } else if (id_node < id_next) { + flecs_diff_insert_removed(world, builder, id_node); + } + + i_node += id_node <= id_next; + i_next += id_next <= id_node; + } + + for (; i_next < next_count; i_next ++) { + flecs_diff_insert_added(world, builder, ids_next[i_next]); + } + + for (; i_node < node_count; i_node ++) { + flecs_diff_insert_removed(world, builder, ids_node[i_node]); + } + + ecs_table_diff_t *diff = flecs_bcalloc(&world->allocators.table_diff); + edge->diff = diff; + flecs_table_diff_build(world, builder, diff, added_offset, removed_offset); + diff->added_flags = added_flags; + diff->removed_flags = removed_flags; + + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsChildOf) { + if (added_count) { + diff->added_flags |= EcsTableEdgeReparent; + } else { + ecs_assert(removed_count != 0, ECS_INTERNAL_ERROR, NULL); + diff->removed_flags |= EcsTableEdgeReparent; + } + } + + ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); +} + +static +void flecs_add_overrides_for_base( + ecs_world_t *world, + ecs_type_t *dst_type, + ecs_id_t pair) +{ + ecs_entity_t base = ecs_pair_second(world, pair); + ecs_assert(base != 0, ECS_INVALID_PARAMETER, + "target of IsA pair is not alive"); + ecs_table_t *base_table = ecs_get_table(world, base); + if (!base_table) { + return; + } + + ecs_id_t *ids = base_table->type.array; + ecs_flags32_t flags = base_table->flags; + if (flags & EcsTableHasOverrides) { + int32_t i, count = base_table->type.count; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + ecs_id_t to_add = 0; + if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { + to_add = id & ~ECS_AUTO_OVERRIDE; + + ecs_flags32_t cr_flags = flecs_component_get_flags(world, to_add); + if (cr_flags & EcsIdDontFragment) { + to_add = 0; + + /* Add flag to base table. Cheaper to do here vs adding an + * observer for OnAdd AUTO_OVERRIDE|* / during table + * creation. */ + base_table->flags |= EcsTableOverrideDontFragment; + } + } else { + ecs_table_record_t *tr = &base_table->_->records[i]; + if (ECS_ID_ON_INSTANTIATE(tr->hdr.cr->flags) == EcsOverride) { + to_add = id; + } + } + + if (to_add) { + ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(to_add), EcsWildcard); + bool exclusive = false; + if (ECS_IS_PAIR(to_add)) { + ecs_component_record_t *cr = flecs_components_get(world, wc); + if (cr) { + exclusive = (cr->flags & EcsIdExclusive) != 0; + } + } + if (!exclusive) { + flecs_type_add(world, dst_type, to_add); + } else { + int32_t column = flecs_type_find(dst_type, wc); + if (column == -1) { + flecs_type_add(world, dst_type, to_add); + } else { + dst_type->array[column] = to_add; + } + } + } + } + } + + if (flags & EcsTableHasIsA) { + const ecs_table_record_t *tr = flecs_component_get_table( + world->cr_isa_wildcard, base_table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i = tr->index, end = i + tr->count; + for (; i != end; i ++) { + flecs_add_overrides_for_base(world, dst_type, ids[i]); + } + } +} + +static +void flecs_add_with_property( + ecs_world_t *world, + ecs_component_record_t *cr_with_wildcard, + ecs_type_t *dst_type, + ecs_entity_t r, + ecs_entity_t o) +{ + r = ecs_get_alive(world, r); + + /* Check if component/relationship has With pairs, which contain ids + * that need to be added to the table. */ + ecs_table_t *table = ecs_get_table(world, r); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + const ecs_table_record_t *tr = flecs_component_get_table( + cr_with_wildcard, table); + if (tr) { + int32_t i = tr->index, end = i + tr->count; + ecs_id_t *ids = table->type.array; + + for (; i < end; i ++) { + ecs_id_t id = ids[i]; + ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL); + ecs_id_t ra = ECS_PAIR_SECOND(id); + ecs_id_t a = ra; + if (o) { + a = ecs_pair(ra, o); + } + + flecs_type_add(world, dst_type, a); + flecs_add_with_property(world, cr_with_wildcard, dst_type, ra, o); + } + } +} + +static +ecs_table_t* flecs_find_table_with( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t with) +{ + ecs_make_alive_id(world, with); + + ecs_component_record_t *cr = NULL; + ecs_entity_t r = 0, o = 0; + + if (ECS_IS_PAIR(with)) { + r = ECS_PAIR_FIRST(with); + o = ECS_PAIR_SECOND(with); + cr = flecs_components_ensure(world, ecs_pair(r, EcsWildcard)); + if (cr->flags & EcsIdExclusive) { + /* Relationship is exclusive, check if table already has it */ + const ecs_table_record_t *tr = flecs_component_get_table(cr, node); + if (tr) { + /* Table already has an instance of the relationship, create + * a new id sequence with the existing id replaced */ + ecs_type_t dst_type = flecs_type_copy(world, &node->type); + ecs_assert(dst_type.array != NULL, ECS_INTERNAL_ERROR, NULL); + dst_type.array[tr->index] = with; + return flecs_table_ensure(world, &dst_type, true, node); + } + } + } else { + cr = flecs_components_ensure(world, with); + r = with; + } + + if (cr->flags & EcsIdDontFragment) { + /* Component doesn't fragment tables */ + node->flags |= EcsTableHasDontFragment; + return node; + } + + /* Create sequence with new id */ + ecs_type_t dst_type; + int res = flecs_type_new_with(world, &dst_type, &node->type, with); + if (res == -1) { + return node; /* Current table already has id */ + } + + if (r == EcsIsA) { + /* If adding a prefab, check if prefab has overrides */ + flecs_add_overrides_for_base(world, &dst_type, with); + } else if (r == EcsChildOf) { + o = ecs_get_alive(world, o); + if (ecs_has_id(world, o, EcsPrefab)) { + flecs_type_add(world, &dst_type, EcsPrefab); + } + } + + if (cr->flags & EcsIdWith) { + ecs_component_record_t *cr_with_wildcard = flecs_components_get(world, + ecs_pair(EcsWith, EcsWildcard)); + /* If id has With property, add targets to type */ + flecs_add_with_property(world, cr_with_wildcard, &dst_type, r, o); + } + + if (with == ecs_id(EcsParent)) { + if (node->flags & EcsTableHasChildOf) { + flecs_type_remove(world, &dst_type, + ecs_pair(EcsChildOf, EcsWildcard)); + } + } else if (ECS_PAIR_FIRST(with) == EcsChildOf) { + if (node->flags & EcsTableHasParent) { + flecs_type_remove(world, &dst_type, ecs_id(EcsParent)); + flecs_type_remove(world, &dst_type, + ecs_pair(EcsParentDepth, EcsWildcard)); + } + } + + return flecs_table_ensure(world, &dst_type, true, node); +} + +static +ecs_table_t* flecs_find_table_without( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t without) +{ + ecs_component_record_t *cr = NULL; + + if (ECS_IS_PAIR(without)) { + ecs_entity_t r = ECS_PAIR_FIRST(without); + cr = flecs_components_get(world, ecs_pair(r, EcsWildcard)); + if (cr) { + if (cr->flags & EcsIdDontFragment) { + node->flags |= EcsTableHasDontFragment; + /* Component doesn't fragment tables */ + return node; + } + } + } else { + cr = flecs_components_get(world, without); + if (cr && cr->flags & EcsIdDontFragment) { + node->flags |= EcsTableHasDontFragment; + /* Component doesn't fragment tables */ + return node; + } + } + + /* Create sequence with new id */ + ecs_type_t dst_type; + int res = flecs_type_new_without(world, &dst_type, &node->type, without); + if (res == -1) { + return node; /* Current table does not have id */ + } + + if (without == ecs_id(EcsParent)) { + flecs_type_remove(world, &dst_type, + ecs_pair(EcsParentDepth, EcsWildcard)); + } + + return flecs_table_ensure(world, &dst_type, true, node); +} + +static +void flecs_table_init_edge( + ecs_table_t *table, + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->id == 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->hdr.next == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->hdr.prev == NULL, ECS_INTERNAL_ERROR, NULL); + + edge->from = table; + edge->to = to; + edge->id = id; +} + +static +void flecs_init_edge_for_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) +{ + flecs_table_init_edge(table, edge, id, to); + + flecs_table_ensure_hi_edge(world, &table->node.add, id); + + if ((table != to) || (table->flags & EcsTableHasDontFragment)) { + /* Add edges are appended to refs.next */ + ecs_graph_edge_hdr_t *to_refs = &to->node.refs; + ecs_graph_edge_hdr_t *next = to_refs->next; + + to_refs->next = &edge->hdr; + edge->hdr.prev = to_refs; + + edge->hdr.next = next; + if (next) { + next->prev = &edge->hdr; + } + + flecs_compute_table_diff(world, table, to, edge, id, false); + } +} + +static +void flecs_init_edge_for_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) +{ + flecs_table_init_edge(table, edge, id, to); + + flecs_table_ensure_hi_edge(world, &table->node.remove, id); + + if ((table != to) || (table->flags & EcsTableHasDontFragment)) { + /* Remove edges are appended to refs.prev */ + ecs_graph_edge_hdr_t *to_refs = &to->node.refs; + ecs_graph_edge_hdr_t *prev = to_refs->prev; + + to_refs->prev = &edge->hdr; + edge->hdr.next = to_refs; + + edge->hdr.prev = prev; + if (prev) { + prev->next = &edge->hdr; + } + + flecs_compute_table_diff(world, table, to, edge, id, true); + } +} + +static +ecs_table_t* flecs_create_edge_for_remove( + ecs_world_t *world, + ecs_table_t *node, + ecs_graph_edge_t *edge, + ecs_id_t id) +{ + ecs_table_t *to = flecs_find_table_without(world, node, id); + flecs_init_edge_for_remove(world, node, edge, id, to); + return to; +} + +static +ecs_table_t* flecs_create_edge_for_add( + ecs_world_t *world, + ecs_table_t *node, + ecs_graph_edge_t *edge, + ecs_id_t id) +{ + ecs_table_t *to = flecs_find_table_with(world, node, id); + flecs_init_edge_for_add(world, node, edge, id, to); + return to; +} + +ecs_table_t* flecs_table_traverse_remove( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Removing 0 from an entity is not valid */ + ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_id_t id = id_ptr[0]; + ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.remove, id); + ecs_table_t *to = edge->to; + + if (!to) { + to = flecs_create_edge_for_remove(world, node, edge, id); + ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (node != to || edge->diff) { + if (edge->diff) { + *diff = *edge->diff; + } else { + diff->added.count = 0; + diff->removed.array = id_ptr; + diff->removed.count = 1; + } + } + + return to; +error: + return NULL; +} + +ecs_table_t* flecs_table_traverse_add( + ecs_world_t *world, + ecs_table_t *node, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Adding 0 to an entity is not valid */ + ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_id_t id = id_ptr[0]; + ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.add, id); + ecs_table_t *to = edge->to; + + if (!to) { + to = flecs_create_edge_for_add(world, node, edge, id); + ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (node != to || edge->diff) { + if (edge->diff) { + *diff = *edge->diff; + } else { + diff->added.array = id_ptr; + diff->added.count = 1; + diff->removed.count = 0; + } + } + + return to; +error: + return NULL; +} + +ecs_table_t* flecs_table_find_or_create( + ecs_world_t *world, + ecs_type_t *type) +{ + flecs_poly_assert(world, ecs_world_t); + return flecs_table_ensure(world, type, false, NULL); +} + +void flecs_init_root_table( + ecs_world_t *world) +{ + flecs_poly_assert(world, ecs_world_t); + + world->store.root.type = (ecs_type_t){0}; + world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t); + flecs_init_table(world, &world->store.root, NULL); + + /* Ensure table indices start at 1, as 0 is reserved for the root */ + uint64_t new_id = flecs_sparse_new_id(&world->store.tables); + ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); + (void)new_id; +} + +void flecs_table_edges_add_flags( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_flags32_t flags) +{ + ecs_graph_node_t *table_node = &table->node; + ecs_graph_edge_hdr_t *node_refs = &table_node->refs; + + /* Add flags to incoming matching add edges */ + if (flags == EcsTableHasOnAdd) { + ecs_graph_edge_hdr_t *next, *cur = node_refs->next; + if (cur) { + do { + ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; + if ((id == EcsAny) || ecs_id_match(edge->id, id)) { + if (!edge->diff) { + edge->diff = flecs_bcalloc(&world->allocators.table_diff); + edge->diff->added.array = flecs_walloc_t(world, ecs_id_t); + edge->diff->added.count = 1; + edge->diff->added.array[0] = edge->id; + } + edge->diff->added_flags |= EcsTableHasOnAdd; + } + next = cur->next; + } while ((cur = next)); + } + } + + /* Add flags to outgoing matching remove edges */ + if (flags == EcsTableHasOnRemove) { + ecs_map_iter_t it = ecs_map_iter(table->node.remove.hi); + while (ecs_map_next(&it)) { + ecs_id_t edge_id = ecs_map_key(&it); + if ((id == EcsAny) || ecs_id_match(edge_id, id)) { + ecs_graph_edge_t *edge = ecs_map_ptr(&it); + if (!edge->diff) { + edge->diff = flecs_bcalloc(&world->allocators.table_diff); + edge->diff->removed.array = flecs_walloc_t(world, ecs_id_t); + edge->diff->removed.count = 1; + edge->diff->removed.array[0] = edge->id; + } + edge->diff->removed_flags |= EcsTableHasOnRemove; + } + } + } +} + +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table) +{ + (void)world; + flecs_poly_assert(world, ecs_world_t); + + ecs_log_push_1(); + + ecs_map_iter_t it; + ecs_graph_node_t *table_node = &table->node; + ecs_graph_edges_t *node_add = &table_node->add; + ecs_graph_edges_t *node_remove = &table_node->remove; + ecs_map_t *add_hi = node_add->hi; + ecs_map_t *remove_hi = node_remove->hi; + ecs_graph_edge_hdr_t *node_refs = &table_node->refs; + + /* Cleanup outgoing edges */ + it = ecs_map_iter(add_hi); + while (ecs_map_next(&it)) { + flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); + } + + it = ecs_map_iter(remove_hi); + while (ecs_map_next(&it)) { + flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); + } + + /* Cleanup incoming add edges */ + ecs_graph_edge_hdr_t *next, *cur = node_refs->next; + if (cur) { + do { + ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; + ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); + next = cur->next; + flecs_table_remove_edge(world, &edge->from->node.add, edge->id, edge); + } while ((cur = next)); + } + + /* Cleanup incoming remove edges */ + cur = node_refs->prev; + if (cur) { + do { + ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; + ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); + next = cur->prev; + flecs_table_remove_edge(world, &edge->from->node.remove, edge->id, edge); + } while ((cur = next)); + } + + if (node_add->lo) { + flecs_bfree(&world->allocators.graph_edge_lo, node_add->lo); + } + if (node_remove->lo) { + flecs_bfree(&world->allocators.graph_edge_lo, node_remove->lo); + } + + ecs_map_fini(add_hi); + ecs_map_fini(remove_hi); + flecs_free_t(&world->allocator, ecs_map_t, add_hi); + flecs_free_t(&world->allocator, ecs_map_t, remove_hi); + table_node->add.lo = NULL; + table_node->remove.lo = NULL; + table_node->add.hi = NULL; + table_node->remove.hi = NULL; + + ecs_log_pop_1(); +} + +void flecs_table_clear_edges_for_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t component) +{ + if (component < FLECS_HI_COMPONENT_ID) { + if (table->node.add.lo) { + ecs_graph_edge_t *add_edge = &table->node.add.lo[component]; + if (add_edge->id) { + flecs_table_disconnect_edge(world, component, add_edge); + ecs_map_remove(table->node.add.hi, component); + } + } + + if (table->node.remove.lo) { + ecs_graph_edge_t *remove_edge = &table->node.remove.lo[component]; + if (remove_edge->id) { + flecs_table_disconnect_edge(world, component, remove_edge); + ecs_map_remove(table->node.remove.hi, component); + } + } + } else { + if (table->node.add.hi) { + ecs_graph_edge_t *add_edge = ecs_map_get_ptr( + table->node.add.hi, component); + if (add_edge) { + flecs_table_disconnect_edge(world, component, add_edge); + ecs_map_remove(table->node.add.hi, component); + } + } + + if (table->node.remove.hi) { + ecs_graph_edge_t *remove_edge = ecs_map_get_ptr( + table->node.remove.hi, component); + if (remove_edge) { + flecs_table_disconnect_edge(world, component, remove_edge); + ecs_map_remove(table->node.remove.hi, component); + } + } + } +} + +ecs_table_t* flecs_find_table_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_table_diff_builder_t *diff) +{ + ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; + table = flecs_table_traverse_add(world, table, &id, &temp_diff); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_table_diff_build_append_table(world, diff, &temp_diff); + return table; +error: + return NULL; +} + +/* Public convenience functions for traversing table graph */ +ecs_table_t* ecs_table_add_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) +{ + ecs_table_diff_t diff; + table = table ? table : &world->store.root; + return flecs_table_traverse_add(world, table, &id, &diff); +} + +ecs_table_t* ecs_table_remove_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) +{ + ecs_table_diff_t diff; + table = table ? table : &world->store.root; + return flecs_table_traverse_remove(world, table, &id, &diff); +} + +ecs_table_t* ecs_table_find( + ecs_world_t *world, + const ecs_id_t *ids, + int32_t id_count) +{ + ecs_type_t type = { + .array = ECS_CONST_CAST(ecs_id_t*, ids), + .count = id_count + }; + return flecs_table_ensure(world, &type, false, NULL); +} + +/** + * @file addons/http.c + * @brief HTTP addon. + * + * This is a heavily modified version of the EmbeddableWebServer (see copyright + * below). This version has been stripped from everything not strictly necessary + * for receiving/replying to simple HTTP requests, and has been modified to use + * the Flecs OS API. + * + * EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and + * CONTRIBUTORS (see below) - All rights reserved. + * + * CONTRIBUTORS: + * Martin Pulec - bug fixes, warning fixes, IPv6 support + * Daniel Barry - bug fix (ifa_addr != NULL) + * + * Released under the BSD 2-clause license: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. THIS SOFTWARE IS + * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#ifdef FLECS_HTTP + + +#ifdef ECS_TARGET_MSVC +#pragma comment(lib, "Ws2_32.lib") +#endif + +#if defined(ECS_TARGET_WINDOWS) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#include +typedef SOCKET ecs_http_socket_t; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __FreeBSD__ +#include +#endif +typedef int ecs_http_socket_t; + +#if !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL (0) +#endif + +#endif + +/* Max length of request method */ +#define ECS_HTTP_METHOD_LEN_MAX (8) + +/* Timeout (s) before connection purge */ +#define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) + +/* Number of dequeues before purging */ +#define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) + +/* Number of retries receiving request */ +#define ECS_HTTP_REQUEST_RECV_RETRY (10) + +/* Minimum interval between dequeueing requests (ms) */ +#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) + +/* Minimum interval between printing statistics (ms) */ +#define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) + +/* Receive buffer size */ +#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (64 * 1024) + +/* Max length of request (path + query + headers + body) */ +#define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) + +/* Total number of outstanding send requests */ +#define ECS_HTTP_SEND_QUEUE_MAX (256) + +/* Send request queue */ +typedef struct ecs_http_send_request_t { + ecs_http_socket_t sock; + char *headers; + int32_t header_length; + char *content; + int32_t content_length; +} ecs_http_send_request_t; + +typedef struct ecs_http_send_queue_t { + ecs_http_send_request_t requests[ECS_HTTP_SEND_QUEUE_MAX]; + int32_t head; + int32_t tail; + ecs_os_thread_t thread; + int32_t wait_ms; +} ecs_http_send_queue_t; + +typedef struct ecs_http_request_key_t { + const char *array; + ecs_size_t count; +} ecs_http_request_key_t; + +typedef struct ecs_http_request_entry_t { + char *content; + int32_t content_length; + int code; + double time; +} ecs_http_request_entry_t; + +/* HTTP server struct */ +struct ecs_http_server_t { + bool should_run; + bool running; + + ecs_http_socket_t sock; + ecs_os_mutex_t lock; + ecs_os_thread_t thread; + + ecs_http_reply_action_t callback; + void *ctx; + + double cache_timeout; + double cache_purge_timeout; + + ecs_sparse_t connections; /* sparse */ + ecs_sparse_t requests; /* sparse */ + + bool initialized; + + uint16_t port; + const char *ipaddr; + + double dequeue_timeout; /* used to not lock request queue too often */ + double stats_timeout; /* used for periodic reporting of statistics */ + + double request_time; /* time spent on requests in last stats interval */ + double request_time_total; /* total time spent on requests */ + int32_t requests_processed; /* requests processed in last stats interval */ + int32_t requests_processed_total; /* total requests processed */ + int32_t dequeue_count; /* number of dequeues in last stats interval */ + ecs_http_send_queue_t send_queue; + + ecs_hashmap_t request_cache; +}; + +/** Fragment state, used by HTTP request parser */ +typedef enum { + HttpFragStateBegin, + HttpFragStateMethod, + HttpFragStatePath, + HttpFragStateVersion, + HttpFragStateHeaderStart, + HttpFragStateHeaderName, + HttpFragStateHeaderValueStart, + HttpFragStateHeaderValue, + HttpFragStateCR, + HttpFragStateCRLF, + HttpFragStateCRLFCR, + HttpFragStateBody, + HttpFragStateDone +} HttpFragState; + +/** A fragment is a partially received HTTP request */ +typedef struct { + HttpFragState state; + ecs_strbuf_t buf; + ecs_http_method_t method; + int32_t body_offset; + int32_t query_offset; + int32_t header_offsets[ECS_HTTP_HEADER_COUNT_MAX]; + int32_t header_value_offsets[ECS_HTTP_HEADER_COUNT_MAX]; + int32_t header_count; + int32_t param_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; + int32_t param_value_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; + int32_t param_count; + int32_t content_length; + char *header_buf_ptr; + char header_buf[32]; + bool parse_content_length; + bool invalid; +} ecs_http_fragment_t; + +/** Extend public connection type with fragment data */ +typedef struct { + ecs_http_connection_t pub; + ecs_http_socket_t sock; + + /* Connection is purged after both timeout expires and connection has + * exceeded retry count. This ensures that a connection does not immediately + * timeout when a frame takes longer than usual */ + double dequeue_timeout; + int32_t dequeue_retries; +} ecs_http_connection_impl_t; + +typedef struct { + ecs_http_request_t pub; + uint64_t conn_id; /* for sanity check */ + char *res; + int32_t req_len; +} ecs_http_request_impl_t; + + +/* Global statistics */ +int64_t ecs_http_request_received_count = 0; +int64_t ecs_http_request_invalid_count = 0; +int64_t ecs_http_request_handled_ok_count = 0; +int64_t ecs_http_request_handled_error_count = 0; +int64_t ecs_http_request_not_handled_count = 0; +int64_t ecs_http_request_preflight_count = 0; +int64_t ecs_http_send_ok_count = 0; +int64_t ecs_http_send_error_count = 0; +int64_t ecs_http_busy_count = 0; + +static +ecs_size_t http_send( + ecs_http_socket_t sock, + const void *buf, + ecs_size_t size, + int flags) +{ + ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); +#ifdef ECS_TARGET_POSIX + ssize_t send_bytes = send(sock, buf, flecs_itosize(size), + flags | MSG_NOSIGNAL); + return flecs_itoi32(send_bytes); +#else + int send_bytes = send(sock, buf, size, flags); + return flecs_itoi32(send_bytes); +#endif +} + +static +ecs_size_t http_recv( + ecs_http_socket_t sock, + void *buf, + ecs_size_t size, + int flags) +{ + ecs_size_t ret; +#ifdef ECS_TARGET_POSIX + ssize_t recv_bytes = recv(sock, buf, flecs_itosize(size), flags); + ret = flecs_itoi32(recv_bytes); +#else + int recv_bytes = recv(sock, buf, size, flags); + ret = flecs_itoi32(recv_bytes); +#endif + if (ret == -1) { + ecs_dbg("recv failed: %s (sock = %d)", ecs_os_strerror(errno), sock); + } else if (ret == 0) { + ecs_dbg("recv: received 0 bytes (sock = %d)", sock); + } + + return ret; +} + +static +void http_sock_set_timeout( + ecs_http_socket_t sock, + int32_t timeout_ms) +{ + int r; +#ifdef ECS_TARGET_POSIX + struct timeval tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); +#else + DWORD t = (DWORD)timeout_ms; + r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&t, sizeof t); +#endif + if (r) { + ecs_warn("http: failed to set socket timeout: %s", + ecs_os_strerror(errno)); + } +} + +static +void http_sock_keep_alive( + ecs_http_socket_t sock) +{ + int v = 1; + if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (const char*)&v, sizeof v)) { + ecs_warn("http: failed to set socket KEEPALIVE: %s", + ecs_os_strerror(errno)); + } +} + +static +void http_sock_nonblock(ecs_http_socket_t sock, bool enable) { + (void)sock; + (void)enable; +#ifdef ECS_TARGET_POSIX + int flags; + flags = fcntl(sock,F_GETFL,0); + if (flags == -1) { + ecs_warn("http: failed to set socket NONBLOCK: %s", + ecs_os_strerror(errno)); + return; + } + if (enable) { + flags = fcntl(sock, F_SETFL, flags | O_NONBLOCK); + } else { + flags = fcntl(sock, F_SETFL, flags & ~O_NONBLOCK); + } + if (flags == -1) { + ecs_warn("http: failed to set socket NONBLOCK: %s", + ecs_os_strerror(errno)); + return; + } +#endif +} + +static +int http_getnameinfo( + const struct sockaddr* addr, + ecs_size_t addr_len, + char *host, + ecs_size_t host_len, + char *port, + ecs_size_t port_len, + int flags) +{ + ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); +#if defined(ECS_TARGET_WINDOWS) + return getnameinfo(addr, addr_len, host, + flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), + flags); +#else + return getnameinfo(addr, flecs_ito(uint32_t, addr_len), host, + flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), + flags); +#endif +} + +static +int http_bind( + ecs_http_socket_t sock, + const struct sockaddr* addr, + ecs_size_t addr_len) +{ + ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); +#if defined(ECS_TARGET_WINDOWS) + return bind(sock, addr, addr_len); +#else + return bind(sock, addr, flecs_ito(uint32_t, addr_len)); +#endif +} + +static +bool http_socket_is_valid( + ecs_http_socket_t sock) +{ +#if defined(ECS_TARGET_WINDOWS) + return sock != INVALID_SOCKET; +#else + return sock >= 0; +#endif +} + +#if defined(ECS_TARGET_WINDOWS) +#define HTTP_SOCKET_INVALID INVALID_SOCKET +#else +#define HTTP_SOCKET_INVALID (-1) +#endif + +static +void http_close( + ecs_http_socket_t *sock) +{ + ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); + +#if defined(ECS_TARGET_WINDOWS) + closesocket(*sock); +#else + ecs_dbg_2("http: closing socket %u", *sock); + shutdown(*sock, SHUT_RDWR); + close(*sock); +#endif + *sock = HTTP_SOCKET_INVALID; +} + +static +ecs_http_socket_t http_accept( + ecs_http_socket_t sock, + struct sockaddr* addr, + ecs_size_t *addr_len) +{ + socklen_t len = (socklen_t)addr_len[0]; + ecs_http_socket_t result = accept(sock, addr, &len); + addr_len[0] = (ecs_size_t)len; + return result; +} + +static +void http_reply_fini(ecs_http_reply_t* reply) { + ecs_assert(reply != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(reply->body.content); +} + +static +void http_request_fini(ecs_http_request_impl_t *req) { + ecs_assert(req != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(req->pub.conn != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(req->pub.conn->server != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(req->pub.conn->id == req->conn_id, ECS_INTERNAL_ERROR, NULL); + ecs_os_free(req->res); + flecs_sparse_remove_t(&req->pub.conn->server->requests, + ecs_http_request_impl_t, req->pub.id); +} + +static +void http_connection_free(ecs_http_connection_impl_t *conn) { + ecs_assert(conn != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(conn->pub.id != 0, ECS_INTERNAL_ERROR, NULL); + uint64_t conn_id = conn->pub.id; + + if (http_socket_is_valid(conn->sock)) { + http_close(&conn->sock); + } + + flecs_sparse_remove_t(&conn->pub.server->connections, + ecs_http_connection_impl_t, conn_id); +} + +// https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int +static +char http_hex_2_int(char a, char b){ + a = (a <= '9') ? (char)(a - '0') : (char)((a & 0x7) + 9); + b = (b <= '9') ? (char)(b - '0') : (char)((b & 0x7) + 9); + if (a < 0) { + return 0; + } + return (char)((a << 4) + b); +} + +static +void http_decode_url_str( + char *str) +{ + char ch, *ptr, *dst = str; + for (ptr = str; (ch = *ptr); ptr++) { + if (ch == '%' && ptr[1]) { + dst[0] = http_hex_2_int(ptr[1], ptr[2]); + dst ++; + ptr += 2; + } else { + dst[0] = ptr[0]; + dst ++; + } + } + dst[0] = '\0'; +} + +static +void http_parse_method( + ecs_http_fragment_t *frag) +{ + char *method = ecs_strbuf_get_small(&frag->buf); + if (!ecs_os_strcmp(method, "GET")) frag->method = EcsHttpGet; + else if (!ecs_os_strcmp(method, "POST")) frag->method = EcsHttpPost; + else if (!ecs_os_strcmp(method, "PUT")) frag->method = EcsHttpPut; + else if (!ecs_os_strcmp(method, "DELETE")) frag->method = EcsHttpDelete; + else if (!ecs_os_strcmp(method, "OPTIONS")) frag->method = EcsHttpOptions; + else { + frag->method = EcsHttpMethodUnsupported; + frag->invalid = true; + } + ecs_strbuf_reset(&frag->buf); +} + +static +bool http_header_writable( + ecs_http_fragment_t *frag) +{ + return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; +} + +static +void http_header_buf_reset( + ecs_http_fragment_t *frag) +{ + frag->header_buf[0] = '\0'; + frag->header_buf_ptr = frag->header_buf; +} + +static +void http_header_buf_append( + ecs_http_fragment_t *frag, + char ch) +{ + if ((frag->header_buf_ptr - frag->header_buf) < + (ECS_SIZEOF(frag->header_buf) - 1)) + { + frag->header_buf_ptr[0] = ch; + frag->header_buf_ptr ++; + } else { + frag->header_buf_ptr[0] = '\0'; + } +} + +static +uint64_t http_request_key_hash(const void *ptr) { + const ecs_http_request_key_t *key = ptr; + const char *array = key->array; + int32_t count = key->count; + return flecs_hash(array, count * ECS_SIZEOF(char)); +} + +static +int http_request_key_compare(const void *ptr_1, const void *ptr_2) { + const ecs_http_request_key_t *type_1 = ptr_1; + const ecs_http_request_key_t *type_2 = ptr_2; + + int32_t count_1 = type_1->count; + int32_t count_2 = type_2->count; + + if (count_1 != count_2) { + return (count_1 > count_2) - (count_1 < count_2); + } + + return ecs_os_memcmp(type_1->array, type_2->array, count_1); +} + +static +ecs_http_request_entry_t* http_find_request_entry( + ecs_http_server_t *srv, + const char *array, + int32_t count) +{ + ecs_http_request_key_t key; + key.array = array; + key.count = count; + + ecs_time_t t = {0, 0}; + ecs_http_request_entry_t *entry = flecs_hashmap_get( + &srv->request_cache, &key, ecs_http_request_entry_t); + + if (entry) { + double tf = ecs_time_measure(&t); + if ((tf - entry->time) < srv->cache_timeout) { + return entry; + } + } + return NULL; +} + +static +void http_insert_request_entry( + ecs_http_server_t *srv, + ecs_http_request_impl_t *req, + ecs_http_reply_t *reply) +{ + int32_t content_length = ecs_strbuf_written(&reply->body); + if (!content_length) { + return; + } + + ecs_http_request_key_t key; + key.array = req->res; + key.count = req->req_len; + ecs_http_request_entry_t *entry = flecs_hashmap_get( + &srv->request_cache, &key, ecs_http_request_entry_t); + if (!entry) { + flecs_hashmap_result_t elem = flecs_hashmap_ensure( + &srv->request_cache, &key, ecs_http_request_entry_t); + ecs_http_request_key_t *elem_key = elem.key; + elem_key->array = ecs_os_memdup_n(key.array, char, key.count); + entry = elem.value; + } else { + ecs_os_free(entry->content); + } + + ecs_time_t t = {0, 0}; + entry->time = ecs_time_measure(&t); + entry->content_length = ecs_strbuf_written(&reply->body); + entry->content = ecs_strbuf_get(&reply->body); + entry->code = reply->code; + ecs_strbuf_appendstrn(&reply->body, + entry->content, entry->content_length); +} + +static +char* http_decode_request( + ecs_http_request_impl_t *req, + ecs_http_fragment_t *frag) +{ + ecs_os_zeromem(req); + + ecs_size_t req_len = frag->buf.length; + char *res = ecs_strbuf_get(&frag->buf); + if (!res) { + return NULL; + } + + req->pub.method = frag->method; + req->pub.path = res + 1; + http_decode_url_str(req->pub.path); + + if (frag->body_offset) { + req->pub.body = &res[frag->body_offset]; + } + int32_t i, count = frag->header_count; + for (i = 0; i < count; i ++) { + req->pub.headers[i].key = &res[frag->header_offsets[i]]; + req->pub.headers[i].value = &res[frag->header_value_offsets[i]]; + } + count = frag->param_count; + for (i = 0; i < count; i ++) { + req->pub.params[i].key = &res[frag->param_offsets[i]]; + req->pub.params[i].value = &res[frag->param_value_offsets[i]]; + /* Safe, member is only const so that end-user can't change it */ + http_decode_url_str(ECS_CONST_CAST(char*, req->pub.params[i].value)); + } + + req->pub.header_count = frag->header_count; + req->pub.param_count = frag->param_count; + req->res = res; + req->req_len = frag->header_offsets[0]; + if (!req->req_len) { + req->req_len = req_len; + } + + return res; +} + +static +ecs_http_request_entry_t* http_enqueue_request( + ecs_http_connection_impl_t *conn, + uint64_t conn_id, + ecs_http_fragment_t *frag) +{ + ecs_http_server_t *srv = conn->pub.server; + + ecs_os_mutex_lock(srv->lock); + bool is_alive = conn->pub.id == conn_id; + + if (!is_alive || frag->invalid) { + /* Don't enqueue invalid requests or requests for purged connections */ + ecs_strbuf_reset(&frag->buf); + } else { + ecs_http_request_impl_t req; + char *res = http_decode_request(&req, frag); + if (res) { + req.pub.conn = (ecs_http_connection_t*)conn; + + /* Check cache for GET requests */ + if (frag->method == EcsHttpGet) { + ecs_http_request_entry_t *entry = + http_find_request_entry(srv, res, frag->header_offsets[0]); + if (entry) { + /* If an entry is found, don't enqueue a request. Instead + * return the cached response immediately. */ + ecs_os_free(res); + return entry; + } + } + + ecs_http_request_impl_t *req_ptr = flecs_sparse_add_t( + &srv->requests, ecs_http_request_impl_t); + *req_ptr = req; + req_ptr->pub.id = flecs_sparse_last_id(&srv->requests); + req_ptr->conn_id = conn->pub.id; + ecs_os_linc(&ecs_http_request_received_count); + } + } + + ecs_os_mutex_unlock(srv->lock); + return NULL; +} + +static +bool http_parse_request( + ecs_http_fragment_t *frag, + const char* req_frag, + ecs_size_t req_frag_len) +{ + int32_t i; + for (i = 0; i < req_frag_len; i++) { + char c = req_frag[i]; + switch (frag->state) { + case HttpFragStateBegin: + ecs_os_memset_t(frag, 0, ecs_http_fragment_t); + frag->state = HttpFragStateMethod; + frag->header_buf_ptr = frag->header_buf; + + /* fall through */ + case HttpFragStateMethod: + if (c == ' ') { + http_parse_method(frag); + ecs_strbuf_reset(&frag->buf); + frag->state = HttpFragStatePath; + frag->buf.content = NULL; + } else { + ecs_strbuf_appendch(&frag->buf, c); + } + break; + case HttpFragStatePath: + if (c == ' ') { + frag->state = HttpFragStateVersion; + ecs_strbuf_appendch(&frag->buf, '\0'); + } else { + if (c == '?' || c == '=' || c == '&') { + ecs_strbuf_appendch(&frag->buf, '\0'); + int32_t offset = ecs_strbuf_written(&frag->buf); + if (c == '?' || c == '&') { + frag->param_offsets[frag->param_count] = offset; + } else { + frag->param_value_offsets[frag->param_count] = offset; + frag->param_count ++; + } + } else { + ecs_strbuf_appendch(&frag->buf, c); + } + } + break; + case HttpFragStateVersion: + if (c == '\r') { + frag->state = HttpFragStateCR; + } /* version is not stored */ + break; + case HttpFragStateHeaderStart: + if (http_header_writable(frag)) { + frag->header_offsets[frag->header_count] = + ecs_strbuf_written(&frag->buf); + } + http_header_buf_reset(frag); + frag->state = HttpFragStateHeaderName; + + /* fall through */ + case HttpFragStateHeaderName: + if (c == ':') { + frag->state = HttpFragStateHeaderValueStart; + http_header_buf_append(frag, '\0'); + frag->parse_content_length = !ecs_os_strcmp( + frag->header_buf, "Content-Length"); + + if (http_header_writable(frag)) { + ecs_strbuf_appendch(&frag->buf, '\0'); + frag->header_value_offsets[frag->header_count] = + ecs_strbuf_written(&frag->buf); + } + } else if (c == '\r') { + frag->state = HttpFragStateCR; + } else { + http_header_buf_append(frag, c); + if (http_header_writable(frag)) { + ecs_strbuf_appendch(&frag->buf, c); + } + } + break; + case HttpFragStateHeaderValueStart: + http_header_buf_reset(frag); + frag->state = HttpFragStateHeaderValue; + if (c == ' ') { /* skip first space */ + break; + } + + /* fall through */ + case HttpFragStateHeaderValue: + if (c == '\r') { + if (frag->parse_content_length) { + http_header_buf_append(frag, '\0'); + int32_t len = atoi(frag->header_buf); + if (len < 0) { + frag->invalid = true; + } else { + frag->content_length = len; + } + frag->parse_content_length = false; + } + if (http_header_writable(frag)) { + int32_t cur = ecs_strbuf_written(&frag->buf); + if (frag->header_offsets[frag->header_count] < cur && + frag->header_value_offsets[frag->header_count] < cur) + { + ecs_strbuf_appendch(&frag->buf, '\0'); + frag->header_count ++; + } + } + frag->state = HttpFragStateCR; + } else { + if (frag->parse_content_length) { + http_header_buf_append(frag, c); + } + if (http_header_writable(frag)) { + ecs_strbuf_appendch(&frag->buf, c); + } + } + break; + case HttpFragStateCR: + if (c == '\n') { + frag->state = HttpFragStateCRLF; + } else { + frag->state = HttpFragStateHeaderStart; + } + break; + case HttpFragStateCRLF: + if (c == '\r') { + frag->state = HttpFragStateCRLFCR; + } else { + frag->state = HttpFragStateHeaderStart; + i--; + } + break; + case HttpFragStateCRLFCR: + if (c == '\n') { + if (frag->content_length != 0) { + frag->body_offset = ecs_strbuf_written(&frag->buf); + frag->state = HttpFragStateBody; + } else { + frag->state = HttpFragStateDone; + } + } else { + frag->state = HttpFragStateHeaderStart; + } + break; + case HttpFragStateBody: { + ecs_strbuf_appendch(&frag->buf, c); + if ((ecs_strbuf_written(&frag->buf) - frag->body_offset) == + frag->content_length) + { + frag->state = HttpFragStateDone; + } + } + break; + case HttpFragStateDone: + break; + } + } + + if (frag->state == HttpFragStateDone) { + return true; + } else { + return false; + } +} + +static +ecs_http_send_request_t* http_send_queue_post( + ecs_http_server_t *srv) +{ + /* This function should only be called while the server is locked. Before + * the lock is released, the returned element should be populated. */ + ecs_http_send_queue_t *sq = &srv->send_queue; + int32_t next = (sq->head + 1) % ECS_HTTP_SEND_QUEUE_MAX; + if (next == sq->tail) { + return NULL; + } + + /* Don't enqueue new requests if server is shutting down */ + if (!srv->should_run) { + return NULL; + } + + /* Return element at end of the queue */ + ecs_http_send_request_t *result = &sq->requests[sq->head]; + sq->head = next; + return result; +} + +static +ecs_http_send_request_t* http_send_queue_get( + ecs_http_server_t *srv) +{ + ecs_os_mutex_lock(srv->lock); + ecs_http_send_queue_t *sq = &srv->send_queue; + if (sq->tail == sq->head) { + return NULL; + } + + int32_t next = (sq->tail + 1) % ECS_HTTP_SEND_QUEUE_MAX; + ecs_http_send_request_t *result = &sq->requests[sq->tail]; + sq->tail = next; + return result; +} + +static +void* http_server_send_queue(void* arg) { + ecs_http_server_t *srv = arg; + int32_t wait_ms = srv->send_queue.wait_ms; + + /* Run for as long as the server is running or there are messages. When the + * server is stopping, no new messages will be enqueued */ + while (srv->should_run || (srv->send_queue.head != srv->send_queue.tail)) { + ecs_http_send_request_t* r = http_send_queue_get(srv); + if (!r) { + ecs_os_mutex_unlock(srv->lock); + /* If the queue is empty, wait so we don't run too fast */ + if (srv->should_run) { + ecs_os_sleep(0, wait_ms * 1000 * 1000); + } + } else { + ecs_http_socket_t sock = r->sock; + char *headers = r->headers; + int32_t headers_length = r->header_length; + char *content = r->content; + int32_t content_length = r->content_length; + ecs_os_mutex_unlock(srv->lock); + + if (http_socket_is_valid(sock)) { + bool error = false; + + http_sock_nonblock(sock, false); + + /* Write headers */ + ecs_size_t written = http_send(sock, headers, headers_length, 0); + if (written != headers_length) { + ecs_err("http: failed to write HTTP response headers: %s", + ecs_os_strerror(errno)); + ecs_os_linc(&ecs_http_send_error_count); + error = true; + } else if (content_length >= 0) { + /* Write content */ + written = http_send(sock, content, content_length, 0); + if (written != content_length) { + ecs_err("http: failed to write HTTP response body: %s", + ecs_os_strerror(errno)); + ecs_os_linc(&ecs_http_send_error_count); + error = true; + } + } + if (!error) { + ecs_os_linc(&ecs_http_send_ok_count); + } + + http_close(&sock); + } else { + ecs_err("http: invalid socket\n"); + } + + ecs_os_free(content); + ecs_os_free(headers); + } + } + return NULL; +} + +static +void http_append_send_headers( + ecs_strbuf_t *hdrs, + int code, + const char* status, + const char* content_type, + ecs_strbuf_t *extra_headers, + ecs_size_t content_len, + bool preflight) +{ + ecs_strbuf_appendlit(hdrs, "HTTP/1.1 "); + ecs_strbuf_appendint(hdrs, code); + ecs_strbuf_appendch(hdrs, ' '); + ecs_strbuf_appendstr(hdrs, status); + ecs_strbuf_appendlit(hdrs, "\r\n"); + + if (content_type) { + ecs_strbuf_appendlit(hdrs, "Content-Type: "); + ecs_strbuf_appendstr(hdrs, content_type); + ecs_strbuf_appendlit(hdrs, "\r\n"); + } + + if (content_len >= 0) { + ecs_strbuf_appendlit(hdrs, "Content-Length: "); + ecs_strbuf_append(hdrs, "%d", content_len); + ecs_strbuf_appendlit(hdrs, "\r\n"); + } + + ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Origin: *\r\n"); + if (preflight) { + ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Private-Network: true\r\n"); + ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Methods: GET, PUT, DELETE, OPTIONS\r\n"); + ecs_strbuf_appendlit(hdrs, "Access-Control-Max-Age: 600\r\n"); + } + + ecs_strbuf_mergebuff(hdrs, extra_headers); + + ecs_strbuf_appendlit(hdrs, "\r\n"); +} + +static +void http_send_reply( + ecs_http_connection_impl_t* conn, + ecs_http_reply_t* reply, + bool preflight) +{ + ecs_strbuf_t hdrs = ECS_STRBUF_INIT; + int32_t content_length = reply->body.length; + char *content = ecs_strbuf_get(&reply->body); + + /* Use asynchronous send queue for outgoing data so send operations won't + * hold up main thread */ + ecs_http_send_request_t *req = NULL; + + if (!preflight) { + req = http_send_queue_post(conn->pub.server); + if (!req) { + reply->code = 503; /* queue full, server is busy */ + ecs_os_linc(&ecs_http_busy_count); + } + } + + http_append_send_headers(&hdrs, reply->code, reply->status, + reply->content_type, &reply->headers, content_length, preflight); + ecs_size_t headers_length = ecs_strbuf_written(&hdrs); + char *headers = ecs_strbuf_get(&hdrs); + + if (!req) { + ecs_size_t written = http_send(conn->sock, headers, headers_length, 0); + if (written != headers_length) { + ecs_err("http: failed to send reply to '%s:%s': %s", + conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); + ecs_os_linc(&ecs_http_send_error_count); + } + ecs_os_free(content); + ecs_os_free(headers); + http_close(&conn->sock); + return; + } + + /* Second, enqueue send request for response body */ + req->sock = conn->sock; + req->headers = headers; + req->header_length = headers_length; + req->content = content; + req->content_length = content_length; + + /* Take ownership of values */ + reply->body.content = NULL; + conn->sock = HTTP_SOCKET_INVALID; +} + +static +void http_recv_connection( + ecs_http_server_t *srv, + ecs_http_connection_impl_t *conn, + uint64_t conn_id, + ecs_http_socket_t sock) +{ + ecs_size_t bytes_read; + char *recv_buf = ecs_os_malloc(ECS_HTTP_SEND_RECV_BUFFER_SIZE); + ecs_http_fragment_t frag = {0}; + int32_t retries = 0; + + ecs_os_sleep(0, 10 * 1000 * 1000); + + do { + if ((bytes_read = http_recv( + sock, recv_buf, ECS_HTTP_SEND_RECV_BUFFER_SIZE, 0)) > 0) + { + bool is_alive = conn->pub.id == conn_id; + if (!is_alive) { + /* Connection has been purged by main thread */ + goto done; + } + + if (http_parse_request(&frag, recv_buf, bytes_read)) { + if (frag.method == EcsHttpOptions) { + ecs_http_reply_t reply; + reply.body = ECS_STRBUF_INIT; + reply.code = 200; + reply.content_type = NULL; + reply.headers = ECS_STRBUF_INIT; + reply.status = "OK"; + http_send_reply(conn, &reply, true); + ecs_os_linc(&ecs_http_request_preflight_count); + } else { + ecs_http_request_entry_t *entry = + http_enqueue_request(conn, conn_id, &frag); + if (entry) { + ecs_http_reply_t reply; + reply.body = ECS_STRBUF_INIT; + reply.code = entry->code; + reply.content_type = "application/json"; + reply.headers = ECS_STRBUF_INIT; + reply.status = "OK"; + ecs_strbuf_appendstrn(&reply.body, + entry->content, entry->content_length); + http_send_reply(conn, &reply, false); + http_connection_free(conn); + + /* Lock was transferred from enqueue_request */ + ecs_os_mutex_unlock(srv->lock); + } + } + } else { + ecs_os_linc(&ecs_http_request_invalid_count); + } + } + + ecs_os_sleep(0, 10 * 1000 * 1000); + } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); + + if (bytes_read == ECS_HTTP_SEND_RECV_BUFFER_SIZE) { + ecs_warn("request exceeded receive buffer size (%d)", + ECS_HTTP_SEND_RECV_BUFFER_SIZE); + } + + if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { + http_close(&sock); + } + +done: + ecs_os_free(recv_buf); + ecs_strbuf_reset(&frag.buf); +} + +typedef struct { + ecs_http_connection_impl_t *conn; + uint64_t id; +} http_conn_res_t; + +static +http_conn_res_t http_init_connection( + ecs_http_server_t *srv, + ecs_http_socket_t sock_conn, + struct sockaddr_storage *remote_addr, + ecs_size_t remote_addr_len) +{ + http_sock_set_timeout(sock_conn, 100); + http_sock_keep_alive(sock_conn); + http_sock_nonblock(sock_conn, true); + + /* Create new connection */ + ecs_os_mutex_lock(srv->lock); + ecs_http_connection_impl_t *conn = flecs_sparse_add_t( + &srv->connections, ecs_http_connection_impl_t); + uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(&srv->connections); + conn->pub.server = srv; + conn->sock = sock_conn; + ecs_os_mutex_unlock(srv->lock); + + char *remote_host = conn->pub.host; + char *remote_port = conn->pub.port; + + /* Fetch name & port info */ + if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, + remote_host, ECS_SIZEOF(conn->pub.host), + remote_port, ECS_SIZEOF(conn->pub.port), + NI_NUMERICHOST | NI_NUMERICSERV)) + { + ecs_os_strcpy(remote_host, "unknown"); + ecs_os_strcpy(remote_port, "unknown"); + } + + ecs_dbg_2("http: connection established from '%s:%s' (socket %u)", + remote_host, remote_port, sock_conn); + + return (http_conn_res_t){ .conn = conn, .id = conn_id }; +} + +static +int http_accept_connections( + ecs_http_server_t* srv, + const struct sockaddr* addr, + ecs_size_t addr_len) +{ +#ifdef ECS_TARGET_WINDOWS + /* If on Windows, test if winsock needs to be initialized */ + SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (INVALID_SOCKET == testsocket && WSANOTINITIALISED == WSAGetLastError()){ + WSADATA data = { 0 }; + int result = WSAStartup(MAKEWORD(2, 2), &data); + if (result) { + ecs_warn("http: WSAStartup failed with GetLastError = %d\n", + GetLastError()); + return 0; + } + } else { + http_close(&testsocket); + } +#endif + + /* Resolve name + port (used for logging) */ + char addr_host[256]; + char addr_port[20]; + + int ret = 0; /* 0 = ok, 1 = port occupied */ + + ecs_http_socket_t sock = HTTP_SOCKET_INVALID; + ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); + + if (http_getnameinfo( + addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, + ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) + { + ecs_os_strcpy(addr_host, "unknown"); + ecs_os_strcpy(addr_port, "unknown"); + } + + ecs_os_mutex_lock(srv->lock); + if (srv->should_run) { + ecs_dbg_2("http: initializing connection socket"); + + sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); + if (!http_socket_is_valid(sock)) { + ecs_err("http: unable to create new connection socket: %s", + ecs_os_strerror(errno)); + ecs_os_mutex_unlock(srv->lock); + goto done; + } + + int reuse = 1, result; + result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char*)&reuse, ECS_SIZEOF(reuse)); + if (result) { + ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); + } + + if (addr->sa_family == AF_INET6) { + int ipv6only = 0; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (char*)&ipv6only, ECS_SIZEOF(ipv6only))) + { + ecs_warn("http: failed to setsockopt: %s", + ecs_os_strerror(errno)); + } + } + + result = http_bind(sock, addr, addr_len); + if (result) { + if (errno == EADDRINUSE) { + ret = 1; + ecs_warn("http: address '%s:%s' in use, retrying with port %u", + addr_host, addr_port, srv->port + 1); + } else { + ecs_err("http: failed to bind to '%s:%s': %s", + addr_host, addr_port, ecs_os_strerror(errno)); + } + + ecs_os_mutex_unlock(srv->lock); + goto done; + } + + http_sock_set_timeout(sock, 1000); + + srv->sock = sock; + + result = listen(srv->sock, SOMAXCONN); + if (result) { + ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", + SOMAXCONN, ecs_os_strerror(errno)); + } + + ecs_trace("http: listening for incoming connections on '%s:%s'", + addr_host, addr_port); + } else { + ecs_dbg_2("http: server shut down while initializing"); + } + ecs_os_mutex_unlock(srv->lock); + + struct sockaddr_storage remote_addr; + ecs_size_t remote_addr_len = 0; + + while (srv->should_run) { + remote_addr_len = ECS_SIZEOF(remote_addr); + ecs_http_socket_t sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, + &remote_addr_len); + + if (!http_socket_is_valid(sock_conn)) { + if (srv->should_run) { + ecs_dbg("http: connection attempt failed: %s", + ecs_os_strerror(errno)); + } + continue; + } + + http_conn_res_t conn = http_init_connection(srv, sock_conn, &remote_addr, remote_addr_len); + http_recv_connection(srv, conn.conn, conn.id, sock_conn); + } + +done: + ecs_os_mutex_lock(srv->lock); + if (http_socket_is_valid(sock) && errno != EBADF) { + http_close(&sock); + srv->sock = sock; + } + ecs_os_mutex_unlock(srv->lock); + + ecs_trace("http: no longer accepting connections on '%s:%s'", + addr_host, addr_port); + + return ret; +} + +static +void* http_server_thread(void* arg) { + ecs_http_server_t *srv = arg; + struct sockaddr_in addr; + ecs_os_zeromem(&addr); + addr.sin_family = AF_INET; + + int retries = 0; +retry: + addr.sin_port = htons(srv->port); + + if (!srv->ipaddr) { + addr.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); + } + + if (http_accept_connections( + srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)) == 1) + { + srv->port ++; + retries ++; + if (retries < 10) { + goto retry; + } else { + ecs_err("http: failed to connect (retried 10 times)"); + } + } + + return NULL; +} + +static +void http_do_request( + ecs_http_server_t *srv, + ecs_http_reply_t *reply, + const ecs_http_request_impl_t *req) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->callback != NULL, ECS_INVALID_OPERATION, + "missing request handler for server"); + + if (srv->callback(ECS_CONST_CAST(ecs_http_request_t*, req), reply, + srv->ctx) == false) + { + reply->status = "Resource not found"; + ecs_os_linc(&ecs_http_request_not_handled_count); + } else { + if (reply->code >= 400) { + ecs_os_linc(&ecs_http_request_handled_error_count); + } else { + ecs_os_linc(&ecs_http_request_handled_ok_count); + } + } +error: + return; +} + +static +void http_handle_request( + ecs_http_server_t *srv, + ecs_http_request_impl_t *req) +{ + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; + ecs_http_connection_impl_t *conn = + (ecs_http_connection_impl_t*)req->pub.conn; + + if (req->pub.method != EcsHttpOptions) { + if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { + reply.code = 404; + reply.status = "Resource not found"; + ecs_os_linc(&ecs_http_request_not_handled_count); + } else { + if (reply.code >= 400) { + ecs_os_linc(&ecs_http_request_handled_error_count); + } else { + ecs_os_linc(&ecs_http_request_handled_ok_count); + } + } + + if (req->pub.method == EcsHttpGet) { + http_insert_request_entry(srv, req, &reply); + } + + http_send_reply(conn, &reply, false); + ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); + } else { + /* Already taken care of */ + } + + http_reply_fini(&reply); + http_request_fini(req); + http_connection_free(conn); +} + +static +void http_purge_request_cache( + ecs_http_server_t *srv, + bool fini) +{ + ecs_time_t t = {0, 0}; + double time = ecs_time_measure(&t); + ecs_map_iter_t it = ecs_map_iter(&srv->request_cache.impl); + while (ecs_map_next(&it)) { + ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); + int32_t i, count = ecs_vec_count(&bucket->values); + ecs_http_request_key_t *keys = ecs_vec_first(&bucket->keys); + ecs_http_request_entry_t *entries = ecs_vec_first(&bucket->values); + for (i = count - 1; i >= 0; i --) { + ecs_http_request_entry_t *entry = &entries[i]; + if (fini || ((time - entry->time) > srv->cache_purge_timeout)) { + ecs_http_request_key_t *key = &keys[i]; + /* Safe, code owns the value */ + ecs_os_free(ECS_CONST_CAST(char*, key->array)); + ecs_os_free(entry->content); + flecs_hm_bucket_remove(&srv->request_cache, bucket, + ecs_map_key(&it), i); + } + } + } + + if (fini) { + flecs_hashmap_fini(&srv->request_cache); + } +} + +static +int32_t http_dequeue_requests( + ecs_http_server_t *srv, + double delta_time) +{ + ecs_os_mutex_lock(srv->lock); + + int32_t i, request_count = flecs_sparse_count(&srv->requests); + for (i = request_count - 1; i >= 1; i --) { + ecs_http_request_impl_t *req = flecs_sparse_get_dense_t( + &srv->requests, ecs_http_request_impl_t, i); + http_handle_request(srv, req); + } + + int32_t connections_count = flecs_sparse_count(&srv->connections); + for (i = connections_count - 1; i >= 1; i --) { + ecs_http_connection_impl_t *conn = flecs_sparse_get_dense_t( + &srv->connections, ecs_http_connection_impl_t, i); + + conn->dequeue_timeout += delta_time; + conn->dequeue_retries ++; + + if ((conn->dequeue_timeout > + (double)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && + (conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) + { + ecs_dbg("http: purging connection '%s:%s' (sock = %d)", + conn->pub.host, conn->pub.port, conn->sock); + http_connection_free(conn); + } + } + + http_purge_request_cache(srv, false); + ecs_os_mutex_unlock(srv->lock); + + return request_count - 1; +} + +const char* ecs_http_get_header( + const ecs_http_request_t* req, + const char* name) +{ + for (ecs_size_t i = 0; i < req->header_count; i++) { + if (!ecs_os_strcmp(req->headers[i].key, name)) { + return req->headers[i].value; + } + } + return NULL; +} + +const char* ecs_http_get_param( + const ecs_http_request_t* req, + const char* name) +{ + for (ecs_size_t i = 0; i < req->param_count; i++) { + if (!ecs_os_strcmp(req->params[i].key, name)) { + return req->params[i].value; + } + } + return NULL; +} + +ecs_http_server_t* ecs_http_server_init( + const ecs_http_server_desc_t *desc) +{ + ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); + if (ecs_os_has_threading()) { + srv->lock = ecs_os_mutex_new(); + } + srv->sock = HTTP_SOCKET_INVALID; + + srv->should_run = false; + srv->initialized = true; + + srv->cache_timeout = desc->cache_timeout; + srv->cache_purge_timeout = desc->cache_purge_timeout; + + if (!ECS_EQZERO(srv->cache_timeout) && + ECS_EQZERO(srv->cache_purge_timeout)) + { + srv->cache_purge_timeout = srv->cache_timeout * 10; + } + + srv->callback = desc->callback; + srv->ctx = desc->ctx; + srv->port = desc->port; + srv->ipaddr = desc->ipaddr; + srv->send_queue.wait_ms = desc->send_queue_wait_ms; + if (!srv->send_queue.wait_ms) { + srv->send_queue.wait_ms = 1; + } + + flecs_sparse_init_t(&srv->connections, NULL, NULL, ecs_http_connection_impl_t); + flecs_sparse_init_t(&srv->requests, NULL, NULL, ecs_http_request_impl_t); + + /* Start at id 1 */ + flecs_sparse_new_id(&srv->connections); + flecs_sparse_new_id(&srv->requests); + + /* Initialize request cache */ + flecs_hashmap_init(&srv->request_cache, + ecs_http_request_key_t, ecs_http_request_entry_t, + http_request_key_hash, http_request_key_compare, NULL); + +#ifndef ECS_TARGET_WINDOWS + /* Ignore pipe signal. SIGPIPE can occur when a message is sent to a client + * but te client already disconnected. */ + signal(SIGPIPE, SIG_IGN); +#endif + + return srv; +} + +void ecs_http_server_fini( + ecs_http_server_t* srv) +{ + if (srv->should_run) { + ecs_http_server_stop(srv); + } + if (ecs_os_has_threading()) { + ecs_os_mutex_free(srv->lock); + } + http_purge_request_cache(srv, true); + flecs_sparse_fini(&srv->requests); + flecs_sparse_fini(&srv->connections); + ecs_os_free(srv); +} + +int ecs_http_server_start( + ecs_http_server_t *srv) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); + ecs_check(!srv->should_run, ECS_INVALID_PARAMETER, NULL); + ecs_check(!srv->thread, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, + "missing OS API implementation"); + + srv->should_run = true; + + ecs_dbg("http: starting server thread"); + + srv->thread = ecs_os_thread_new(http_server_thread, srv); + if (!srv->thread) { + goto error; + } + + srv->send_queue.thread = ecs_os_thread_new(http_server_send_queue, srv); + if (!srv->send_queue.thread) { + goto error; + } + + return 0; +error: + return -1; +} + +void ecs_http_server_stop( + ecs_http_server_t* srv) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->initialized, ECS_INVALID_OPERATION, + "cannot stop HTTP server: not initialized"); + ecs_check(srv->should_run, ECS_INVALID_PARAMETER, + "cannot stop HTTP server: already stopped/stopping"); + ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, + "missing OS API implementation"); + + /* Stop server thread */ + ecs_dbg("http: shutting down server thread"); + + ecs_os_mutex_lock(srv->lock); + srv->should_run = false; + if (http_socket_is_valid(srv->sock)) { + http_close(&srv->sock); + } + ecs_os_mutex_unlock(srv->lock); + + ecs_os_thread_join(srv->thread); + ecs_os_thread_join(srv->send_queue.thread); + ecs_trace("http: server threads shut down"); + + /* Cleanup all outstanding requests */ + int i, count = flecs_sparse_count(&srv->requests); + for (i = count - 1; i >= 1; i --) { + http_request_fini(flecs_sparse_get_dense_t( + &srv->requests, ecs_http_request_impl_t, i)); + } + + /* Close all connections */ + count = flecs_sparse_count(&srv->connections); + for (i = count - 1; i >= 1; i --) { + http_connection_free(flecs_sparse_get_dense_t( + &srv->connections, ecs_http_connection_impl_t, i)); + } + + ecs_assert(flecs_sparse_count(&srv->connections) == 1, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_sparse_count(&srv->requests) == 1, + ECS_INTERNAL_ERROR, NULL); + + srv->thread = 0; +error: + return; +} + +void ecs_http_server_dequeue( + ecs_http_server_t* srv, + ecs_ftime_t delta_time) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); + ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); + + srv->dequeue_timeout += (double)delta_time; + srv->stats_timeout += (double)delta_time; + + if ((1000 * srv->dequeue_timeout) > (double)ECS_HTTP_MIN_DEQUEUE_INTERVAL) { + srv->dequeue_timeout = 0; + + ecs_time_t t = {0}; + ecs_time_measure(&t); + int32_t request_count = http_dequeue_requests(srv, srv->dequeue_timeout); + srv->requests_processed += request_count; + srv->requests_processed_total += request_count; + double time_spent = ecs_time_measure(&t); + srv->request_time += time_spent; + srv->request_time_total += time_spent; + srv->dequeue_count ++; + } + + if ((1000 * srv->stats_timeout) > (double)ECS_HTTP_MIN_STATS_INTERVAL) { + srv->stats_timeout = 0; + ecs_dbg("http: processed %d requests in %.3fs (avg %.3fs / dequeue)", + srv->requests_processed, srv->request_time, + (srv->request_time / (double)srv->dequeue_count)); + srv->requests_processed = 0; + srv->request_time = 0; + srv->dequeue_count = 0; + } + +error: + return; +} + +int ecs_http_server_http_request( + ecs_http_server_t* srv, + const char *req, + ecs_size_t len, + ecs_http_reply_t *reply_out) +{ + if (!len) { + len = ecs_os_strlen(req); + } + + ecs_http_fragment_t frag = {0}; + if (!http_parse_request(&frag, req, len)) { + ecs_strbuf_reset(&frag.buf); + reply_out->code = 400; + return -1; + } + + ecs_http_request_impl_t request; + char *res = http_decode_request(&request, &frag); + if (!res) { + reply_out->code = 400; + return -1; + } + + ecs_http_request_entry_t *entry = + http_find_request_entry(srv, request.res, request.req_len); + if (entry) { + reply_out->body = ECS_STRBUF_INIT; + reply_out->code = entry->code; + reply_out->content_type = "application/json"; + reply_out->headers = ECS_STRBUF_INIT; + reply_out->status = "OK"; + ecs_strbuf_appendstrn(&reply_out->body, + entry->content, entry->content_length); + } else { + http_do_request(srv, reply_out, &request); + + if (request.pub.method == EcsHttpGet) { + http_insert_request_entry(srv, &request, reply_out); + } + } + + ecs_os_free(res); + + http_purge_request_cache(srv, false); + + return (reply_out->code >= 400) ? -1 : 0; +} + +int ecs_http_server_request( + ecs_http_server_t* srv, + const char *method, + const char *req, + const char *body, + ecs_http_reply_t *reply_out) +{ + ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(method != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(req != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(reply_out != NULL, ECS_INVALID_PARAMETER, NULL); + + const char *http_ver = " HTTP/1.1\r\n"; + int32_t method_len = ecs_os_strlen(method); + int32_t req_len = ecs_os_strlen(req); + int32_t body_len = body ? ecs_os_strlen(body) : 0; + int32_t http_ver_len = ecs_os_strlen(http_ver); + char reqbuf[1024], *reqstr = reqbuf; + char content_length[32] = {0}; + + if (body_len) { + ecs_os_snprintf(content_length, 32, + "Content-Length: %d\r\n\r\n", body_len); + } + + int32_t content_length_len = ecs_os_strlen(content_length); + + int32_t len = method_len + req_len + content_length_len + body_len + + http_ver_len; + + len += 3; + + if (len >= 1024) { + reqstr = ecs_os_malloc(len); + } + + char *ptr = reqstr; + ecs_os_memcpy(ptr, method, method_len); ptr += method_len; + ptr[0] = ' '; ptr ++; + ecs_os_memcpy(ptr, req, req_len); ptr += req_len; + ecs_os_memcpy(ptr, http_ver, http_ver_len); ptr += http_ver_len; + + if (body) { + ecs_os_memcpy(ptr, content_length, content_length_len); + ptr += content_length_len; + ecs_os_memcpy(ptr, body, body_len); + ptr += body_len; + } + + ptr[0] = '\r'; + ptr[1] = '\n'; + + int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); + if (reqbuf != reqstr) { + ecs_os_free(reqstr); + } + + return result; +error: + return -1; +} + +void* ecs_http_server_ctx( + ecs_http_server_t* srv) +{ + return srv->ctx; +} + +#endif + +/** + * @file addons/json/deserialize.c + * @brief Deserialize JSON strings into (component) values. + */ + + +#ifdef FLECS_JSON + +typedef struct { + ecs_allocator_t *a; + ecs_vec_t table_type; + ecs_vec_t remove_ids; + ecs_map_t anonymous_ids; + ecs_map_t missing_reflection; + const char *expr; +} ecs_from_json_ctx_t; + +static +void flecs_from_json_ctx_init( + ecs_allocator_t *a, + ecs_from_json_ctx_t *ctx) +{ + ctx->a = a; + ecs_vec_init_t(a, &ctx->table_type, ecs_id_t, 0); + ecs_vec_init_t(a, &ctx->remove_ids, ecs_id_t, 0); + ecs_map_init(&ctx->anonymous_ids, a); + ecs_map_init(&ctx->missing_reflection, a); +} + +static +void flecs_from_json_ctx_fini( + ecs_from_json_ctx_t *ctx) +{ + ecs_vec_fini_t(ctx->a, &ctx->table_type, ecs_record_t*); + ecs_vec_fini_t(ctx->a, &ctx->remove_ids, ecs_record_t*); + ecs_map_fini(&ctx->anonymous_ids); + ecs_map_fini(&ctx->missing_reflection); +} + +static +ecs_entity_t flecs_json_new_id( + ecs_world_t *world, + ecs_entity_t ser_id) +{ + /* Try to honor low id requirements */ + if (ser_id < FLECS_HI_COMPONENT_ID) { + return ecs_new_low_id(world); + } else { + return ecs_new(world); + } +} + +static +void flecs_json_missing_reflection( + ecs_world_t *world, + ecs_id_t id, + const char *json, + ecs_from_json_ctx_t *ctx, + const ecs_from_json_desc_t *desc) +{ + if (!desc->strict || ecs_map_get(&ctx->missing_reflection, id)) { + return; + } + + /* Don't spam log when multiple values of a type can't be deserialized */ + ecs_map_ensure(&ctx->missing_reflection, id); + + char *id_str = ecs_id_str(world, id); + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "missing reflection for '%s'", id_str); + ecs_os_free(id_str); +} + +static +ecs_entity_t flecs_json_lookup( + ecs_world_t *world, + ecs_entity_t parent, + const char *name, + const ecs_from_json_desc_t *desc) +{ + ecs_entity_t scope = 0; + if (parent) { + scope = ecs_set_scope(world, parent); + } + + ecs_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); + if (parent) { + ecs_set_scope(world, scope); + } + + return result; +} + +static +void flecs_json_mark_reserved( + ecs_map_t *anonymous_ids, + ecs_entity_t e) +{ + ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); + ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); + reserved[0] = 0; +} + +static +ecs_entity_t flecs_json_ensure_entity( + ecs_world_t *world, + const char *name, + void *ctx) +{ + ecs_entity_t e = 0; + ecs_map_t *anonymous_ids = ctx; + + if (flecs_name_is_id(name)) { + /* Anonymous entity, find or create mapping to new id */ + ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(&name[1])); + ecs_entity_t *deser_id = ecs_map_get(anonymous_ids, ser_id); + if (deser_id) { + if (!deser_id[0]) { + /* Id is already issued by deserializer, create new id */ + deser_id[0] = flecs_json_new_id(world, ser_id); + + /* Mark new id as reserved */ + flecs_json_mark_reserved(anonymous_ids, deser_id[0]); + } else { + /* Id mapping exists */ + } + } else { + /* Id has not yet been issued by deserializer, which means it's safe + * to use. This allows the deserializer to bind to existing + * anonymous ids, as they will never be reissued. */ + deser_id = ecs_map_ensure(anonymous_ids, ser_id); + if (!ecs_exists(world, ser_id) || + (ecs_is_alive(world, ser_id) && !ecs_get_name(world, ser_id))) + { + /* Only use existing id if it's alive or doesn't exist yet. The + * id could have been recycled for another entity + * Also don't use existing id if the existing entity is not + * anonymous. */ + deser_id[0] = ser_id; + ecs_make_alive(world, ser_id); + } else { + /* If id exists and is not alive, create a new id */ + deser_id[0] = flecs_json_new_id(world, ser_id); + + /* Mark new id as reserved */ + flecs_json_mark_reserved(anonymous_ids, deser_id[0]); + } + } + + e = deser_id[0]; + } else { + e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, false); + if (!e) { + e = ecs_entity(world, { .name = name }); + flecs_json_mark_reserved(anonymous_ids, e); + } + } + + return e; +} + +static +bool flecs_json_add_id_to_type( + ecs_id_t id) +{ + if (id == ecs_pair_t(EcsIdentifier, EcsName)) { + return false; + } + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsChildOf) { + return false; + } + return true; +} + +static +const char* flecs_json_deser_tags( + ecs_world_t *world, + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc, + ecs_from_json_ctx_t *ctx) +{ + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; + + const char *expr = ctx->expr, *lah; + + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } + + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + json = lah; + goto end; + } + + do { + char *str = NULL; + json = flecs_json_expect_string(json, token, &str, desc); + if (!json) { + goto error; + } + + ecs_entity_t tag = flecs_json_lookup(world, 0, str, desc); + if (flecs_json_add_id_to_type(tag)) { + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = tag; + } + + ecs_add_id(world, e, tag); + + if (str != token) { + ecs_os_free(str); + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonComma) { + break; + } + } while (true); + + if (token_kind != JsonArrayClose) { + ecs_parser_error(NULL, expr, json - expr, "expected }"); + goto error; + } + + +end: + return json; +error: + return NULL; +} + +static +const char* flecs_json_deser_pairs( + ecs_world_t *world, + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc, + ecs_from_json_ctx_t *ctx) +{ + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; + + const char *expr = ctx->expr, *lah; + + json = flecs_json_expect(json, JsonObjectOpen, token, desc); + if (!json) { + goto error; + } + + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonObjectClose) { + json = lah; + goto end; + } + + do { + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } + + ecs_entity_t rel = flecs_json_lookup(world, 0, token, desc); + + bool multiple_targets = false; + + do { + json = flecs_json_parse(json, &token_kind, token); + + if (token_kind == JsonString) { + ecs_entity_t tgt = flecs_json_lookup(world, 0, token, desc); + ecs_id_t id = ecs_pair(rel, tgt); + ecs_add_id(world, e, id); + if (flecs_json_add_id_to_type(id)) { + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; + } + } else if (token_kind == JsonLargeString) { + ecs_strbuf_t large_token = ECS_STRBUF_INIT; + json = flecs_json_parse_large_string(json, &large_token); + if (!json) { + break; + } + + char *str = ecs_strbuf_get(&large_token); + ecs_entity_t tgt = flecs_json_lookup(world, 0, str, desc); + ecs_os_free(str); + ecs_id_t id = ecs_pair(rel, tgt); + ecs_add_id(world, e, id); + if (flecs_json_add_id_to_type(id)) { + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; + } + } else if (token_kind == JsonArrayOpen) { + if (multiple_targets) { + ecs_parser_error(NULL, expr, json - expr, + "expected string"); + goto error; + } + + multiple_targets = true; + } else if (token_kind == JsonArrayClose) { + if (!multiple_targets) { + ecs_parser_error(NULL, expr, json - expr, + "unexpected ]"); + goto error; + } + + multiple_targets = false; + } else if (token_kind == JsonComma) { + if (!multiple_targets) { + ecs_parser_error(NULL, expr, json - expr, + "unexpected ,"); + goto error; + } + } else { + ecs_parser_error(NULL, expr, json - expr, + "expected array or string"); + goto error; + } + } while (multiple_targets); + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonComma) { + break; + } + } while (true); + + if (token_kind != JsonObjectClose) { + ecs_parser_error(NULL, expr, json - expr, "expected }"); + goto error; + } + +end: + return json; +error: + return NULL; +} + +static +const char* flecs_json_deser_components( + ecs_world_t *world, + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc, + ecs_from_json_ctx_t *ctx) +{ + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; + + const char *expr = ctx->expr, *lah; + + json = flecs_json_expect(json, JsonObjectOpen, token, desc); + if (!json) { + goto error; + } + + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonObjectClose) { + json = lah; + goto end; + } + + do { + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } + + ecs_id_t id = 0; + + if (token[0] != '(') { + id = flecs_json_lookup(world, 0, token, desc); + } else { + char token_buffer[256]; + ecs_term_t term = {0}; + if (!flecs_term_parse(world, NULL, token, token_buffer, &term)) { + goto error; + } + + ecs_assert(term.first.name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(term.second.name != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t rel = flecs_json_lookup( + world, 0, term.first.name, desc); + ecs_entity_t tgt = flecs_json_lookup( + world, 0, term.second.name, desc); + + id = ecs_pair(rel, tgt); + } + + bool skip = false; + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == ecs_id(EcsIdentifier) + && ECS_PAIR_SECOND(id) != EcsAlias) { + skip = true; + } + + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonNull) { + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (!ti) { + flecs_json_missing_reflection(world, id, json, ctx, desc); + if (desc->strict) { + goto error; + } + + json = flecs_parse_ws_eol(json); + + json = flecs_json_skip_object(json + 1, token, desc); + if (!json) { + goto error; + } + } else { + void *ptr = ecs_ensure_id(world, e, id, + flecs_ito(size_t, ti->size)); + + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonNull) { + if (!skip) { + ecs_entity_t type = ti->component; + const char *next = ecs_ptr_from_json( + world, type, ptr, json, desc); + if (!next) { + flecs_json_missing_reflection( + world, id, json, ctx, desc); + if (desc->strict) { + goto error; + } + + json = flecs_parse_ws_eol(json); + + json = flecs_json_skip_object(json + 1, token, desc); + if (!json) { + goto error; + } + } else { + json = next; + ecs_modified_id(world, e, id); + } + } else { + json = flecs_parse_ws_eol(json); + json = flecs_json_skip_object(json + 1, token, desc); + if (!json) { + goto error; + } + } + } else { + json = lah; + } + } + } else { + ecs_add_id(world, e, id); + json = lah; + } + + /* Don't add ids that have their own fields in serialized data. */ + if (flecs_json_add_id_to_type(id)) { + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonComma) { + break; + } + } while (true); + + if (token_kind != JsonObjectClose) { + ecs_parser_error(NULL, expr, json - expr, "expected }"); + goto error; + } + +end: + return json; +error: + return NULL; +} + +static +const char* flecs_entity_from_json( + ecs_world_t *world, + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc, + ecs_from_json_ctx_t *ctx) +{ + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; + + const char *expr = ctx->expr, *lah; + + ecs_vec_clear(&ctx->table_type); + + ecs_entity_t parent = 0; + + json = flecs_json_expect(json, JsonObjectOpen, token, desc); + if (!json) { + goto error; + } + + lah = flecs_json_parse(json, &token_kind, token); + if (!lah) { + goto error; + } + + if (token_kind == JsonObjectClose) { + json = lah; + goto end; + } + + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } + + if (!ecs_os_strcmp(token, "parent")) { + char *str = NULL; + json = flecs_json_expect_string(json, token, &str, desc); + if (!json) { + goto error; + } + + parent = flecs_json_lookup(world, 0, str, desc); + + if (e) { + ecs_add_pair(world, e, EcsChildOf, parent); + } + + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = + ecs_pair(EcsChildOf, parent); + + if (str != token) ecs_os_free(str); + + json = flecs_json_parse_next_member(json, token, &token_kind, desc); + if (!json) { + goto error; + } + if (token_kind == JsonObjectClose) { + goto end; + } + } + + if (!ecs_os_strcmp(token, "name")) { + char *str = NULL; + json = flecs_json_expect_string(json, token, &str, desc); + if (!json) { + goto error; + } + + if (str[0] != '#') { + if (!e) { + e = flecs_json_lookup(world, parent, str, desc); + } else { + ecs_set_name(world, e, str); + } + + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = + ecs_pair_t(EcsIdentifier, EcsName); + } + + if (str != token) ecs_os_free(str); + + json = flecs_json_parse_next_member(json, token, &token_kind, desc); + if (!json) { + goto error; + } + if (token_kind == JsonObjectClose) { + goto end; + } + } + + if (!ecs_os_strcmp(token, "id")) { + json = flecs_json_parse(json, &token_kind, token); + if (!json) { + goto error; + } + + uint64_t id; + if (token_kind == JsonNumber || token_kind == JsonLargeInt) { + id = flecs_ito(uint64_t, atoll(token)); + } else { + ecs_parser_error(NULL, expr, json - expr, "expected entity id"); + goto error; + } + + json = flecs_json_parse_next_member(json, token, &token_kind, desc); + if (!json) { + goto error; + } + + if (!ecs_os_strcmp(token, "version")) { + json = flecs_json_parse(json, &token_kind, token); + if (!json) { + goto error; + } + + uint64_t version; + if (token_kind == JsonNumber || token_kind == JsonLargeInt) { + version = flecs_ito(uint64_t, atoll(token)); + } else { + ecs_parser_error(NULL, expr, json - expr, "expected version"); + goto error; + } + + id |= version << 32; + + json = flecs_json_parse_next_member(json, token, &token_kind, desc); + if (!json) { + goto error; + } + } + + if (!e) { + if (ecs_is_alive(world, id) && !ecs_get_name(world, id)) { + e = id; + } else { + char name[32]; + ecs_os_snprintf(name, 32, "#%u", (uint32_t)id); + e = flecs_json_lookup(world, 0, name, desc); + } + } else { + /* If we already have an id, ignore explicit id */ + } + + if (token_kind == JsonObjectClose) { + goto end; + } + } + + if (!e) { + ecs_parser_error(NULL, expr, json - expr, "failed to create entity"); + return NULL; + } + + if (!ecs_os_strcmp(token, "has_alerts")) { + json = flecs_json_expect(json, JsonBoolean, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_parse_next_member(json, token, &token_kind, desc); + if (!json) { + goto error; + } + if (token_kind == JsonObjectClose) { + goto end; + } + } + + if (!ecs_os_strcmp(token, "tags")) { + json = flecs_json_deser_tags(world, e, json, desc, ctx); + if (!json) { + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonObjectClose) { + goto end; + } else if (token_kind != JsonComma) { + ecs_parser_error(NULL, expr, json - expr, "expected ','"); + goto error; + } + + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } + } + + if (!ecs_os_strcmp(token, "pairs")) { + json = flecs_json_deser_pairs(world, e, json, desc, ctx); + if (!json) { + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonObjectClose) { + goto end; + } else if (token_kind != JsonComma) { + ecs_parser_error(NULL, expr, json - expr, "expected ','"); + goto error; + } + + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } + } + + if (!ecs_os_strcmp(token, "components")) { + json = flecs_json_deser_components(world, e, json, desc, ctx); + if (!json) { + goto error; + } + } + + json = flecs_json_expect(json, JsonObjectClose, token, desc); + if (!json) { + goto error; + } + + ecs_record_t *r = flecs_entities_get(world, e); + ecs_table_t *table = r ? r->table : NULL; + if (table) { + ecs_id_t *ids = ecs_vec_first(&ctx->table_type); + int32_t ids_count = ecs_vec_count(&ctx->table_type); + qsort(ids, flecs_itosize(ids_count), sizeof(ecs_id_t), flecs_id_qsort_cmp); + + ecs_table_t *dst_table = ecs_table_find(world, + ecs_vec_first(&ctx->table_type), ecs_vec_count(&ctx->table_type)); + if (dst_table->type.count == 0) { + dst_table = NULL; + } + + /* Entity had existing components that weren't in the serialized data */ + if (table != dst_table) { + ecs_assert(ecs_get_target(world, e, EcsChildOf, 0) != EcsFlecsCore, + ECS_INVALID_OPERATION, "%s\n[%s] => \n[%s]", + ecs_get_path(world, e), + ecs_table_str(world, table), + ecs_table_str(world, dst_table)); + + if (!dst_table) { + ecs_clear(world, e); + } else { + ecs_vec_clear(&ctx->remove_ids); + + ecs_type_t *type = &table->type, *dst_type = &dst_table->type; + int32_t i = 0, i_dst = 0; + for (; (i_dst < dst_type->count) && (i < type->count); ) { + ecs_id_t id = type->array[i], dst_id = dst_type->array[i_dst]; + + if (dst_id > id) { + ecs_vec_append_t( + ctx->a, &ctx->remove_ids, ecs_id_t)[0] = id; + } + + i_dst += dst_id <= id; + i += dst_id >= id; + } + + ecs_type_t removed = { + .array = ecs_vec_first(&ctx->remove_ids), + .count = ecs_vec_count(&ctx->remove_ids) + }; + + ecs_commit(world, e, r, dst_table, NULL, &removed); + } + + ecs_assert(ecs_get_table(world, e) == dst_table, + ECS_INTERNAL_ERROR, NULL); + } + } + +end: + return json; +error: + return NULL; +} + +const char* ecs_entity_from_json( + ecs_world_t *world, + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc_arg) +{ + ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, + "cannot deserialize while world is deferred"); + + ecs_from_json_desc_t desc = {0}; + if (desc_arg) { + desc = *desc_arg; + } + + desc.expr = json; + + ecs_allocator_t *a = &world->allocator; + ecs_from_json_ctx_t ctx; + flecs_from_json_ctx_init(a, &ctx); + ctx.expr = json; + + if (!desc.lookup_action) { + desc.lookup_action = flecs_json_ensure_entity; + desc.lookup_ctx = &ctx.anonymous_ids; + } + + json = flecs_entity_from_json(world, e, json, &desc, &ctx); + + flecs_from_json_ctx_fini(&ctx); + return json; +} + +const char* ecs_world_from_json( + ecs_world_t *world, + const char *json, + const ecs_from_json_desc_t *desc_arg) +{ + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; + + ecs_from_json_desc_t desc = {0}; + if (desc_arg) { + desc = *desc_arg; + } + + desc.expr = json; + + ecs_allocator_t *a = &world->allocator; + ecs_from_json_ctx_t ctx; + flecs_from_json_ctx_init(a, &ctx); + + const char *expr = json, *lah; + ctx.expr = expr; + + if (!desc.lookup_action) { + desc.lookup_action = flecs_json_ensure_entity; + desc.lookup_ctx = &ctx.anonymous_ids; + } + + json = flecs_json_expect(json, JsonObjectOpen, token, &desc); + if (!json) { + goto error; + } + + json = flecs_json_expect_member_name(json, token, "results", &desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, &desc); + if (!json) { + goto error; + } + + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + json = lah; + goto end; + } + + do { + json = flecs_entity_from_json(world, 0, json, &desc, &ctx); + if (!json) { + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonComma) { + if (token_kind != JsonArrayClose) { + ecs_parser_error(NULL, expr, json - expr, "expected ']'"); + goto error; + } + break; + } + } while (true); + +end: + json = flecs_json_expect(json, JsonObjectClose, token, &desc); + + flecs_from_json_ctx_fini(&ctx); + return json; +error: + flecs_from_json_ctx_fini(&ctx); + return NULL; +} + +const char* ecs_world_from_json_file( + ecs_world_t *world, + const char *filename, + const ecs_from_json_desc_t *desc) +{ + char *json = flecs_load_from_file(filename); + if (!json) { + ecs_err("file not found: %s", filename); + return NULL; + } + + const char *result = ecs_world_from_json(world, json, desc); + ecs_os_free(json); + return result; +} + +#endif + +/** + * @file addons/json/deserialize_value.c + * @brief Deserialize JSON strings into (component) values. + */ + +/** + * @file addons/script/script.h + * @brief Flecs script implementation. + */ + +#ifndef FLECS_SCRIPT_PRIVATE_H +#define FLECS_SCRIPT_PRIVATE_H + +/** + * @file addons/parser/parser.h + * @brief Parser addon. + */ + +#ifndef FLECS_PARSER_H +#define FLECS_PARSER_H + + +typedef struct ecs_script_impl_t ecs_script_impl_t; +typedef struct ecs_script_scope_t ecs_script_scope_t; + +#ifndef ECS_PARSER_MAX_RECURSION_DEPTH +#define ECS_PARSER_MAX_RECURSION_DEPTH (64) +#endif + +typedef struct ecs_parser_t { + const char *name; + const char *code; + + const char *pos; + char *token_cur; + char *token_keep; + bool significant_newline; + bool merge_variable_members; + bool function_token; + int16_t scope_depth; + int16_t expr_depth; + + ecs_world_t *world; + + /* For script parser */ + ecs_script_impl_t *script; + ecs_script_scope_t *scope; + + /* For term parser */ + ecs_term_t *term; + ecs_oper_kind_t extra_oper; + ecs_term_ref_t *extra_args; +} ecs_parser_t; + +/** + * @file addons/parser/tokenizer.h + * @brief Parser tokenizer. + */ + +#ifndef FLECS_PARSER_TOKENIZER_H +#define FLECS_PARSER_TOKENIZER_H + +/* Tokenizer */ +typedef enum ecs_token_kind_t { + EcsTokEnd = '\0', + EcsTokUnknown, + EcsTokScopeOpen = '{', + EcsTokScopeClose = '}', + EcsTokParenOpen = '(', + EcsTokParenClose = ')', + EcsTokBracketOpen = '[', + EcsTokBracketClose = ']', + EcsTokMember = '.', + EcsTokComma = ',', + EcsTokSemiColon = ';', + EcsTokColon = ':', + EcsTokAssign = '=', + EcsTokAdd = '+', + EcsTokSub = '-', + EcsTokMul = '*', + EcsTokDiv = '/', + EcsTokMod = '%', + EcsTokBitwiseOr = '|', + EcsTokBitwiseAnd = '&', + EcsTokNot = '!', + EcsTokOptional = '?', + EcsTokAnnotation = '@', + EcsTokNewline = '\n', + EcsTokChar = '\'', + EcsTokEq = 100, + EcsTokNeq = 101, + EcsTokGt = 102, + EcsTokGtEq = 103, + EcsTokLt = 104, + EcsTokLtEq = 105, + EcsTokAnd = 106, + EcsTokOr = 107, + EcsTokMatch = 108, + EcsTokRange = 109, + EcsTokShiftLeft = 110, + EcsTokShiftRight = 111, + EcsTokIdentifier = 112, + EcsTokFunction = 113, + EcsTokString = 114, + EcsTokNumber = 115, + EcsTokKeywordModule = 116, + EcsTokKeywordUsing = 117, + EcsTokKeywordWith = 118, + EcsTokKeywordIf = 119, + EcsTokKeywordFor = 120, + EcsTokKeywordIn = 121, + EcsTokKeywordElse = 122, + EcsTokKeywordTemplate = 130, + EcsTokKeywordProp = 131, + EcsTokKeywordConst = 132, + EcsTokKeywordMatch = 133, + EcsTokKeywordNew = 134, + EcsTokKeywordExport = 135, + EcsTokAddAssign = 136, + EcsTokMulAssign = 137, +} ecs_token_kind_t; + +typedef struct ecs_token_t { + const char *value; + ecs_token_kind_t kind; +} ecs_token_t; + +typedef struct ecs_tokens_t { + int32_t count; + ecs_token_t tokens[256]; +} ecs_tokens_t; + +typedef struct ecs_tokenizer_t { + ecs_tokens_t stack; + ecs_token_t *tokens; +} ecs_tokenizer_t; + +const char* flecs_tokenizer_until( + ecs_parser_t *parser, + const char *ptr, + ecs_token_t *out, + char until); + +const char* flecs_token_kind_str( + ecs_token_kind_t kind); + +const char* flecs_token_str( + ecs_token_kind_t kind); + +const char* flecs_token( + ecs_parser_t *parser, + const char *ptr, + ecs_token_t *out, + bool is_lookahead); + +const char* flecs_scan_whitespace( + ecs_parser_t *parser, + const char *pos); + +const char* flecs_tokenizer_identifier( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out); + +#endif + + +#endif + + +#ifdef FLECS_SCRIPT + +typedef struct ecs_script_entity_t ecs_script_entity_t; + +#define flecs_script_impl(script) ((ecs_script_impl_t*)script) + +struct ecs_script_impl_t { + ecs_script_t pub; + ecs_allocator_t allocator; + ecs_script_scope_t *root; + ecs_expr_node_t *expr; /* Only set if script is just an expression */ + char *token_buffer; + char *token_remaining; /* Remaining space in token buffer */ + const char *next_token; /* First character after expression */ + int32_t token_buffer_size; + int32_t refcount; +}; + +typedef struct ecs_function_calldata_t { + ecs_entity_t function; + struct { + ecs_function_callback_t callback; + ecs_vector_function_callback_t vector_callback; + } is; + int32_t vector_elem_count; + void *ctx; +} ecs_function_calldata_t; + +/** + * @file addons/script/ast.h + * @brief Script AST. + */ + +#ifndef FLECS_SCRIPT_AST_H +#define FLECS_SCRIPT_AST_H + +typedef enum ecs_script_node_kind_t { + EcsAstScope, + EcsAstTag, + EcsAstComponent, + EcsAstDefaultComponent, + EcsAstVarComponent, + EcsAstWithVar, + EcsAstWithTag, + EcsAstWithComponent, + EcsAstWith, + EcsAstUsing, + EcsAstModule, + EcsAstAnnotation, + EcsAstTemplate, + EcsAstProp, + EcsAstConst, + EcsAstExportConst, + EcsAstEntity, + EcsAstPairScope, + EcsAstIf, + EcsAstFor +} ecs_script_node_kind_t; + +typedef struct ecs_script_node_t { + ecs_script_node_kind_t kind; + const char *pos; +} ecs_script_node_t; + +struct ecs_script_scope_t { + ecs_script_node_t node; + ecs_vec_t stmts; + ecs_script_scope_t *parent; + ecs_id_t default_component_eval; + + /* Array with component ids that are added in scope. Used to limit + * archetype moves. */ + ecs_vec_t components; /* vec */ +}; + +typedef struct ecs_script_id_t { + const char *first; + const char *second; + ecs_id_t flag; + ecs_id_t eval; + + /* If first or second refer to a variable, these are the cached variable + * stack pointers so we don't have to lookup variables by name. */ + int32_t first_sp; + int32_t second_sp; + + /* In case first/second are specified as interpolated strings. */ + ecs_expr_node_t *first_expr; + ecs_expr_node_t *second_expr; + + /* If true, the lookup result for this id cannot be cached. This is the case + * for entities that are defined inside of templates, which have different + * values for each instantiation. */ + bool dynamic; +} ecs_script_id_t; + +typedef struct ecs_script_tag_t { + ecs_script_node_t node; + ecs_script_id_t id; +} ecs_script_tag_t; + +typedef struct ecs_script_component_t { + ecs_script_node_t node; + ecs_script_id_t id; + ecs_expr_node_t *expr; + ecs_value_t eval; + bool is_collection; +} ecs_script_component_t; + +typedef struct ecs_script_default_component_t { + ecs_script_node_t node; + ecs_expr_node_t *expr; + ecs_value_t eval; +} ecs_script_default_component_t; + +typedef struct ecs_script_var_component_t { + ecs_script_node_t node; + const char *name; + int32_t sp; +} ecs_script_var_component_t; + +struct ecs_script_entity_t { + ecs_script_node_t node; + const char *kind; + const char *name; + bool name_is_var; + bool kind_w_expr; + bool non_fragmenting_parent; + ecs_script_scope_t *scope; + ecs_expr_node_t *name_expr; + + /* Populated during eval */ + ecs_script_entity_t *parent; + ecs_entity_t eval; + ecs_entity_t eval_kind; +}; + +typedef struct ecs_script_with_t { + ecs_script_node_t node; + ecs_script_scope_t *expressions; + ecs_script_scope_t *scope; +} ecs_script_with_t; + +typedef struct ecs_script_inherit_t { + ecs_script_node_t node; + ecs_script_scope_t *base_list; +} ecs_script_inherit_t; + +typedef struct ecs_script_pair_scope_t { + ecs_script_node_t node; + ecs_script_id_t id; + ecs_script_scope_t *scope; +} ecs_script_pair_scope_t; + +typedef struct ecs_script_using_t { + ecs_script_node_t node; + const char *name; +} ecs_script_using_t; + +typedef struct ecs_script_module_t { + ecs_script_node_t node; + const char *name; +} ecs_script_module_t; + +typedef struct ecs_script_annot_t { + ecs_script_node_t node; + const char *name; + const char *expr; +} ecs_script_annot_t; + +typedef struct ecs_script_template_node_t { + ecs_script_node_t node; + const char *name; + ecs_script_scope_t* scope; +} ecs_script_template_node_t; + +typedef struct ecs_script_var_node_t { + ecs_script_node_t node; + const char *name; + const char *type; + ecs_expr_node_t *expr; +} ecs_script_var_node_t; + +typedef struct ecs_script_if_t { + ecs_script_node_t node; + ecs_script_scope_t *if_true; + ecs_script_scope_t *if_false; + ecs_expr_node_t *expr; +} ecs_script_if_t; + +typedef struct ecs_script_for_range_t { + ecs_script_node_t node; + const char *loop_var; + ecs_expr_node_t *from; + ecs_expr_node_t *to; + ecs_script_scope_t *scope; +} ecs_script_for_range_t; + +#define ecs_script_node(kind, node)\ + ((ecs_script_##kind##_t*)node) + +bool flecs_scope_is_empty( + ecs_script_scope_t *scope); + +ecs_script_scope_t* flecs_script_insert_scope( + ecs_parser_t *parser); + +ecs_script_entity_t* flecs_script_insert_entity( + ecs_parser_t *parser, + const char *name); + +ecs_script_pair_scope_t* flecs_script_insert_pair_scope( + ecs_parser_t *parser, + const char *first, + const char *second); + +ecs_script_with_t* flecs_script_insert_with( + ecs_parser_t *parser); + +ecs_script_using_t* flecs_script_insert_using( + ecs_parser_t *parser, + const char *name); + +ecs_script_module_t* flecs_script_insert_module( + ecs_parser_t *parser, + const char *name); + +ecs_script_template_node_t* flecs_script_insert_template( + ecs_parser_t *parser, + const char *name); + +ecs_script_annot_t* flecs_script_insert_annot( + ecs_parser_t *parser, + const char *name, + const char *expr); + +ecs_script_var_node_t* flecs_script_insert_var( + ecs_parser_t *parser, + const char *name); + +ecs_script_tag_t* flecs_script_insert_tag( + ecs_parser_t *parser, + const char *name); + +ecs_script_tag_t* flecs_script_insert_pair_tag( + ecs_parser_t *parser, + const char *first, + const char *second); + +ecs_script_component_t* flecs_script_insert_component( + ecs_parser_t *parser, + const char *name); + +ecs_script_component_t* flecs_script_insert_pair_component( + ecs_parser_t *parser, + const char *first, + const char *second); + +ecs_script_default_component_t* flecs_script_insert_default_component( + ecs_parser_t *parser); + +ecs_script_var_component_t* flecs_script_insert_var_component( + ecs_parser_t *parser, + const char *name); + +ecs_script_if_t* flecs_script_insert_if( + ecs_parser_t *parser); + +ecs_script_for_range_t* flecs_script_insert_for_range( + ecs_parser_t *parser); + +#endif + +/** + * @file addons/script/expr/expr.h + * @brief Script expression support. + */ + +#ifndef FLECS_EXPR_SCRIPT_H +#define FLECS_EXPR_SCRIPT_H + +/** + * @file addons/script/expr/stack.h + * @brief Script expression AST. + */ + +#ifndef FLECS_SCRIPT_EXPR_STACK_H +#define FLECS_SCRIPT_EXPR_STACK_H + +#define FLECS_EXPR_STACK_MAX (256) +#define FLECS_EXPR_SMALL_DATA_SIZE (24) + + +typedef union ecs_expr_small_value_t { + bool bool_; + char char_; + ecs_byte_t byte_; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + intptr_t iptr; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + uintptr_t uptr; + double f32; + double f64; + char *string; + ecs_entity_t entity; + ecs_id_t id; + + /* Avoid allocations for small trivial types */ + char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; +} ecs_expr_small_value_t; + +typedef struct ecs_expr_value_t { + ecs_value_t value; + const ecs_type_info_t *type_info; + bool owned; /* Is value owned by the runtime */ +} ecs_expr_value_t; + +typedef struct ecs_expr_stack_frame_t { + ecs_stack_cursor_t *cur; + int32_t sp; +} ecs_expr_stack_frame_t; + +typedef struct ecs_expr_stack_t { + ecs_expr_value_t values[FLECS_EXPR_STACK_MAX]; + ecs_expr_stack_frame_t frames[FLECS_EXPR_STACK_MAX]; + ecs_stack_t stack; + int32_t frame; +} ecs_expr_stack_t; + +void flecs_expr_stack_init( + ecs_expr_stack_t *stack); + +void flecs_expr_stack_fini( + ecs_expr_stack_t *stack); + +ecs_expr_value_t* flecs_expr_stack_alloc( + ecs_expr_stack_t *stack, + const ecs_type_info_t *ti); + +ecs_expr_value_t* flecs_expr_stack_result( + ecs_expr_stack_t *stack, + ecs_expr_node_t *node); + +void flecs_expr_stack_push( + ecs_expr_stack_t *stack); + +void flecs_expr_stack_pop( + ecs_expr_stack_t *stack); + +#endif + +/** + * @file addons/script/expr_ast.h + * @brief Script expression AST. + */ + +#ifndef FLECS_SCRIPT_EXPR_AST_H +#define FLECS_SCRIPT_EXPR_AST_H + +#define FLECS_EXPR_SMALL_DATA_SIZE (24) + +typedef enum ecs_expr_node_kind_t { + EcsExprValue, + EcsExprInterpolatedString, + EcsExprInitializer, + EcsExprEmptyInitializer, + EcsExprUnary, + EcsExprBinary, + EcsExprIdentifier, + EcsExprVariable, + EcsExprGlobalVariable, + EcsExprFunction, + EcsExprMethod, + EcsExprMember, + EcsExprElement, + EcsExprComponent, + EcsExprCast, + EcsExprCastNumber, + EcsExprMatch, + EcsExprNew +} ecs_expr_node_kind_t; + +struct ecs_expr_node_t { + ecs_expr_node_kind_t kind; + ecs_entity_t type; + const ecs_type_info_t *type_info; + const char *pos; +}; + +typedef struct ecs_expr_value_node_t { + ecs_expr_node_t node; + void *ptr; + ecs_expr_small_value_t storage; +} ecs_expr_value_node_t; + +typedef struct ecs_expr_interpolated_string_t { + ecs_expr_node_t node; + char *value; /* modified by parser */ + char *buffer; /* for storing expr tokens */ + ecs_size_t buffer_size; + ecs_vec_t fragments; /* vec */ + ecs_vec_t expressions; /* vec */ +} ecs_expr_interpolated_string_t; + +typedef struct ecs_expr_initializer_element_t { + const char *member; + ecs_expr_node_t *value; + uintptr_t offset; + ecs_token_kind_t operator; +} ecs_expr_initializer_element_t; + +typedef struct ecs_expr_initializer_t { + ecs_expr_node_t node; + ecs_vec_t elements; + const ecs_type_info_t *type_info; + bool is_collection; + bool is_dynamic; +} ecs_expr_initializer_t; + +typedef struct ecs_expr_variable_t { + ecs_expr_node_t node; + const char *name; + ecs_value_t global_value; /* Only set for global variables */ + int32_t sp; /* For fast variable lookups */ +} ecs_expr_variable_t; + +typedef struct ecs_expr_identifier_t { + ecs_expr_node_t node; + const char *value; + ecs_expr_node_t *expr; +} ecs_expr_identifier_t; + +typedef struct ecs_expr_unary_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_token_kind_t operator; +} ecs_expr_unary_t; + +typedef struct ecs_expr_binary_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + ecs_expr_node_t *right; + ecs_token_kind_t operator; + + /* For vector operations */ + ecs_entity_t vector_type; + int32_t vector_count; +} ecs_expr_binary_t; + +typedef struct ecs_expr_member_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + const char *member_name; + uintptr_t offset; +} ecs_expr_member_t; + +typedef struct ecs_expr_function_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + ecs_expr_initializer_t *args; + const char *function_name; + ecs_function_calldata_t calldata; +} ecs_expr_function_t; + +typedef struct ecs_expr_element_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + ecs_expr_node_t *index; + ecs_size_t elem_size; +} ecs_expr_element_t; + +typedef struct ecs_expr_component_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_id_t component; +} ecs_expr_component_t; + +typedef struct ecs_expr_cast_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; +} ecs_expr_cast_t; + +typedef struct ecs_expr_match_element_t { + ecs_expr_node_t *compare; + ecs_expr_node_t *expr; +} ecs_expr_match_element_t; + +typedef struct ecs_expr_match_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_vec_t elements; + ecs_expr_match_element_t any; +} ecs_expr_match_t; + +typedef struct ecs_expr_new_t { + ecs_expr_node_t node; + ecs_script_entity_t *entity; +} ecs_expr_new_t; + +ecs_expr_value_node_t* flecs_expr_value_from( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type); + +ecs_expr_variable_t* flecs_expr_variable_from( + ecs_script_t *script, + ecs_expr_node_t *node, + const char *name); + +ecs_expr_member_t* flecs_expr_member_from( + ecs_script_t *script, + ecs_expr_node_t *node, + const char *name); + +ecs_expr_value_node_t* flecs_expr_bool( + ecs_parser_t *parser, + bool value); + +ecs_expr_value_node_t *flecs_expr_char( + ecs_parser_t *parser, + const char *value); + +ecs_expr_value_node_t* flecs_expr_int( + ecs_parser_t *parser, + int64_t value); + +ecs_expr_value_node_t* flecs_expr_uint( + ecs_parser_t *parser, + uint64_t value); + +ecs_expr_value_node_t* flecs_expr_float( + ecs_parser_t *parser, + double value); + +ecs_expr_value_node_t* flecs_expr_string( + ecs_parser_t *parser, + const char *value); + +ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( + ecs_parser_t *parser, + const char *value); + +ecs_expr_value_node_t* flecs_expr_entity( + ecs_parser_t *parser, + ecs_entity_t value); + +ecs_expr_initializer_t* flecs_expr_initializer( + ecs_parser_t *parser); + +ecs_expr_identifier_t* flecs_expr_identifier( + ecs_parser_t *parser, + const char *value); + +ecs_expr_variable_t* flecs_expr_variable( + ecs_parser_t *parser, + const char *value); + +ecs_expr_unary_t* flecs_expr_unary( + ecs_parser_t *parser); + +ecs_expr_binary_t* flecs_expr_binary( + ecs_parser_t *parser); + +ecs_expr_member_t* flecs_expr_member( + ecs_parser_t *parser); + +ecs_expr_function_t* flecs_expr_function( + ecs_parser_t *parser); + +ecs_expr_element_t* flecs_expr_element( + ecs_parser_t *parser); + +ecs_expr_match_t* flecs_expr_match( + ecs_parser_t *parser); + +ecs_expr_new_t* flecs_expr_new( + ecs_parser_t *parser); + +ecs_expr_cast_t* flecs_expr_cast( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type); + +#endif + +/** + * @file addons/script/exor_visit.h + * @brief Script AST visitor utilities. + */ + +#ifndef FLECS_EXPR_SCRIPT_VISIT_H +#define FLECS_EXPR_SCRIPT_VISIT_H + +#define flecs_expr_visit_error(script, node, ...) \ + ecs_parser_error( \ + script->name, script->code, \ + ((const ecs_expr_node_t*)node)->pos - script->code, \ + __VA_ARGS__); + +int flecs_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_expr_eval_desc_t *desc); + +int flecs_expr_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node, + const ecs_expr_eval_desc_t *desc); + +int flecs_expr_visit_eval( + const ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_expr_eval_desc_t *desc, + ecs_value_t *out); + +void flecs_expr_visit_free( + ecs_script_t *script, + ecs_expr_node_t *node); + +ecs_script_var_t flecs_expr_find_var( + ecs_script_t *script, + const char *name); + +#endif + + +int flecs_value_copy_to( + ecs_world_t *world, + ecs_value_t *dst, + const ecs_expr_value_t *src); + +int flecs_value_move_to( + ecs_world_t *world, + ecs_value_t *dst, + ecs_value_t *src); + +int flecs_value_binary( + const ecs_script_t *script, + const ecs_value_t *left, + const ecs_value_t *right, + ecs_value_t *out, + ecs_token_kind_t operator); + +int flecs_value_unary( + const ecs_script_t *script, + const ecs_value_t *expr, + ecs_value_t *out, + ecs_token_kind_t operator); + +const char* flecs_script_parse_expr( + ecs_parser_t *parser, + const char *pos, + ecs_token_kind_t left_oper, + ecs_expr_node_t **out); + +const char* flecs_script_parse_initializer( + ecs_parser_t *parser, + const char *pos, + char until, + ecs_expr_initializer_t **node_out); + +void flecs_expr_to_str_buf( + const ecs_script_t *world, + const ecs_expr_node_t *expr, + ecs_strbuf_t *buf, + bool colors); + +bool flecs_string_is_interpolated( + const char *str); + +char* flecs_string_escape( + char *str); + +bool flecs_value_is_0( + const ecs_value_t *value); + +bool flecs_expr_is_type_integer( + ecs_entity_t type); + +bool flecs_expr_is_type_number( + ecs_entity_t type); + +ecs_size_t flecs_expr_storage_size( + ecs_entity_t type); + +int flecs_expr_initializer_validate_assign( + const ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_expr_initializer_element_t *elem, + ecs_entity_t type, + ecs_size_t value_size); + +#endif + +/** + * @file addons/script/visit.h + * @brief Script AST visitor utilities. + */ + +#ifndef FLECS_SCRIPT_VISIT_H +#define FLECS_SCRIPT_VISIT_H + +typedef struct ecs_script_visit_t ecs_script_visit_t; + +typedef int (*ecs_visit_action_t)( + ecs_script_visit_t *visitor, + ecs_script_node_t *node); + +/* Visitors track both scope nodes and statement nodes on the traversal stack. + * For deeply nested scopes this requires roughly 2x the parser nesting depth, + * plus the root scope node. */ +#define ECS_SCRIPT_VISIT_MAX_DEPTH ((ECS_PARSER_MAX_RECURSION_DEPTH * 2) + 4) + +struct ecs_script_visit_t { + ecs_script_impl_t *script; + ecs_visit_action_t visit; + ecs_script_node_t* nodes[ECS_SCRIPT_VISIT_MAX_DEPTH]; + ecs_script_node_t *prev, *next; + int32_t depth; +}; + +int ecs_script_visit_( + ecs_script_visit_t *visitor, + ecs_visit_action_t visit, + ecs_script_impl_t *script); + +#define ecs_script_visit(script, visitor, visit) \ + ecs_script_visit_((ecs_script_visit_t*)visitor,\ + visit,\ + script) + +int ecs_script_visit_from_( + ecs_script_visit_t *visitor, + ecs_visit_action_t visit, + ecs_script_impl_t *script, + ecs_script_node_t *node, + int32_t depth); + +#define ecs_script_visit_from(script, visitor, visit, from, depth) \ + ecs_script_visit_from_((ecs_script_visit_t*)visitor,\ + visit,\ + script, \ + from, \ + depth) + +int ecs_script_visit_node_( + ecs_script_visit_t *v, + ecs_script_node_t *node); + +#define ecs_script_visit_node(visitor, node) \ + ecs_script_visit_node_((ecs_script_visit_t*)visitor, \ + (ecs_script_node_t*)node) + +int ecs_script_visit_scope_( + ecs_script_visit_t *v, + ecs_script_scope_t *node); + +#define ecs_script_visit_scope(visitor, node) \ + ecs_script_visit_scope_((ecs_script_visit_t*)visitor, node) + +void ecs_script_visit_push_( + ecs_script_visit_t *v, + ecs_script_node_t *node); + +#define ecs_script_visit_push(visitor, node) \ + ecs_script_visit_push_((ecs_script_visit_t*)visitor, node) + +void ecs_script_visit_pop_( + ecs_script_visit_t *v, + ecs_script_node_t *node); + +#define ecs_script_visit_pop(visitor, node) \ + ecs_script_visit_pop_((ecs_script_visit_t*)visitor, node) + +ecs_script_node_t* ecs_script_parent_node_( + ecs_script_visit_t *v); + +#define ecs_script_parent_node(visitor) \ + ecs_script_parent_node_((ecs_script_visit_t*)visitor) + +ecs_script_scope_t* ecs_script_current_scope_( + ecs_script_visit_t *v); + +#define ecs_script_current_scope(visitor) \ + ecs_script_current_scope_((ecs_script_visit_t*)visitor) + +ecs_script_node_t* ecs_script_parent_( + ecs_script_visit_t *v, + ecs_script_node_t *node); + +#define ecs_script_parent(visitor, node) \ + ecs_script_parent_((ecs_script_visit_t*)visitor, (ecs_script_node_t*)node) + +ecs_script_node_t* ecs_script_next_node_( + ecs_script_visit_t *v); + +#define ecs_script_next_node(visitor) \ + ecs_script_next_node_((ecs_script_visit_t*)visitor) + +int32_t ecs_script_node_line_number_( + ecs_script_impl_t *script, + ecs_script_node_t *node); + +#define ecs_script_node_line_number(script, node) \ + ecs_script_node_line_number_(script, (ecs_script_node_t*)node) + +#endif + +/** + * @file addons/script/visit_eval.h + * @brief Script evaluation visitor. + */ + +#ifndef FLECS_SCRIPT_VISIT_EVAL_H +#define FLECS_SCRIPT_VISIT_EVAL_H + +typedef struct ecs_script_eval_visitor_t { + ecs_script_visit_t base; + ecs_world_t *world; + ecs_script_runtime_t *r; + ecs_script_template_t *template; /* Set when creating template */ + ecs_entity_t template_entity; /* Set when creating template instance */ + ecs_entity_t module; + ecs_entity_t parent; + ecs_script_entity_t *entity; + ecs_entity_t with_relationship; + int32_t with_relationship_sp; + bool is_with_scope; + bool dynamic_variable_binding; + ecs_script_vars_t *vars; +} ecs_script_eval_visitor_t; + +void flecs_script_eval_error_( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node, + const char *fmt, + ...); + +#define flecs_script_eval_error(v, node, ...)\ + flecs_script_eval_error_(v, (ecs_script_node_t*)node, __VA_ARGS__) + +int flecs_script_find_entity( + ecs_script_eval_visitor_t *v, + ecs_entity_t from, + const char *path, + ecs_expr_node_t **name_expr, + int32_t *frame_offset, + ecs_entity_t *out, + bool *is_var); + +ecs_script_var_t* flecs_script_find_var( + const ecs_script_vars_t *vars, + const char *name, + int32_t *frame_offset); + +ecs_entity_t flecs_script_create_entity( + ecs_script_eval_visitor_t *v, + const char *name); + +const ecs_type_info_t* flecs_script_get_type_info( + ecs_script_eval_visitor_t *v, + void *node, + ecs_id_t id); + +int flecs_script_eval_expr( + ecs_script_eval_visitor_t *v, + ecs_expr_node_t **expr_ptr, + ecs_value_t *value); + +void flecs_script_eval_visit_init( + const ecs_script_impl_t *script, + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc); + +void flecs_script_eval_visit_fini( + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc); + +int flecs_script_eval_node( + ecs_script_visit_t *v, + ecs_script_node_t *node); + +int flecs_script_check_node( + ecs_script_visit_t *v, + ecs_script_node_t *node); + +int flecs_script_check_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node); + +/* Functions shared between check and eval visitor */ + +int flecs_script_eval_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node); + +int flecs_script_eval_id( + ecs_script_eval_visitor_t *v, + void *node, + ecs_script_id_t *id); + +int flecs_script_eval_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node); + +int flecs_script_eval_const( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node, + bool export); + +ecs_entity_t flecs_script_find_entity_action( + const ecs_world_t *world, + const char *path, + void *ctx); + +#endif + +/** + * @file addons/script/template.h + * @brief Script template implementation. + */ + +#ifndef FLECS_SCRIPT_TEMPLATE_H +#define FLECS_SCRIPT_TEMPLATE_H + +extern ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); + +struct ecs_script_template_t { + /* Template handle */ + ecs_entity_t entity; + + /* Template AST node */ + ecs_script_template_node_t *node; + + /* Hoisted using statements */ + ecs_vec_t using_; + + /* Hoisted variables */ + ecs_script_vars_t *vars; + + /* Default values for props */ + ecs_vec_t prop_defaults; + + /* Type info for template component */ + const ecs_type_info_t *type_info; + + /* Annotations to apply to template instance */ + ecs_vec_t annot; + + /* Use non-fragmenting hierarchy */ + bool non_fragmenting_parent; +}; + +#define ECS_TEMPLATE_SMALL_SIZE (36) + +/* Event used for deferring template instantiation */ +typedef struct EcsScriptTemplateSetEvent { + ecs_entity_t template_entity; + ecs_entity_t *entities; + void *data; + int32_t count; + + /* Storage for small template types */ + int64_t _align; /* Align data storage to 8 bytes */ + char data_storage[ECS_TEMPLATE_SMALL_SIZE]; + ecs_entity_t entity_storage; +} EcsScriptTemplateSetEvent; + +int flecs_script_eval_template( + ecs_script_eval_visitor_t *v, + ecs_script_template_node_t *template); + +ecs_script_template_t* flecs_script_template_init( + ecs_script_impl_t *script); + +void flecs_script_template_fini( + ecs_script_impl_t *script, + ecs_script_template_t *template); + +void flecs_script_template_import( + ecs_world_t *world); + +#endif + + +struct ecs_script_runtime_t { + ecs_allocator_t allocator; + ecs_expr_stack_t expr_stack; + ecs_stack_t stack; + ecs_vec_t using; + ecs_vec_t with; + ecs_vec_t with_type_info; + ecs_vec_t annot; +}; + +ecs_script_t* flecs_script_new( + ecs_world_t *world); + +ecs_script_scope_t* flecs_script_scope_new( + ecs_parser_t *parser); + +int flecs_script_visit_free( + ecs_script_t *script); + +int flecs_script_visit_free_node( + ecs_script_t *script, + ecs_script_node_t *node); + +ecs_script_vars_t* flecs_script_vars_push( + ecs_script_vars_t *parent, + ecs_stack_t *stack, + ecs_allocator_t *allocator); + +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world); + +void flecs_script_register_builtin_functions( + ecs_world_t *world); + +void flecs_function_import( + ecs_world_t *world); + +int flecs_script_check( + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc); + +const char* flecs_script_stmt( + ecs_parser_t *parser, + const char *pos); + +int ecs_script_ast_node_to_buf( + const ecs_script_t *script, + ecs_script_node_t *node, + ecs_strbuf_t *buf, + bool colors, + int32_t depth); + +void ecs_script_runtime_clear( + ecs_script_runtime_t *r); + +int flecs_script_apply_annot( + ecs_script_eval_visitor_t *v, + ecs_script_entity_t *node, + ecs_script_annot_t *annot); + +/* Script functions */ +double flecs_lerp( + double a, + double b, + double t); + +void FlecsScriptMathPerlinImport( + ecs_world_t *world); + +#endif // FLECS_SCRIPT +#endif // FLECS_SCRIPT_PRIVATE_H + + +#ifdef FLECS_JSON + +const char* ecs_ptr_from_json( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr, + const char *json, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t token_kind = 0; + char token_buffer[ECS_MAX_TOKEN_SIZE], t_lah[ECS_MAX_TOKEN_SIZE]; + char *token = token_buffer; + int depth = 0; + + bool strict = desc ? desc->strict : false; + bool skip = false; + int skip_depth = 0; + + const char *name = NULL; + const char *expr = NULL; + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, ptr); + if (cur.valid == false) { + return NULL; + } + + if (desc) { + name = desc->name; + expr = desc->expr; + cur.lookup_action = desc->lookup_action; + cur.lookup_ctx = desc->lookup_ctx; + } + + while ((json = flecs_json_parse(json, &token_kind, token))) { + if (skip) { + /* Skip over tokens in case an unknown member was encountered */ + if (token_kind == JsonObjectOpen || token_kind == JsonArrayOpen) { + skip_depth ++; + } else + if (token_kind == JsonObjectClose || token_kind == JsonArrayClose) { + skip_depth --; + if (!skip_depth) { + skip = false; + } + } else { + if (!skip_depth) { + skip = false; + } + } + + continue; + } + + if (token_kind == JsonLargeString) { + ecs_strbuf_t large_token = ECS_STRBUF_INIT; + json = flecs_json_parse_large_string(json, &large_token); + if (!json) { + break; + } + + token = ecs_strbuf_get(&large_token); + token_kind = JsonString; + } + + if (token_kind == JsonObjectOpen) { + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } + + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, json - expr, "expected '['"); + return NULL; + } + } else if (token_kind == JsonObjectClose) { + depth --; + + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, json - expr, "expected ']'"); + return NULL; + } + + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } else if (token_kind == JsonArrayOpen) { + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } + + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, json - expr, "expected '{'"); + return NULL; + } + } else if (token_kind == JsonArrayClose) { + depth --; + + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, json - expr, "expected '}'"); + return NULL; + } + + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } else if (token_kind == JsonComma) { + if (ecs_meta_next(&cur) != 0) { + goto error; + } + } else if (token_kind == JsonNull) { + if (ecs_meta_set_null(&cur) != 0) { + goto error; + } + } else if (token_kind == JsonString) { + const char *lah = flecs_json_parse( + json, &token_kind, t_lah); + if (token_kind == JsonColon) { + /* Member assignment */ + json = lah; + if (strict) { + if (ecs_meta_dotmember(&cur, token) != 0) { + goto error; + } + } else { + if (ecs_meta_try_dotmember(&cur, token) != 0) { + skip = true; + } + } + } else { + if (ecs_meta_set_string(&cur, token) != 0) { + goto error; + } + } + } else if (token_kind == JsonNumber) { + double number = atof(token); + if (ecs_meta_set_float(&cur, number) != 0) { + goto error; + } + } else if (token_kind == JsonLargeInt) { + int64_t number = flecs_ito(int64_t, atoll(token)); + if (ecs_meta_set_int(&cur, number) != 0) { + goto error; + } + } else if (token_kind == JsonNull) { + if (ecs_meta_set_null(&cur) != 0) { + goto error; + } + } else if (token_kind == JsonTrue) { + if (ecs_meta_set_bool(&cur, true) != 0) { + goto error; + } + } else if (token_kind == JsonFalse) { + if (ecs_meta_set_bool(&cur, false) != 0) { + goto error; + } + } else { + goto error; + } + + if (token != token_buffer) { + ecs_os_free(token); + token = token_buffer; + } + + if (!depth) { + break; + } + } + + return json; +error: + return NULL; +} + +#endif + +/** + * @file addons/json/json.c + * @brief JSON serializer utilities. + */ + + +#ifdef FLECS_JSON + +static +const char* flecs_json_token_str( + ecs_json_token_t token_kind) +{ + switch(token_kind) { + case JsonObjectOpen: return "{"; + case JsonObjectClose: return "}"; + case JsonArrayOpen: return "["; + case JsonArrayClose: return "]"; + case JsonColon: return ":"; + case JsonComma: return ","; + case JsonNumber: return "number"; + case JsonLargeInt: return "large integer"; + case JsonLargeString: + case JsonString: return "string"; + case JsonBoolean: return "bool"; + case JsonTrue: return "true"; + case JsonFalse: return "false"; + case JsonNull: return "null"; + case JsonInvalid: return "invalid"; + default: + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } +error: + return "<>"; +} + +const char* flecs_json_parse( + const char *json, + ecs_json_token_t *token_kind, + char *token) +{ + ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); + json = flecs_parse_ws_eol(json); + + char ch = json[0]; + + if (ch == '{') { + token_kind[0] = JsonObjectOpen; + return json + 1; + } else if (ch == '}') { + token_kind[0] = JsonObjectClose; + return json + 1; + } else if (ch == '[') { + token_kind[0] = JsonArrayOpen; + return json + 1; + } else if (ch == ']') { + token_kind[0] = JsonArrayClose; + return json + 1; + } else if (ch == ':') { + token_kind[0] = JsonColon; + return json + 1; + } else if (ch == ',') { + token_kind[0] = JsonComma; + return json + 1; + } else if (ch == '"') { + const char *start = json; + char *token_ptr = token; + json ++; + for (; (ch = json[0]); ) { + if (token_ptr - token >= ECS_MAX_TOKEN_SIZE) { + /* Token doesn't fit in buffer, signal to app to try again with + * dynamic buffer. */ + token_kind[0] = JsonLargeString; + return start; + } + + if (ch == '"') { + json ++; + token_ptr[0] = '\0'; + break; + } + + json = flecs_chrparse(json, token_ptr ++); + } + + if (!ch) { + token_kind[0] = JsonInvalid; + return NULL; + } else { + token_kind[0] = JsonString; + return json; + } + } else if (isdigit(ch) || (ch == '-')) { + token_kind[0] = JsonNumber; + const char *result = flecs_parse_digit(json, token); + + /* Cheap initial check if parsed token could represent large int */ + if (result - json > 15) { + /* Less cheap secondary check to see if number is integer */ + if (!strchr(token, '.')) { + token_kind[0] = JsonLargeInt; + } + } + + return result; + } else if (isalpha(ch)) { + if (!ecs_os_strncmp(json, "null", 4)) { + token_kind[0] = JsonNull; + json += 4; + } else + if (!ecs_os_strncmp(json, "true", 4)) { + token_kind[0] = JsonTrue; + json += 4; + } else + if (!ecs_os_strncmp(json, "false", 5)) { + token_kind[0] = JsonFalse; + json += 5; + } + + if (isalpha(json[0]) || isdigit(json[0])) { + token_kind[0] = JsonInvalid; + return NULL; + } + + return json; + } else { + token_kind[0] = JsonInvalid; + return NULL; + } +} + +const char* flecs_json_parse_large_string( + const char *json, + ecs_strbuf_t *buf) +{ + if (json[0] != '"') { + return NULL; /* can only parse strings */ + } + + char ch, ch_out; + json ++; + for (; (ch = json[0]); ) { + if (ch == '"') { + json ++; + break; + } + + json = flecs_chrparse(json, &ch_out); + ecs_strbuf_appendch(buf, ch_out); + } + + if (!ch) { + return NULL; + } else { + return json; + } +} + +const char* flecs_json_parse_next_member( + const char *json, + char *token, + ecs_json_token_t *token_kind, + const ecs_from_json_desc_t *desc) +{ + json = flecs_json_parse(json, token_kind, token); + if (*token_kind == JsonObjectClose) { + return json; + } + + if (*token_kind != JsonComma) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expecteded } or ,"); + return NULL; + } + + json = flecs_json_parse(json, token_kind, token); + if (*token_kind != JsonString) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expecteded member name"); + return NULL; + } + + char temp_token[ECS_MAX_TOKEN_SIZE]; + ecs_json_token_t temp_token_kind; + + json = flecs_json_parse(json, &temp_token_kind, temp_token); + if (temp_token_kind != JsonColon) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expecteded :"); + return NULL; + } + + return json; +} + +const char* flecs_json_expect( + const char *json, + ecs_json_token_t token_kind, + char *token, + const ecs_from_json_desc_t *desc) +{ + /* Strings must be handled by flecs_json_expect_string for LargeString */ + ecs_assert(token_kind != JsonString, ECS_INTERNAL_ERROR, NULL); + + ecs_json_token_t kind = 0; + const char *lah = flecs_json_parse(json, &kind, token); + + if (kind == JsonInvalid) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "invalid json"); + return NULL; + } else if (kind != token_kind) { + if (token_kind == JsonBoolean && + (kind == JsonTrue || kind == JsonFalse)) + { + /* ok */ + } else { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected %s, got %s", + flecs_json_token_str(token_kind), flecs_json_token_str(kind)); + flecs_dump_backtrace(stdout); + return NULL; + } + } + + return lah; +} + +const char* flecs_json_expect_string( + const char *json, + char *token, + char **out, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t token_kind = 0; + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonInvalid) { + ecs_parser_error( + desc->name, desc->expr, json - desc->expr, "invalid json"); + return NULL; + } else if (token_kind != JsonString && token_kind != JsonLargeString) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected string"); + return NULL; + } + + if (token_kind == JsonLargeString) { + ecs_strbuf_t large_token = ECS_STRBUF_INIT; + json = flecs_json_parse_large_string(json, &large_token); + if (!json) { + return NULL; + } + + if (out) { + *out = ecs_strbuf_get(&large_token); + } else { + ecs_strbuf_reset(&large_token); + } + } else if (out) { + *out = token; + } + + return json; +} + +const char* flecs_json_expect_member( + const char *json, + char *token, + const ecs_from_json_desc_t *desc) +{ + char *out = NULL; + json = flecs_json_expect_string(json, token, &out, desc); + if (!json) { + return NULL; + } + + if (out != token) { + ecs_os_free(out); + } + + json = flecs_json_expect(json, JsonColon, token, desc); + if (!json) { + return NULL; + } + return json; +} + +const char* flecs_json_expect_next_member( + const char *json, + char *token, + const ecs_from_json_desc_t *desc) +{ + json = flecs_json_expect(json, JsonComma, token, desc); + if (!json) { + return NULL; + } + + return flecs_json_expect_member(json, token, desc); +} + +const char* flecs_json_expect_member_name( + const char *json, + char *token, + const char *member_name, + const ecs_from_json_desc_t *desc) +{ + json = flecs_json_expect_member(json, token, desc); + if (!json) { + return NULL; + } + if (ecs_os_strcmp(token, member_name)) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected member '%s'", member_name); + return NULL; + } + return json; +} + +static +const char* flecs_json_skip_string( + const char *json) +{ + if (json[0] != '"') { + return NULL; /* can only skip strings */ + } + + char ch, ch_out; + json ++; + for (; (ch = json[0]); ) { + if (ch == '"') { + json ++; + break; + } + + json = flecs_chrparse(json, &ch_out); + } + + if (!ch) { + return NULL; + } else { + return json; + } +} + +const char* flecs_json_skip_object( + const char *json, + char *token, + const ecs_from_json_desc_t *desc) +{ + ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_json_token_t token_kind = 0; + + while ((json = flecs_json_parse(json, &token_kind, token))) { + if (token_kind == JsonObjectOpen) { + json = flecs_json_skip_object(json, token, desc); + } else if (token_kind == JsonArrayOpen) { + json = flecs_json_skip_array(json, token, desc); + } else if (token_kind == JsonLargeString) { + json = flecs_json_skip_string(json); + } else if (token_kind == JsonObjectClose) { + return json; + } else if (token_kind == JsonArrayClose) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected }, got ]"); + return NULL; + } + + ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); + } + + ecs_parser_error(desc->name, json, 0, + "expected }, got end of string"); + return NULL; +} + +const char* flecs_json_skip_array( + const char *json, + char *token, + const ecs_from_json_desc_t *desc) +{ + ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_json_token_t token_kind = 0; + + while ((json = flecs_json_parse(json, &token_kind, token))) { + if (token_kind == JsonObjectOpen) { + json = flecs_json_skip_object(json, token, desc); + } else if (token_kind == JsonArrayOpen) { + json = flecs_json_skip_array(json, token, desc); + } else if (token_kind == JsonLargeString) { + json = flecs_json_skip_string(json); + } else if (token_kind == JsonObjectClose) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ]"); + return NULL; + } else if (token_kind == JsonArrayClose) { + return json; + } + + ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); + } + + ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); + return NULL; +} + +void flecs_json_next( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_next(buf); +} + +void flecs_json_number( + ecs_strbuf_t *buf, + double value) +{ + ecs_strbuf_appendflt(buf, value, '"'); +} + +void flecs_json_u32( + ecs_strbuf_t *buf, + uint32_t value) +{ + ecs_strbuf_appendint(buf, flecs_uto(int64_t, value)); +} + +void flecs_json_true( + ecs_strbuf_t *buf) +{ + ecs_strbuf_appendlit(buf, "true"); +} + +void flecs_json_false( + ecs_strbuf_t *buf) +{ + ecs_strbuf_appendlit(buf, "false"); +} + +void flecs_json_bool( + ecs_strbuf_t *buf, + bool value) +{ + if (value) { + flecs_json_true(buf); + } else { + flecs_json_false(buf); + } +} + +void flecs_json_null( + ecs_strbuf_t *buf) +{ + ecs_strbuf_appendlit(buf, "null"); +} + +void flecs_json_array_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "[", ", "); +} + +void flecs_json_array_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "]"); +} + +void flecs_json_object_push( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_push(buf, "{", ", "); +} + +void flecs_json_object_pop( + ecs_strbuf_t *buf) +{ + ecs_strbuf_list_pop(buf, "}"); +} + +void flecs_json_string( + ecs_strbuf_t *buf, + const char *value) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstr(buf, value); + ecs_strbuf_appendch(buf, '"'); +} + +void flecs_json_string_escape( + ecs_strbuf_t *buf, + const char *value) +{ + if (!value) { + ecs_strbuf_appendlit(buf, "null"); + return; + } + + ecs_size_t length = flecs_stresc(NULL, 0, '"', value); + if (length == ecs_os_strlen(value)) { + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstrn(buf, value, length); + ecs_strbuf_appendch(buf, '"'); + } else { + char *out = ecs_os_malloc(length + 3); + flecs_stresc(out + 1, length, '"', value); + out[0] = '"'; + out[length + 1] = '"'; + out[length + 2] = '\0'; + ecs_strbuf_appendstr(buf, out); + ecs_os_free(out); + } +} + +void flecs_json_member( + ecs_strbuf_t *buf, + const char *name) +{ + flecs_json_membern(buf, name, ecs_os_strlen(name)); +} + +void flecs_json_membern( + ecs_strbuf_t *buf, + const char *name, + int32_t name_len) +{ + ecs_strbuf_list_appendch(buf, '"'); + ecs_strbuf_appendstrn(buf, name, name_len); + ecs_strbuf_appendlit(buf, "\":"); +} + +void flecs_json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + ecs_strbuf_appendch(buf, '"'); + ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf, true); + ecs_strbuf_appendch(buf, '"'); +} + +static +const char* flecs_json_entity_label( + const ecs_world_t *world, + ecs_entity_t e) +{ + const char *lbl = NULL; + if (!e) { + return "#0"; + } +#ifdef FLECS_DOC + lbl = ecs_doc_get_name(world, e); +#else + lbl = ecs_get_name(world, e); +#endif + return lbl; +} + +void flecs_json_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + const char *lbl = flecs_json_entity_label(world, e); + if (lbl) { + flecs_json_string_escape(buf, lbl); + } else { + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendch(buf, '#'); + ecs_strbuf_appendint(buf, (uint32_t)e); + ecs_strbuf_appendch(buf, '"'); + } +} + +void flecs_json_path_or_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e, + bool path) +{ + if (!path) { + flecs_json_label(buf, world, e); + } else { + flecs_json_path(buf, world, e); + } +} + +void flecs_json_color( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e) +{ + (void)world; + (void)e; + + const char *color = NULL; +#ifdef FLECS_DOC + color = ecs_doc_get_color(world, e); +#endif + + if (color) { + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendstr(buf, color); + ecs_strbuf_appendch(buf, '"'); + } else { + ecs_strbuf_appendch(buf, '0'); + } +} + +void flecs_json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id) +{ + ecs_strbuf_appendch(buf, '['); + + if (ECS_IS_PAIR(id)) { + ecs_entity_t first = ecs_pair_first(world, id); + ecs_entity_t second = ecs_pair_second(world, id); + ecs_strbuf_appendch(buf, '"'); + ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf, true); + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendch(buf, ','); + ecs_strbuf_appendch(buf, '"'); + if (ECS_IS_VALUE_PAIR(id)) { + ecs_strbuf_appendch(buf, '@'); + ecs_strbuf_appendint(buf, ECS_PAIR_SECOND(id)); + } else { + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); + } + ecs_strbuf_appendch(buf, '"'); + } else { + ecs_strbuf_appendch(buf, '"'); + ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); + ecs_strbuf_appendch(buf, '"'); + } + + ecs_strbuf_appendch(buf, ']'); +} + +static +void flecs_json_id_member_fullpath( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id) +{ + if (ECS_IS_PAIR(id)) { + ecs_strbuf_appendch(buf, '('); + ecs_entity_t first = ecs_pair_first(world, id); + ecs_entity_t second = ecs_pair_second(world, id); + ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf, true); + ecs_strbuf_appendch(buf, ','); + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); + ecs_strbuf_appendch(buf, ')'); + } else { + ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); + } +} + +void flecs_json_id_member( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id, + bool fullpath) +{ + ecs_id_t flags = id & ECS_ID_FLAGS_MASK; + + if (flags & ECS_AUTO_OVERRIDE) { + ecs_strbuf_appendlit(buf, "auto_override|"); + id &= ~ECS_AUTO_OVERRIDE; + } + + if (flags & ECS_TOGGLE) { + ecs_strbuf_appendlit(buf, "toggle|"); + id &= ~ECS_TOGGLE; + } + + if (fullpath) { + flecs_json_id_member_fullpath(buf, world, id); + return; + } + + if (ECS_IS_PAIR(id)) { + ecs_strbuf_appendch(buf, '('); + ecs_entity_t first = ecs_pair_first(world, id); + ecs_entity_t second = ecs_pair_second(world, id); + { + const char *lbl = flecs_json_entity_label(world, first); + if (lbl) { + ecs_strbuf_appendstr(buf, lbl); + } + } + ecs_strbuf_appendch(buf, ','); + { + const char *lbl = flecs_json_entity_label(world, second); + if (lbl) { + ecs_strbuf_appendstr(buf, lbl); + } + } + ecs_strbuf_appendch(buf, ')'); + } else { + const char *lbl = flecs_json_entity_label(world, id & ECS_COMPONENT_MASK); + if (lbl) { + ecs_strbuf_appendstr(buf, lbl); + } + } +} + +ecs_primitive_kind_t flecs_json_op_to_primitive_kind( + ecs_meta_op_kind_t kind) +{ + return kind - EcsOpPrimitive; +} + +bool flecs_json_should_serialize( + ecs_entity_t entity, + const ecs_iter_t *it, + const ecs_json_ser_ctx_t *ser_ctx) +{ + if (!ecs_map_is_init(&ser_ctx->serialized)) { + return true; + } + + if (ecs_map_get(&ser_ctx->serialized, entity) != NULL) { + return false; + } + + if (it->query) { + ecs_iter_t temp_it; + bool result = ecs_query_has(it->query, entity, &temp_it); + if (result) { + ecs_iter_fini(&temp_it); + } else { + return false; + } + } + + return true; +} + +void flecs_json_mark_serialized( + ecs_entity_t entity, + ecs_json_ser_ctx_t *ser_ctx) +{ + if (ecs_map_is_init(&ser_ctx->serialized)) { + ecs_map_ensure(&ser_ctx->serialized, entity); + } +} +#endif + +/** + * @file addons/json/serialize_entity.c + * @brief Serialize single entity. + */ + +/** + * @file addons/meta/meta.h + * @brief Private functions for meta addon. + */ + +#ifndef FLECS_META_PRIVATE_H +#define FLECS_META_PRIVATE_H + + +#ifdef FLECS_META + +void flecs_meta_type_serializer_init( + ecs_iter_t *it); + +void flecs_type_serializer_dtor( + EcsTypeSerializer *ptr); + +ecs_meta_op_kind_t flecs_meta_primitive_to_op_kind( + ecs_primitive_kind_t kind); + +bool flecs_unit_validate( + ecs_world_t *world, + ecs_entity_t t, + EcsUnit *data); + +void flecs_meta_import_definitions( + ecs_world_t *world); + +int flecs_expr_ser_primitive( + const ecs_world_t *world, + ecs_primitive_kind_t kind, + const void *base, + ecs_strbuf_t *str, + bool is_expr); + +void flecs_rtt_init_default_hooks( + ecs_iter_t *it); + +const char* flecs_type_kind_str( + ecs_type_kind_t kind); + +const char* flecs_primitive_type_kind_str( + ecs_primitive_kind_t kind); + +const char* flecs_meta_op_kind_str( + ecs_meta_op_kind_t kind); + +#endif + +#endif + + +#ifdef FLECS_JSON + +int flecs_entity_to_json_buf( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx) +{ + ecs_record_t *r = ecs_record_find(world, entity); + if (!r || !r->table) { + flecs_json_object_push(buf); + flecs_json_member(buf, "name"); + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendch(buf, '#'); + ecs_strbuf_appendint(buf, (uint32_t)entity); + ecs_strbuf_appendch(buf, '"'); + flecs_json_object_pop(buf); + return 0; + } + + /* Create iterator that's populated just with entity */ + int32_t row = ECS_RECORD_TO_ROW(r->row); + ecs_iter_t it = { + .world = ECS_CONST_CAST(ecs_world_t*, world), + .real_world = ECS_CONST_CAST(ecs_world_t*, world), + .table = r->table, + .offset = row, + .count = 1, + .entities = &ecs_table_entities(r->table)[row], + .field_count = 0 + }; + + if (flecs_json_serialize_iter_result( + world, &it, buf, desc, ser_ctx)) + { + return -1; + } + + return 0; +} + +int ecs_entity_to_json_buf( + ecs_world_t *stage, + ecs_entity_t entity, + ecs_strbuf_t *buf, + const ecs_entity_to_json_desc_t *desc) +{ + const ecs_world_t *world = ecs_get_world(stage); + + if (!entity || !ecs_is_valid(world, entity)) { + return -1; + } + + /* Cache component record for flecs.doc ids */ + ecs_json_ser_ctx_t ser_ctx; + ecs_os_zeromem(&ser_ctx); +#ifdef FLECS_DOC + ser_ctx.cr_doc_name = flecs_components_get(world, + ecs_pair_t(EcsDocDescription, EcsName)); + ser_ctx.cr_doc_color = flecs_components_get(world, + ecs_pair_t(EcsDocDescription, EcsDocColor)); +#endif + + /* Initialize iterator parameters */ + ecs_iter_to_json_desc_t iter_desc = { + .serialize_table = true, + .serialize_entity_ids = desc ? desc->serialize_entity_id : false, + .serialize_values = desc ? desc->serialize_values : true, + .serialize_builtin = desc ? desc->serialize_builtin : false, + .serialize_doc = desc ? desc->serialize_doc : false, + .serialize_matches = desc ? desc->serialize_matches : false, + .serialize_refs = desc ? desc->serialize_refs : 0, + .serialize_alerts = desc ? desc->serialize_alerts : false, + .serialize_full_paths = desc ? desc->serialize_full_paths : true, + .serialize_inherited = desc ? desc->serialize_inherited : false, + .serialize_type_info = desc ? desc->serialize_type_info : false, + .component_filter = desc ? desc->component_filter : NULL, + }; + + int result = flecs_entity_to_json_buf( + world, entity, buf, &iter_desc, &ser_ctx); + + return result; +} + +char* ecs_entity_to_json( + ecs_world_t *world, + ecs_entity_t entity, + const ecs_entity_to_json_desc_t *desc) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { + ecs_strbuf_reset(&buf); + return NULL; + } + + return ecs_strbuf_get(&buf); +} + +#endif + +/** + * @file addons/json/serialize_field_info.c + * @brief Serialize query field information to JSON. + */ + + +#ifdef FLECS_JSON + +static +bool flecs_json_serialize_get_field_ctx( + const ecs_world_t *world, + const ecs_iter_t *it, + int32_t f, + ecs_json_ser_ctx_t *ser_ctx, + const ecs_iter_to_json_desc_t *desc) +{ + ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; + if (it->query) { + return flecs_json_serialize_get_value_ctx( + world, it->query->ids[f], value_ctx, desc); + } else if (it->ids[f]) { + return flecs_json_serialize_get_value_ctx( + world, it->ids[f], value_ctx, desc); + } else { + return false; + } +} + + +void flecs_json_serialize_field( + const ecs_world_t *world, + const ecs_iter_t *it, + const ecs_query_t *q, + int field, + ecs_strbuf_t *buf, + ecs_json_ser_ctx_t *ctx) +{ + flecs_json_object_push(buf); + flecs_json_memberl(buf, "id"); + + flecs_json_serialize_get_field_ctx(world, it, field, ctx, NULL); + ecs_json_value_ser_ctx_t *value_ctx = &ctx->value_ctx[field]; + + if (value_ctx->id_label) { + flecs_json_string(buf, value_ctx->id_label); + + const ecs_term_t *term = &q->terms[0]; + int t; + for (t = 0; t < q->term_count; t ++) { + if (q->terms[t].field_index == field) { + term = &q->terms[t]; + break; + } + } + + if (term->oper != EcsNot) { + if (term->oper == EcsOptional) { + flecs_json_memberl(buf, "optional"); + flecs_json_bool(buf, true); + } + + if (ECS_IS_PAIR(term->id)) { + if ((term->first.id & EcsIsEntity) && ECS_TERM_REF_ID(&term->first)) { + if (ecs_has_id(world, ECS_TERM_REF_ID(&term->first), EcsExclusive)) { + flecs_json_memberl(buf, "exclusive"); + flecs_json_bool(buf, true); + } + } + } + + if (term->inout != EcsInOutNone) { + if (value_ctx->type) { + flecs_json_memberl(buf, "type"); + flecs_json_label(buf, world, value_ctx->type); + + const char *symbol = ecs_get_symbol(world, value_ctx->type); + if (symbol) { + flecs_json_memberl(buf, "symbol"); + flecs_json_string(buf, symbol); + } + } + + if (value_ctx->ser) { + flecs_json_memberl(buf, "schema"); + ecs_type_info_to_json_buf(world, value_ctx->type, buf); + } + } + } else { + flecs_json_memberl(buf, "not"); + flecs_json_bool(buf, true); + } + } else { + ecs_strbuf_appendlit(buf, "0"); + } + + flecs_json_object_pop(buf); +} + +#endif + +/** + * @file addons/json/serialize_iter.c + * @brief Serialize iterator to JSON. + */ + + +#ifdef FLECS_JSON + +static +void flecs_json_serialize_id_str( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf) +{ + ecs_strbuf_appendch(buf, '"'); + if (ECS_IS_PAIR(id)) { + ecs_entity_t first = ecs_pair_first(world, id); + ecs_entity_t second = ecs_pair_first(world, id); + ecs_strbuf_appendch(buf, '('); + ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf, true); + ecs_strbuf_appendch(buf, ','); + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); + ecs_strbuf_appendch(buf, ')'); + } else { + ecs_get_path_w_sep_buf( + world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); + } + ecs_strbuf_appendch(buf, '"'); +} + +static +void flecs_json_serialize_type_info( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + flecs_json_memberl(buf, "type_info"); + flecs_json_object_push(buf); + + int32_t field_count = it->field_count; + if (!field_count) { + goto done; + } + + if (it->flags & EcsIterNoData) { + goto done; + } + + for (int i = 0; i < field_count; i ++) { + flecs_json_next(buf); + ecs_entity_t typeid = 0; + if (it->query->terms[i].inout != EcsInOutNone) { + typeid = ecs_get_typeid(world, it->query->terms[i].id); + } + if (typeid) { + flecs_json_serialize_id_str(world, typeid, buf); + ecs_strbuf_appendch(buf, ':'); + ecs_type_info_to_json_buf(world, typeid, buf); + } else { + flecs_json_serialize_id_str(world, it->query->terms[i].id, buf); + ecs_strbuf_appendlit(buf, ":0"); + } + } + +done: + flecs_json_object_pop(buf); +} + +static +void flecs_json_serialize_field_info( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + ecs_json_ser_ctx_t *ctx) +{ + int32_t field_count = it->field_count; + if (!field_count || !it->query) { + return; + } + + const ecs_query_t *q = it->query; + + flecs_json_memberl(buf, "field_info"); + flecs_json_array_push(buf); + + int f; + for (f = 0; f < field_count; f ++) { + flecs_json_next(buf); + flecs_json_serialize_field(world, it, q, f, buf, ctx); + } + + flecs_json_array_pop(buf); +} + +static +void flecs_json_serialize_query_info( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + if (!it->query) { + return; + } + + const ecs_query_t *q = it->query; + flecs_json_memberl(buf, "query_info"); + flecs_json_serialize_query(world, q, buf); +} + +static +void flecs_json_serialize_query_plan( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + (void)world; + (void)buf; + (void)desc; + + if (!desc->query) { + return; + } + + const ecs_query_t *q = desc->query; + flecs_poly_assert(q, ecs_query_t); + const ecs_query_t *cq = ecs_query_get_cache_query(q); + + flecs_json_memberl(buf, "query_plan"); + + bool prev_color = ecs_log_enable_colors(true); + char *plan = ecs_query_plan(q); + char *cache_plan = NULL; + if (cq) { + flecs_poly_assert(cq, ecs_query_t); + cache_plan = ecs_query_plan(cq); + } + + ecs_strbuf_t plan_buf = ECS_STRBUF_INIT; + if (plan) { + ecs_strbuf_appendstr(&plan_buf, plan); + } else { + if (q->term_count) { + ecs_strbuf_append(&plan_buf, " %sOptimized out (trivial query)\n", ECS_GREY); + } + } + + if (cq) { + ecs_strbuf_appendstr(&plan_buf, "\n\n"); + ecs_strbuf_appendstr(&plan_buf, " Cache plan\n"); + ecs_strbuf_appendstr(&plan_buf, " ---\n"); + + if (cache_plan) { + ecs_strbuf_appendstr(&plan_buf, cache_plan); + } else { + ecs_strbuf_append(&plan_buf, " %sOptimized out (trivial query)\n", ECS_GREY); + } + } + + char *plan_str = ecs_strbuf_get(&plan_buf); + if (plan_str) { + flecs_json_string_escape(buf, plan_str); + ecs_os_free(plan_str); + } else { + flecs_json_null(buf); + } + + ecs_os_free(plan); + ecs_os_free(cache_plan); + ecs_log_enable_colors(prev_color); +} + +static +void flecs_json_serialize_query_profile( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_iter_t *it, + const ecs_iter_to_json_desc_t *desc) +{ + if (!desc->query) { + return; + } + + ecs_time_t t = {0}; + int32_t result_count = 0, entity_count = 0, i, sample_count = 100; + ecs_size_t component_bytes = 0, shared_component_bytes = 0; + double eval_time = 0, eval_min = 0, eval_max = 0; + ecs_time_measure(&t); + + for (i = 0; i < sample_count; i ++) { + result_count = 0; + entity_count = 0; + component_bytes = 0; + shared_component_bytes = 0; + + ecs_iter_t qit = ecs_query_iter(world, desc->query); + while (ecs_query_next(&qit)) { + result_count ++; + entity_count += qit.count; + + int8_t f, field_count = qit.field_count; + for (f = 0; f < field_count; f ++) { + size_t size = ecs_field_size(&qit, f); + if (ecs_field_is_set(&qit, f) && size) { + if (ecs_field_is_self(&qit, f)) { + component_bytes += + flecs_uto(ecs_size_t, size) * qit.count; + } else { + shared_component_bytes += flecs_uto(ecs_size_t, size); + } + } + } + } + + double time_measure = ecs_time_measure(&t); + if (!i) { + eval_min = time_measure; + } else if (time_measure < eval_min) { + eval_min = time_measure; + } + + if (time_measure > eval_max) { + eval_max = time_measure; + } + + eval_time += time_measure; + + /* Don't profile for too long */ + if (eval_time > 0.001) { + i ++; + break; + } + } + + eval_time /= i; + + flecs_json_memberl(buf, "query_profile"); + flecs_json_object_push(buf); + if (it->query) { + /* Correct for profiler */ + ECS_CONST_CAST(ecs_query_t*, it->query)->eval_count -= i; + flecs_json_memberl(buf, "eval_count"); + flecs_json_number(buf, it->query->eval_count); + } + flecs_json_memberl(buf, "result_count"); + flecs_json_number(buf, result_count); + flecs_json_memberl(buf, "entity_count"); + flecs_json_number(buf, entity_count); + + flecs_json_memberl(buf, "eval_time_avg_us"); + flecs_json_number(buf, eval_time * 1000.0 * 1000.0); + flecs_json_memberl(buf, "eval_time_min_us"); + flecs_json_number(buf, eval_min * 1000.0 * 1000.0); + flecs_json_memberl(buf, "eval_time_max_us"); + flecs_json_number(buf, eval_max * 1000.0 * 1000.0); + + flecs_json_memberl(buf, "component_bytes"); + flecs_json_number(buf, component_bytes); + flecs_json_memberl(buf, "shared_component_bytes"); + flecs_json_number(buf, shared_component_bytes); + + flecs_json_object_pop(buf); +} + +static +void flecs_iter_free_ser_ctx( + ecs_iter_t *it, + ecs_json_ser_ctx_t *ser_ctx) +{ + int32_t f, field_count = it->field_count; + for (f = 0; f < field_count; f ++) { + ecs_os_free(ser_ctx->value_ctx[f].id_label); + } +} + +int ecs_iter_to_json_buf( + ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + ecs_world_t *world = it->real_world; + + /* Cache component record for flecs.doc ids */ + ecs_json_ser_ctx_t ser_ctx; + ecs_os_zeromem(&ser_ctx); +#ifdef FLECS_DOC + ser_ctx.cr_doc_name = flecs_components_get(world, + ecs_pair_t(EcsDocDescription, EcsName)); + ser_ctx.cr_doc_color = flecs_components_get(world, + ecs_pair_t(EcsDocDescription, EcsDocColor)); +#endif + + flecs_json_object_push(buf); + + /* Serialize type info if enabled */ + if (desc && desc->serialize_type_info) { + flecs_json_serialize_type_info(world, it, buf); + } + + /* Serialize field info if enabled */ + if (desc && desc->serialize_field_info) { + flecs_json_serialize_field_info(world, it, buf, &ser_ctx); + } + + /* Serialize query info if enabled */ + if (desc && desc->serialize_query_info) { + flecs_json_serialize_query_info(world, it, buf); + } + + /* Serialize query plan if enabled */ + if (desc && desc->serialize_query_plan) { + flecs_json_serialize_query_plan(world, buf, desc); + } + + /* Profile query */ + if (desc && desc->serialize_query_profile) { + flecs_json_serialize_query_profile(world, buf, it, desc); + } + + /* Serialize results */ + if (!desc || !desc->dont_serialize_results) { + flecs_json_memberl(buf, "results"); + flecs_json_array_push(buf); + + /* If serializing entire table, don't bother letting the iterator populate + * data fields as we'll be iterating all columns. */ + if (desc && desc->serialize_table) { + ECS_BIT_SET(it->flags, EcsIterNoData); + } + + /* Keep track of serialized entities. This allows entities to be + * serialized depth first, which avoids weird side effects from children + * being created before parents. */ + if (desc && desc->serialize_parents_before_children) { + ecs_map_init(&ser_ctx.serialized, &world->allocator); + } + + ecs_iter_next_action_t next = it->next; + while (next(it)) { + if (flecs_json_serialize_iter_result(world, it, buf, desc, &ser_ctx)) { + ecs_strbuf_reset(buf); + flecs_iter_free_ser_ctx(it, &ser_ctx); + ecs_iter_fini(it); + ecs_map_fini(&ser_ctx.serialized); + return -1; + } + } + + if (desc && desc->serialize_parents_before_children) { + ecs_map_fini(&ser_ctx.serialized); + } + + flecs_json_array_pop(buf); + } else { + ecs_iter_fini(it); + } + + flecs_iter_free_ser_ctx(it, &ser_ctx); + + flecs_json_object_pop(buf); + + return 0; +} + +char* ecs_iter_to_json( + ecs_iter_t *it, + const ecs_iter_to_json_desc_t *desc) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (ecs_iter_to_json_buf(it, &buf, desc)) { + ecs_strbuf_reset(&buf); + return NULL; + } + + return ecs_strbuf_get(&buf); +} + +#endif + +/** + * @file addons/json/serialize_iter_rows.c + * @brief Serialize (component) values to JSON strings. + */ + + +#ifdef FLECS_JSON + +static +bool flecs_json_skip_variable( + const char *name) +{ + if (!name || name[0] == '_' || !ecs_os_strcmp(name, "this")) { + return true; + } else { + return false; + } +} + +bool flecs_json_serialize_vars( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + if (!it->query) { + return 0; + } + + char **variable_names = it->query->vars; + int32_t var_count = it->query->var_count; + int32_t actual_count = 0; + + for (int i = 1; i < var_count; i ++) { + const char *var_name = variable_names[i]; + if (flecs_json_skip_variable(var_name)) continue; + + ecs_entity_t var = ecs_iter_get_vars(it)[i].entity; + if (!var) { + /* Can't happen, but not the place of the serializer to complain */ + continue; + } + + if (!actual_count) { + flecs_json_memberl(buf, "vars"); + flecs_json_object_push(buf); + actual_count ++; + } + + flecs_json_member(buf, var_name); + flecs_json_path_or_label(buf, world, var, + desc ? desc->serialize_full_paths : true); + } + + if (actual_count) { + flecs_json_object_pop(buf); + } + + return actual_count != 0; +} + +int flecs_json_serialize_matches( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity) +{ + flecs_json_memberl(buf, "matches"); + flecs_json_array_push(buf); + + ecs_component_record_t *cr = flecs_components_get(world, + ecs_pair_t(EcsPoly, EcsQuery)); + + if (cr) { + ecs_table_cache_iter_t it; + if (cr && flecs_table_cache_iter((ecs_table_cache_t*)cr, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); + + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_query_t *q = queries[i].poly; + if (!q) { + continue; + } + + ecs_assert(flecs_poly_is(q, ecs_query_t), + ECS_INTERNAL_ERROR, NULL); + + if (!(q->flags & EcsQueryMatchThis)) { + continue; + } + + ecs_iter_t qit = ecs_query_iter(world, q); + if (!ecs_iter_get_vars(&qit)) { + ecs_iter_fini(&qit); + continue; + } + + ecs_iter_set_var(&qit, 0, entity); + if (ecs_iter_is_true(&qit)) { + flecs_json_next(buf); + flecs_json_path(buf, world, entities[i]); + } + } + } + } + } + + flecs_json_array_pop(buf); + + return 0; +} + +static +int flecs_json_serialize_refs_cr( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_component_record_t *cr) +{ + char *id_str = ecs_id_str(world, ecs_pair_first(world, cr->id)); + + flecs_json_member(buf, id_str); + ecs_os_free(id_str); + + flecs_json_array_push(buf); + + ecs_table_cache_iter_t it; + if (cr && flecs_table_cache_all_iter((ecs_table_cache_t*)cr, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + flecs_json_next(buf); + flecs_json_path(buf, world, e); + } + } + } + + flecs_json_array_pop(buf); + + return 0; +} + +int flecs_json_serialize_refs( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity, + ecs_entity_t relationship) +{ + flecs_json_memberl(buf, "refs"); + flecs_json_object_push(buf); + + ecs_component_record_t *cr = flecs_components_get(world, + ecs_pair(relationship, entity)); + + if (cr) { + if (relationship == EcsWildcard) { + ecs_component_record_t *cur = cr; + while ((cur = flecs_component_second_next(cur))) { + flecs_json_serialize_refs_cr(world, buf, cur); + } + } else { + flecs_json_serialize_refs_cr(world, buf, cr); + } + } + + flecs_json_object_pop(buf); + + return 0; +} + +#ifdef FLECS_ALERTS +static +int flecs_json_serialize_entity_alerts( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity, + const EcsAlertsActive *alerts, + bool self) +{ + ecs_assert(alerts != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_iter_t it = ecs_map_iter(&alerts->alerts); + while (ecs_map_next(&it)) { + flecs_json_next(buf); + flecs_json_object_push(buf); + ecs_entity_t ai = ecs_map_value(&it); + char *alert_name = ecs_get_path(world, ai); + flecs_json_memberl(buf, "alert"); + flecs_json_string(buf, alert_name); + ecs_os_free(alert_name); + + ecs_entity_t severity_id = ecs_get_target( + world, ai, ecs_id(EcsAlert), 0); + const char *severity = ecs_get_name(world, severity_id); + + const EcsAlertInstance *alert = ecs_get( + world, ai, EcsAlertInstance); + if (alert) { + if (alert->message) { + flecs_json_memberl(buf, "message"); + flecs_json_string(buf, alert->message); + } + flecs_json_memberl(buf, "severity"); + flecs_json_string(buf, severity); + + if (!self) { + char *path = ecs_get_path(world, entity); + flecs_json_memberl(buf, "path"); + flecs_json_string(buf, path); + ecs_os_free(path); + } + } + flecs_json_object_pop(buf); + } + + return 0; +} + +static +int flecs_json_serialize_children_alerts( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity) +{ + ecs_query_t *q = ecs_query(ECS_CONST_CAST(ecs_world_t*, world), { + .terms = {{ .id = ecs_pair(EcsChildOf, entity) }} + }); + + ecs_iter_t it = ecs_query_iter(world, q); + while (ecs_query_next(&it)) { + EcsAlertsActive *alerts = ecs_table_get_id( + world, it.table, ecs_id(EcsAlertsActive), it.offset); + + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_entity_t child = it.entities[i]; + if (alerts) { + if (flecs_json_serialize_entity_alerts( + world, buf, child, &alerts[i], false)) + { + goto error; + } + } + + ecs_record_t *r = flecs_entities_get(world, it.entities[i]); + if (r->row & EcsEntityIsTraversable) { + if (flecs_json_serialize_children_alerts( + world, buf, child)) + { + goto error; + } + } + } + } + + ecs_query_fini(q); + + return 0; +error: + return -1; +} +#endif + +int flecs_json_serialize_alerts( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity) +{ + (void)world; + (void)buf; + (void)entity; + +#ifdef FLECS_ALERTS + if (!ecs_id(EcsAlertsActive)) { + return 0; /* Alert module not imported */ + } + + flecs_json_memberl(buf, "alerts"); + flecs_json_array_push(buf); + const EcsAlertsActive *alerts = ecs_get(world, entity, EcsAlertsActive); + if (alerts) { + flecs_json_serialize_entity_alerts(world, buf, entity, alerts, true); + } + flecs_json_serialize_children_alerts(world, buf, entity); + flecs_json_array_pop(buf); +#endif + return 0; +} + +bool flecs_json_serialize_get_value_ctx( + const ecs_world_t *world, + ecs_id_t id, + ecs_json_value_ser_ctx_t *ctx, + const ecs_iter_to_json_desc_t *desc) +{ + if (!id) { + return false; + } + + if (!ctx->initialized) { + ctx->initialized = true; + + ecs_strbuf_t idlbl = ECS_STRBUF_INIT; + flecs_json_id_member(&idlbl, world, id, + desc ? desc->serialize_full_paths : true); + ctx->id_label = ecs_strbuf_get(&idlbl); + + ecs_entity_t type = ecs_get_typeid(world, id); + if (!type) { + return false; + } + + ctx->type = type; + ctx->ser = ecs_get(world, type, EcsTypeSerializer); + if (!ctx->ser) { + return false; + } + + return true; + } else { + return ctx->ser != NULL; + } +} + +bool flecs_json_serialize_iter_this( + const ecs_iter_t *it, + const char *parent_path, + const ecs_json_this_data_t *this_data, + int32_t row, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx) +{ + ecs_assert(row < it->count, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = it->real_world; + + ecs_assert(this_data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(this_data->ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t e = this_data->ids[row]; + + /* Skip entity if it already has been serialized */ + if (!flecs_json_should_serialize(e, it, ser_ctx)) { + return false; + } + + flecs_json_mark_serialized(e, ser_ctx); + + /* Serialize parent first. Only necessary if this is a table with Parent + * component, as this will have been done at the table level for fragmenting + * relationships. */ + ecs_entity_t parent = 0; + if (this_data->parents) { + parent = this_data->parents[row].value; + ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); + if (ecs_map_is_init(&ser_ctx->serialized)) { + flecs_entity_to_json_buf(world, parent, buf, desc, ser_ctx); + } + } + + flecs_json_next(buf); + flecs_json_object_push(buf); + + if (parent_path) { + flecs_json_memberl(buf, "parent"); + flecs_json_string(buf, parent_path); + } else if (parent) { + flecs_json_memberl(buf, "parent"); + char *path = ecs_get_path_w_sep(world, 0, parent, ".", ""); + flecs_json_string(buf, path); + ecs_os_free(path); + } + + flecs_json_memberl(buf, "name"); + if (this_data->names) { + if (this_data->table) { + flecs_json_string(buf, this_data->names[row].value); + } + } else { + bool name_set = false; + if (!this_data->table) { + const char *name = ecs_get_name(world, e); + if (name) { + flecs_json_string(buf, name); + name_set = true; + } + } + + if (!name_set) { + ecs_strbuf_appendlit(buf, "\"#"); + ecs_strbuf_appendint(buf, flecs_uto(int64_t, + (uint32_t)it->entities[row])); + ecs_strbuf_appendlit(buf, "\""); + } + } + + if (desc && desc->serialize_entity_ids) { + flecs_json_memberl(buf, "id"); + flecs_json_u32(buf, (uint32_t)e); + if (e != (uint32_t)e) { + flecs_json_memberl(buf, "version"); + flecs_json_u32(buf, (uint32_t)(e >> 32)); + } + } + +#ifdef FLECS_DOC + if (desc && desc->serialize_doc) { + flecs_json_memberl(buf, "doc"); + flecs_json_object_push(buf); + if (this_data->label) { + flecs_json_memberl(buf, "label"); + flecs_json_string_escape(buf, this_data->label[row].value); + } else { + flecs_json_memberl(buf, "label"); + if (this_data->names) { + flecs_json_string(buf, this_data->names[row].value); + } else { + ecs_strbuf_appendlit(buf, "\"#"); + ecs_strbuf_appendint(buf, flecs_uto(int64_t, + (uint32_t)it->entities[row])); + ecs_strbuf_appendlit(buf, "\""); + } + } + + if (this_data->brief) { + flecs_json_memberl(buf, "brief"); + flecs_json_string_escape(buf, this_data->brief[row].value); + } + + if (this_data->detail) { + flecs_json_memberl(buf, "detail"); + flecs_json_string_escape(buf, this_data->detail[row].value); + } + + if (this_data->color) { + flecs_json_memberl(buf, "color"); + flecs_json_string_escape(buf, this_data->color[row].value); + } + + if (this_data->link) { + flecs_json_memberl(buf, "link"); + flecs_json_string_escape(buf, this_data->link[row].value); + } + + flecs_json_object_pop(buf); + } +#endif + + if (this_data->has_alerts) { + flecs_json_memberl(buf, "has_alerts"); + flecs_json_true(buf); + } + + return true; +} + +int flecs_json_serialize_iter_result( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx) +{ + char *parent_path = NULL; + ecs_json_this_data_t this_data = { .table = it->table }; + + int32_t count = it->count; + bool has_this = true; + if (!count) { + count = 1; /* Query without this variable */ + has_this = false; + } else { + ecs_table_t *table = it->table; + if (table) { + this_data.ids = &ecs_table_entities(table)[it->offset]; + + /* Get path to parent once for entire table */ + if (table->flags & EcsTableHasChildOf) { + const ecs_table_record_t *tr = flecs_component_get_table( + world->cr_childof_wildcard, table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t parent = ecs_pair_second( + world, table->type.array[tr->index]); + parent_path = ecs_get_path_w_sep(world, 0, parent, ".", ""); + + /* If matching a query check if we need to serialize the parent + * of the table before its children. Don't do this if query + * isn't set, as that means that we're serializing a single + * entity. */ + if (ecs_map_is_init(&ser_ctx->serialized)) { + if (flecs_json_should_serialize(parent, it, ser_ctx)) { + if (flecs_entity_to_json_buf( + world, parent, buf, desc, ser_ctx)) + { + return -1; + } + + flecs_json_mark_serialized(parent, ser_ctx); + } + } + } + + /* Fetch name column once vs. calling ecs_get_name for each row */ + if (table->flags & EcsTableHasName) { + this_data.names = ecs_table_get_id(it->world, it->table, + ecs_pair_t(EcsIdentifier, EcsName), it->offset); + } + + /* Same for Parent column */ + if (table->flags & EcsTableHasParent) { + this_data.parents = ecs_table_get_id(it->world, it->table, + ecs_id(EcsParent), it->offset); + } + + /* Get entity labels */ +#ifdef FLECS_DOC + if (desc && desc->serialize_doc) { + this_data.label = ecs_table_get_id(it->world, it->table, + ecs_pair_t(EcsDocDescription, EcsName), it->offset); + this_data.brief = ecs_table_get_id(it->world, it->table, + ecs_pair_t(EcsDocDescription, EcsDocBrief), it->offset); + this_data.detail = ecs_table_get_id(it->world, it->table, + ecs_pair_t(EcsDocDescription, EcsDocDetail), it->offset); + this_data.color = ecs_table_get_id(it->world, it->table, + ecs_pair_t(EcsDocDescription, EcsDocColor), it->offset); + this_data.link = ecs_table_get_id(it->world, it->table, + ecs_pair_t(EcsDocDescription, EcsDocLink), it->offset); + } +#endif + +#ifdef FLECS_ALERTS + if (it->table && (ecs_id(EcsAlertsActive) != 0)) { + /* Only add field if alerts addon is imported */ + if (ecs_table_has_id(world, table, ecs_id(EcsAlertsActive))) { + this_data.has_alerts = true; + } + } +#endif + } else { + this_data.ids = it->entities; + } + } + + if (desc && desc->serialize_table) { + if (flecs_json_serialize_iter_result_table(world, it, buf, + desc, ser_ctx, count, has_this, parent_path, &this_data)) + { + goto error; + } + } else { + if (flecs_json_serialize_iter_result_query(world, it, buf, ser_ctx, + desc, count, has_this, parent_path, &this_data)) + { + goto error; + } + } + + ecs_os_free(parent_path); + return 0; +error: + ecs_os_free(parent_path); + return -1; +} + +#endif + +/** + * @file addons/json/serialize_iter_result_query.c + * @brief Serialize matched query data of result. + */ + + +#ifdef FLECS_JSON + +static +bool flecs_json_serialize_iter_result_is_set( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + if (!(it->flags & EcsIterHasCondSet)) { + return false; + } + + flecs_json_memberl(buf, "is_set"); + flecs_json_array_push(buf); + + int8_t i, count = it->field_count; + for (i = 0; i < count; i ++) { + ecs_strbuf_list_next(buf); + if (ecs_field_is_set(it, i)) { + flecs_json_true(buf); + } else { + flecs_json_false(buf); + } + } + + flecs_json_array_pop(buf); + + return true; +} + +static +bool flecs_json_serialize_iter_result_ids( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + const ecs_query_t *q = it->query; + if (!q) { + return false; + } + + ecs_world_t *world = it->world; + int16_t f, field_count = flecs_ito(int16_t, it->field_count); + uint32_t field_mask = (uint32_t)((1llu << field_count) - 1); + + if (q->static_id_fields == field_mask) { + /* All matched ids are static, nothing to serialize */ + return false; + } + + flecs_json_memberl(buf, "ids"); + flecs_json_array_push(buf); + + for (f = 0; f < field_count; f ++) { + ecs_termset_t field_bit = (ecs_termset_t)(1u << f); + + if (!(it->set_fields & field_bit)) { + /* Don't serialize ids for fields that aren't set */ + ecs_strbuf_list_appendlit(buf, "0"); + continue; + } + + if (q->static_id_fields & field_bit) { + /* Only add non-static ids to save bandwidth/performance */ + ecs_strbuf_list_appendlit(buf, "0"); + continue; + } + + flecs_json_next(buf); + flecs_json_id(buf, world, it->ids[f]); + } + + flecs_json_array_pop(buf); + + return true; +} + +static +bool flecs_json_serialize_iter_result_sources( + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + const ecs_query_t *q = it->query; + if (!q) { + return false; + } + + ecs_world_t *world = it->world; + int32_t f, field_count = it->field_count; + + for (f = 0; f < field_count; f ++) { + if (it->sources[f]) { + break; + } + } + + if (f == field_count) { + /* All fields are matched on $this */ + return false; + } + + flecs_json_memberl(buf, "sources"); + flecs_json_array_push(buf); + + for (f = 0; f < field_count; f ++) { + ecs_termset_t field_bit = (ecs_termset_t)(1u << f); + + if (!(it->set_fields & field_bit)) { + /* Don't serialize source for fields that aren't set */ + ecs_strbuf_list_appendlit(buf, "0"); + continue; + } + + if (!it->sources[f]) { + /* Don't serialize source for fields that have $this source */ + ecs_strbuf_list_appendlit(buf, "0"); + continue; + } + + flecs_json_next(buf); + flecs_json_path(buf, world, it->sources[f]); + } + + flecs_json_array_pop(buf); + + return true; +} + +static +bool flecs_json_serialize_common_for_table( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + ecs_strbuf_list_push(buf, "", ","); + flecs_json_serialize_vars(world, it, buf, desc); + + bool result = false; + if (!desc || desc->serialize_fields) { + ecs_strbuf_list_appendlit(buf, "\"fields\":"); + flecs_json_object_push(buf); + result |= flecs_json_serialize_iter_result_is_set(it, buf); + result |= flecs_json_serialize_iter_result_ids(it, buf); + result |= flecs_json_serialize_iter_result_sources(it, buf); + } + + ecs_strbuf_list_pop(buf, ""); + return result; +} + +static +int flecs_json_serialize_iter_result_field_values( + const ecs_world_t *world, + const ecs_iter_t *it, + int32_t i, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx) +{ + int8_t f, field_count = it->field_count; + if (!field_count) { + return 0; + } + + ecs_strbuf_appendlit(buf, "\"values\":"); + flecs_json_array_push(buf); + + ecs_termset_t fields = it->set_fields; + if (it->query) { + fields &= it->query->data_fields; + } + + ecs_termset_t row_fields = it->query ? it->query->row_fields : 0; + + for (f = 0; f < field_count; f ++) { + ecs_termset_t field_bit = (ecs_termset_t)(1u << f); + if (!(fields & field_bit)) { + ecs_strbuf_list_appendlit(buf, "0"); + continue; + } + + ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; + if (!flecs_json_serialize_get_value_ctx( + world, it->ids[f], value_ctx, desc)) + { + ecs_strbuf_list_appendlit(buf, "0"); + continue; + } + + void *ptr; + if (row_fields & field_bit) { + ptr = ecs_field_at_w_size(it, 0, f, i); + } else { + ecs_size_t size = it->sizes[f]; + ptr = ecs_field_w_size(it, flecs_itosize(size), f); + + if (!ptr) { + ecs_strbuf_list_appendlit(buf, "0"); + continue; + } + + if (!it->sources[f]) { + ptr = ECS_ELEM(ptr, size, i); + } + } + + flecs_json_next(buf); + if (flecs_json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { + return -1; + } + } + + flecs_json_array_pop(buf); + + return 0; +} + +int flecs_json_serialize_iter_result_query( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + ecs_json_ser_ctx_t *ser_ctx, + const ecs_iter_to_json_desc_t *desc, + int32_t count, + bool has_this, + const char *parent_path, + const ecs_json_this_data_t *this_data) +{ + /* Serialize tags, pairs, vars once, since they're the same for each row */ + ecs_strbuf_t common_data_buf = ECS_STRBUF_INIT; + bool common_field_data = flecs_json_serialize_common_for_table( + world, it, &common_data_buf, desc); + int32_t common_data_len = ecs_strbuf_written(&common_data_buf); + char *common_data = NULL; + if (!common_data_len) { + ecs_strbuf_reset(&common_data_buf); + } else { + common_data = ecs_strbuf_get(&common_data_buf); + } + + int32_t i; + for (i = 0; i < count; i ++) { + if (has_this) { + if (!flecs_json_serialize_iter_this( + it, parent_path, this_data, i, buf, desc, ser_ctx)) + { + continue; + } + } else { + flecs_json_next(buf); + flecs_json_object_push(buf); + } + + if (common_data) { + ecs_strbuf_list_appendstrn(buf, + common_data, common_data_len); + } + + if (!desc || desc->serialize_fields) { + bool has_values = !desc || desc->serialize_values; + if (it->flags & EcsIterNoData || !it->field_count) { + has_values = false; + } + + const ecs_query_t *q = it->query; + if (q && !q->data_fields) { + has_values = false; + } + + if (has_values) { + if (common_field_data) { + flecs_json_next(buf); + } + + if (flecs_json_serialize_iter_result_field_values( + world, it, i, buf, desc, ser_ctx)) + { + ecs_os_free(common_data); + return -1; + } + } + + ecs_strbuf_appendstr(buf, "}"); // "fields": { + } + + flecs_json_object_pop(buf); + } + + ecs_os_free(common_data); + + return 0; +} + +#endif + +/** + * @file addons/json/serialize_iter_result_table.c + * @brief Serialize all components of matched entity. + */ + + +#ifdef FLECS_JSON + +#define FLECS_JSON_MAX_TABLE_COMPONENTS (256) + +bool flecs_json_is_builtin( + ecs_id_t id) +{ + if (ECS_IS_PAIR(id)) { + if (ECS_PAIR_FIRST(id) == EcsChildOf) { + return true; + } + if (id == ecs_pair_t(EcsIdentifier, EcsName)) { + return true; + } + } + return false; +} + +static +bool flecs_json_serialize_table_type_info( + const ecs_world_t *world, + ecs_table_t *table, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + flecs_json_memberl(buf, "type_info"); + flecs_json_object_push(buf); + + int32_t i, type_count = table->type.count; + for (i = 0; i < type_count; i ++) { + const ecs_table_record_t *tr = &table->_->records[i]; + ecs_component_record_t *cr = tr->hdr.cr; + ecs_id_t id = table->type.array[i]; + if (!(cr->flags & EcsIdSparse) && + (!table->column_map || (table->column_map[i] == -1))) + { + continue; + } + + if (!desc || !desc->serialize_builtin) { + if (flecs_json_is_builtin(id)) { + continue; + } + } + + const ecs_type_info_t *ti = cr->type_info; + if (!ti) { + continue; + } + + flecs_json_next(buf); + ecs_strbuf_appendlit(buf, "\""); + flecs_json_id_member(buf, world, id, desc->serialize_full_paths); + ecs_strbuf_appendlit(buf, "\":"); + + ecs_type_info_to_json_buf(world, ti->component, buf); + } + + flecs_json_object_pop(buf); + + return true; +} + +static +bool flecs_json_serialize_table_tags( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_table_t *src_table, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + int16_t f, type_count = flecs_ito(int16_t, table->type.count); + ecs_id_t *ids = table->type.array; + int16_t *column_map = table->column_map; + + int32_t tag_count = 0; + ecs_table_record_t *trs = table->_->records; + for (f = 0; f < type_count; f ++) { + ecs_id_t id = ids[f]; + if (ECS_IS_PAIR(id)) { + continue; + } + + if (!desc || !desc->serialize_builtin) { + if (flecs_json_is_builtin(id)) { + continue; + } + } + + if (column_map && column_map[f] != -1) { + continue; /* Ignore components */ + } + + const ecs_table_record_t *tr = &trs[f]; + ecs_component_record_t *cr = tr->hdr.cr; + + if (src_table) { + if (!(cr->flags & EcsIdOnInstantiateInherit)) { + continue; + } + } + if (cr->flags & EcsIdSparse) { + continue; + } + + if (!tag_count) { + flecs_json_memberl(buf, "tags"); + flecs_json_array_push(buf); + } + + flecs_json_next(buf); + + ecs_strbuf_appendlit(buf, "\""); + flecs_json_id_member(buf, world, id, + desc ? desc->serialize_full_paths : true); + ecs_strbuf_appendlit(buf, "\""); + + tag_count ++; + } + + if (tag_count) { + flecs_json_array_pop(buf); + } + + return tag_count != 0; +} + +static +bool flecs_json_serialize_table_pairs( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_table_t *src_table, + int32_t row, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + int16_t f, type_count = flecs_ito(int16_t, table->type.count); + ecs_id_t *ids = table->type.array; + int16_t *column_map = table->column_map; + + (void)row; + + int32_t pair_count = 0; + bool same_first = false; + + ecs_table_record_t *trs = table->_->records; + for (f = 0; f < type_count; f ++) { + ecs_id_t id = ids[f]; + if (!ECS_IS_PAIR(id)) { + continue; + } + + if (!desc || !desc->serialize_builtin) { + if (flecs_json_is_builtin(id)) { + continue; + } + } + + if (column_map && column_map[f] != -1) { + continue; /* Ignore components */ + } + + const ecs_table_record_t *tr = &trs[f]; + ecs_component_record_t *cr = tr->hdr.cr; + + if (src_table) { + if (!(cr->flags & EcsIdOnInstantiateInherit)) { + continue; + } + } + if (cr->flags & EcsIdSparse) { + continue; + } + + ecs_entity_t first = flecs_entities_get_alive( + world, ECS_PAIR_FIRST(id)); + + if (!pair_count) { + flecs_json_memberl(buf, "pairs"); + flecs_json_object_push(buf); + } + + bool is_last = f == (type_count - 1); + bool is_same = !is_last && + (ECS_PAIR_FIRST(ids[f + 1]) == ECS_PAIR_FIRST(id)); + + if (same_first && f && ECS_PAIR_FIRST(ids[f - 1]) != ECS_PAIR_FIRST(id)) { + /* New pair has different first elem, so close array */ + flecs_json_array_pop(buf); + same_first = false; + } + + if (!same_first) { + /* Only append pair label if we're not appending to array */ + flecs_json_next(buf); + flecs_json_path_or_label(buf, world, first, + desc ? desc->serialize_full_paths : true); + ecs_strbuf_appendlit(buf, ":"); + + /* Open array scope if this is a pair with multiple targets */ + if (is_same) { + flecs_json_array_push(buf); + same_first = true; + } + } + if (same_first) { + flecs_json_next(buf); + } + + if (ECS_IS_VALUE_PAIR(id)) { + ecs_strbuf_appendch(buf, '\"'); + ecs_strbuf_appendch(buf, '@'); + ecs_strbuf_appendint(buf, ECS_PAIR_SECOND(id)); + ecs_strbuf_appendch(buf, '\"'); + } else { + ecs_entity_t second = flecs_entities_get_alive( + world, ECS_PAIR_SECOND(id)); + + flecs_json_path_or_label(buf, world, second, + desc ? desc->serialize_full_paths : true); + } + + pair_count ++; + } + + if (same_first) { + flecs_json_array_pop(buf); + } + + if (pair_count) { + flecs_json_object_pop(buf); + } + + return pair_count != 0; +} + +static +int flecs_json_serialize_table_components( + const ecs_world_t *world, + ecs_table_t *table, + const ecs_table_t *src_table, + ecs_strbuf_t *buf, + ecs_json_value_ser_ctx_t *values_ctx, + const ecs_iter_to_json_desc_t *desc, + int32_t row, + int32_t *component_count) +{ + int32_t i, count = table->type.count; + for (i = 0; i < count; i ++) { + if (component_count[0] == FLECS_JSON_MAX_TABLE_COMPONENTS) { + break; + } + + ecs_id_t id = table->type.array[i]; + if(desc->component_filter && !desc->component_filter(world, id)) { + continue; + } + if (!desc || !desc->serialize_builtin) { + if (flecs_json_is_builtin(id)) { + continue; + } + } + + void *ptr; + const ecs_table_record_t *tr = &table->_->records[i]; + ecs_component_record_t *cr = tr->hdr.cr; + + if (src_table) { + if (!(cr->flags & EcsIdOnInstantiateInherit)) { + continue; + } + } + + const ecs_type_info_t *ti; + int32_t column_index = table->column_map ? table->column_map[i] : -1; + if (column_index != -1) { + ecs_column_t *column = &table->data.columns[column_index]; + ti = column->ti; + ptr = ECS_ELEM(column->data, ti->size, row); + } else { + if (!(cr->flags & EcsIdSparse)) { + continue; + } + ecs_entity_t e = ecs_table_entities(table)[row]; + ptr = flecs_sparse_get(cr->sparse, 0, e); + ti = cr->type_info; + } + + if (!ptr) { + continue; + } + + if (!component_count[0]) { + flecs_json_memberl(buf, "components"); + flecs_json_object_push(buf); + } + + bool has_reflection; + const EcsTypeSerializer *type_ser; + if (values_ctx) { + ecs_json_value_ser_ctx_t *value_ctx = + &values_ctx[component_count[0]]; + has_reflection = flecs_json_serialize_get_value_ctx( + world, id, value_ctx, desc); + flecs_json_member(buf, value_ctx->id_label); + type_ser = value_ctx->ser; + } else { + ecs_strbuf_list_next(buf); + ecs_strbuf_appendlit(buf, "\""); + flecs_json_id_member(buf, world, id, + desc ? desc->serialize_full_paths : true); + ecs_strbuf_appendlit(buf, "\":"); + type_ser = NULL; + if (!desc || desc->serialize_values) { + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + type_ser = ecs_get(world, ti->component, EcsTypeSerializer); + } + has_reflection = type_ser != NULL; + } + + component_count[0] ++; + + if (has_reflection && (!desc || desc->serialize_values)) { + ecs_assert(type_ser != NULL, ECS_INTERNAL_ERROR, NULL); + if (flecs_json_ser_type( + world, &type_ser->ops, ptr, buf) != 0) + { + goto error; + } + } else { + ecs_strbuf_appendlit(buf, "null"); + } + } + + if (component_count[0]) { + flecs_json_object_pop(buf); + } + + return 0; +error: + return -1; +} + +static +int flecs_json_serialize_table_inherited_type( + const ecs_world_t *world, + ecs_table_t *table, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + bool *has_inherited) +{ + if (!(table->flags & EcsTableHasIsA)) { + return 0; + } + + const ecs_table_record_t *tr = flecs_component_get_table( + world->cr_isa_wildcard, table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); /* Table has IsA flag */ + + int32_t i, start = tr->index, end = start + tr->count; + for (i = start; i < end; i ++) { + ecs_entity_t base = ecs_pair_second(world, table->type.array[i]); + ecs_record_t *base_record = ecs_record_find(world, base); + if (!base_record || !base_record->table) { + continue; + } + + ecs_table_t *base_table = base_record->table; + if (flecs_json_serialize_table_inherited_type( + world, base_table, buf, desc, has_inherited)) + { + return -1; + } + + char *base_name = ecs_get_path(world, base); + flecs_json_member(buf, base_name); + flecs_json_object_push(buf); + ecs_os_free(base_name); + + flecs_json_serialize_table_tags( + world, base_table, table, buf, desc); + + flecs_json_serialize_table_pairs( + world, base_table, table, ECS_RECORD_TO_ROW(base_record->row), + buf, desc); + + int32_t component_count = 0; + if (flecs_json_serialize_table_components( + world, base_table, table, buf, NULL, desc, + ECS_RECORD_TO_ROW(base_record->row), &component_count)) + { + return -1; + } + + if (desc->serialize_type_info) { + flecs_json_serialize_table_type_info( + world, base_table, buf, desc); + } + + flecs_json_object_pop(buf); + } + + *has_inherited = true; + return 0; +} + +static +int flecs_json_serialize_table_inherited( + const ecs_world_t *world, + ecs_table_t *table, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + bool *has_inherited) +{ + if (!(table->flags & EcsTableHasIsA)) { + return 0; + } + + flecs_json_memberl(buf, "inherited"); + flecs_json_object_push(buf); + if (flecs_json_serialize_table_inherited_type( + world, table, buf, desc, has_inherited)) + { + return -1; + } + flecs_json_object_pop(buf); + return 0; +} + +static +int flecs_json_serialize_table_tags_pairs_vars( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_table_t *table, + int32_t row, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + bool *result_out) +{ + *result_out = false; + ecs_strbuf_list_push(buf, "", ","); + *result_out |= flecs_json_serialize_table_tags(world, table, NULL, buf, desc); + *result_out |= flecs_json_serialize_table_pairs(world, table, NULL, row, buf, desc); + *result_out |= flecs_json_serialize_vars(world, it, buf, desc); + + if (desc->serialize_inherited) { + if (flecs_json_serialize_table_inherited( + world, table, buf, desc, result_out)) + { + return -1; + } + } + + if (desc->serialize_type_info) { + /* If we're serializing tables and are requesting type info, it must be + * added to each result. */ + *result_out |= flecs_json_serialize_table_type_info(world, table, buf, desc); + } + + ecs_strbuf_list_pop(buf, ""); + if (!*result_out) { + ecs_strbuf_reset(buf); + } + + return 0; +} + +int flecs_json_serialize_iter_result_table( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx, + int32_t count, + bool has_this, + const char *parent_path, + const ecs_json_this_data_t *this_data) +{ + ecs_table_t *table = it->table; + if (!table || !count) { + return 0; + } + + /* Serialize tags, pairs, vars once, since they're the same for each row */ + ecs_strbuf_t tags_pairs_vars_buf = ECS_STRBUF_INIT; + int32_t tags_pairs_vars_len = 0; + char *tags_pairs_vars = NULL; + + bool has_tags_pairs_vars = false; + if (flecs_json_serialize_table_tags_pairs_vars( + world, it, table, 0, &tags_pairs_vars_buf, desc, &has_tags_pairs_vars)) + { + return -1; + } + + if (has_tags_pairs_vars) { + tags_pairs_vars_len = ecs_strbuf_written(&tags_pairs_vars_buf); + tags_pairs_vars = ecs_strbuf_get(&tags_pairs_vars_buf); + } + + /* If one entity has more than 256 components (oof), bad luck */ + ecs_json_value_ser_ctx_t values_ctx[FLECS_JSON_MAX_TABLE_COMPONENTS] = {{0}}; + int32_t component_count = 0; + + int32_t i, end = it->offset + count; + int result = 0; + for (i = it->offset; i < end; i ++) { + if (has_this) { + ecs_json_this_data_t this_data_cpy = *this_data; + if (!flecs_json_serialize_iter_this(it, parent_path, + &this_data_cpy, i - it->offset, buf, desc, ser_ctx)) + { + continue; + } + } else { + flecs_json_next(buf); + flecs_json_object_push(buf); + } + + if (tags_pairs_vars) { + ecs_strbuf_list_appendstrn(buf, + tags_pairs_vars, tags_pairs_vars_len); + } + + component_count = 0; /* Each row has the same number of components */ + if (flecs_json_serialize_table_components( + world, table, NULL, buf, values_ctx, desc, i, &component_count)) + { + result = -1; + break; + } + + if (desc->serialize_matches) { + flecs_json_serialize_matches( + world, buf, it->entities[i - it->offset]); + } + + if (desc->serialize_refs) { + flecs_json_serialize_refs(world, buf, it->entities[i - it->offset], + desc->serialize_refs); + } + + if (desc->serialize_alerts) { + flecs_json_serialize_alerts(world, buf, + it->entities[i - it->offset]); + } + + flecs_json_object_pop(buf); + } + + for (i = 0; i < component_count; i ++) { + ecs_os_free(values_ctx[i].id_label); + } + + ecs_os_free(tags_pairs_vars); + + return result; +} + +#endif + +/** + * @file addons/json/serialize_query_info.c + * @brief Serialize (component) values to JSON strings. + */ + + +#ifdef FLECS_JSON + +static +const char* flecs_json_inout_str( + int16_t kind) +{ + switch(kind) { + case EcsIn: return "in"; + case EcsOut: return "out"; + case EcsInOut: return "inout"; + case EcsInOutNone: return "none"; + case EcsInOutFilter: return "filter"; + case EcsInOutDefault: return "default"; + default: return "unknown"; + } +} + +static +const char* flecs_json_oper_str( + int16_t kind) +{ + switch(kind) { + case EcsAnd: return "and"; + case EcsNot: return "not"; + case EcsOr: return "or"; + case EcsOptional: return "optional"; + case EcsAndFrom: return "andfrom"; + case EcsNotFrom: return "notfrom"; + case EcsOrFrom: return "orfrom"; + default: return "unknown"; + } +} + +static +void flecs_json_serialize_term_entity( + const ecs_world_t *world, + ecs_entity_t e, + ecs_strbuf_t *buf) +{ + flecs_json_memberl(buf, "entity"); + flecs_json_path(buf, world, e); + + if (e) { + const char *symbol = ecs_get_symbol(world, e); + if (symbol) { + flecs_json_memberl(buf, "symbol"); + flecs_json_string(buf, symbol); + } + + if (ecs_has(world, e, EcsComponent)) { + flecs_json_memberl(buf, "type"); + flecs_json_true(buf); + } + } +} + +static +void flecs_json_serialize_term_ref( + const ecs_world_t *world, + const ecs_term_ref_t *ref, + ecs_strbuf_t *buf) +{ + flecs_json_object_push(buf); + if (ref->id & EcsIsEntity) { + flecs_json_serialize_term_entity(world, ECS_TERM_REF_ID(ref), buf); + } else if (ref->id & EcsIsVariable) { + flecs_json_memberl(buf, "var"); + if (ref->name) { + flecs_json_string(buf, ref->name); + } else if (ref->id) { + if (ECS_TERM_REF_ID(ref) == EcsThis) { + flecs_json_string(buf, "this"); + } else { + flecs_json_path(buf, world, ECS_TERM_REF_ID(ref)); + } + } + } else if (ref->id & EcsIsName) { + flecs_json_memberl(buf, "name"); + flecs_json_string(buf, ref->name); + } + flecs_json_object_pop(buf); +} + +static +void flecs_json_serialize_term_trav( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_strbuf_t *buf) +{ + if (term->trav) { + flecs_json_memberl(buf, "trav"); + flecs_json_object_push(buf); + flecs_json_serialize_term_entity(world, term->trav, buf); + flecs_json_object_pop(buf); + } + + flecs_json_memberl(buf, "flags"); + flecs_json_array_push(buf); + if (term->src.id & EcsSelf) { + flecs_json_next(buf); + flecs_json_string(buf, "self"); + } + if (term->src.id & EcsCascade) { + flecs_json_next(buf); + flecs_json_string(buf, "cascade"); + } else + if (term->src.id & EcsUp) { + flecs_json_next(buf); + flecs_json_string(buf, "up"); + } + flecs_json_array_pop(buf); +} + +static +void flecs_json_serialize_term( + const ecs_world_t *world, + const ecs_query_t *q, + int t, + ecs_strbuf_t *buf) +{ + const ecs_term_t *term = &q->terms[t]; + + flecs_json_object_push(buf); + flecs_json_memberl(buf, "inout"); + flecs_json_string(buf, flecs_json_inout_str(term->inout)); + + flecs_json_memberl(buf, "has_value"); + flecs_json_bool(buf, !!((1llu << term->field_index) & q->data_fields)); + + ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); + if (term->first.id & EcsIsEntity && first_id) { + if (ecs_has_pair(world, first_id, EcsOnInstantiate, EcsInherit)) { + flecs_json_memberl(buf, "can_inherit"); + flecs_json_true(buf); + } + } + + flecs_json_memberl(buf, "oper"); + flecs_json_string(buf, flecs_json_oper_str(term->oper)); + + flecs_json_memberl(buf, "src"); + flecs_json_serialize_term_ref(world, &term->src, buf); + + flecs_json_memberl(buf, "first"); + flecs_json_serialize_term_ref(world, &term->first, buf); + + if (ECS_TERM_REF_ID(&term->second) || term->second.name || term->second.id & EcsIsEntity) { + flecs_json_memberl(buf, "second"); + flecs_json_serialize_term_ref(world, &term->second, buf); + } + + flecs_json_serialize_term_trav(world, term, buf); + + flecs_json_object_pop(buf); +} + +void flecs_json_serialize_query( + const ecs_world_t *world, + const ecs_query_t *q, + ecs_strbuf_t *buf) +{ + flecs_json_object_push(buf); + + if (q->var_count) { + flecs_json_memberl(buf, "vars"); + flecs_json_array_push(buf); + int32_t v, first = 0; + + if (!(q->flags & EcsQueryMatchThis)) { + first = 1; + } + + for (v = first; v < q->var_count; v ++) { + flecs_json_next(buf); + if (q->vars[v]) { + flecs_json_string_escape(buf, q->vars[v]); + } else { + flecs_json_string(buf, "this"); + } + } + flecs_json_array_pop(buf); + } + + flecs_json_memberl(buf, "terms"); + flecs_json_array_push(buf); + int t; + for (t = 0; t < q->term_count; t ++) { + flecs_json_next(buf); + flecs_json_serialize_term(world, q, t, buf); + } + flecs_json_array_pop(buf); + + + flecs_json_object_pop(buf); +} + +#endif + +/** + * @file addons/json/serialize_type_info.c + * @brief Serialize type (reflection) information to JSON. + */ + + +#ifdef FLECS_JSON + +static +int flecs_json_typeinfo_ser_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf); + +static +int flecs_json_typeinfo_ser_type_slice( + const ecs_world_t *world, + const ecs_meta_op_t *ops, + int32_t op_count, + ecs_strbuf_t *str); + +static +int flecs_json_typeinfo_ser_primitive( + ecs_primitive_kind_t kind, + ecs_strbuf_t *str) +{ + switch(kind) { + case EcsBool: + flecs_json_string(str, "bool"); + break; + case EcsChar: + case EcsString: + flecs_json_string(str, "text"); + break; + case EcsByte: + flecs_json_string(str, "byte"); + break; + case EcsU8: + case EcsU16: + case EcsU32: + case EcsU64: + case EcsI8: + case EcsI16: + case EcsI32: + case EcsI64: + case EcsIPtr: + case EcsUPtr: + flecs_json_string(str, "int"); + break; + case EcsF32: + case EcsF64: + flecs_json_string(str, "float"); + break; + case EcsEntity: + flecs_json_string(str, "entity"); + break; + case EcsId: + flecs_json_string(str, "id"); + break; + default: + return -1; + } + + ecs_strbuf_list_next(str); + + return 0; +} + +static +void flecs_json_typeinfo_ser_constants( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + ecs_iter_t it = ecs_each_id(world, ecs_pair(EcsChildOf, type)); + while (ecs_each_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + flecs_json_next(str); + flecs_json_string(str, ecs_get_name(world, it.entities[i])); + } + } +} + +static +void flecs_json_typeinfo_ser_enum( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + ecs_strbuf_list_appendstr(str, "\"enum\""); + flecs_json_typeinfo_ser_constants(world, type, str); +} + +static +void flecs_json_typeinfo_ser_bitmask( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *str) +{ + ecs_strbuf_list_appendstr(str, "\"bitmask\""); + flecs_json_typeinfo_ser_constants(world, type, str); +} + +/* Serialize unit information */ +static +int flecs_json_typeinfo_ser_unit( + const ecs_world_t *world, + ecs_strbuf_t *str, + ecs_entity_t unit) +{ + flecs_json_memberl(str, "unit"); + flecs_json_path(str, world, unit); + + const EcsUnit *uptr = ecs_get(world, unit, EcsUnit); + if (uptr) { + if (uptr->symbol) { + flecs_json_memberl(str, "symbol"); + flecs_json_string(str, uptr->symbol); + } + ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0); + if (quantity) { + flecs_json_memberl(str, "quantity"); + flecs_json_path(str, world, quantity); + } + } + + return 0; +} + +static +void flecs_json_typeinfo_ser_range( + ecs_strbuf_t *str, + const char *kind, + ecs_member_value_range_t *range) +{ + flecs_json_member(str, kind); + flecs_json_array_push(str); + flecs_json_next(str); + flecs_json_number(str, range->min); + flecs_json_next(str); + flecs_json_number(str, range->max); + flecs_json_array_pop(str); +} + +static +int flecs_json_typeinfo_ser_scope( + const ecs_world_t *world, + const ecs_meta_op_t *op, + ecs_strbuf_t *str) +{ + if (flecs_json_typeinfo_ser_type_slice( + world, &op[1], op->op_count - 2, str)) + { + return -1; + } + + ecs_entity_t unit = ecs_get_target_for(world, op->type, EcsIsA, EcsUnit); + if (unit) { + flecs_json_member(str, "@self"); + flecs_json_array_push(str); + flecs_json_object_push(str); + flecs_json_typeinfo_ser_unit(world, str, unit); + flecs_json_object_pop(str); + flecs_json_array_pop(str); + } + + return 0; +} + +static +int flecs_json_typeinfo_ser_array( + const ecs_world_t *world, + const ecs_meta_op_t *op, + ecs_strbuf_t *str) +{ + ecs_strbuf_list_appendstr(str, "\"array\""); + + flecs_json_next(str); + if (flecs_json_typeinfo_ser_scope(world, op, str)) { + goto error; + } + + ecs_strbuf_list_append(str, "%u", ecs_meta_op_get_elem_count(op, NULL)); + + return 0; +error: + return -1; +} + +static +int flecs_json_typeinfo_ser_vector( + const ecs_world_t *world, + const ecs_meta_op_t *op, + ecs_strbuf_t *str) +{ + ecs_strbuf_list_appendstr(str, "\"vector\""); + + flecs_json_next(str); + if (flecs_json_typeinfo_ser_scope(world, op, str)) { + goto error; + } + + return 0; +error: + return -1; +} + +/* Iterate over a slice of the type ops array */ +static +int flecs_json_typeinfo_ser_type_slice( + const ecs_world_t *world, + const ecs_meta_op_t *ops, + int32_t op_count, + ecs_strbuf_t *str) +{ + const EcsStruct *st = NULL; + if (ops[0].name) { /* If a member, previous operation is PushStruct */ + const ecs_meta_op_t *push = &ops[-1]; + ecs_assert(push->kind == EcsOpPushStruct, ECS_INTERNAL_ERROR, NULL); + st = ecs_get(world, push->type, EcsStruct); + ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); + } + + for (int i = 0; i < op_count; i ++) { + const ecs_meta_op_t *op = &ops[i]; + + if (op->name) { + flecs_json_member(str, op->name); + } + + if (op->kind == EcsOpForward) { + if (flecs_json_typeinfo_ser_type(world, op->type, str)) { + goto error; + } + continue; + } else if (op->kind == EcsOpOpaqueStruct || + op->kind == EcsOpOpaqueArray || op->kind == EcsOpOpaqueVector || + op->kind == EcsOpOpaqueValue) + { + const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); + ecs_assert(ct != NULL, ECS_INTERNAL_ERROR, NULL); + if (flecs_json_typeinfo_ser_type(world, ct->as_type, str)) { + goto error; + } + continue; + } else if (op->kind == EcsOpPushStruct) { + flecs_json_object_push(str); + if (flecs_json_typeinfo_ser_scope(world, op, str)) { + return -1; + } + flecs_json_object_pop(str); + i += op->op_count - 1; + continue; + } + + flecs_json_array_push(str); + + switch(op->kind) { + case EcsOpPushArray: + if (flecs_json_typeinfo_ser_array(world, op, str)) { + goto error; + } + i += op->op_count - 1; + break; + case EcsOpPushVector: + if (flecs_json_typeinfo_ser_vector(world, op, str)) { + goto error; + } + i += op->op_count - 1; + break; + case EcsOpEnum: + flecs_json_typeinfo_ser_enum(world, op->type, str); + break; + case EcsOpBitmask: + flecs_json_typeinfo_ser_bitmask(world, op->type, str); + break; + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + case EcsOpString: + if (flecs_json_typeinfo_ser_primitive( + flecs_json_op_to_primitive_kind(op->kind), str)) + { + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + break; + case EcsOpScope: + case EcsOpPrimitive: + case EcsOpPop: + case EcsOpForward: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpOpaqueValue: + case EcsOpPushStruct: + default: + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + + if (op->name) { + ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_member_t *m = ecs_vec_get_t( + &st->members, ecs_member_t, op->member_index); + ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); + + bool value_range = ECS_NEQ(m->range.min, m->range.max); + bool error_range = ECS_NEQ(m->error_range.min, m->error_range.max); + bool warning_range = ECS_NEQ( + m->warning_range.min, m->warning_range.max); + + ecs_entity_t unit = m->unit; + if (unit || error_range || warning_range || value_range) { + flecs_json_next(str); + flecs_json_object_push(str); + + if (unit) { + flecs_json_typeinfo_ser_unit(world, str, unit); + } + if (value_range) { + flecs_json_typeinfo_ser_range(str, "range", &m->range); + } + if (error_range) { + flecs_json_typeinfo_ser_range( + str, "error_range", &m->error_range); + } + if (warning_range) { + flecs_json_typeinfo_ser_range( + str, "warning_range", &m->warning_range); + } + + flecs_json_object_pop(str); + } + } + + flecs_json_array_pop(str); + } + + return 0; +error: + return -1; +} + +static +int flecs_json_typeinfo_ser_type( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf) +{ + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + ecs_strbuf_appendch(buf, '0'); + return 0; + } + + const EcsTypeSerializer *ser = ecs_get( + world, type, EcsTypeSerializer); + if (!ser) { + ecs_strbuf_appendch(buf, '0'); + return 0; + } + + ecs_meta_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_op_t); + int32_t count = ecs_vec_count(&ser->ops); + + if (flecs_json_typeinfo_ser_type_slice(world, ops, count, buf)) { + return -1; + } + + return 0; +} + +int ecs_type_info_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf) +{ + return flecs_json_typeinfo_ser_type(world, type, buf); +} + +char* ecs_type_info_to_json( + const ecs_world_t *world, + ecs_entity_t type) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_type_info_to_json_buf(world, type, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } + + return ecs_strbuf_get(&str); +} + +#endif + +/** + * @file addons/json/serialize_value.c + * @brief Serialize value to JSON. + */ + + +#ifdef FLECS_JSON + +static +int flecs_json_ser_type_slice( + const ecs_world_t *world, + ecs_meta_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str); + +/* Serialize enumeration */ +static +int flecs_json_ser_enum( + const ecs_world_t *world, + ecs_meta_op_t *op, + const void *base, + ecs_strbuf_t *str) +{ + ecs_map_key_t value; + ecs_meta_op_kind_t kind = op->underlying_kind; + + if (kind == EcsOpU8 || kind == EcsOpI8) { + value = *(const uint8_t*)base; + } else if (kind == EcsOpU16 || kind == EcsOpI16) { + value = *(const uint16_t*)base; + } else if (kind == EcsOpU32 || kind == EcsOpI32) { + value = *(const uint32_t*)base; + } else if (kind == EcsOpUPtr || kind == EcsOpIPtr) { + value = *(const uintptr_t*)base; + } else if (kind == EcsOpU64 || kind == EcsOpI64) { + value = *(const uint64_t*)base; + } else { + ecs_abort(ECS_INTERNAL_ERROR, "invalid underlying type"); + } + + /* Enumeration constants are stored in a map that is keyed on the + * enumeration value. */ + ecs_enum_constant_t *constant = ecs_map_get_deref(op->is.constants, + ecs_enum_constant_t, value); + if (!constant) { + /* If the value is not found, it is not a valid enumeration constant */ + char *name = ecs_get_path(world, op->type); + ecs_err("enumeration value '%d' of type '%s' is not a valid constant", + value, name); + ecs_os_free(name); + goto error; + } + + ecs_strbuf_appendch(str, '"'); + ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); + ecs_strbuf_appendch(str, '"'); + + return 0; +error: + return -1; +} + +/* Serialize bitmask */ +static +int flecs_json_ser_bitmask( + const ecs_world_t *world, + ecs_meta_op_t *op, + const void *ptr, + ecs_strbuf_t *str) +{ + uint32_t value = *(const uint32_t*)ptr; + if (!value) { + ecs_strbuf_appendch(str, '0'); + return 0; + } + + ecs_strbuf_list_push(str, "\"", "|"); + + /* Multiple flags can be set at a given time. Iterate through all the flags + * and append the ones that are set. */ + ecs_map_iter_t it = ecs_map_iter(op->is.constants); + while (ecs_map_next(&it)) { + ecs_bitmask_constant_t *constant = ecs_map_ptr(&it); + ecs_map_key_t key = ecs_map_key(&it); + if ((value & key) == key) { + ecs_strbuf_list_appendstr(str, + ecs_get_name(world, constant->constant)); + value -= (uint32_t)key; + } + } + + if (value != 0) { + /* All bits must have been matched by a constant */ + char *name = ecs_get_path(world, op->type); + ecs_err("bitmask value '%u' of type '%s' contains invalid/unknown bits", + value, name); + ecs_os_free(name); + goto error; + } + + ecs_strbuf_list_pop(str, "\""); + + return 0; +error: + return -1; +} + +typedef struct json_serializer_ctx_t { + ecs_strbuf_t *str; + bool is_collection; +} json_serializer_ctx_t; + +static +int flecs_json_ser_opaque_value( + const ecs_serializer_t *ser, + ecs_entity_t type, + const void *value) +{ + json_serializer_ctx_t *json_ser = ser->ctx; + if (json_ser->is_collection) { + ecs_strbuf_list_next(json_ser->str); + } + return ecs_ptr_to_json_buf(ser->world, type, value, json_ser->str); +} + +static +int flecs_json_ser_opaque_member( + const ecs_serializer_t *ser, + const char *name) +{ + json_serializer_ctx_t *json_ser = ser->ctx; + flecs_json_member(json_ser->str, name); + return 0; +} + +static +int flecs_json_ser_opaque( + const ecs_world_t *world, + ecs_meta_op_t *op, + const void *base, + ecs_strbuf_t *str, + ecs_meta_op_kind_t kind) +{ + bool is_struct = kind == EcsOpOpaqueStruct; + bool is_collection = kind == EcsOpOpaqueVector || kind == EcsOpOpaqueArray; + + if (is_struct) { + flecs_json_object_push(str); + } else if (is_collection) { + flecs_json_array_push(str); + } + + json_serializer_ctx_t json_ser = { + .str = str, .is_collection = is_collection + }; + + ecs_serializer_t ser = { + .world = world, + .value = flecs_json_ser_opaque_value, + .member = is_struct ? flecs_json_ser_opaque_member : NULL, + .ctx = &json_ser + }; + + ecs_assert(op->is.opaque != NULL, ECS_INTERNAL_ERROR, NULL); + if (op->is.opaque(&ser, base)) { + return -1; + } + + if (is_struct) { + flecs_json_object_pop(str); + } else if (is_collection) { + flecs_json_array_pop(str); + } + + return 0; +} + +static +int flecs_json_ser_scope( + const ecs_world_t *world, + ecs_meta_op_t *op, + const void *base, + ecs_strbuf_t *str) +{ + if (flecs_json_ser_type_slice(world, &op[1], op->op_count - 2, base, str)) { + return -1; + } + + return 0; +} + +static +int flecs_json_ser_array( + const ecs_world_t *world, + ecs_meta_op_t *ops, + const void *array, + int32_t elem_size, + int32_t count, + ecs_strbuf_t *str) +{ + flecs_json_array_push(str); + + int32_t i; + for (i = 0; i < count; i ++) { + ecs_strbuf_list_next(str); + void *ptr = ECS_ELEM(array, elem_size, i); + if (flecs_json_ser_scope(world, ops, ptr, str)) { + return -1; + } + } + + flecs_json_array_pop(str); + return 0; +} + +static +int flecs_json_ser_forward( + const ecs_world_t *world, + ecs_entity_t type, + const void *base, + ecs_strbuf_t *str) +{ + const EcsTypeSerializer *ts = ecs_get(world, type, EcsTypeSerializer); + if (!ts) { + ecs_err("missing type serializer for '%s'", + flecs_errstr(ecs_get_path(world, type))); + return -1; + } + + return flecs_json_ser_type_slice(world, ecs_vec_first(&ts->ops), + ecs_vec_count(&ts->ops), base, str); +} + +/* Iterate over a slice of the type ops array */ +static +int flecs_json_ser_type_slice( + const ecs_world_t *world, + ecs_meta_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str) +{ + for (int i = 0; i < op_count; i ++) { + ecs_meta_op_t *op = &ops[i]; + const void *ptr = ECS_OFFSET(base, op->offset); + + if (op->name) { + flecs_json_member(str, op->name); + } + + bool large_int = false; + if (op->kind == EcsOpI64) { + if (*(const int64_t*)ptr >= 2147483648) { + large_int = true; + } + } else if (op->kind == EcsOpU64) { + if (*(const uint64_t*)ptr >= 2147483648) { + large_int = true; + } + } + + if (large_int) { + ecs_strbuf_appendch(str, '"'); + } + + switch(op->kind) { + case EcsOpPushStruct: { + flecs_json_object_push(str); + if (flecs_json_ser_scope(world, op, ptr, str)) { + return -1; + } + flecs_json_object_pop(str); + + i += op->op_count - 1; + break; + } + case EcsOpPushVector: { + const ecs_vec_t *vec = ptr; + if (flecs_json_ser_array(world, op, + vec->array, op->elem_size, vec->count, str)) + { + goto error; + } + + i += op->op_count - 1; + break; + } + case EcsOpPushArray: { + if (flecs_json_ser_array(world, op, ptr, + op->elem_size, ecs_meta_op_get_elem_count(op, ptr), str)) + { + goto error; + } + + i += op->op_count - 1; + break; + } + case EcsOpForward: { + if (flecs_json_ser_forward(world, op->type, + ECS_OFFSET(base, op->offset), str)) + { + goto error; + } + break; + } + case EcsOpF32: + ecs_strbuf_appendflt(str, + (ecs_f64_t)*(const ecs_f32_t*)ptr, '"'); + break; + case EcsOpF64: + ecs_strbuf_appendflt(str, *(const ecs_f64_t*)ptr, '"'); + break; + case EcsOpEnum: + if (flecs_json_ser_enum(world, op, ptr, str)) { + goto error; + } + break; + case EcsOpBitmask: + if (flecs_json_ser_bitmask(world, op, ptr, str)) { + goto error; + } + break; + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpOpaqueValue: + if (flecs_json_ser_opaque(world, op, ptr, str, op->kind)) { + goto error; + } + break; + case EcsOpEntity: { + ecs_entity_t e = *(const ecs_entity_t*)ptr; + if (!e) { + ecs_strbuf_appendlit(str, "\"#0\""); + } else { + flecs_json_path(str, world, e); + } + break; + } + case EcsOpId: { + ecs_id_t id = *(const ecs_id_t*)ptr; + if (!id) { + ecs_strbuf_appendlit(str, "\"#0\""); + } else { + flecs_json_id(str, world, id); + } + break; + } + case EcsOpU64: + case EcsOpI64: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + if (flecs_expr_ser_primitive(world, + flecs_json_op_to_primitive_kind(op->kind), ptr, str, true)) + { + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + break; + case EcsOpPrimitive: + case EcsOpScope: + case EcsOpPop: + default: + ecs_throw(ECS_INTERNAL_ERROR, + "unexpected serializer operation"); + } + + if (large_int) { + ecs_strbuf_appendch(str, '"'); + } + } + + return 0; +error: + return -1; +} + +/* Iterate over the type ops of a type */ +int flecs_json_ser_type( + const ecs_world_t *world, + const ecs_vec_t *v_ops, + const void *base, + ecs_strbuf_t *str) +{ + ecs_meta_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_op_t); + int32_t count = ecs_vec_count(v_ops); + return flecs_json_ser_type_slice(world, ops, count, base, str); +} + +static +int flecs_array_to_json_buf_w_type_data( + const ecs_world_t *world, + const void *ptr, + int32_t count, + ecs_strbuf_t *buf, + const EcsComponent *comp, + const EcsTypeSerializer *ser) +{ + if (count) { + ecs_size_t size = comp->size; + + flecs_json_array_push(buf); + + do { + ecs_strbuf_list_next(buf); + if (flecs_json_ser_type(world, &ser->ops, ptr, buf)) { + return -1; + } + + ptr = ECS_OFFSET(ptr, size); + } while (-- count); + + flecs_json_array_pop(buf); + } else { + if (flecs_json_ser_type(world, &ser->ops, ptr, buf)) { + return -1; + } + } + + return 0; +} + +int ecs_array_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + int32_t count, + ecs_strbuf_t *buf) +{ + ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL); + + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (!comp) { + char *path = ecs_get_path(world, type); + ecs_err("cannot serialize to JSON, '%s' is not a component", path); + ecs_os_free(path); + return -1; + } + + const EcsTypeSerializer *ser = ecs_get( + world, type, EcsTypeSerializer); + if (!ser) { + char *path = ecs_get_path(world, type); + ecs_err("cannot serialize to JSON, '%s' has no reflection data", path); + ecs_os_free(path); + return -1; + } + + return flecs_array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); +} + +char* ecs_array_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr, + int32_t count) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } + + return ecs_strbuf_get(&str); +} + +int ecs_ptr_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf) +{ + return ecs_array_to_json_buf(world, type, ptr, 0, buf); +} + +char* ecs_ptr_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) +{ + return ecs_array_to_json(world, type, ptr, 0); +} + +#endif + +/** + * @file addons/json/serialize_world.c + * @brief Serialize world to JSON. + */ + + +#ifdef FLECS_JSON + +int ecs_world_to_json_buf( + ecs_world_t *world, + ecs_strbuf_t *buf_out, + const ecs_world_to_json_desc_t *desc) +{ + ecs_query_desc_t query_desc = {0}; + + if (desc && desc->serialize_builtin && desc->serialize_modules) { + query_desc.terms[0].id = EcsAny; + } else { + bool serialize_builtin = desc && desc->serialize_builtin; + bool serialize_modules = desc && desc->serialize_modules; + int32_t term_id = 0; + + if (!serialize_builtin) { + query_desc.terms[term_id].id = ecs_pair(EcsChildOf, EcsFlecs); + query_desc.terms[term_id].oper = EcsNot; + query_desc.terms[term_id].src.id = EcsSelf | EcsUp; + term_id ++; + } + if (!serialize_modules) { + query_desc.terms[term_id].id = EcsModule; + query_desc.terms[term_id].oper = EcsNot; + query_desc.terms[term_id].src.id = EcsSelf | EcsUp; + } + } + + query_desc.flags = EcsQueryMatchDisabled|EcsQueryMatchPrefab; + + ecs_query_t *q = ecs_query_init(world, &query_desc); + if (!q) { + return -1; + } + + ecs_iter_t it = ecs_query_iter(world, q); + ecs_iter_to_json_desc_t json_desc = { + .serialize_table = true, + .serialize_full_paths = true, + .serialize_entity_ids = true, + .serialize_values = true, + .serialize_parents_before_children = true + }; + + int ret = ecs_iter_to_json_buf(&it, buf_out, &json_desc); + ecs_query_fini(q); + return ret; +} + +char* ecs_world_to_json( + ecs_world_t *world, + const ecs_world_to_json_desc_t *desc) +{ + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (ecs_world_to_json_buf(world, &buf, desc)) { + ecs_strbuf_reset(&buf); + return NULL; + } + + return ecs_strbuf_get(&buf); +} + +#endif + + +/** + * @file addons/meta/c_utils.c + * @brief C utilities for meta addon. + */ + + +#ifdef FLECS_META + +#define ECS_META_IDENTIFIER_LENGTH (256) + +#define ecs_meta_error(ctx, ptr, ...)\ + ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); + +typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH]; + +typedef struct flecs_meta_utils_parse_ctx_t { + const char *name; + const char *desc; +} flecs_meta_utils_parse_ctx_t; + +typedef struct flecs_meta_utils_type_t { + ecs_meta_token_t type; + ecs_meta_token_t params; + bool is_const; + bool is_ptr; +} flecs_meta_utils_type_t; + +typedef struct flecs_meta_utils_member_t { + flecs_meta_utils_type_t type; + ecs_meta_token_t name; + int64_t count; + bool is_partial; +} flecs_meta_utils_member_t; + +typedef struct flecs_meta_utils_constant_t { + ecs_meta_token_t name; + int64_t value; + bool is_value_set; +} flecs_meta_utils_constant_t; + +typedef struct flecs_meta_utils_params_t { + flecs_meta_utils_type_t key_type; + flecs_meta_utils_type_t type; + int64_t count; + bool is_key_value; + bool is_fixed_size; +} flecs_meta_utils_params_t; + +static +const char* skip_scope(const char *ptr, flecs_meta_utils_parse_ctx_t *ctx) { + /* Keep track of which characters were used to open the scope */ + char stack[256]; + int32_t sp = 0; + char ch; + + while ((ch = *ptr)) { + if (ch == '(' || ch == '<') { + stack[sp] = ch; + + sp ++; + if (sp >= 256) { + ecs_meta_error(ctx, ptr, "maximum level of nesting reached"); + goto error; + } + } else if (ch == ')' || ch == '>') { + sp --; + if ((sp < 0) || (ch == '>' && stack[sp] != '<') || + (ch == ')' && stack[sp] != '(')) + { + ecs_meta_error(ctx, ptr, "mismatching %c in identifier", ch); + goto error; + } + } + + ptr ++; + + if (!sp) { + break; + } + } + + return ptr; +error: + return NULL; +} + +static +const char* parse_c_digit( + const char *ptr, + int64_t *value_out) +{ + char token[24]; + ptr = flecs_parse_ws_eol(ptr); + ptr = flecs_parse_digit(ptr, token); + if (!ptr) { + goto error; + } + + *value_out = strtol(token, NULL, 0); + + return flecs_parse_ws_eol(ptr); +error: + return NULL; +} + +static +const char* parse_c_identifier( + const char *ptr, + char *buff, + char *params, + flecs_meta_utils_parse_ctx_t *ctx) +{ + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + char *bptr = buff, ch; + + if (params) { + params[0] = '\0'; + } + + /* Ignore whitespaces */ + ptr = flecs_parse_ws_eol(ptr); + ch = *ptr; + + if (!isalpha(ch) && (ch != '_')) { + ecs_meta_error(ctx, ptr, "invalid identifier (starts with '%c')", ch); + goto error; + } + + while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' && + ch != '>' && ch != '}' && ch != '*') + { + /* Type definitions can contain macros or templates */ + if (ch == '(' || ch == '<') { + if (!params) { + ecs_meta_error(ctx, ptr, "unexpected %c", *ptr); + goto error; + } + + const char *end = skip_scope(ptr, ctx); + ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr)); + params[end - ptr] = '\0'; + + ptr = end; + } else { + *bptr = ch; + bptr ++; + ptr ++; + } + } + + *bptr = '\0'; + + if (!ch) { + ecs_meta_error(ctx, ptr, "unexpected end of token"); + goto error; + } + + return ptr; +error: + return NULL; +} + +static +const char * flecs_meta_utils_open_scope( + const char *ptr, + flecs_meta_utils_parse_ctx_t *ctx) +{ + /* Skip initial whitespaces */ + ptr = flecs_parse_ws_eol(ptr); + + /* Is this the start of the type definition? */ + if (ctx->desc == ptr) { + if (*ptr != '{') { + ecs_meta_error(ctx, ptr, "missing '{' in struct definition"); + goto error; + } + + ptr ++; + ptr = flecs_parse_ws_eol(ptr); + } + + /* Is this the end of the type definition? */ + if (!*ptr) { + ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); + goto error; + } + + /* Is this the end of the type definition? */ + if (*ptr == '}') { + ptr = flecs_parse_ws_eol(ptr + 1); + if (*ptr) { + ecs_meta_error(ctx, ptr, + "stray characters after struct definition"); + goto error; + } + return NULL; + } + + return ptr; +error: + return NULL; +} + +static +const char* flecs_meta_utils_parse_constant( + const char *ptr, + flecs_meta_utils_constant_t *token, + flecs_meta_utils_parse_ctx_t *ctx) +{ + ptr = flecs_meta_utils_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } + + token->is_value_set = false; + + /* Parse token, constant identifier */ + ptr = parse_c_identifier(ptr, token->name, NULL, ctx); + if (!ptr) { + return NULL; + } + + ptr = flecs_parse_ws_eol(ptr); + if (!ptr) { + return NULL; + } + + /* Explicit value assignment */ + if (*ptr == '=') { + int64_t value = 0; + ptr = parse_c_digit(ptr + 1, &value); + token->value = value; + token->is_value_set = true; + } + + /* Expect a ',' or '}' */ + if (*ptr != ',' && *ptr != '}') { + ecs_meta_error(ctx, ptr, "missing , after enum constant"); + goto error; + } + + if (*ptr == ',') { + return ptr + 1; + } else { + return ptr; + } +error: + return NULL; +} + +static +const char* flecs_meta_utils_parse_type( + const char *ptr, + flecs_meta_utils_type_t *token, + flecs_meta_utils_parse_ctx_t *ctx) +{ + token->is_ptr = false; + token->is_const = false; + + ptr = flecs_parse_ws_eol(ptr); + + /* Parse token, expect type identifier or ECS_PROPERTY */ + ptr = parse_c_identifier(ptr, token->type, token->params, ctx); + if (!ptr) { + goto error; + } + + if (!strcmp(token->type, "ECS_PRIVATE")) { + /* Members from this point are not stored in metadata */ + ptr += ecs_os_strlen(ptr); + goto done; + } + + /* If token is const, set const flag and continue parsing type */ + if (!strcmp(token->type, "const")) { + token->is_const = true; + + /* Parse type after const */ + ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); + } + + /* Check if type is a pointer */ + ptr = flecs_parse_ws_eol(ptr); + if (*ptr == '*') { + token->is_ptr = true; + ptr ++; + } + +done: + return ptr; +error: + return NULL; +} + +static +const char* flecs_meta_utils_parse_member( + const char *ptr, + flecs_meta_utils_member_t *token, + flecs_meta_utils_parse_ctx_t *ctx) +{ + ptr = flecs_meta_utils_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } + + token->count = 0; + token->is_partial = false; + + /* Parse member type */ + ptr = flecs_meta_utils_parse_type(ptr, &token->type, ctx); + if (!ptr) { + token->is_partial = true; + goto error; + } + + if (!ptr[0]) { + return ptr; + } + + /* Next token is the identifier */ + ptr = parse_c_identifier(ptr, token->name, NULL, ctx); + if (!ptr) { + goto error; + } + + /* Skip whitespace between member and [ or ; */ + ptr = flecs_parse_ws_eol(ptr); + + /* Check if this is an array */ + char *array_start = strchr(token->name, '['); + if (!array_start) { + /* If the [ was separated by a space, it will not be parsed as part of + * the name */ + if (*ptr == '[') { + /* safe, will not be modified */ + array_start = ECS_CONST_CAST(char*, ptr); + } + } + + if (array_start) { + /* Check if the [ matches with a ] */ + char *array_end = strchr(array_start, ']'); + if (!array_end) { + ecs_meta_error(ctx, ptr, "missing ']'"); + goto error; + + } else if (array_end - array_start == 0) { + ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); + goto error; + } + + token->count = atoi(array_start + 1); + + if (array_start == ptr) { + /* If [ was found after name, continue parsing after ] */ + ptr = array_end + 1; + } else { + /* If [ was found in name, replace it with 0 terminator */ + array_start[0] = '\0'; + } + } + + /* Expect a ; */ + if (*ptr != ';') { + ecs_meta_error(ctx, ptr, "missing ; after member declaration"); + goto error; + } + + return ptr + 1; +error: + return NULL; +} + +static +int flecs_meta_utils_parse_desc( + const char *ptr, + flecs_meta_utils_params_t *token, + flecs_meta_utils_parse_ctx_t *ctx) +{ + token->is_key_value = false; + token->is_fixed_size = false; + + ptr = flecs_parse_ws_eol(ptr); + if (*ptr != '(' && *ptr != '<') { + ecs_meta_error(ctx, ptr, + "expected '(' at start of collection definition"); + goto error; + } + + ptr ++; + + /* Parse type identifier */ + ptr = flecs_meta_utils_parse_type(ptr, &token->type, ctx); + if (!ptr) { + goto error; + } + + ptr = flecs_parse_ws_eol(ptr); + + /* If next token is a ',' the first type was a key type */ + if (*ptr == ',') { + ptr = flecs_parse_ws_eol(ptr + 1); + + if (isdigit(*ptr)) { + int64_t value; + ptr = parse_c_digit(ptr, &value); + if (!ptr) { + goto error; + } + + token->count = value; + token->is_fixed_size = true; + } else { + token->key_type = token->type; + + /* Parse element type */ + ptr = flecs_meta_utils_parse_type(ptr, &token->type, ctx); + ptr = flecs_parse_ws_eol(ptr); + + token->is_key_value = true; + } + } + + if (*ptr != ')' && *ptr != '>') { + ecs_meta_error(ctx, ptr, + "expected ')' at end of collection definition"); + goto error; + } + + return 0; +error: + return -1; +} + +static +ecs_entity_t flecs_meta_utils_lookup( + ecs_world_t *world, + flecs_meta_utils_type_t *token, + const char *ptr, + int64_t count, + flecs_meta_utils_parse_ctx_t *ctx); + +static +ecs_entity_t flecs_meta_utils_lookup_array( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + flecs_meta_utils_parse_ctx_t *ctx) +{ + flecs_meta_utils_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; + + flecs_meta_utils_params_t params; + if (flecs_meta_utils_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } + if (!params.is_fixed_size) { + ecs_meta_error(ctx, params_decl, "missing size for array"); + goto error; + } + + if (!params.count) { + ecs_meta_error(ctx, params_decl, "invalid array size"); + goto error; + } + + ecs_entity_t element_type = ecs_lookup_symbol( + world, params.type.type, true, true); + if (!element_type) { + ecs_meta_error(ctx, params_decl, "unknown element type '%s'", + params.type.type); + } + + if (!e) { + e = ecs_new(world); + } + + ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); + + ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); + + return e; +error: + return 0; +} + +static +ecs_entity_t flecs_meta_utils_lookup_vector( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + flecs_meta_utils_parse_ctx_t *ctx) +{ + flecs_meta_utils_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; + + flecs_meta_utils_params_t params; + if (flecs_meta_utils_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } + + if (params.is_key_value) { + ecs_meta_error(ctx, params_decl, + "unexpected key value parameters for vector"); + goto error; + } + + ecs_entity_t element_type = flecs_meta_utils_lookup( + world, ¶ms.type, params_decl, 1, ¶m_ctx); + + if (!e) { + e = ecs_new(world); + } + + ecs_set(world, e, EcsVector, { element_type }); + + return e; +error: + return 0; +} + +static +ecs_entity_t flecs_meta_utils_lookup_bitmask( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + flecs_meta_utils_parse_ctx_t *ctx) +{ + (void)e; + + flecs_meta_utils_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; + + flecs_meta_utils_params_t params; + if (flecs_meta_utils_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } + + if (params.is_key_value) { + ecs_meta_error(ctx, params_decl, + "unexpected key value parameters for bitmask"); + goto error; + } + + if (params.is_fixed_size) { + ecs_meta_error(ctx, params_decl, + "unexpected size for bitmask"); + goto error; + } + + ecs_entity_t bitmask_type = flecs_meta_utils_lookup( + world, ¶ms.type, params_decl, 1, ¶m_ctx); + ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); + +#ifndef FLECS_NDEBUG + /* Make sure this is a bitmask type */ + const EcsType *type_ptr = ecs_get(world, bitmask_type, EcsType); + ecs_check(type_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(type_ptr->kind == EcsBitmaskType, ECS_INVALID_PARAMETER, NULL); +#endif + + return bitmask_type; +error: + return 0; +} + +static +ecs_entity_t flecs_meta_utils_lookup( + ecs_world_t *world, + flecs_meta_utils_type_t *token, + const char *ptr, + int64_t count, + flecs_meta_utils_parse_ctx_t *ctx) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(token != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + const char *typename = token->type; + ecs_entity_t type = 0; + + /* Parse vector type */ + if (!token->is_ptr) { + if (!ecs_os_strcmp(typename, "ecs_array")) { + type = flecs_meta_utils_lookup_array(world, 0, token->params, ctx); + + } else if (!ecs_os_strcmp(typename, "ecs_vector") || + !ecs_os_strcmp(typename, "flecs::vector")) + { + type = flecs_meta_utils_lookup_vector(world, 0, token->params, ctx); + + } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { + type = flecs_meta_utils_lookup_bitmask(world, 0, token->params, ctx); + + } else if (!ecs_os_strcmp(typename, "flecs::byte")) { + type = ecs_id(ecs_byte_t); + + } else if (!ecs_os_strcmp(typename, "char")) { + type = ecs_id(ecs_char_t); + + } else if (!ecs_os_strcmp(typename, "bool") || + !ecs_os_strcmp(typename, "_Bool")) + { + type = ecs_id(ecs_bool_t); + + } else if (!ecs_os_strcmp(typename, "int8_t")) { + type = ecs_id(ecs_i8_t); + } else if (!ecs_os_strcmp(typename, "int16_t")) { + type = ecs_id(ecs_i16_t); + } else if (!ecs_os_strcmp(typename, "int32_t")) { + type = ecs_id(ecs_i32_t); + } else if (!ecs_os_strcmp(typename, "int64_t")) { + type = ecs_id(ecs_i64_t); + + } else if (!ecs_os_strcmp(typename, "uint8_t")) { + type = ecs_id(ecs_u8_t); + } else if (!ecs_os_strcmp(typename, "uint16_t")) { + type = ecs_id(ecs_u16_t); + } else if (!ecs_os_strcmp(typename, "uint32_t")) { + type = ecs_id(ecs_u32_t); + } else if (!ecs_os_strcmp(typename, "uint64_t")) { + type = ecs_id(ecs_u64_t); + + } else if (!ecs_os_strcmp(typename, "float")) { + type = ecs_id(ecs_f32_t); + } else if (!ecs_os_strcmp(typename, "double")) { + type = ecs_id(ecs_f64_t); + + } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { + type = ecs_id(ecs_entity_t); + + } else if (!ecs_os_strcmp(typename, "ecs_id_t")) { + type = ecs_id(ecs_id_t); + + } else if (!ecs_os_strcmp(typename, "char*")) { + type = ecs_id(ecs_string_t); + } else { + type = ecs_lookup_symbol(world, typename, true, true); + } + } else { + if (!ecs_os_strcmp(typename, "char")) { + typename = "flecs.meta.string"; + } else + if (token->is_ptr) { + typename = "flecs.meta.uptr"; + } else + if (!ecs_os_strcmp(typename, "char*") || + !ecs_os_strcmp(typename, "flecs::string")) + { + typename = "flecs.meta.string"; + } + + type = ecs_lookup_symbol(world, typename, true, true); + } + + if (count != 0) { + ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); + + type = ecs_insert(world, ecs_value(EcsArray, {type, (int32_t)count})); + } + + if (!type) { + ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); + goto error; + } + + return type; +error: + return 0; +} + +static +int flecs_meta_utils_parse_struct( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) +{ + const char *ptr = desc; + const char *name = ecs_get_name(world, t); + + flecs_meta_utils_member_t token; + flecs_meta_utils_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; + + ecs_struct_desc_t struct_desc = { + .entity = t + }; + + int32_t member = 0; + int32_t result = 0; + + while ((ptr = flecs_meta_utils_parse_member(ptr, &token, &ctx)) && ptr[0]) { + ecs_entity_t type = flecs_meta_utils_lookup( + world, &token.type, ptr, 0, &ctx); + if (!type) { + goto error; + } + + if (member == (ECS_MEMBER_DESC_CACHE_SIZE - 1)) { + ecs_err("too many members (%d) defined for struct " + "(increase ECS_MEMBER_DESC_CACHE_SIZE)", + ECS_MEMBER_DESC_CACHE_SIZE); + result = -1; + goto done; + } + + struct_desc.members[member].name = ecs_os_strdup(token.name); + struct_desc.members[member].type = type; + struct_desc.members[member].count = flecs_ito(int32_t, token.count); + member ++; + } + + if (!ecs_struct_init(world, &struct_desc)) { + goto error; + } + +done: + for (int i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { + if (!struct_desc.members[i].name) { + break; + } + ecs_os_free(ECS_CONST_CAST(char*, struct_desc.members[i].name)); + } + + return result; +error: + return -1; +} + +static +int flecs_meta_utils_parse_constants( + ecs_world_t *world, + ecs_entity_t t, + const char *desc, + bool is_bitmask) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + + const char *ptr = desc; + const char *name = ecs_get_name(world, t); + int32_t name_len = ecs_os_strlen(name); + const ecs_world_info_t *info = ecs_get_world_info(world); + const char *name_prefix = info->name_prefix; + int32_t name_prefix_len = name_prefix ? ecs_os_strlen(name_prefix) : 0; + + flecs_meta_utils_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; + + flecs_meta_utils_constant_t token; + int64_t last_value = 0; + + ecs_entity_t old_scope = ecs_set_scope(world, t); + + while ((ptr = flecs_meta_utils_parse_constant(ptr, &token, &ctx))) { + if (token.is_value_set) { + last_value = token.value; + } else if (is_bitmask) { + ecs_meta_error(&ctx, ptr, + "bitmask requires explicit value assignment"); + goto error; + } + + if (name_prefix) { + if (!ecs_os_strncmp(token.name, name_prefix, name_prefix_len)) { + ecs_os_memmove(token.name, token.name + name_prefix_len, + ecs_os_strlen(token.name) - name_prefix_len + 1); + } + } + + if (!ecs_os_strncmp(token.name, name, name_len)) { + ecs_os_memmove(token.name, token.name + name_len, + ecs_os_strlen(token.name) - name_len + 1); + } + + ecs_entity_t c = ecs_entity(world, { + .name = token.name + }); + + if (!is_bitmask) { + ecs_set_pair_second(world, c, EcsConstant, ecs_i32_t, + {(ecs_i32_t)last_value}); + } else { + ecs_set_pair_second(world, c, EcsConstant, ecs_u32_t, + {(ecs_u32_t)last_value}); + } + + last_value ++; + } + + ecs_set_scope(world, old_scope); + + return 0; +error: + return -1; +} + +static +int flecs_meta_utils_parse_enum( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) +{ + ecs_add(world, t, EcsEnum); + return flecs_meta_utils_parse_constants(world, t, desc, false); +} + +static +int flecs_meta_utils_parse_bitmask( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) +{ + ecs_add(world, t, EcsBitmask); + return flecs_meta_utils_parse_constants(world, t, desc, true); +} + +int ecs_meta_from_desc( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_kind_t kind, + const char *desc) +{ + switch(kind) { + case EcsStructType: + if (flecs_meta_utils_parse_struct(world, component, desc)) { + goto error; + } + break; + case EcsEnumType: + if (flecs_meta_utils_parse_enum(world, component, desc)) { + goto error; + } + break; + case EcsBitmaskType: + if (flecs_meta_utils_parse_bitmask(world, component, desc)) { + goto error; + } + break; + case EcsPrimitiveType: + case EcsArrayType: + case EcsVectorType: + case EcsOpaqueType: + break; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); + } + + return 0; +error: + return -1; +} + +#endif + +/** + * @file addons/meta/cursor.c + * @brief API for assigning values of runtime types with reflection. + */ + +#include + +#ifdef FLECS_META +#ifdef FLECS_QUERY_DSL +#endif + +const char* flecs_meta_op_kind_str( + ecs_meta_op_kind_t kind) +{ + switch(kind) { + + case EcsOpEnum: return "Enum"; + case EcsOpBitmask: return "Bitmask"; + case EcsOpForward: return "Forward"; + case EcsOpPushStruct: return "Push"; + case EcsOpPushArray: return "PushArray"; + case EcsOpPushVector: return "PushVector"; + case EcsOpOpaqueValue: return "OpaqueValue"; + case EcsOpOpaqueStruct: return "OpaqueStruct"; + case EcsOpOpaqueArray: return "OpaqueArray"; + case EcsOpOpaqueVector: return "OpaqueVector"; + case EcsOpPop: return "Pop"; + case EcsOpPrimitive: return "Primitive"; + case EcsOpBool: return "Bool"; + case EcsOpChar: return "Char"; + case EcsOpByte: return "Byte"; + case EcsOpU8: return "U8"; + case EcsOpU16: return "U16"; + case EcsOpU32: return "U32"; + case EcsOpU64: return "U64"; + case EcsOpI8: return "I8"; + case EcsOpI16: return "I16"; + case EcsOpI32: return "I32"; + case EcsOpI64: return "I64"; + case EcsOpF32: return "F32"; + case EcsOpF64: return "F64"; + case EcsOpUPtr: return "UPtr"; + case EcsOpIPtr: return "IPtr"; + case EcsOpString: return "String"; + case EcsOpEntity: return "Entity"; + case EcsOpId: return "Id"; + case EcsOpScope: return "Scope"; + default: return "<< invalid kind >>"; + } +} + +ecs_size_t ecs_meta_op_get_elem_count( + const ecs_meta_op_t *op, + const void *ptr) +{ + ecs_assert(op->kind == EcsOpPushArray || op->kind == EcsOpPushVector, + ECS_INTERNAL_ERROR, NULL); + if (op->kind == EcsOpPushArray) { + const ecs_meta_op_t *pop = &op[op->op_count - 1]; + ecs_assert(pop != NULL, ECS_INTERNAL_ERROR, NULL); + return pop->elem_size; + } else if (op->kind == EcsOpPushVector) { + return ((const ecs_vec_t*)ptr)->count; + } + ecs_throw(ECS_INVALID_OPERATION, + "cannot get element count for %s operation", + flecs_meta_op_kind_str(op->kind)); +error: + return 0; +} + +/* Get current scope */ +static +ecs_meta_scope_t* flecs_cursor_get_scope( + const ecs_meta_cursor_t *cursor) +{ + ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); + return ECS_CONST_CAST(ecs_meta_scope_t*, &cursor->scope[cursor->depth]); +error: + return NULL; +} + +/* Restore scope, if dotmember was used */ +static +ecs_meta_scope_t* flecs_cursor_restore_scope( + ecs_meta_cursor_t *cursor, + const ecs_meta_scope_t* scope) +{ + ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(scope != NULL, ECS_INVALID_PARAMETER, NULL); + if (scope->prev_depth) { + cursor->depth = scope->prev_depth; + } +error: + return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; +} + +/* Get current operation for scope */ +static +ecs_meta_op_t* flecs_cursor_get_op( + ecs_meta_scope_t *scope) +{ + ecs_assert(scope->ops != NULL, ECS_INVALID_OPERATION, + "type serializer is missing instructions"); + return &scope->ops[scope->ops_cur]; +} + +static +ecs_meta_op_kind_t flecs_cursor_get_collection_kind( + ecs_meta_scope_t *scope) +{ + /* Can only get collection kind for collection scope */ + ecs_assert(scope->is_collection, ECS_INTERNAL_ERROR, NULL); + + /* If this is a collection scope, the operation preceding the + * current one must be PushArray or PushVector. + * We can't get this from parent->ops, because if the collection was + * pushed from a struct/other collection scope, it will have done so + * with a Forward instruction. */ + ecs_meta_op_t *push = &scope->ops[-1]; + ecs_assert(push->kind == EcsOpPushArray || + push->kind == EcsOpPushVector, ECS_INTERNAL_ERROR, NULL); + return push->kind; +} + +static +ecs_size_t flecs_cursor_get_elem_size( + ecs_meta_scope_t *scope) +{ + /* Can only get collection kind for collection scope */ + ecs_assert(scope->is_collection, ECS_INTERNAL_ERROR, NULL); + + /* The first operation in a collection scope always has the element size. + * The reason is that if this is a primitive scope it only contains a single + * operation with the type and size, and if it's a composite type the first + * operation is a Push for the element type. */ + ecs_assert(scope->ops[0].type_info != NULL, ECS_INTERNAL_ERROR, NULL); + return scope->ops[0].type_info->size; +} + +/* Get pointer to current field/element */ +static +void* flecs_meta_cursor_get_ptr( + const ecs_world_t *world, + const ecs_meta_cursor_t *cursor, + ecs_meta_scope_t *scope) +{ + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + const EcsOpaque *opaque = scope->opaque; + + if (!opaque) { + if (scope->is_collection) { + ecs_assert(cursor->scope != scope, ECS_INTERNAL_ERROR, NULL); + /* Parent scope contains the index to the current element and total + * number of elements in collection. */ + ecs_meta_scope_t *parent = &scope[-1]; + ecs_size_t elem_size = flecs_cursor_get_elem_size(scope); + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + ecs_meta_op_kind_t kind = flecs_cursor_get_collection_kind(scope); + + if (kind == EcsOpPushVector) { + if (parent->elem >= parent->elem_count) { + ecs_vec_t *vec = flecs_meta_cursor_get_ptr( + world, cursor, parent); + if (vec) { + ecs_vec_init_if(vec, elem_size); + ecs_vec_set_min_count_w_type_info( + NULL, vec, elem_size, parent->elem + 1, + op->type_info); + scope->ptr = vec->array; + if (parent->elem >= parent->elem_count) { + parent->elem_count = parent->elem + 1; + } + } + } + } + + scope->is_empty_scope = false; + + void *elem = ECS_ELEM(scope->ptr, elem_size, parent->elem); + return ECS_OFFSET(elem, op->offset); + } else { + return ECS_OFFSET(scope->ptr, op->offset); + } + } + + /* Opaque type */ + + if (scope->is_collection) { + ecs_assert(scope != cursor->scope, ECS_INTERNAL_ERROR, NULL); + ecs_meta_scope_t *parent = &scope[-1]; + + if (!opaque->ensure_element) { + char *str = ecs_get_path(world, scope->type); + ecs_err("missing ensure_element() for opaque type %s", str); + ecs_os_free(str); + return NULL; + } + + scope->is_empty_scope = false; + + if (scope->ptr) { + void *opaque_ptr = opaque->ensure_element(scope->ptr, + flecs_itosize(parent->elem)); + ecs_assert(opaque_ptr != NULL, ECS_INVALID_OPERATION, + "ensure_element() returned NULL"); + return opaque_ptr; + } + + return NULL; + } else if (op->name) { + if (!opaque->ensure_member) { + char *str = ecs_get_path(world, scope->type); + ecs_err("missing ensure_member() for opaque type %s", str); + ecs_os_free(str); + return NULL; + } + + ecs_assert(scope->ptr != NULL, ECS_INTERNAL_ERROR, NULL); + return opaque->ensure_member(scope->ptr, op->name); + } else { + ecs_err("invalid operation for opaque type"); + return NULL; + } +} + +static +int flecs_meta_cursor_push_type( + const ecs_world_t *world, + ecs_meta_scope_t *scope, + ecs_entity_t type, + void *ptr) +{ + const EcsTypeSerializer *ser = ecs_get( + world, type, EcsTypeSerializer); + if (ser == NULL) { + char *str = ecs_id_str(world, type); + ecs_err("cannot open scope for '%s' (missing reflection data)", str); + ecs_os_free(str); + return -1; + } + + scope[0] = (ecs_meta_scope_t) { + .type = type, + .ops = ecs_vec_first_t(&ser->ops, ecs_meta_op_t), + .ptr = ptr + }; + + return 0; +} + +ecs_meta_cursor_t ecs_meta_cursor( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_meta_cursor_t result = { + .world = world, + .valid = true + }; + + if (flecs_meta_cursor_push_type(world, result.scope, type, ptr) < 0) { + result.valid = false; + } + + return result; +error: + return (ecs_meta_cursor_t){ 0 }; +} + +void* ecs_meta_get_ptr( + ecs_meta_cursor_t *cursor) +{ + return flecs_meta_cursor_get_ptr(cursor->world, cursor, + flecs_cursor_get_scope(cursor)); +} + +static +int flecs_meta_array_bounds_check( + ecs_meta_cursor_t *cursor, + ecs_meta_scope_t *scope) +{ + ecs_assert(cursor->scope != scope, ECS_INTERNAL_ERROR, NULL); + ecs_meta_scope_t *parent = &scope[-1]; + ecs_meta_op_kind_t kind = flecs_cursor_get_collection_kind(scope); + if (parent->ptr && kind != EcsOpPushVector) { + if (parent->elem >= parent->elem_count || parent->elem < 0) { + char *str = ecs_get_path(cursor->world, parent->type); + ecs_err("out of collection bounds for type '%s' (%d elements vs. size %d)", + str, parent->elem + 1, parent->elem_count); + ecs_os_free(str); + return -1; + } + } + + return 0; +} + +int ecs_meta_next( + ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + scope = flecs_cursor_restore_scope(cursor, scope); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + + if (scope->is_collection) { + ecs_assert(cursor->scope != scope, ECS_INTERNAL_ERROR, NULL); + ecs_meta_scope_t *parent = &scope[-1]; + parent->elem ++; + + scope->ops_cur = 0; + + if (scope->opaque) { + return 0; + } + + if (flecs_meta_array_bounds_check(cursor, scope)) { + return -1; + } + + return 0; + } + + scope->ops_cur += op->op_count; + + if (scope->ops_cur >= scope->ops_count) { + char *str = ecs_get_path(cursor->world, scope->type); + ecs_err("too many elements for scope for type %s", str); + ecs_os_free(str); + return -1; + } + + return 0; +} + +int ecs_meta_elem( + ecs_meta_cursor_t *cursor, + int32_t elem) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + if (!scope->is_collection) { + ecs_err("ecs_meta_elem can be used for collections only"); + return -1; + } + + ecs_assert(scope != cursor->scope, ECS_INTERNAL_ERROR, NULL); + ecs_meta_scope_t *parent = &scope[-1]; + + parent->elem = elem; + scope->ops_cur = 0; + scope->is_moved_scope = true; + + if (flecs_meta_array_bounds_check(cursor, scope)) { + return -1; + } + + return 0; +} + +static +int flecs_meta_member( + ecs_meta_cursor_t *cursor, + const char *name, + bool try) +{ + if (cursor->depth == 0) { + if (!try) ecs_err("cannot move to member in root scope (push first)"); + return -1; + } + + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + scope = flecs_cursor_restore_scope(cursor, scope); + + ecs_hashmap_t *members = scope->members; + const ecs_world_t *world = cursor->world; + + if (!members) { + if (!try) ecs_err("cannot move to member '%s' for non-struct type '%s'", + name, flecs_errstr(ecs_get_path(world, scope->type))); + return -1; + } + + const uint64_t *cur_ptr = flecs_name_index_find_ptr(members, name, 0, 0); + if (!cur_ptr) { + if (!try) ecs_err("unknown member '%s' for type '%s'", + name, flecs_errstr(ecs_get_path(world, scope->type))); + return -1; + } + + scope->ops_cur = flecs_uto(int16_t, cur_ptr[0]); + + const EcsOpaque *opaque = scope->opaque; + if (opaque) { + if (!opaque->ensure_member) { + if (!try) ecs_err("missing ensure_member for opaque type %s", + flecs_errstr(ecs_get_path(world, scope->type))); + } + } + + return 0; +} + +int ecs_meta_member( + ecs_meta_cursor_t *cursor, + const char *name) +{ + return flecs_meta_member(cursor, name, false); +} + +int ecs_meta_try_member( + ecs_meta_cursor_t *cursor, + const char *name) +{ + return flecs_meta_member(cursor, name, true); +} + +static +const char* flecs_meta_parse_member( + const char *start, + char *token_out) +{ + const char *ptr; + char ch; + for (ptr = start; (ch = *ptr); ptr ++) { + if (ch == '.') { + break; + } + } + + int32_t len = flecs_ito(int32_t, ptr - start); + if (len >= ECS_MAX_TOKEN_SIZE) { + len = ECS_MAX_TOKEN_SIZE - 1; + } + + ecs_os_memcpy(token_out, start, len); + token_out[len] = '\0'; + if (ch == '.') { + ptr ++; + } + + return ptr; +} + +static +int flecs_meta_dotmember( + ecs_meta_cursor_t *cursor, + const char *name, + bool try) +{ + ecs_meta_scope_t *cur_scope = flecs_cursor_get_scope(cursor); + flecs_cursor_restore_scope(cursor, cur_scope); + + int16_t prev_depth = cursor->depth; + int dotcount = 0; + + char token[ECS_MAX_TOKEN_SIZE]; + const char *ptr = name; + while ((ptr = flecs_meta_parse_member(ptr, token))) { + if (dotcount) { + ecs_meta_push(cursor); + } + + if (flecs_meta_member(cursor, token, try)) { + goto error; + } + + if (!ptr[0]) { + break; + } + + dotcount ++; + } + + cur_scope = flecs_cursor_get_scope(cursor); + if (dotcount) { + cur_scope->prev_depth = prev_depth; + } + + return 0; +error: + return -1; +} + +int ecs_meta_dotmember( + ecs_meta_cursor_t *cursor, + const char *name) +{ + return flecs_meta_dotmember(cursor, name, false); +} + +int ecs_meta_try_dotmember( + ecs_meta_cursor_t *cursor, + const char *name) +{ + return flecs_meta_dotmember(cursor, name, true); +} + +int ecs_meta_push( + ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + const ecs_world_t *world = cursor->world; + + /* Allow push for primitive scope if it's the root. Useful for scenarios + * where a value always has to be surrounded by { }. */ + if (cursor->depth == 0) { + if (!cursor->is_primitive_scope && (op->kind > EcsOpScope)) { + cursor->is_primitive_scope = true; + return 0; + } + } + + void *ptr = flecs_meta_cursor_get_ptr(world, cursor, scope); + cursor->depth ++; + ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, + ECS_INVALID_PARAMETER, NULL); + + ecs_meta_scope_t *next_scope = flecs_cursor_get_scope(cursor); + *next_scope = (ecs_meta_scope_t) {0}; + + /* Check if we need to forward to serializer of other type. */ + if (op->kind == EcsOpForward) { + const EcsTypeSerializer *ts = ecs_get( + world, op->type, EcsTypeSerializer); + ecs_assert(ts != NULL, ECS_INTERNAL_ERROR, NULL); + op = ecs_vec_first(&ts->ops); + } else if (op->kind == EcsOpOpaqueStruct) { + const EcsOpaque *opaque = ecs_get(world, op->type, EcsOpaque); + next_scope->opaque = opaque; + + const EcsTypeSerializer *ts = ecs_get( + world, opaque->as_type, EcsTypeSerializer); + ecs_assert(ts != NULL, ECS_INTERNAL_ERROR, NULL); + op = ecs_vec_first(&ts->ops); + + } else if (op->kind == EcsOpOpaqueVector || op->kind == EcsOpOpaqueArray) { + const EcsOpaque *opaque = ecs_get(world, op->type, EcsOpaque); + next_scope->opaque = opaque; + + const EcsTypeSerializer *ts = ecs_get( + world, opaque->as_type, EcsTypeSerializer); + ecs_assert(ts != NULL, ECS_INTERNAL_ERROR, NULL); + op = ecs_vec_first(&ts->ops); + + next_scope->is_collection = true; + next_scope->is_empty_scope = true; + next_scope->is_moved_scope = false; + scope->elem = 0; + } + + next_scope->ops = &op[1]; /* op after push */ + next_scope->ops_count = op->op_count - 1; + next_scope->ptr = ptr; + next_scope->type = op->type; + next_scope->members = op->is.members; + + if (!next_scope->opaque) { + if (op->kind == EcsOpPushStruct) { + ecs_assert(next_scope->members != NULL, ECS_INTERNAL_ERROR, NULL); + } else + if (op->kind == EcsOpPushArray) { + next_scope->is_collection = true; + + ecs_meta_op_t *pop = &op[op->op_count - 1]; + ecs_assert(pop->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); + scope->elem = 0; + scope->elem_count = pop->elem_size; /* Array count is encoded on pop */ + } else + if (op->kind == EcsOpPushVector) { + ecs_vec_t *vec = ptr; + + next_scope->ptr = vec ? vec->array : NULL; + next_scope->is_collection = true; + next_scope->is_empty_scope = true; + next_scope->is_moved_scope = false; + + scope->elem = 0; + scope->elem_count = vec ? vec->count : 0; + } else { + goto error; + } + } + + return 0; +error: { + char *path = ecs_get_path(world, scope->type); + ecs_err("cannot open scope for type '%s'", path); + ecs_os_free(path); + return -1; +} +} + +int ecs_meta_pop( + ecs_meta_cursor_t *cursor) +{ + if (cursor->is_primitive_scope) { + cursor->is_primitive_scope = false; + return 0; + } + + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + scope = flecs_cursor_restore_scope(cursor, scope); + cursor->depth --; + if (cursor->depth < 0) { + ecs_err("unexpected end of scope"); + return -1; + } + + ecs_meta_scope_t *next_scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(next_scope); + + if (op->kind == EcsOpPushStruct || op->kind == EcsOpPushArray || + op->kind == EcsOpPushVector) + { + next_scope->ops_cur += flecs_ito(int16_t, op->op_count - 1); + + if (op->kind == EcsOpPushVector) { + /* If scope got moved around in this is a partially assigned vector + * so don't shrink it. */ + if (!scope->is_moved_scope) { + ecs_assert(cursor->scope != scope, ECS_INTERNAL_ERROR, NULL); + ecs_meta_scope_t *parent = &scope[-1]; + ecs_vec_t *vec = parent->ptr; + + if (vec) { + ecs_size_t elem_size = flecs_cursor_get_elem_size(scope); + ecs_assert(elem_size > 0, ECS_INTERNAL_ERROR, NULL); + ecs_vec_init_if(vec, elem_size); + + ecs_meta_op_t *elem_op = &scope->ops[0]; + if (scope->is_empty_scope) { + ecs_vec_set_count_w_type_info( + NULL, vec, elem_size, 0, elem_op->type_info); + } else { + ecs_vec_set_count_w_type_info(NULL, vec, elem_size, + parent->elem + 1, elem_op->type_info); + } + } + } + } + + /* push + op_count should point to the operation after pop */ + op = flecs_cursor_get_op(next_scope); + ecs_assert(op->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); + } else if (op->kind == EcsOpForward || op->kind == EcsOpOpaqueStruct || + op->kind == EcsOpOpaqueArray) + { + next_scope->ops_cur += flecs_ito(int16_t, op->op_count - 1); + } else if (op->kind == EcsOpOpaqueVector) { + const EcsOpaque *opaque = scope->opaque; + + /* When popping an opaque collection type, call resize to make + * sure the vector isn't larger than the number of elements we + * deserialized. If the opaque type represents an array, don't call + * resize. */ + ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!opaque->resize) { + char *str = ecs_get_path(cursor->world, scope->type); + ecs_err("missing resize for opaque type %s", str); + ecs_os_free(str); + return -1; + } + + if (scope->ptr) { + if (scope->is_empty_scope) { + /* If no values were serialized for scope, resize + * collection to 0 elements. */ + opaque->resize(scope->ptr, 0); + } else { + /* Otherwise resize collection to the index of the last + * deserialized element + 1 */ + opaque->resize(scope->ptr, + flecs_ito(size_t, next_scope->elem + 1)); + } + } + } else { + /* should not have been able to push if the previous scope was not + * a complex or collection type */ + ecs_assert(false, ECS_INTERNAL_ERROR, "invalid operation for pop: %s", + flecs_meta_op_kind_str(op->kind)); + } + + return 0; +} + +bool ecs_meta_is_collection( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + return scope->is_collection; +} + +ecs_entity_t ecs_meta_get_type( + const ecs_meta_cursor_t *cursor) +{ + if (cursor->depth == 0) { + return cursor->scope[0].type; + } + + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + return op->type; +} + +ecs_entity_t ecs_meta_get_unit( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_entity_t type = scope->type; + const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); + if (!st) { + return 0; + } + + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + ecs_member_t *m = ecs_vec_get_t( + &st->members, ecs_member_t, op->member_index); + + return m->unit; +} + +const char* ecs_meta_get_member( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + return op->name; +} + +ecs_entity_t ecs_meta_get_member_id( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_entity_t type = scope->type; + const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); + if (!st) { + return 0; + } + + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + ecs_member_t *m = ecs_vec_get_t( + &st->members, ecs_member_t, op->member_index); + + return m->member; +} + +/* Utilities for type conversions and bounds checking */ +static struct { + int64_t min, max; +} ecs_meta_bounds_signed[EcsMetaTypeOpKindLast + 1] = { + [EcsOpBool] = {false, true}, + [EcsOpChar] = {INT8_MIN, INT8_MAX}, + [EcsOpByte] = {0, UINT8_MAX}, + [EcsOpU8] = {0, UINT8_MAX}, + [EcsOpU16] = {0, UINT16_MAX}, + [EcsOpU32] = {0, UINT32_MAX}, + [EcsOpU64] = {0, INT64_MAX}, + [EcsOpI8] = {INT8_MIN, INT8_MAX}, + [EcsOpI16] = {INT16_MIN, INT16_MAX}, + [EcsOpI32] = {INT32_MIN, INT32_MAX}, + [EcsOpI64] = {INT64_MIN, INT64_MAX}, + [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : INT64_MAX)}, + [EcsOpIPtr] = { + ((sizeof(void*) == 4) ? INT32_MIN : INT64_MIN), + ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX) + }, + [EcsOpEntity] = {0, INT64_MAX}, + [EcsOpId] = {0, INT64_MAX}, + [EcsOpEnum] = {INT32_MIN, INT32_MAX}, + [EcsOpBitmask] = {0, INT32_MAX} +}; + +static struct { + uint64_t min, max; +} ecs_meta_bounds_unsigned[EcsMetaTypeOpKindLast + 1] = { + [EcsOpBool] = {false, true}, + [EcsOpChar] = {0, INT8_MAX}, + [EcsOpByte] = {0, UINT8_MAX}, + [EcsOpU8] = {0, UINT8_MAX}, + [EcsOpU16] = {0, UINT16_MAX}, + [EcsOpU32] = {0, UINT32_MAX}, + [EcsOpU64] = {0, UINT64_MAX}, + [EcsOpI8] = {0, INT8_MAX}, + [EcsOpI16] = {0, INT16_MAX}, + [EcsOpI32] = {0, INT32_MAX}, + [EcsOpI64] = {0, INT64_MAX}, + [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : UINT64_MAX)}, + [EcsOpIPtr] = {0, ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX)}, + [EcsOpEntity] = {0, UINT64_MAX}, + [EcsOpId] = {0, UINT64_MAX}, + [EcsOpEnum] = {0, INT32_MAX}, + [EcsOpBitmask] = {0, UINT32_MAX} +}; + +static struct { + double min, max; +} ecs_meta_bounds_float[EcsMetaTypeOpKindLast + 1] = { + [EcsOpBool] = {false, true}, + [EcsOpChar] = {INT8_MIN, INT8_MAX}, + [EcsOpByte] = {0, UINT8_MAX}, + [EcsOpU8] = {0, UINT8_MAX}, + [EcsOpU16] = {0, UINT16_MAX}, + [EcsOpU32] = {0, UINT32_MAX}, + [EcsOpU64] = {0, (double)UINT64_MAX}, + [EcsOpI8] = {INT8_MIN, INT8_MAX}, + [EcsOpI16] = {INT16_MIN, INT16_MAX}, + [EcsOpI32] = {INT32_MIN, INT32_MAX}, + [EcsOpI64] = {INT64_MIN, (double)INT64_MAX}, + [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : (double)UINT64_MAX)}, + [EcsOpIPtr] = { + ((sizeof(void*) == 4) ? INT32_MIN : (double)INT64_MIN), + ((sizeof(void*) == 4) ? INT32_MAX : (double)INT64_MAX) + }, + [EcsOpEntity] = {0, (double)UINT64_MAX}, + [EcsOpId] = {0, (double)UINT64_MAX}, + [EcsOpEnum] = {INT32_MIN, INT32_MAX}, + [EcsOpBitmask] = {0, UINT32_MAX} +}; + +#define set_T(T, ptr, value)\ + ((T*)ptr)[0] = ((T)value) + +#define case_T(kind, T, dst, src)\ +case kind:\ + set_T(T, dst, src);\ + break + +#define case_T_checked(kind, T, dst, src, bounds)\ +case kind:\ + if ((src < bounds[kind].min) || (src > bounds[kind].max)){\ + ecs_err("value %.0f is out of bounds for type %s", (double)src,\ + flecs_meta_op_kind_str(kind));\ + return -1;\ + }\ + set_T(T, dst, src);\ + break + +#define cases_T_float(dst, src)\ + case_T(EcsOpF32, ecs_f32_t, dst, src);\ + case_T(EcsOpF64, ecs_f64_t, dst, src) + +#define cases_T_signed(dst, src, bounds)\ + case_T_checked(EcsOpChar, ecs_char_t, dst, src, bounds);\ + case_T_checked(EcsOpI8, ecs_i8_t, dst, src, bounds);\ + case_T_checked(EcsOpI16, ecs_i16_t, dst, src, bounds);\ + case_T_checked(EcsOpI32, ecs_i32_t, dst, src, bounds);\ + case_T_checked(EcsOpI64, ecs_i64_t, dst, src, bounds);\ + case_T_checked(EcsOpIPtr, ecs_iptr_t, dst, src, bounds);\ + case EcsOpEnum: {\ + switch(op->underlying_kind) {\ + case_T_checked(EcsOpI8, ecs_i8_t, dst, src, bounds);\ + case_T_checked(EcsOpI16, ecs_i16_t, dst, src, bounds);\ + case_T_checked(EcsOpI32, ecs_i32_t, dst, src, bounds);\ + case_T_checked(EcsOpI64, ecs_i64_t, dst, src, bounds);\ + case_T_checked(EcsOpIPtr, ecs_iptr_t, dst, src, bounds);\ + case_T_checked(EcsOpU8, ecs_u8_t, dst, src, bounds);\ + case_T_checked(EcsOpU16, ecs_u16_t, dst, src, bounds);\ + case_T_checked(EcsOpU32, ecs_u32_t, dst, src, bounds);\ + case_T_checked(EcsOpU64, ecs_u64_t, dst, src, bounds);\ + case_T_checked(EcsOpUPtr, ecs_uptr_t, dst, src, bounds);\ + case EcsOpPushStruct:\ + case EcsOpPushArray:\ + case EcsOpPushVector:\ + case EcsOpPop:\ + case EcsOpOpaqueStruct:\ + case EcsOpOpaqueArray:\ + case EcsOpOpaqueVector:\ + case EcsOpForward:\ + case EcsOpScope:\ + case EcsOpOpaqueValue:\ + case EcsOpEnum:\ + case EcsOpBitmask:\ + case EcsOpPrimitive:\ + case EcsOpBool:\ + case EcsOpChar:\ + case EcsOpByte:\ + case EcsOpF32:\ + case EcsOpF64:\ + case EcsOpString:\ + case EcsOpEntity:\ + case EcsOpId:\ + break;\ + }\ + break;\ + }\ + +#define cases_T_unsigned(dst, src, bounds)\ + case_T_checked(EcsOpByte, ecs_byte_t, dst, src, bounds);\ + case_T_checked(EcsOpU8, ecs_u8_t, dst, src, bounds);\ + case_T_checked(EcsOpU16, ecs_u16_t, dst, src, bounds);\ + case_T_checked(EcsOpU32, ecs_u32_t, dst, src, bounds);\ + case_T_checked(EcsOpU64, ecs_u64_t, dst, src, bounds);\ + case_T_checked(EcsOpUPtr, ecs_uptr_t, dst, src, bounds);\ + case_T_checked(EcsOpEntity, ecs_u64_t, dst, src, bounds);\ + case_T_checked(EcsOpId, ecs_u64_t, dst, src, bounds);\ + case_T_checked(EcsOpBitmask, ecs_u32_t, dst, src, bounds) + +#define cases_T_bool(dst, src)\ +case EcsOpBool:\ + set_T(ecs_bool_t, dst, value != 0);\ + break + +static +void flecs_meta_conversion_error( + ecs_meta_cursor_t *cursor, + ecs_meta_op_t *op, + const char *from) +{ + if (op->kind == EcsOpPop) { + ecs_err("cursor: out of bounds"); + } else { + char *path = ecs_get_path(cursor->world, op->type); + ecs_err("unsupported conversion from %s to '%s' (for %s)", + from, path, flecs_meta_op_kind_str(op->kind)); + ecs_os_free(path); + } +} + +int ecs_meta_set_bool( + ecs_meta_cursor_t *cursor, + bool value) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_signed); + cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); + case EcsOpString: { + char *result; + if (value) { + result = ecs_os_strdup("true"); + } else { + result = ecs_os_strdup("false"); + } + ecs_os_free(*(ecs_string_t*)ptr); + set_T(ecs_string_t, ptr, result); + break; + } + case EcsOpOpaqueValue: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); + if (opaque->assign_bool) { + opaque->assign_bool(ptr, value); + break; + } + } + /* fall through */ + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpScope: + case EcsOpPrimitive: + case EcsOpF32: + case EcsOpF64: + flecs_meta_conversion_error(cursor, op, "bool"); + return -1; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_char( + ecs_meta_cursor_t *cursor, + char value) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_signed); + case EcsOpString: { + char *result = flecs_asprintf("%c", value); + ecs_os_free(*(ecs_string_t*)ptr); + set_T(ecs_string_t, ptr, result); + break; + } + case EcsOpOpaqueValue: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); + if (opaque->assign_char) { /* preferred operation */ + opaque->assign_char(ptr, value); + break; + } else if (opaque->assign_uint) { + opaque->assign_uint(ptr, (uint64_t)value); + break; + } else if (opaque->assign_int) { + opaque->assign_int(ptr, value); + break; + } + } + /* fall through */ + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpScope: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpEntity: + case EcsOpId: + flecs_meta_conversion_error(cursor, op, "char"); + return -1; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_int( + ecs_meta_cursor_t *cursor, + int64_t value) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + ecs_assert(ptr != NULL, ECS_INVALID_OPERATION, "no object to assign"); + + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_signed); + cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); + cases_T_float(ptr, value); + case EcsOpString: { + char *result = flecs_asprintf("%"PRId64, value); + ecs_os_free(*(ecs_string_t*)ptr); + set_T(ecs_string_t, ptr, result); + break; + } + case EcsOpOpaqueValue: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); + if (opaque->assign_int) { /* preferred operation */ + opaque->assign_int(ptr, value); + break; + } else if (opaque->assign_float) { /* most expressive */ + opaque->assign_float(ptr, (double)value); + break; + } else if (opaque->assign_uint && (value > 0)) { + opaque->assign_uint(ptr, flecs_ito(uint64_t, value)); + break; + } else if (opaque->assign_char && (value > 0) && (value < 256)) { + opaque->assign_char(ptr, flecs_ito(char, value)); + break; + } + } + /* fall through */ + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpScope: + case EcsOpPrimitive: { + if(!value) return ecs_meta_set_null(cursor); + flecs_meta_conversion_error(cursor, op, "int"); + return -1; + } + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_uint( + ecs_meta_cursor_t *cursor, + uint64_t value) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + ecs_assert(ptr != NULL, ECS_INVALID_OPERATION, "no object to assign"); + + switch(op->kind) { + cases_T_bool(ptr, value); + cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); + cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); + cases_T_float(ptr, value); + case EcsOpString: { + char *result = flecs_asprintf("%"PRIu64, value); + ecs_os_free(*(ecs_string_t*)ptr); + set_T(ecs_string_t, ptr, result); + break; + } + case EcsOpOpaqueValue: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); + if (opaque->assign_uint) { /* preferred operation */ + opaque->assign_uint(ptr, value); + break; + } else if (opaque->assign_float) { /* most expressive */ + opaque->assign_float(ptr, (double)value); + break; + } else if (opaque->assign_int && (value < INT64_MAX)) { + opaque->assign_int(ptr, flecs_uto(int64_t, value)); + break; + } else if (opaque->assign_char && (value < 256)) { + opaque->assign_char(ptr, flecs_uto(char, value)); + break; + } + } + /* fall through */ + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpScope: + case EcsOpPrimitive: + if(!value) return ecs_meta_set_null(cursor); + flecs_meta_conversion_error(cursor, op, "uint"); + return -1; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_float( + ecs_meta_cursor_t *cursor, + double value) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + ecs_assert(ptr != NULL, ECS_INVALID_OPERATION, "no object to assign"); + + switch(op->kind) { + case EcsOpBool: + if (ECS_EQZERO(value)) { + set_T(bool, ptr, false); + } else { + set_T(bool, ptr, true); + } + break; + cases_T_signed(ptr, value, ecs_meta_bounds_float); + cases_T_unsigned(ptr, value, ecs_meta_bounds_float); + cases_T_float(ptr, value); + case EcsOpString: { + char *result = flecs_asprintf("%f", value); + ecs_os_free(*(ecs_string_t*)ptr); + set_T(ecs_string_t, ptr, result); + break; + } + case EcsOpOpaqueValue: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); + if (opaque->assign_float) { /* preferred operation */ + opaque->assign_float(ptr, value); + break; + } else if (opaque->assign_int && /* most expressive */ + (value <= (double)INT64_MAX) && (value >= (double)INT64_MIN)) + { + opaque->assign_int(ptr, (int64_t)value); + break; + } else if (opaque->assign_uint && (value >= 0)) { + opaque->assign_uint(ptr, (uint64_t)value); + break; + } else if (opaque->assign_entity && (value >= 0)) { + opaque->assign_entity( + ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), + (ecs_entity_t)value); + break; + } + } + /* fall through */ + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpScope: + case EcsOpPrimitive: + flecs_meta_conversion_error(cursor, op, "float"); + return -1; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_value( + ecs_meta_cursor_t *cursor, + const ecs_value_t *value) +{ + ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t type = value->type; + ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); + if (!value->ptr) { + ecs_err("value pointer is null"); + return -1; + } + const EcsType *mt = ecs_get(cursor->world, type, EcsType); + if (!mt) { + ecs_err("type of value does not have reflection data"); + return -1; + } + + if (mt->kind == EcsPrimitiveType) { + const EcsPrimitive *prim = ecs_get(cursor->world, type, EcsPrimitive); + ecs_check(prim != NULL, ECS_INTERNAL_ERROR, NULL); + switch(prim->kind) { + case EcsBool: return ecs_meta_set_bool(cursor, *(bool*)value->ptr); + case EcsChar: return ecs_meta_set_char(cursor, *(char*)value->ptr); + case EcsByte: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); + case EcsU8: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); + case EcsU16: return ecs_meta_set_uint(cursor, *(uint16_t*)value->ptr); + case EcsU32: return ecs_meta_set_uint(cursor, *(uint32_t*)value->ptr); + case EcsU64: return ecs_meta_set_uint(cursor, *(uint64_t*)value->ptr); + case EcsI8: return ecs_meta_set_int(cursor, *(int8_t*)value->ptr); + case EcsI16: return ecs_meta_set_int(cursor, *(int16_t*)value->ptr); + case EcsI32: return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); + case EcsI64: return ecs_meta_set_int(cursor, *(int64_t*)value->ptr); + case EcsF32: return ecs_meta_set_float(cursor, (double)*(float*)value->ptr); + case EcsF64: return ecs_meta_set_float(cursor, *(double*)value->ptr); + case EcsUPtr: return ecs_meta_set_uint(cursor, *(uintptr_t*)value->ptr); + case EcsIPtr: return ecs_meta_set_int(cursor, *(intptr_t*)value->ptr); + case EcsString: { + char *str = *(char**)value->ptr; + if (!str) { + return ecs_meta_set_null(cursor); + } + return ecs_meta_set_string(cursor, str); + } + case EcsEntity: return ecs_meta_set_entity(cursor, *(ecs_entity_t*)value->ptr); + case EcsId: return ecs_meta_set_id(cursor, *(ecs_id_t*)value->ptr); + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); + goto error; + } + } else if (mt->kind == EcsEnumType) { + return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); + } else if (mt->kind == EcsBitmaskType) { + return ecs_meta_set_int(cursor, *(uint32_t*)value->ptr); + } else { + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + if (op->type != value->type) { + char *type_str = ecs_get_path(cursor->world, value->type); + flecs_meta_conversion_error(cursor, op, type_str); + ecs_os_free(type_str); + goto error; + } + return ecs_value_copy(cursor->world, value->type, ptr, value->ptr); + } + +error: + return -1; +} + +static +int flecs_meta_add_bitmask_constant( + ecs_meta_cursor_t *cursor, + ecs_meta_op_t *op, + void *out, + const char *value) +{ + ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); + + if (!ecs_os_strcmp(value, "0")) { + return 0; + } + + ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); + if (!c) { + char *path = ecs_get_path(cursor->world, op->type); + ecs_err("unresolved bitmask constant '%s' for type '%s'", value, path); + ecs_os_free(path); + return -1; + } + + const ecs_u32_t *v = ecs_get_pair_second( + cursor->world, c, EcsConstant, ecs_u32_t); + if (v == NULL) { + char *path = ecs_get_path(cursor->world, op->type); + ecs_err("'%s' is not an bitmask constant for type '%s'", value, path); + ecs_os_free(path); + return -1; + } + + *(ecs_u32_t*)out |= v[0]; + + return 0; +} + +static +int flecs_meta_parse_bitmask( + ecs_meta_cursor_t *cursor, + ecs_meta_op_t *op, + void *out, + const char *value) +{ + char token[ECS_MAX_TOKEN_SIZE]; + + const char *prev = value, *ptr = value; + + *(ecs_u32_t*)out = 0; + + while ((ptr = strchr(ptr, '|'))) { + ecs_os_memcpy(token, prev, ptr - prev); + token[ptr - prev] = '\0'; + if (flecs_meta_add_bitmask_constant(cursor, op, out, token) != 0) { + return -1; + } + + ptr ++; + prev = ptr; + } + + if (flecs_meta_add_bitmask_constant(cursor, op, out, prev) != 0) { + return -1; + } + + return 0; +} + +static +int flecs_meta_cursor_lookup( + ecs_meta_cursor_t *cursor, + const char *value, + ecs_entity_t *out) +{ + if (ecs_os_strcmp(value, "#0")) { + if (cursor->lookup_action) { + *out = cursor->lookup_action( + ECS_CONST_CAST(ecs_world_t*, cursor->world), value, + cursor->lookup_ctx); + } else { + *out = ecs_lookup_from(cursor->world, 0, value); + } + if (!*out) { + ecs_err("unresolved entity identifier '%s'", value); + return -1; + } + } + return 0; +} + +static +bool flecs_meta_valid_digit( + const char *str) +{ + return str[0] == '-' || isdigit(str[0]); +} + +int ecs_meta_set_string( + ecs_meta_cursor_t *cursor, + const char *value) +{ + if (!value) { + return ecs_meta_set_null(cursor); + } + + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + if (!ptr) { + ecs_err("no object to assign"); + goto error; + } + + switch(op->kind) { + case EcsOpI8: + case EcsOpU8: + case EcsOpByte: + case EcsOpI16: + case EcsOpU16: + case EcsOpI32: + case EcsOpU32: + case EcsOpI64: + case EcsOpU64: + case EcsOpIPtr: + case EcsOpUPtr: + case EcsOpF32: + case EcsOpF64: + if (!flecs_meta_valid_digit(value)) { + ecs_err("expected number, got '%s'", value); + goto error; + } + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpOpaqueValue: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpString: + case EcsOpEntity: + case EcsOpId: + case EcsOpScope: + break; + } + + switch(op->kind) { + case EcsOpBool: + if (!ecs_os_strcmp(value, "true")) { + set_T(ecs_bool_t, ptr, true); + } else if (!ecs_os_strcmp(value, "false")) { + set_T(ecs_bool_t, ptr, false); + } else if (isdigit(value[0])) { + if (!ecs_os_strcmp(value, "0")) { + set_T(ecs_bool_t, ptr, false); + } else { + set_T(ecs_bool_t, ptr, true); + } + } else { + ecs_err("invalid value for boolean '%s'", value); + goto error; + } + break; + case EcsOpI8: + case EcsOpU8: + case EcsOpByte: + set_T(ecs_i8_t, ptr, atol(value)); + break; + case EcsOpChar: + set_T(char, ptr, value[0]); + break; + case EcsOpI16: + case EcsOpU16: + set_T(ecs_i16_t, ptr, atol(value)); + break; + case EcsOpI32: + case EcsOpU32: + set_T(ecs_i32_t, ptr, atol(value)); + break; + case EcsOpI64: + case EcsOpU64: + set_T(ecs_i64_t, ptr, atoll(value)); + break; + case EcsOpIPtr: + case EcsOpUPtr: + set_T(ecs_iptr_t, ptr, atoll(value)); + break; + case EcsOpF32: + set_T(ecs_f32_t, ptr, atof(value)); + break; + case EcsOpF64: + set_T(ecs_f64_t, ptr, atof(value)); + break; + case EcsOpString: { + if (*(ecs_string_t*)ptr == value) { + break; + } + ecs_os_free(*(ecs_string_t*)ptr); + char *result = ecs_os_strdup(value); + set_T(ecs_string_t, ptr, result); + break; + } + case EcsOpEnum: { + ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); + if (!c) { + char *path = ecs_get_path(cursor->world, op->type); + ecs_err("unresolved enum constant '%s' for type '%s'", value, path); + ecs_os_free(path); + goto error; + } + + ecs_entity_t underlying = ecs_get_target( + cursor->world, c, EcsConstant, 0); + if (!underlying) { + char *path = ecs_get_path(cursor->world, op->type); + ecs_err("'%s' is not an enum constant for type '%s'", value, path); + ecs_os_free(path); + goto error; + } + + const void *cptr = ecs_get_id( + cursor->world, c, ecs_pair(EcsConstant, underlying)); + if (!cptr) { + char *path = ecs_get_path(cursor->world, op->type); + ecs_err("constant '%s' enum '%s' is of an invalid underlying type", + value, path); + ecs_os_free(path); + goto error; + } + + ecs_value_t cv = { + .type = underlying, + .ptr = ECS_CONST_CAST(void*, cptr) + }; + ecs_meta_set_value(cursor, &cv); + break; + } + case EcsOpBitmask: + if (flecs_meta_parse_bitmask(cursor, op, ptr, value) != 0) { + goto error; + } + break; + case EcsOpEntity: { + ecs_entity_t e = 0; + if (flecs_meta_cursor_lookup(cursor, value, &e)) { + goto error; + } + set_T(ecs_entity_t, ptr, e); + break; + } + case EcsOpId: { + #ifdef FLECS_QUERY_DSL + ecs_id_t id = 0; + if (flecs_id_parse(cursor->world, NULL, value, &id) == NULL) { + goto error; + } + + set_T(ecs_id_t, ptr, id); + #else + ecs_err("cannot parse component expression: script addon required"); + #endif + break; + } + case EcsOpPop: + ecs_err("excess element '%s' in scope", value); + goto error; + case EcsOpOpaqueValue: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, + "entity %s is not an opaque type but serializer thinks so", + ecs_get_name(cursor->world, op->type)); + if (opaque->assign_string) { /* preferred */ + opaque->assign_string(ptr, value); + break; + } else if (opaque->assign_char && value[0] && !value[1]) { + opaque->assign_char(ptr, value[0]); + break; + } else if (opaque->assign_entity) { + ecs_entity_t e = 0; + if (flecs_meta_cursor_lookup(cursor, value, &e)) { + goto error; + } + opaque->assign_entity(ptr, + ECS_CONST_CAST(ecs_world_t*, cursor->world), e); + break; + } + } + /* fall through */ + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpForward: + case EcsOpPrimitive: + case EcsOpScope: + ecs_err("unsupported conversion from string '%s' to '%s'", + value, flecs_meta_op_kind_str(op->kind)); + goto error; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_string_literal( + ecs_meta_cursor_t *cursor, + const char *value) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + + if (!value) { + return -1; + } + + ecs_size_t len = ecs_os_strlen(value); + if (value[0] != '\"' || value[len - 1] != '\"') { + ecs_err("invalid string literal '%s'", value); + goto error; + } + + switch(op->kind) { + case EcsOpChar: + set_T(ecs_char_t, ptr, value[1]); + break; + + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpOpaqueValue: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + case EcsOpEntity: + case EcsOpId: + len -= 2; + + char *result = ecs_os_malloc(len + 1); + ecs_os_memcpy(result, value + 1, len); + result[len] = '\0'; + + if (ecs_meta_set_string(cursor, result)) { + ecs_os_free(result); + return -1; + } + + ecs_os_free(result); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_entity( + ecs_meta_cursor_t *cursor, + ecs_entity_t value) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + + switch(op->kind) { + case EcsOpEntity: + set_T(ecs_entity_t, ptr, value); + break; + case EcsOpId: + set_T(ecs_id_t, ptr, value); /* entities are valid ids */ + break; + case EcsOpString: { + char *result = ecs_get_path(cursor->world, value); + ecs_os_free(*(ecs_string_t*)ptr); + set_T(ecs_string_t, ptr, result); + break; + } + case EcsOpOpaqueValue: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + if (opaque && opaque->assign_entity) { + opaque->assign_entity(ptr, + ECS_CONST_CAST(ecs_world_t*, cursor->world), value); + break; + } + } + /* fall through */ + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + flecs_meta_conversion_error(cursor, op, "entity"); + goto error; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_id( + ecs_meta_cursor_t *cursor, + ecs_entity_t value) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + + switch(op->kind) { + case EcsOpId: + set_T(ecs_id_t, ptr, value); + break; + case EcsOpString: { + char *result = ecs_id_str(cursor->world, value); + ecs_os_free(*(ecs_string_t*)ptr); + set_T(ecs_string_t, ptr, result); + break; + } + case EcsOpOpaqueValue: { + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + if (opaque && opaque->assign_id) { + opaque->assign_id(ptr, + ECS_CONST_CAST(ecs_world_t*, cursor->world), value); + break; + } + } + /* fall through */ + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + flecs_meta_conversion_error(cursor, op, "id"); + goto error; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + + return 0; +error: + return -1; +} + +int ecs_meta_set_null( + ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + if (!ptr) { + ecs_err("no object to assign"); + goto error; + } + switch (op->kind) { + case EcsOpString: + ecs_os_free(*(char**)ptr); + set_T(ecs_string_t, ptr, NULL); + break; + case EcsOpOpaqueValue: { + const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); + if (ot && ot->assign_null) { + ot->assign_null(ptr); + break; + } + } + /* fall through */ + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + flecs_meta_conversion_error(cursor, op, "null"); + goto error; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + + return 0; +error: + return -1; +} + +bool ecs_meta_get_bool( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + switch(op->kind) { + case EcsOpBool: return *(ecs_bool_t*)ptr; + case EcsOpI8: return *(ecs_i8_t*)ptr != 0; + case EcsOpU8: return *(ecs_u8_t*)ptr != 0; + case EcsOpChar: return *(ecs_char_t*)ptr != 0; + case EcsOpByte: return *(ecs_u8_t*)ptr != 0; + case EcsOpI16: return *(ecs_i16_t*)ptr != 0; + case EcsOpU16: return *(ecs_u16_t*)ptr != 0; + case EcsOpI32: return *(ecs_i32_t*)ptr != 0; + case EcsOpU32: return *(ecs_u32_t*)ptr != 0; + case EcsOpI64: return *(ecs_i64_t*)ptr != 0; + case EcsOpU64: return *(ecs_u64_t*)ptr != 0; + case EcsOpIPtr: return *(ecs_iptr_t*)ptr != 0; + case EcsOpUPtr: return *(ecs_uptr_t*)ptr != 0; + case EcsOpF32: return ECS_NEQZERO(*(ecs_f32_t*)ptr); + case EcsOpF64: return ECS_NEQZERO(*(ecs_f64_t*)ptr); + case EcsOpString: return *(const char**)ptr != NULL; + case EcsOpEnum: return *(ecs_i32_t*)ptr != 0; + case EcsOpBitmask: return *(ecs_u32_t*)ptr != 0; + case EcsOpEntity: return *(ecs_entity_t*)ptr != 0; + case EcsOpId: return *(ecs_id_t*)ptr != 0; + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpOpaqueValue: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpScope: + case EcsOpPrimitive: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for bool"); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } + +error: + return 0; +} + +char ecs_meta_get_char( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + switch(op->kind) { + case EcsOpChar: + return *(ecs_char_t*)ptr != 0; + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpOpaqueValue: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpBool: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + case EcsOpEntity: + case EcsOpId: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for char"); + break; + } + +error: + return 0; +} + +int64_t ecs_meta_get_int( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + switch(op->kind) { + case EcsOpBool: return *(const ecs_bool_t*)ptr; + case EcsOpI8: return *(const ecs_i8_t*)ptr; + case EcsOpU8: return *(const ecs_u8_t*)ptr; + case EcsOpChar: return *(const ecs_char_t*)ptr; + case EcsOpByte: return *(const ecs_u8_t*)ptr; + case EcsOpI16: return *(const ecs_i16_t*)ptr; + case EcsOpU16: return *(const ecs_u16_t*)ptr; + case EcsOpI32: return *(const ecs_i32_t*)ptr; + case EcsOpU32: return *(const ecs_u32_t*)ptr; + case EcsOpI64: return *(const ecs_i64_t*)ptr; + case EcsOpU64: return flecs_uto(int64_t, *(const ecs_u64_t*)ptr); + case EcsOpIPtr: return *(const ecs_iptr_t*)ptr; + case EcsOpUPtr: return flecs_uto(int64_t, *(const ecs_uptr_t*)ptr); + case EcsOpF32: return (int64_t)*(const ecs_f32_t*)ptr; + case EcsOpF64: return (int64_t)*(const ecs_f64_t*)ptr; + case EcsOpString: return atoi(*(const char**)ptr); + case EcsOpEnum: return *(const ecs_i32_t*)ptr; + case EcsOpBitmask: return *(const ecs_u32_t*)ptr; + case EcsOpEntity: + ecs_throw(ECS_INVALID_PARAMETER, + "invalid conversion from entity to int"); + break; + case EcsOpId: + ecs_throw(ECS_INVALID_PARAMETER, + "invalid conversion from id to int"); + break; + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpOpaqueValue: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpScope: + case EcsOpPrimitive: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } +error: + return 0; +} + +uint64_t ecs_meta_get_uint( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + switch(op->kind) { + case EcsOpBool: return *(ecs_bool_t*)ptr; + case EcsOpI8: return flecs_ito(uint64_t, *(const ecs_i8_t*)ptr); + case EcsOpU8: return *(ecs_u8_t*)ptr; + case EcsOpChar: return flecs_ito(uint64_t, *(const ecs_char_t*)ptr); + case EcsOpByte: return flecs_ito(uint64_t, *(const ecs_u8_t*)ptr); + case EcsOpI16: return flecs_ito(uint64_t, *(const ecs_i16_t*)ptr); + case EcsOpU16: return *(ecs_u16_t*)ptr; + case EcsOpI32: return flecs_ito(uint64_t, *(const ecs_i32_t*)ptr); + case EcsOpU32: return *(ecs_u32_t*)ptr; + case EcsOpI64: return flecs_ito(uint64_t, *(const ecs_i64_t*)ptr); + case EcsOpU64: return *(ecs_u64_t*)ptr; + case EcsOpIPtr: return flecs_ito(uint64_t, *(const ecs_i64_t*)ptr); + case EcsOpUPtr: return *(ecs_uptr_t*)ptr; + case EcsOpF32: return flecs_ito(uint64_t, *(const ecs_f32_t*)ptr); + case EcsOpF64: return flecs_ito(uint64_t, *(const ecs_f64_t*)ptr); + case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); + case EcsOpEnum: return flecs_ito(uint64_t, *(const ecs_i32_t*)ptr); + case EcsOpBitmask: return *(const ecs_u32_t*)ptr; + case EcsOpEntity: return *(const ecs_entity_t*)ptr; + case EcsOpId: return *(const ecs_id_t*)ptr; + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpOpaqueValue: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpScope: + case EcsOpPrimitive: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } +error: + return 0; +} + +static +double flecs_meta_to_float( + ecs_meta_op_kind_t kind, + const void *ptr) +{ + switch(kind) { + case EcsOpBool: return *(const ecs_bool_t*)ptr; + case EcsOpI8: return *(const ecs_i8_t*)ptr; + case EcsOpU8: return *(const ecs_u8_t*)ptr; + case EcsOpChar: return *(const ecs_char_t*)ptr; + case EcsOpByte: return *(const ecs_u8_t*)ptr; + case EcsOpI16: return *(const ecs_i16_t*)ptr; + case EcsOpU16: return *(const ecs_u16_t*)ptr; + case EcsOpI32: return *(const ecs_i32_t*)ptr; + case EcsOpU32: return *(const ecs_u32_t*)ptr; + case EcsOpI64: return (double)*(const ecs_i64_t*)ptr; + case EcsOpU64: return (double)*(const ecs_u64_t*)ptr; + case EcsOpIPtr: return (double)*(const ecs_iptr_t*)ptr; + case EcsOpUPtr: return (double)*(const ecs_uptr_t*)ptr; + case EcsOpF32: return (double)*(const ecs_f32_t*)ptr; + case EcsOpF64: return *(const ecs_f64_t*)ptr; + case EcsOpString: return atof(*ECS_CONST_CAST(const char**, ptr)); + case EcsOpEnum: return *(const ecs_i32_t*)ptr; + case EcsOpBitmask: return *(const ecs_u32_t*)ptr; + case EcsOpEntity: + ecs_throw(ECS_INVALID_PARAMETER, + "invalid conversion from entity to float"); + break; + case EcsOpId: + ecs_throw(ECS_INVALID_PARAMETER, + "invalid conversion from id to float"); + break; + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpOpaqueValue: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpScope: + case EcsOpPrimitive: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); + break; + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + break; + } +error: + return 0; +} + +double ecs_meta_get_float( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + return flecs_meta_to_float(op->kind, ptr); +} + +/* Handler to get string from opaque (see ecs_meta_get_string below) */ +static int ecs_meta_get_string_value_from_opaque( + const struct ecs_serializer_t *ser, ecs_entity_t type, const void *value) +{ + if(type != ecs_id(ecs_string_t)) { + ecs_err("Expected value call for opaque type to be a string"); + return -1; + } + char*** ctx = (char ***) ser->ctx; + *ctx = ECS_CONST_CAST(char**, value); + return 0; +} + +/* Handler to get string from opaque (see ecs_meta_get_string below) */ +static int ecs_meta_get_string_member_from_opaque( + const struct ecs_serializer_t* ser, const char* name) +{ + (void)ser; // silence unused warning + (void)name; // silence unused warning + ecs_err("Unexpected member call when serializing string from opaque"); + return -1; +} + +const char* ecs_meta_get_string( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + switch(op->kind) { + case EcsOpString: return *(const char**)ptr; + case EcsOpOpaqueValue: { + /* If opaque type happens to map to a string, retrieve it. + Otherwise, fallback to default case (error). */ + const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); + if(opaque && opaque->as_type == ecs_id(ecs_string_t) && opaque->serialize) { + char** str = NULL; + ecs_serializer_t ser = { + .world = cursor->world, + .value = ecs_meta_get_string_value_from_opaque, + .member = ecs_meta_get_string_member_from_opaque, + .ctx = &str + }; + opaque->serialize(&ser, ptr); + if(str && *str) + return *str; + /* invalid string, so fall through */ + } + /* Not a compatible opaque type, so fall through */ + } + /* fall through */ + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpChar: + case EcsOpBool: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); + break; + } +error: + return 0; +} + +ecs_entity_t ecs_meta_get_entity( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + switch(op->kind) { + case EcsOpEntity: return *(ecs_entity_t*)ptr; + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpOpaqueValue: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpChar: + case EcsOpBool: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + case EcsOpId: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); + break; + } +error: + return 0; +} + +ecs_entity_t ecs_meta_get_id( + const ecs_meta_cursor_t *cursor) +{ + ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); + ecs_meta_op_t *op = flecs_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); + switch(op->kind) { + case EcsOpEntity: return *(ecs_id_t*)ptr; /* Entities are valid ids */ + case EcsOpId: return *(ecs_id_t*)ptr; + case EcsOpPushStruct: + case EcsOpPushArray: + case EcsOpPushVector: + case EcsOpPop: + case EcsOpForward: + case EcsOpOpaqueValue: + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpScope: + case EcsOpEnum: + case EcsOpBitmask: + case EcsOpPrimitive: + case EcsOpChar: + case EcsOpBool: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpString: + ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); + break; + } +error: + return 0; +} + +double ecs_meta_ptr_to_float( + ecs_primitive_kind_t type_kind, + const void *ptr) +{ + ecs_meta_op_kind_t kind = flecs_meta_primitive_to_op_kind(type_kind); + return flecs_meta_to_float(kind, ptr); +} + +#endif + + +/** + * @file addons/meta/definitions.c + * @brief Reflection definitions for builtin types. + */ + +/** + * @file addons/meta/meta.h + * @brief Type support for meta addon. + */ + +#ifndef FLECS_META_TYPE_SUPPORT_H +#define FLECS_META_TYPE_SUPPORT_H + + +#ifdef FLECS_META + +int flecs_init_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_type_kind_t kind, + ecs_size_t size, + ecs_size_t alignment); + +#define flecs_init_type_t(world, type, kind, T) \ + flecs_init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) + +int flecs_compare_string( + const void *str_a, + const void *str_b, + const ecs_type_info_t *ti); + +bool flecs_equals_string( + const void *str_a, + const void *str_b, + const ecs_type_info_t *ti); + +void flecs_meta_primitives_init( + ecs_world_t *world); + +void flecs_meta_enum_init( + ecs_world_t *world); + +void flecs_meta_struct_init( + ecs_world_t *world); + +void flecs_meta_array_init( + ecs_world_t *world); + +void flecs_meta_opaque_init( + ecs_world_t *world); + +void flecs_meta_units_init( + ecs_world_t *world); + +#endif + +#endif + + +#ifdef FLECS_META + +/* Opaque type serializatior addon vector */ +static +int flecs_addon_vec_serialize(const ecs_serializer_t *ser, const void *ptr) { + char ***data = ECS_CONST_CAST(char***, ptr); + char **addons = data[0]; + do { + ser->value(ser, ecs_id(ecs_string_t), addons); + } while((++ addons)[0]); + return 0; +} + +static +size_t flecs_addon_vec_count(const void *ptr) { + int32_t count = 0; + char ***data = ECS_CONST_CAST(char***, ptr); + char **addons = data[0]; + do { + ++ count; + } while(addons[count]); + return flecs_ito(size_t, count); +} + +static +int flecs_const_str_serialize(const ecs_serializer_t *ser, const void *ptr) { + char **data = ECS_CONST_CAST(char**, ptr); + ser->value(ser, ecs_id(ecs_string_t), data); + return 0; +} + +/* Initialize reflection data for core components */ +static +void flecs_meta_import_core_definitions( + ecs_world_t *world) +{ + ecs_struct(world, { + .entity = ecs_id(EcsComponent), + .members = { + { .name = "size", .type = ecs_id(ecs_i32_t) }, + { .name = "alignment", .type = ecs_id(ecs_i32_t) } + } + }); + + ecs_struct(world, { + .entity = ecs_id(EcsIdentifier), + .members = { + { + .name = "value", + .type = ecs_id(ecs_string_t), + .offset = offsetof(EcsIdentifier, value) + } + } + }); + + ecs_struct(world, { + .entity = ecs_id(EcsParent), + .members = { + { .name = "value", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_struct(world, { + .entity = ecs_id(EcsDefaultChildComponent), + .members = { + { .name = "component", .type = ecs_id(ecs_entity_t) } + } + }); + + /* Define const string as an opaque type that maps to string + This enables reflection for strings that are in .rodata, + (read-only) so that the meta add-on does not try to free them. + This opaque type defines how to serialize (read) the string, + but won't let users assign a new value. + */ + ecs_entity_t const_string = ecs_opaque(world, { + .entity = ecs_component(world, { + .entity = ecs_entity(world, { + .name = "flecs.core.const_string_t", + .root_sep = "" + }), + .type = { + .size = ECS_SIZEOF(const char*), + .alignment = ECS_ALIGNOF(const char*), + .hooks = { + .cmp = flecs_compare_string, + .equals = flecs_equals_string + } + } + }), + .type = { + .as_type = ecs_id(ecs_string_t), + .serialize = flecs_const_str_serialize, + } + }); + + ecs_entity_t string_vec = ecs_vector(world, { + .entity = ecs_entity(world, { + .name = "flecs.core.string_vec_t", + .root_sep = "" + }), + .type = ecs_id(ecs_string_t) + }); + + ecs_entity_t addon_vec = ecs_opaque(world, { + .entity = ecs_component(world, { + .entity = ecs_entity(world, { + .name = "flecs.core.addon_vec_t", + .root_sep = "" + }), + .type = { + .size = ECS_SIZEOF(char**), + .alignment = ECS_ALIGNOF(char**) + } + }), + .type = { + .as_type = string_vec, + .serialize = flecs_addon_vec_serialize, + .count = flecs_addon_vec_count, + } + }); + + ecs_struct(world, { + .entity = ecs_entity(world, { + .name = "flecs.core.BuildInfo", + .root_sep = "" + }), + .members = { + { .name = "compiler", .type = const_string }, + { .name = "addons", .type = addon_vec }, + { .name = "flags", .type = addon_vec }, + { .name = "version", .type = const_string }, + { .name = "version_major", .type = ecs_id(ecs_i16_t) }, + { .name = "version_minor", .type = ecs_id(ecs_i16_t) }, + { .name = "version_patch", .type = ecs_id(ecs_i16_t) }, + { .name = "debug", .type = ecs_id(ecs_bool_t) }, + { .name = "sanitize", .type = ecs_id(ecs_bool_t) }, + { .name = "perf_trace", .type = ecs_id(ecs_bool_t) } + } + }); +} + +/* Initialize reflection data for meta components */ +static +void flecs_meta_import_meta_definitions( + ecs_world_t *world) +{ + ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ + .entity = ecs_entity(world, { .name = "TypeKind" }), + .constants = { + { .name = "PrimitiveType" }, + { .name = "BitmaskType" }, + { .name = "EnumType" }, + { .name = "StructType" }, + { .name = "ArrayType" }, + { .name = "VectorType" }, + { .name = "OpaqueType" } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsType), + .members = { + { .name = "kind", .type = type_kind } + } + }); + + ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ + .entity = ecs_entity(world, { .name = "PrimitiveKind" }), + .constants = { + { .name = "Bool", 1 }, + { .name = "Char" }, + { .name = "Byte" }, + { .name = "U8" }, + { .name = "U16" }, + { .name = "U32" }, + { .name = "U64 "}, + { .name = "I8" }, + { .name = "I16" }, + { .name = "I32" }, + { .name = "I64" }, + { .name = "F32" }, + { .name = "F64" }, + { .name = "UPtr "}, + { .name = "IPtr" }, + { .name = "String" }, + { .name = "Entity" }, + { .name = "Id" } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsPrimitive), + .members = { + { .name = "kind", .type = primitive_kind } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsMember), + .members = { + { .name = "type", .type = ecs_id(ecs_entity_t) }, + { .name = "count", .type = ecs_id(ecs_i32_t) }, + { .name = "unit", .type = ecs_id(ecs_entity_t) }, + { .name = "offset", .type = ecs_id(ecs_i32_t) }, + { .name = "use_offset", .type = ecs_id(ecs_bool_t) } + } + }); + + ecs_entity_t vr = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, { .name = "value_range" }), + .members = { + { .name = "min", .type = ecs_id(ecs_f64_t) }, + { .name = "max", .type = ecs_id(ecs_f64_t) } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsMemberRanges), + .members = { + { .name = "value", .type = vr }, + { .name = "warning", .type = vr }, + { .name = "error", .type = vr } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsEnum), + .members = { + { .name = "underlying_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsArray), + .members = { + { .name = "type", .type = ecs_id(ecs_entity_t) }, + { .name = "count", .type = ecs_id(ecs_i32_t) }, + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsVector), + .members = { + { .name = "type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsOpaque), + .members = { + { .name = "as_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, { .name = "unit_translation" }), + .members = { + { .name = "factor", .type = ecs_id(ecs_i32_t) }, + { .name = "power", .type = ecs_id(ecs_i32_t) } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsUnit), + .members = { + { .name = "symbol", .type = ecs_id(ecs_string_t) }, + { .name = "prefix", .type = ecs_id(ecs_entity_t) }, + { .name = "base", .type = ecs_id(ecs_entity_t) }, + { .name = "over", .type = ecs_id(ecs_entity_t) }, + { .name = "translation", .type = ut } + } + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_id(EcsUnitPrefix), + .members = { + { .name = "symbol", .type = ecs_id(ecs_string_t) }, + { .name = "translation", .type = ut } + } + }); +} + +void flecs_meta_import_definitions( + ecs_world_t *world) +{ + flecs_meta_import_core_definitions(world); + flecs_meta_import_meta_definitions(world); +} + +#endif + +/** + * @file addons/meta/meta.c + * @brief Meta addon. + */ + + +#ifdef FLECS_META + +void flecs_type_serializer_dtor( + EcsTypeSerializer *ptr) +{ + int32_t i, count = ecs_vec_count(&ptr->ops); + ecs_meta_op_t *ops = ecs_vec_first(&ptr->ops); + + for (i = 0; i < count; i ++) { + ecs_meta_op_t *op = &ops[i]; + if (op->kind == EcsOpPushStruct) { + if (op->is.members) { + flecs_name_index_free(op->is.members); + } + } + } + + ecs_vec_fini_t(NULL, &ptr->ops, ecs_meta_op_t); +} + +static ECS_COPY(EcsTypeSerializer, dst, src, { + flecs_type_serializer_dtor(dst); + + dst->ops = ecs_vec_copy_t(NULL, &src->ops, ecs_meta_op_t); + + int32_t o, count = ecs_vec_count(&dst->ops); + ecs_meta_op_t *ops = ecs_vec_first_t(&dst->ops, ecs_meta_op_t); + + for (o = 0; o < count; o ++) { + ecs_meta_op_t *op = &ops[o]; + if (op->kind == EcsOpPushStruct) { + if (op->is.members) { + op->is.members = flecs_name_index_copy(op->is.members); + } + } + } +}) + +static ECS_MOVE(EcsTypeSerializer, dst, src, { + flecs_type_serializer_dtor(dst); + dst->ops = src->ops; + src->ops = (ecs_vec_t){0}; +}) + +static ECS_DTOR(EcsTypeSerializer, ptr, { + flecs_type_serializer_dtor(ptr); +}) + +const char* flecs_type_kind_str( + ecs_type_kind_t kind) +{ + switch(kind) { + case EcsPrimitiveType: return "Primitive"; + case EcsBitmaskType: return "Bitmask"; + case EcsEnumType: return "Enum"; + case EcsStructType: return "Struct"; + case EcsArrayType: return "Array"; + case EcsVectorType: return "Vector"; + case EcsOpaqueType: return "Opaque"; + default: return "unknown"; + } +} + +const char* flecs_primitive_type_kind_str( + ecs_primitive_kind_t kind) +{ + switch(kind) { + case EcsBool: return "Bool"; + case EcsChar: return "Char"; + case EcsByte: return "Byte"; + case EcsU8: return "U8"; + case EcsU16: return "U16"; + case EcsU32: return "U32"; + case EcsU64: return "U64"; + case EcsI8: return "I8"; + case EcsI16: return "I16"; + case EcsI32: return "I32"; + case EcsI64: return "I64"; + case EcsF32: return "F32"; + case EcsF64: return "F64"; + case EcsUPtr: return "UPtr"; + case EcsIPtr: return "IPtr"; + case EcsString: return "String"; + case EcsEntity: return "Entity"; + case EcsId: return "Id"; + default: return "unknown"; + } +} + + +int flecs_init_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_type_kind_t kind, + ecs_size_t size, + ecs_size_t alignment) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + + EcsType *meta_type = ecs_ensure(world, type, EcsType); + if (meta_type->kind == 0) { + meta_type->kind = kind; + + const EcsComponent *cptr = ecs_get(world, type, EcsComponent); + + /* Determine if this is an existing type or a reflection-defined type + * (runtime type) */ + meta_type->existing = (cptr != NULL) && (cptr->size != 0); + + /* For existing types, ensure that component has a default constructor, + * to prevent crashing serializers on uninitialized values. For runtime + * types (rtt), the default hooks are set by + * flecs_meta_rtt_init_default_hooks */ + ecs_type_info_t *ti = flecs_type_info_ensure(world, type); + if (meta_type->existing) { + if(!ti->hooks.ctor) { + ti->hooks.ctor = flecs_default_ctor; + } + + if(kind == EcsEnumType) { + /* Generate compare/equals hooks for enums, copying + the underlying type's hooks, which should be + any of the default primitive integral compare hooks, + i.e. ecs_compare_i8, _i16 _32... */ + const EcsEnum* enum_info = ecs_get(world, type, EcsEnum); + ecs_assert(enum_info != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_hooks_t *enum_hooks = ecs_get_hooks_id( + world, enum_info->underlying_type); + ecs_assert(!(enum_hooks->flags & + (ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL)), + ECS_INTERNAL_ERROR, NULL); + ti->hooks.cmp = enum_hooks->cmp; + ti->hooks.equals = enum_hooks->equals; + ti->hooks.flags &= + ~(ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL); + ti->hooks.flags |= ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS; + } + } + } else { + if (meta_type->kind != kind) { + ecs_err("type '%s' reregistered as '%s' (was '%s')", + ecs_get_name(world, type), + flecs_type_kind_str(kind), + flecs_type_kind_str(meta_type->kind)); + return -1; + } + } + + if (!meta_type->existing) { + EcsComponent *comp = ecs_ensure(world, type, EcsComponent); + comp->size = size; + comp->alignment = alignment; + ecs_modified(world, type, EcsComponent); + } else { + const EcsComponent *comp = ecs_get(world, type, EcsComponent); + if (comp->size) { + if (comp->size < size) { + ecs_err( + "computed size (%d) for '%s' is larger than actual type (%d)", + size, ecs_get_name(world, type), comp->size); + return -1; + } + if (comp->alignment < alignment) { + ecs_err( + "computed alignment (%d) for '%s' is larger than actual type (%d)", + alignment, ecs_get_name(world, type), comp->alignment); + return -1; + } + if (comp->size == size && comp->alignment != alignment) { + if (comp->alignment < alignment) { + ecs_err("computed size for '%s' matches with actual type but " + "alignment is different (%d vs. %d)", + ecs_get_name(world, type), alignment, comp->alignment); + } + } + + meta_type->partial = comp->size != size; + } + } + + ecs_modified(world, type, EcsType); + + return 0; +} + +void FlecsMetaImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsMeta); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsTypeSerializer); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsType), + .name = "type", .symbol = "EcsType", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsType), + .type.alignment = ECS_ALIGNOF(EcsType), + }); + + ecs_set_hooks(world, EcsType, { .ctor = flecs_default_ctor }); + + ecs_set_hooks(world, EcsTypeSerializer, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsTypeSerializer), + .copy = ecs_copy(EcsTypeSerializer), + .dtor = ecs_dtor(EcsTypeSerializer) + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsType) }, + .events = {EcsOnSet}, + .callback = flecs_meta_type_serializer_init, + .global_observer = true + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsType) }, + .events = {EcsOnSet}, + .callback = flecs_rtt_init_default_hooks, + .global_observer = true + }); + + /* Import type support for different type kinds */ + flecs_meta_primitives_init(world); + flecs_meta_enum_init(world); + flecs_meta_struct_init(world); + flecs_meta_array_init(world); + flecs_meta_opaque_init(world); + flecs_meta_units_init(world); + + /* Import reflection definitions for builtin types */ + flecs_meta_import_definitions(world); +} + +#endif + +/* + * @file addons/meta/rtt_lifecycle.c + * @brief Runtime components lifecycle management + */ + + +#ifdef FLECS_META + +/* Stores all the information necessary to forward a hook call to a + * struct's member type */ +typedef struct ecs_rtt_call_data_t { + union { + ecs_xtor_t xtor; + ecs_move_t move; + ecs_copy_t copy; + ecs_cmp_t cmp; + ecs_equals_t equals; + } hook; + const ecs_type_info_t *type_info; + int32_t offset; + int32_t count; +} ecs_rtt_call_data_t; + +/* Lifecycle context for runtime structs */ +typedef struct ecs_rtt_struct_ctx_t { + ecs_vec_t vctor; /* vector */ + ecs_vec_t vdtor; /* vector */ + ecs_vec_t vmove; /* vector */ + ecs_vec_t vcopy; /* vector */ + ecs_vec_t vcmp; /* vector */ + ecs_vec_t vequals; /* vector */ + +} ecs_rtt_struct_ctx_t; + +/* Lifecycle context for runtime arrays */ +typedef struct ecs_rtt_array_ctx_t { + const ecs_type_info_t *type_info; + int32_t elem_count; +} ecs_rtt_array_ctx_t; + +/* Lifecycle context for runtime vectors */ +typedef struct ecs_rtt_vector_ctx_t { + const ecs_type_info_t *type_info; +} ecs_rtt_vector_ctx_t; + +/* Generic copy assign hook */ +static +void flecs_rtt_default_copy( + void *dst_ptr, + const void *src_ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + ecs_os_memcpy(dst_ptr, src_ptr, count * type_info->size); +} + +/* Generic move assign hook */ +static +void flecs_rtt_default_move( + void *dst_ptr, + void *src_ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + flecs_rtt_default_copy(dst_ptr, src_ptr, count, type_info); +} + +/* + * + * RTT struct support + * + */ + +/* Invokes struct member type's constructor/destructor using saved information + * in the lifecycle context */ +static +void flecs_rtt_struct_xtor( + ecs_vec_t *xtor_data_vec, + void *ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + int cb_count = ecs_vec_count(xtor_data_vec); + int i, j; + for (j = 0; j < count; j++) { + void *elem_ptr = ECS_ELEM(ptr, type_info->size, j); + for (i = 0; i < cb_count; i++) { + ecs_rtt_call_data_t *xtor_data = + ecs_vec_get_t(xtor_data_vec, ecs_rtt_call_data_t, i); + xtor_data->hook.xtor( + ECS_OFFSET(elem_ptr, xtor_data->offset), + xtor_data->count, + xtor_data->type_info); + } + } +} + +/* Generic struct constructor. It will read hook information call data from + * the structs's lifecycle context and call the constructors configured when + * the type was created. */ +static +void flecs_rtt_struct_ctor( + void *ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_rtt_struct_xtor(&rtt_ctx->vctor, ptr, count, type_info); +} + +/* Generic struct destructor. It will read hook information call data from + * the structs's lifecycle context and call the constructors configured when + * the type was created. */ +static +void flecs_rtt_struct_dtor( + void *ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_rtt_struct_xtor(&rtt_ctx->vdtor, ptr, count, type_info); +} + +/* Generic move hook. It will read hook information call data from the + * structs's lifecycle context and call the move hooks configured when + * the type was created. */ +static +void flecs_rtt_struct_move( + void *dst_ptr, + void *src_ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + int cb_count = ecs_vec_count(&rtt_ctx->vmove); + int i, j; + for (j = 0; j < count; j++) { + ecs_size_t elem_offset = type_info->size * j; + void *elem_dst_ptr = ECS_OFFSET(dst_ptr, elem_offset); + void *elem_src_ptr = ECS_OFFSET(src_ptr, elem_offset); + for (i = 0; i < cb_count; i++) { + ecs_rtt_call_data_t *move_data = + ecs_vec_get_t(&rtt_ctx->vmove, ecs_rtt_call_data_t, i); + move_data->hook.move( + ECS_OFFSET(elem_dst_ptr, move_data->offset), + ECS_OFFSET(elem_src_ptr, move_data->offset), + move_data->count, + move_data->type_info); + } + } +} + +/* Generic copy hook. It will read hook information call data from the + * structs's lifecycle context and call the copy hooks configured when + * the type was created. */ +static +void flecs_rtt_struct_copy( + void *dst_ptr, + const void *src_ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + int cb_count = ecs_vec_count(&rtt_ctx->vcopy); + int i, j; + for (j = 0; j < count; j++) { + ecs_size_t elem_offset = type_info->size * j; + void *elem_dst_ptr = ECS_OFFSET(dst_ptr, elem_offset); + const void *elem_src_ptr = ECS_OFFSET(src_ptr, elem_offset); + for (i = 0; i < cb_count; i++) { + ecs_rtt_call_data_t *copy_data = + ecs_vec_get_t(&rtt_ctx->vcopy, ecs_rtt_call_data_t, i); + copy_data->hook.copy( + ECS_OFFSET(elem_dst_ptr, copy_data->offset), + ECS_OFFSET(elem_src_ptr, copy_data->offset), + copy_data->count, + copy_data->type_info); + } + } +} + +/* Generic compare hook. It will read hook information call data from the + * structs's lifecycle context and call the compare hooks configured when + * the type was created. */ +static +int flecs_rtt_struct_cmp( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return 0; + } + + ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + int cb_count = ecs_vec_count(&rtt_ctx->vcmp); + int i; + for (i = 0; i < cb_count; i++) { + ecs_rtt_call_data_t *comp_data = + ecs_vec_get_t(&rtt_ctx->vcmp, ecs_rtt_call_data_t, i); + int c = comp_data->hook.cmp( + ECS_OFFSET(a_ptr, comp_data->offset), + ECS_OFFSET(b_ptr, comp_data->offset), + comp_data->type_info); + if (c != 0) { + return c; + } + } + return 0; +} + +/* Generic equals hook. It will read hook information call data from the + * structs's lifecycle context and call the equals hooks configured when + * the type was created. */ +static +bool flecs_rtt_struct_equals( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return true; + } + + ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + int cb_count = ecs_vec_count(&rtt_ctx->vequals); + int i; + for (i = 0; i < cb_count; i++) { + ecs_rtt_call_data_t *comp_data = + ecs_vec_get_t(&rtt_ctx->vequals, ecs_rtt_call_data_t, i); + bool eq = comp_data->hook.equals( + ECS_OFFSET(a_ptr, comp_data->offset), + ECS_OFFSET(b_ptr, comp_data->offset), + comp_data->type_info); + if (!eq) { + return false; + } + } + return true; +} + +static +void flecs_rtt_free_lifecycle_nop( + void *ctx) +{ + (void)ctx; +} + +static +void flecs_rtt_free_lifecycle_struct_ctx( + void *ctx) +{ + if (!ctx) { + return; + } + + ecs_rtt_struct_ctx_t *lifecycle_ctx = ctx; + + ecs_vec_fini_t(NULL, &lifecycle_ctx->vctor, ecs_rtt_call_data_t); + ecs_vec_fini_t(NULL, &lifecycle_ctx->vdtor, ecs_rtt_call_data_t); + ecs_vec_fini_t(NULL, &lifecycle_ctx->vmove, ecs_rtt_call_data_t); + ecs_vec_fini_t(NULL, &lifecycle_ctx->vcopy, ecs_rtt_call_data_t); + ecs_vec_fini_t(NULL, &lifecycle_ctx->vcmp, ecs_rtt_call_data_t); + ecs_vec_fini_t(NULL, &lifecycle_ctx->vequals, ecs_rtt_call_data_t); + + ecs_os_free(ctx); +} + +static +ecs_rtt_struct_ctx_t * flecs_rtt_configure_struct_hooks( + ecs_world_t *world, + const ecs_type_info_t *ti, + ecs_flags32_t flags, + bool ctor, + bool dtor, + bool move, + bool copy, + bool cmp, + bool equals) +{ + ecs_type_hooks_t hooks = ti->hooks; + if (hooks.lifecycle_ctx_free) { + hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); + } + + hooks.ctor = ctor && !(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) ? + flecs_rtt_struct_ctor : NULL; + + hooks.dtor = dtor && !(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) ? + flecs_rtt_struct_dtor : NULL; + + hooks.move = move && !(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) ? + flecs_rtt_struct_move : NULL; + + hooks.copy = copy && !(flags & ECS_TYPE_HOOK_COPY_ILLEGAL) ? + flecs_rtt_struct_copy : NULL; + + hooks.cmp = cmp && !(flags & ECS_TYPE_HOOK_CMP_ILLEGAL) ? + flecs_rtt_struct_cmp : NULL; + + hooks.equals = equals && !(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) ? + flecs_rtt_struct_equals : NULL; + + ecs_rtt_struct_ctx_t *rtt_ctx = NULL; + if (hooks.ctor || hooks.dtor || hooks.move || hooks.copy + || hooks.cmp || hooks.equals) { + rtt_ctx = ecs_os_malloc_t(ecs_rtt_struct_ctx_t); + ecs_vec_init_t(NULL, &rtt_ctx->vctor, ecs_rtt_call_data_t, 0); + ecs_vec_init_t(NULL, &rtt_ctx->vdtor, ecs_rtt_call_data_t, 0); + ecs_vec_init_t(NULL, &rtt_ctx->vmove, ecs_rtt_call_data_t, 0); + ecs_vec_init_t(NULL, &rtt_ctx->vcopy, ecs_rtt_call_data_t, 0); + ecs_vec_init_t(NULL, &rtt_ctx->vcmp, ecs_rtt_call_data_t, 0); + ecs_vec_init_t(NULL, &rtt_ctx->vequals, ecs_rtt_call_data_t, 0); + + hooks.lifecycle_ctx = rtt_ctx; + hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_struct_ctx; + } else { + hooks.lifecycle_ctx = NULL; + hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_nop; + } + + hooks.flags = flags; + hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; + ecs_set_hooks_id(world, ti->component, &hooks); + return rtt_ctx; +} + +/* Checks if a struct member's types have hooks installed. If so, it generates + * and installs required hooks for the struct type itself. These hooks will + * invoke the member hooks when necessary */ +static +void flecs_rtt_init_default_hooks_struct( + ecs_world_t *world, + ecs_entity_t component, + const ecs_type_info_t *ti) +{ + /* Obtain struct information to figure out what members it contains: */ + const EcsStruct *struct_info = ecs_get(world, component, EcsStruct); + ecs_assert(struct_info != NULL, ECS_INTERNAL_ERROR, NULL); + + /* These flags will be set to true if we determine we need to generate a + * hook of a particular type: */ + bool ctor_hook_required = false; + bool dtor_hook_required = false; + bool move_hook_required = false; + bool copy_hook_required = false; + bool valid_cmp = true; + bool valid_equals = true; + + /* Iterate all struct members and see if any member type has hooks. If so, + * the struct itself will need to have that hook: */ + int i, member_count = ecs_vec_count(&struct_info->members); + ecs_member_t *members = ecs_vec_first(&struct_info->members); + ecs_flags32_t flags = 0; + for (i = 0; i < member_count; i++) { + ecs_member_t *m = &members[i]; + const ecs_type_info_t *member_ti = ecs_get_type_info(world, m->type); + ctor_hook_required |= member_ti->hooks.ctor && + member_ti->hooks.ctor != flecs_default_ctor; + dtor_hook_required |= member_ti->hooks.dtor != NULL; + move_hook_required |= member_ti->hooks.move != NULL; + copy_hook_required |= member_ti->hooks.copy != NULL; + /* A struct has a valid cmp/equals hook if all its members have it: */ + valid_cmp &= member_ti->hooks.cmp != NULL; + valid_equals &= member_ti->hooks.equals != NULL; + flags |= member_ti->hooks.flags; + } + + /* If any hook is required, then create a lifecycle context and configure a + * generic hook that will interpret that context: */ + ecs_rtt_struct_ctx_t *rtt_ctx = flecs_rtt_configure_struct_hooks( + world, + ti, + flags, + ctor_hook_required, + dtor_hook_required, + move_hook_required, + copy_hook_required, + valid_cmp, + valid_equals + ); + + if (!rtt_ctx) { + return; /* no hook forwarding required */ + } + + /* At least a hook was configured, therefore examine each struct member to + * build the vector of calls that will then be executed by the generic hook + * handler: */ + for (i = 0; i < member_count; i++) { + ecs_member_t *m = &members[i]; + const ecs_type_info_t *member_ti = ecs_get_type_info(world, m->type); + if (ctor_hook_required) { + ecs_rtt_call_data_t *ctor_data = + ecs_vec_append_t(NULL, &rtt_ctx->vctor, ecs_rtt_call_data_t); + ctor_data->count = m->count ? m->count : 1; + ctor_data->offset = m->offset; + ctor_data->type_info = member_ti; + if (member_ti->hooks.ctor) { + ctor_data->hook.xtor = member_ti->hooks.ctor; + } else { + ctor_data->hook.xtor = flecs_default_ctor; + } + } + if (dtor_hook_required && member_ti->hooks.dtor) { + ecs_rtt_call_data_t *dtor_data = + ecs_vec_append_t(NULL, &rtt_ctx->vdtor, ecs_rtt_call_data_t); + dtor_data->count = m->count ? m->count : 1; + dtor_data->offset = m->offset; + dtor_data->type_info = member_ti; + dtor_data->hook.xtor = member_ti->hooks.dtor; + } + if (move_hook_required) { + ecs_rtt_call_data_t *move_data = + ecs_vec_append_t(NULL, &rtt_ctx->vmove, ecs_rtt_call_data_t); + move_data->offset = m->offset; + move_data->type_info = member_ti; + move_data->count = m->count ? m->count : 1; + if (member_ti->hooks.move) { + move_data->hook.move = member_ti->hooks.move; + } else { + move_data->hook.move = flecs_rtt_default_move; + } + } + if (copy_hook_required) { + ecs_rtt_call_data_t *copy_data = + ecs_vec_append_t(NULL, &rtt_ctx->vcopy, ecs_rtt_call_data_t); + copy_data->offset = m->offset; + copy_data->type_info = member_ti; + copy_data->count = m->count ? m->count : 1; + if (member_ti->hooks.copy) { + copy_data->hook.copy = member_ti->hooks.copy; + } else { + copy_data->hook.copy = flecs_rtt_default_copy; + } + } + if (valid_cmp) { + ecs_rtt_call_data_t *comp_data = + ecs_vec_append_t(NULL, &rtt_ctx->vcmp, ecs_rtt_call_data_t); + comp_data->offset = m->offset; + comp_data->type_info = member_ti; + comp_data->count = 1; + ecs_assert(member_ti->hooks.cmp, ECS_INTERNAL_ERROR, NULL); + comp_data->hook.cmp = member_ti->hooks.cmp; + } + if (valid_equals) { + ecs_rtt_call_data_t *comp_data = + ecs_vec_append_t(NULL, &rtt_ctx->vequals, ecs_rtt_call_data_t); + comp_data->offset = m->offset; + comp_data->type_info = member_ti; + comp_data->count = 1; + ecs_assert(member_ti->hooks.equals, ECS_INTERNAL_ERROR, NULL); + comp_data->hook.equals = member_ti->hooks.equals; + } + } +} + +/* + * + * RTT array support + * + */ + +static +void flecs_rtt_free_lifecycle_array_ctx( + void *ctx) +{ + if (!ctx) { + return; + } + + ecs_os_free(ctx); +} + +/* Generic array constructor. It will invoke the constructor of the underlying + * type for all the elements */ +static +void flecs_rtt_array_ctor( + void *ptr, + int32_t count, /* note: "count" is how many arrays to initialize, not how + many elements are in the array */ + const ecs_type_info_t *type_info) +{ + ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_xtor_t ctor = rtt_ctx->type_info->hooks.ctor; + int i; + for (i = 0; i < count; i++) { + void *arr = ECS_ELEM(ptr, type_info->size, i); + ctor(arr, rtt_ctx->elem_count, rtt_ctx->type_info); + } +} + +/* Generic array constructor. It will invoke the destructor of the underlying + * type for all the elements */ +static +void flecs_rtt_array_dtor( + void *ptr, + int32_t count, /* note: "count" is how many arrays to destroy, not how + many elements are in the array */ + const ecs_type_info_t *type_info) +{ + ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_xtor_t dtor = rtt_ctx->type_info->hooks.dtor; + int i; + for (i = 0; i < count; i++) { + void *arr = ECS_ELEM(ptr, type_info->size, i); + dtor(arr, rtt_ctx->elem_count, rtt_ctx->type_info); + } +} + +/* Generic array move hook. It will invoke the move hook of the underlying + * type for all the elements */ +static +void flecs_rtt_array_move( + void *dst_ptr, + void *src_ptr, + int32_t count, /* note: "count" is how many arrays to move, not how + many elements are in the array */ + const ecs_type_info_t *type_info) +{ + ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_move_t move = rtt_ctx->type_info->hooks.move; + int i; + for (i = 0; i < count; i++) { + void *src_arr = ECS_ELEM(src_ptr, type_info->size, i); + void *dst_arr = ECS_ELEM(dst_ptr, type_info->size, i); + move(dst_arr, src_arr, rtt_ctx->elem_count, rtt_ctx->type_info); + } +} + +/* Generic array copy hook. It will invoke the copy hook of the underlying + * type for all the elements */ +static +void flecs_rtt_array_copy( + void *dst_ptr, + const void *src_ptr, + int32_t count, /* note: "count" is how many arrays to copy, not how + many elements are in the array */ + const ecs_type_info_t *type_info) +{ + ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_copy_t copy = rtt_ctx->type_info->hooks.copy; + int i; + for (i = 0; i < count; i++) { + const void *src_arr = ECS_ELEM(src_ptr, type_info->size, i); + void *dst_arr = ECS_ELEM(dst_ptr, type_info->size, i); + copy(dst_arr, src_arr, rtt_ctx->elem_count, rtt_ctx->type_info); + } +} + +/* Generic array compare hook. It will invoke the compare hook of the underlying + * type for each element */ +static +int flecs_rtt_array_cmp( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return 0; + } + + ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_cmp_t cmp = rtt_ctx->type_info->hooks.cmp; + ecs_assert(cmp, ECS_INVALID_PARAMETER, NULL); + ecs_size_t element_size = rtt_ctx->type_info->size; + int i; + for (i = 0; i < rtt_ctx->elem_count; i++) { + const void *a_element = ECS_ELEM(a_ptr, element_size, i); + const void *b_element = ECS_ELEM(b_ptr, element_size, i); + int c = cmp(a_element, b_element, rtt_ctx->type_info); + if(c != 0) { + return c; + } + } + return 0; +} + +/* Generic array equals hook. It will invoke the equals hook of the underlying + * type for each element */ +static +bool flecs_rtt_array_equals( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return true; + } + + ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_equals_t equals = rtt_ctx->type_info->hooks.equals; + ecs_assert(equals, ECS_INVALID_PARAMETER, NULL); + ecs_size_t element_size = rtt_ctx->type_info->size; + int i; + for (i = 0; i < rtt_ctx->elem_count; i++) { + const void *a_element = ECS_ELEM(a_ptr, element_size, i); + const void *b_element = ECS_ELEM(b_ptr, element_size, i); + bool eq = equals(a_element, b_element, rtt_ctx->type_info); + if(!eq) { + return false; + } + } + return true; +} + +/* Checks if an array's underlying type has hooks installed. If so, it generates + * and installs required hooks for the array type itself. These hooks will + * invoke the underlying type's hook for each element in the array. */ +static +void flecs_rtt_init_default_hooks_array( + ecs_world_t *world, + ecs_entity_t component) +{ + const EcsArray *array_info = ecs_get(world, component, EcsArray); + ecs_assert(array_info != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *element_ti = + ecs_get_type_info(world, array_info->type); + ecs_flags32_t flags = element_ti->hooks.flags; + bool ctor_hook_required = + element_ti->hooks.ctor && element_ti->hooks.ctor != flecs_default_ctor; + bool dtor_hook_required = element_ti->hooks.dtor != NULL; + bool move_hook_required = element_ti->hooks.move != NULL; + bool copy_hook_required = element_ti->hooks.copy != NULL; + bool valid_cmp = element_ti->hooks.cmp != NULL && !(flags & ECS_TYPE_HOOK_CMP_ILLEGAL); + bool valid_equals = element_ti->hooks.equals != NULL && !(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL); + + + ecs_type_hooks_t hooks = *ecs_get_hooks_id(world, component); + + hooks.ctor = ctor_hook_required && !(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) ? + flecs_rtt_array_ctor : NULL; + hooks.dtor = dtor_hook_required && !(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) ? + flecs_rtt_array_dtor : NULL; + hooks.move = move_hook_required && !(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) ? + flecs_rtt_array_move : NULL; + hooks.copy = copy_hook_required && !(flags & ECS_TYPE_HOOK_COPY_ILLEGAL) ? + flecs_rtt_array_copy : NULL; + hooks.cmp = valid_cmp && !(flags & ECS_TYPE_HOOK_CMP_ILLEGAL) ? + flecs_rtt_array_cmp : NULL; + hooks.equals = valid_equals && !(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) ? + flecs_rtt_array_equals : NULL; + + + if (hooks.lifecycle_ctx_free) { + hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); + hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_nop; + } + + if (hooks.ctor || hooks.dtor || hooks.move || + hooks.copy || hooks.cmp || hooks.equals) + { + ecs_rtt_array_ctx_t *rtt_ctx = ecs_os_malloc_t(ecs_rtt_array_ctx_t); + rtt_ctx->type_info = element_ti; + rtt_ctx->elem_count = array_info->count; + if (hooks.lifecycle_ctx_free) { + hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); + } + + hooks.lifecycle_ctx = rtt_ctx; + hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_array_ctx; + } + + hooks.flags = flags; + hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; + ecs_set_hooks_id(world, component, &hooks); +} + +/* + * + * RTT vector support + * + */ + +static +void flecs_rtt_free_lifecycle_vector_ctx( + void *ctx) +{ + if (!ctx) { + return; + } + + ecs_os_free(ctx); +} + +/* Generic vector constructor. Makes sure the vector structure is initialized to + * 0 elements */ +static +void flecs_rtt_vector_ctor( + void *ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + ecs_rtt_vector_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + int i; + for (i = 0; i < count; i++) { + ecs_vec_t *vec = ECS_ELEM(ptr, type_info->size, i); + ecs_vec_init(NULL, vec, rtt_ctx->type_info->size, 0); + } +} + +/* Generic vector destructor. It will invoke the destructor for each element of + * the vector and finalize resources associated to the vector itself. */ +static +void flecs_rtt_vector_dtor( + void *ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + ecs_rtt_vector_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_xtor_t dtor = rtt_ctx->type_info->hooks.dtor; + int i; + for (i = 0; i < count; i++) { + ecs_vec_t *vec = ECS_ELEM(ptr, type_info->size, i); + int32_t num_elements = ecs_vec_count(vec); + if (dtor && num_elements) { + dtor(ecs_vec_first(vec), num_elements, rtt_ctx->type_info); + } + ecs_vec_fini(NULL, vec, rtt_ctx->type_info->size); + } +} + +/* Generic vector move hook. */ +static +void flecs_rtt_vector_move( + void *dst_ptr, + void *src_ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + flecs_rtt_vector_dtor(dst_ptr, count, type_info); + int i; + for (i = 0; i < count; i++) { + ecs_vec_t *src_vec = ECS_ELEM(src_ptr, type_info->size, i); + ecs_vec_t *dst_vec = ECS_ELEM(dst_ptr, type_info->size, i); + *dst_vec = *src_vec; + src_vec->array = NULL; + src_vec->count = 0; + } +} + +/* Generic vector copy hook. It makes a deep copy of vector contents */ +static +void flecs_rtt_vector_copy( + void *dst_ptr, + const void *src_ptr, + int32_t count, + const ecs_type_info_t *type_info) +{ + ecs_rtt_vector_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + flecs_rtt_vector_dtor(dst_ptr, count, type_info); + ecs_copy_t copy = rtt_ctx->type_info->hooks.copy + ? rtt_ctx->type_info->hooks.copy + : flecs_rtt_default_copy; + ecs_xtor_t ctor = rtt_ctx->type_info->hooks.ctor + ? rtt_ctx->type_info->hooks.ctor + : flecs_default_ctor; + ecs_xtor_t dtor = rtt_ctx->type_info->hooks.dtor; + int i; + for (i = 0; i < count; i++) { + const ecs_vec_t *src_vec = ECS_ELEM(src_ptr, type_info->size, i); + ecs_vec_t *dst_vec = ECS_ELEM(dst_ptr, type_info->size, i); + ecs_vec_init_if(dst_vec, rtt_ctx->type_info->size); + int32_t src_count = ecs_vec_count(src_vec); + int32_t dst_count = ecs_vec_count(dst_vec); + if (dtor && dst_count) { + dtor(ecs_vec_first(dst_vec), dst_count, rtt_ctx->type_info); + } + ecs_vec_set_count(NULL, dst_vec, rtt_ctx->type_info->size, src_count); + ctor(ecs_vec_first(dst_vec), src_count, rtt_ctx->type_info); + copy( + ecs_vec_first(dst_vec), + ecs_vec_first(src_vec), + src_count, + rtt_ctx->type_info); + } +} + +/* Generic vector compare hook. */ +static +int flecs_rtt_vector_cmp( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return 0; + } + + const ecs_vec_t *vec_a = a_ptr; + const ecs_vec_t *vec_b = b_ptr; + + ecs_size_t count_a = ecs_vec_count(vec_a); + ecs_size_t count_b = ecs_vec_count(vec_b); + { + int c = count_a - count_b; + if(c != 0) { + return c; + } + } + + ecs_rtt_vector_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_cmp_t cmp = rtt_ctx->type_info->hooks.cmp; + ecs_assert(cmp, ECS_INVALID_PARAMETER, NULL); + + ecs_size_t element_size = rtt_ctx->type_info->size; + const void *a = ecs_vec_first(vec_a); + const void *b = ecs_vec_first(vec_b); + + int i; + for (i = 0; i < count_a; i++) { + const void *a_element = ECS_ELEM(a, element_size, i); + const void *b_element = ECS_ELEM(b, element_size, i); + int c = cmp(a_element, b_element, rtt_ctx->type_info); + if(c != 0) { + return c; + } + } + return 0; +} + +/* Generic vector equals hook. */ +static +bool flecs_rtt_vector_equals( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return true; + } + + const ecs_vec_t *vec_a = a_ptr; + const ecs_vec_t *vec_b = b_ptr; + + ecs_size_t count_a = ecs_vec_count(vec_a); + ecs_size_t count_b = ecs_vec_count(vec_b); + { + int c = count_a - count_b; + if(c != 0) { + return c; + } + } + + ecs_rtt_vector_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_equals_t equals = rtt_ctx->type_info->hooks.equals; + ecs_assert(equals, ECS_INVALID_PARAMETER, NULL); + + ecs_size_t element_size = rtt_ctx->type_info->size; + const void *a = ecs_vec_first(vec_a); + const void *b = ecs_vec_first(vec_b); + + int i; + for (i = 0; i < count_a; i++) { + const void *a_element = ECS_ELEM(a, element_size, i); + const void *b_element = ECS_ELEM(b, element_size, i); + int eq = equals(a_element, b_element, rtt_ctx->type_info); + if(!eq) { + return false; + } + } + return true; +} + +/* Generates and installs required hooks for managing the vector and underlying + * type lifecycle. Vectors always have hooks because at the very least the + * vector structure itself must be initialized/destroyed/copied/moved, even if + * empty. */ +static +void flecs_rtt_init_default_hooks_vector( + ecs_world_t *world, + ecs_entity_t component) +{ + const EcsVector *vector_info = ecs_get(world, component, EcsVector); + ecs_assert(vector_info != NULL, ECS_INTERNAL_ERROR, NULL); + if (!ecs_is_alive(world, vector_info->type)) { + ecs_err("vector '%s' has invalid element type", ecs_get_name(world, component)); + return; + } + + const ecs_type_info_t *element_ti = + ecs_get_type_info(world, vector_info->type); + if (!element_ti) { + ecs_err("vector '%s' has invalid element type", ecs_get_name(world, component)); + return; + } + + ecs_rtt_vector_ctx_t *rtt_ctx = ecs_os_malloc_t(ecs_rtt_vector_ctx_t); + rtt_ctx->type_info = element_ti; + ecs_flags32_t flags = element_ti->hooks.flags; + + ecs_type_hooks_t hooks = *ecs_get_hooks_id(world, component); + + if (hooks.lifecycle_ctx_free) { + hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); + } + hooks.lifecycle_ctx = rtt_ctx; + hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_vector_ctx; + + hooks.ctor = flecs_rtt_vector_ctor; + hooks.dtor = flecs_rtt_vector_dtor; + hooks.move = flecs_rtt_vector_move; + hooks.copy = flecs_rtt_vector_copy; + + if (element_ti->hooks.cmp != NULL && !(flags & ECS_TYPE_HOOK_CMP_ILLEGAL)) { + hooks.cmp = flecs_rtt_vector_cmp; + } else { + hooks.cmp = NULL; + } + + if (element_ti->hooks.equals != NULL && !(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL)) { + hooks.equals = flecs_rtt_vector_equals; + } else { + hooks.equals = NULL; + } + + + /* propagate only the compare/equals hook illegal flag, if set */ + hooks.flags |= flags & (ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL); + + hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; + ecs_set_hooks_id(world, component, &hooks); +} + +void flecs_rtt_init_default_hooks( + ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + EcsType *type_field = ecs_field(it, EcsType, 0); + + int i; + for (i = 0; i < it->count; i++) { + EcsType *type = &type_field[i]; + if (type->existing) { + continue; /* non-rtt type. Ignore. */ + } + + /* If a component is defined from reflection data, configure appropriate + * default hooks. + * - For trivial types, at least set a default constructor so memory is + * zero-initialized + * - For struct types, configure a hook that in turn calls hooks of + * member types, if those member types have hooks defined themselves. + * - For array types, configure a hook that in turn calls hooks for the + * underlying type, for each element in the array. + * - For vector types, configure hooks to manage the vector structure + * itself, move the vector and deep-copy vector elements + * */ + + ecs_entity_t component = it->entities[i]; + + /* Skip configuring hooks for ids already in use */ + const ecs_world_t* w = ecs_get_world(world); + if(ecs_id_in_use(w, component) || + ecs_id_in_use(w, ecs_pair(component, EcsWildcard))) { + continue; + } + + const ecs_type_info_t *ti = ecs_get_type_info(world, component); + ecs_assert(ti,ECS_INTERNAL_ERROR,NULL); + + if (type->kind == EcsStructType) { + flecs_rtt_init_default_hooks_struct(world, component, ti); + } else if (type->kind == EcsArrayType) { + flecs_rtt_init_default_hooks_array(world, component); + } else if (type->kind == EcsVectorType) { + flecs_rtt_init_default_hooks_vector(world, component); + } + + ecs_type_hooks_t hooks = ti->hooks; + /* Make sure there is at least a default constructor. This ensures that + * a new component value does not contain uninitialized memory, which + * could cause serializers to crash when for example inspecting string + * fields. */ + if(!ti->hooks.ctor && !(ti->hooks.flags & ECS_TYPE_HOOK_CTOR_ILLEGAL)) { + hooks.ctor = flecs_default_ctor; + } + + hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; + ecs_set_hooks_id( + world, + component, + &hooks); + } +} + +#endif + +/** + * @file addons/meta/serializer.c + * @brief Build instruction list for serializing/deserializing types. + */ + + +#ifdef FLECS_META + +static +int flecs_meta_serialize_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops, + ecs_vec_t *stack); + +static +int flecs_meta_serialize_push_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_vec_t *stack) +{ + ecs_entity_t *types = ecs_vec_first(stack); + int32_t i, count = ecs_vec_count(stack); + for (i = 0; i < count; i ++) { + if (types[i] == type) { + char *path = ecs_get_path(world, type); + ecs_err("recursive type definition for '%s'", path); + ecs_os_free(path); + return -1; + } + } + + ecs_vec_append_t(NULL, stack, ecs_entity_t)[0] = type; + return 0; +} + +static +void flecs_meta_serialize_pop_type( + ecs_vec_t *stack) +{ + ecs_assert(ecs_vec_count(stack) > 0, ECS_INTERNAL_ERROR, NULL); + ecs_vec_remove_last(stack); +} + +/* Serialize a primitive value */ +int flecs_expr_ser_primitive( + const ecs_world_t *world, + ecs_primitive_kind_t kind, + const void *base, + ecs_strbuf_t *str, + bool is_expr) +{ + switch(kind) { + case EcsBool: + if (*(const bool*)base) { + ecs_strbuf_appendlit(str, "true"); + } else { + ecs_strbuf_appendlit(str, "false"); + } + break; + case EcsChar: { + char chbuf[3]; + char ch = *(const char*)base; + if (ch) { + flecs_chresc(chbuf, *(const char*)base, '"'); + if (is_expr) ecs_strbuf_appendch(str, '"'); + ecs_strbuf_appendstr(str, chbuf); + if (is_expr) ecs_strbuf_appendch(str, '"'); + } else { + ecs_strbuf_appendch(str, '0'); + } + break; + } + case EcsByte: + ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base)); + break; + case EcsU8: + ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base)); + break; + case EcsU16: + ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint16_t*)base)); + break; + case EcsU32: + ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint32_t*)base)); + break; + case EcsU64: + ecs_strbuf_append(str, "%llu", *(const uint64_t*)base); + break; + case EcsI8: + ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int8_t*)base)); + break; + case EcsI16: + ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int16_t*)base)); + break; + case EcsI32: + ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int32_t*)base)); + break; + case EcsI64: + ecs_strbuf_appendint(str, *(const int64_t*)base); + break; + case EcsF32: + ecs_strbuf_appendflt(str, (double)*(const float*)base, 0); + break; + case EcsF64: + ecs_strbuf_appendflt(str, *(const double*)base, 0); + break; + case EcsIPtr: + ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const intptr_t*)base)); + break; + case EcsUPtr: + ecs_strbuf_append(str, "%u", *(const uintptr_t*)base); + break; + case EcsString: { + const char *value = *ECS_CONST_CAST(const char**, base); + if (value) { + if (!is_expr) { + ecs_strbuf_appendstr(str, value); + } else { + ecs_size_t length = flecs_stresc(NULL, 0, '"', value); + if (length == ecs_os_strlen(value)) { + ecs_strbuf_appendch(str, '"'); + ecs_strbuf_appendstrn(str, value, length); + ecs_strbuf_appendch(str, '"'); + } else { + char *out = ecs_os_malloc(length + 3); + flecs_stresc(out + 1, length, '"', value); + out[0] = '"'; + out[length + 1] = '"'; + out[length + 2] = '\0'; + ecs_strbuf_appendstr(str, out); + ecs_os_free(out); + } + } + } else { + ecs_strbuf_appendlit(str, "null"); + } + break; + } + case EcsEntity: { + ecs_entity_t e = *(const ecs_entity_t*)base; + if (!e) { + ecs_strbuf_appendlit(str, "#0"); + } else { + ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str, false); + } + break; + } + case EcsId: { + ecs_id_t id = *(const ecs_id_t*)base; + if (!id) { + ecs_strbuf_appendlit(str, "#0"); + } else { + ecs_id_str_buf(world, id, str); + } + break; + } + default: + ecs_err("invalid primitive kind"); + return -1; + } + + return 0; +} + +ecs_meta_op_kind_t flecs_meta_primitive_to_op_kind( + ecs_primitive_kind_t kind) +{ + switch(kind) { + case EcsBool: return EcsOpBool; + case EcsChar: return EcsOpChar; + case EcsByte: return EcsOpByte; + case EcsU8: return EcsOpU8; + case EcsU16: return EcsOpU16; + case EcsU32: return EcsOpU32; + case EcsU64: return EcsOpU64; + case EcsI8: return EcsOpI8; + case EcsI16: return EcsOpI16; + case EcsI32: return EcsOpI32; + case EcsI64: return EcsOpI64; + case EcsF32: return EcsOpF32; + case EcsF64: return EcsOpF64; + case EcsUPtr: return EcsOpUPtr; + case EcsIPtr: return EcsOpIPtr; + case EcsString: return EcsOpString; + case EcsEntity: return EcsOpEntity; + case EcsId: return EcsOpId; + default: ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +static +ecs_meta_op_t* flecs_meta_ops_add(ecs_vec_t *ops, ecs_meta_op_kind_t kind) { + ecs_meta_op_t *op = ecs_vec_append_t(NULL, ops, ecs_meta_op_t); + op->kind = kind; + op->offset = 0; + op->op_count = 1; + op->type_info = NULL; + op->name = NULL; + op->is.members = NULL; + op->type = 0; + op->member_index = 0; + return op; +} + +static +ecs_meta_op_t* flecs_meta_ops_get(ecs_vec_t *ops, int32_t index) { + ecs_meta_op_t* op = ecs_vec_get_t(ops, ecs_meta_op_t, index); + ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); + return op; +} + +static +int flecs_meta_serialize_primitive( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); + if (!ptr) { + char *name = ecs_get_path(world, type); + ecs_err("entity '%s' is not a primitive type", name); + ecs_os_free(name); + return -1; + } + + ecs_meta_op_t *op = flecs_meta_ops_add(ops, + flecs_meta_primitive_to_op_kind(ptr->kind)); + op->offset = offset; + op->type = type; + op->type_info = ecs_get_type_info(world, type); + ecs_assert(op->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + return 0; +} + +static +int flecs_meta_serialize_enum( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + (void)world; + + ecs_meta_op_t *op = flecs_meta_ops_add(ops, EcsOpEnum); + op->offset = offset; + op->type = type; + op->type_info = ecs_get_type_info(world, type); + ecs_assert(op->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + + const EcsConstants *constants = ecs_get(world, type, EcsConstants); + ecs_assert(constants != NULL, ECS_INVALID_PARAMETER, NULL); + op->is.constants = constants->constants; + ecs_assert(op->is.constants != NULL, ECS_INTERNAL_ERROR, NULL); + + const EcsEnum *enum_type = ecs_get(world, type, EcsEnum); + ecs_assert(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_entity_t underlying = enum_type->underlying_type; + ecs_assert(underlying != 0, ECS_INTERNAL_ERROR, NULL); + + const EcsPrimitive *prim_type = ecs_get(world, underlying, EcsPrimitive); + ecs_assert(prim_type != NULL, ECS_INTERNAL_ERROR, NULL); + + switch(prim_type->kind) { + case EcsU8: + op->underlying_kind = EcsOpU8; + break; + case EcsU16: + op->underlying_kind = EcsOpU16; + break; + case EcsU32: + op->underlying_kind = EcsOpU32; + break; + case EcsUPtr: + op->underlying_kind = EcsOpUPtr; + break; + case EcsU64: + op->underlying_kind = EcsOpU64; + break; + case EcsI8: + op->underlying_kind = EcsOpI8; + break; + case EcsI16: + op->underlying_kind = EcsOpI16; + break; + case EcsI32: + op->underlying_kind = EcsOpI32; + break; + case EcsIPtr: + op->underlying_kind = EcsOpIPtr; + break; + case EcsI64: + op->underlying_kind = EcsOpI64; + break; + case EcsBool: + case EcsChar: + case EcsByte: + case EcsF32: + case EcsF64: + case EcsString: + case EcsEntity: + case EcsId: + ecs_abort(ECS_INTERNAL_ERROR, + "invalid primitive type kind for underlying enum type"); + } + + return 0; +} + +static +int flecs_meta_serialize_bitmask( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + (void)world; + + ecs_meta_op_t *op = flecs_meta_ops_add(ops, EcsOpBitmask); + op->offset = offset; + op->type = type; + op->type_info = ecs_get_type_info(world, type); + ecs_assert(op->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + + const EcsConstants *bitmask_type = ecs_get(world, type, EcsConstants); + ecs_assert(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); + op->is.constants = bitmask_type->constants; + ecs_assert(op->is.constants != NULL, ECS_INTERNAL_ERROR, NULL); + + return 0; +} + +static +int flecs_meta_serialize_array_inline( + ecs_world_t *world, + ecs_entity_t elem_type, + int32_t count, + ecs_size_t offset, + ecs_vec_t *ops, + ecs_vec_t *stack) +{ + int32_t first = ecs_vec_count(ops); + + { + ecs_meta_op_t *op = flecs_meta_ops_add(ops, EcsOpPushArray); + op->type = elem_type; + op->type_info = NULL; + op->elem_size = flecs_type_size(world, elem_type); + op->offset = offset; + } + + if (flecs_meta_serialize_type(world, elem_type, 0, ops, stack) != 0) { + return -1; + } + + { + ecs_meta_op_t *op = flecs_meta_ops_add(ops, EcsOpPop); + op->type = elem_type; + op->type_info = NULL; + op->offset = 0; + op->elem_size = count; + } + + flecs_meta_ops_get(ops, first)->op_count = + flecs_ito(int16_t, ecs_vec_count(ops) - first); + + return 0; +} + +static +int flecs_meta_serialize_array_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_vec_t *ops, + ecs_vec_t *stack) +{ + const EcsArray *ptr = ecs_get(world, type, EcsArray); + if (!ptr) { + return -1; + } + + return flecs_meta_serialize_array_inline( + world, ptr->type, ptr->count, 0, ops, stack); +} + +static +int flecs_meta_serialize_vector_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_vec_t *ops, + ecs_vec_t *stack) +{ + const EcsVector *ptr = ecs_get(world, type, EcsVector); + if (!ptr) { + return -1; /* Should never happen, will trigger internal error */ + } + + int32_t first = ecs_vec_count(ops); + + { + ecs_meta_op_t *op = flecs_meta_ops_add(ops, EcsOpPushVector); + op->offset = 0; + op->type = type; + op->type_info = ecs_get_type_info(world, type); + ecs_assert(op->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + op->elem_size = flecs_type_size(world, ptr->type); + } + + if (flecs_meta_serialize_type(world, ptr->type, 0, ops, stack) != 0) { + return -1; + } + + { + ecs_meta_op_t *op = flecs_meta_ops_add(ops, EcsOpPop); + op->offset = 0; + op->type = type; + op->type_info = ecs_get_type_info(world, type); + ecs_assert(op->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + } + + flecs_meta_ops_get(ops, first)->op_count = + flecs_ito(int16_t, ecs_vec_count(ops) - first); + + return 0; +} + +static +int flecs_meta_serialize_forward( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + (void)world; + ecs_meta_op_t *op = flecs_meta_ops_add(ops, EcsOpForward); + op->offset = offset; + op->type = type; + op->type_info = ecs_get_type_info(world, type); + ecs_assert(op->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + return 0; +} + +static +int flecs_meta_serialize_opaque_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops) +{ + (void)world; + const EcsOpaque *o = ecs_get(world, type, EcsOpaque); + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + const EcsType *t = ecs_get(world, o->as_type, EcsType); + ecs_assert(t != NULL, ECS_INTERNAL_ERROR, + "missing reflection for Opaque::as_type"); + + ecs_meta_op_kind_t kind = EcsOpOpaqueValue; + if (t->kind == EcsArrayType) { + kind = EcsOpOpaqueArray; + } else if (t->kind == EcsVectorType) { + kind = EcsOpOpaqueVector; + } else if (t->kind == EcsStructType) { + kind = EcsOpOpaqueStruct; + } + + ecs_meta_op_t *op = flecs_meta_ops_add(ops, kind); + op->offset = offset; + op->type = type; + op->type_info = ecs_get_type_info(world, type); + ecs_assert(op->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + op->is.opaque = o->serialize; + return 0; +} + +static +int flecs_meta_serialize_struct( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops, + ecs_vec_t *stack) +{ + const EcsStruct *ptr = ecs_get(world, type, EcsStruct); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t cur, first = ecs_vec_count(ops); + ecs_meta_op_t *op = flecs_meta_ops_add(ops, EcsOpPushStruct); + op->offset = offset; + op->type = type; + op->type_info = ecs_get_type_info(world, type); + ecs_assert(op->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_member_t *members = ecs_vec_first(&ptr->members); + int32_t i, count = ecs_vec_count(&ptr->members); + + ecs_hashmap_t *member_index = NULL; + if (count) { + op->is.members = member_index = flecs_name_index_new(&world->allocator); + } + + for (i = 0; i < count; i ++) { + ecs_member_t *member = &members[i]; + + cur = ecs_vec_count(ops); + + if (member->count >= 1) { + if (flecs_meta_serialize_array_inline( + world, member->type, member->count, member->offset, ops, stack)) + { + continue; + } + + op = flecs_meta_ops_get(ops, cur); + } else { + if (flecs_meta_serialize_type( + world, member->type, member->offset, ops, stack)) + { + continue; + } + + op = flecs_meta_ops_get(ops, cur); + if (!op->type) { + op->type = member->type; + } + + op->op_count = flecs_ito(int16_t, ecs_vec_count(ops) - cur); + } + + const char *member_name = member->name; + op->name = member_name; + op->member_index = flecs_ito(int16_t, i); + + flecs_name_index_ensure( + member_index, flecs_ito(uint64_t, cur - first - 1), + member_name, 0, 0); + } + + ecs_meta_op_t *pop = flecs_meta_ops_add(ops, EcsOpPop); + pop->type = type; + flecs_meta_ops_get(ops, first)->op_count = + flecs_ito(int16_t, ecs_vec_count(ops) - first); + + return 0; +} + +static +int flecs_meta_serialize_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t offset, + ecs_vec_t *ops, + ecs_vec_t *stack) +{ + const EcsType *ptr = ecs_get(world, type, EcsType); + if (!ptr) { + char *path = ecs_get_path(world, type); + ecs_err("missing reflection data for type '%s'", path); + ecs_os_free(path); + return -1; + } + + if (flecs_meta_serialize_push_type(world, type, stack)) { + return -1; + } + + int ret = 0; + switch(ptr->kind) { + case EcsPrimitiveType: + ret = flecs_meta_serialize_primitive(world, type, offset, ops); + break; + case EcsEnumType: + ret = flecs_meta_serialize_enum(world, type, offset, ops); + break; + case EcsBitmaskType: + ret = flecs_meta_serialize_bitmask(world, type, offset, ops); + break; + case EcsStructType: + ret = flecs_meta_serialize_struct(world, type, offset, ops, stack); + break; + case EcsArrayType: + ret = flecs_meta_serialize_forward(world, type, offset, ops); + break; + case EcsVectorType: + ret = flecs_meta_serialize_forward(world, type, offset, ops); + break; + case EcsOpaqueType: + ret = flecs_meta_serialize_opaque_type(world, type, offset, ops); + break; + } + + flecs_meta_serialize_pop_type(stack); + + return ret; +} + +void flecs_meta_type_serializer_init( + ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t type = it->entities[i]; + ecs_vec_t ops; + ecs_vec_init_t(NULL, &ops, ecs_meta_op_t, 0); + ecs_vec_t stack; + ecs_vec_init_t(NULL, &stack, ecs_entity_t, 0); + + const EcsType *type_ptr = ecs_get(world, type, EcsType); + if (!type_ptr) { + char *path = ecs_get_path(world, type); + ecs_err("missing reflection data for type '%s'", path); + ecs_os_free(path); + ecs_vec_fini_t(NULL, &stack, ecs_entity_t); + continue; + } + + int ret; + if (type_ptr->kind == EcsArrayType) { + ret = flecs_meta_serialize_array_type(world, type, &ops, &stack); + } else if (type_ptr->kind == EcsVectorType) { + ret = flecs_meta_serialize_vector_type(world, type, &ops, &stack); + } else { + ret = flecs_meta_serialize_type(world, type, 0, &ops, &stack); + } + + if (ret != 0) { + ecs_vec_fini_t(NULL, &ops, ecs_meta_op_t); + ecs_vec_fini_t(NULL, &stack, ecs_entity_t); + continue; + } + + EcsTypeSerializer *ptr = ecs_ensure(world, type, EcsTypeSerializer); + if (ptr->ops.array) { + flecs_type_serializer_dtor(ptr); + } + + ptr->kind = type_ptr->kind; + ptr->ops = ops; + ecs_vec_fini_t(NULL, &stack, ecs_entity_t); + } +} + +char* ecs_meta_serializer_to_str( + ecs_world_t *world, + ecs_entity_t type) +{ + const EcsTypeSerializer *ptr = ecs_get( + world, type, EcsTypeSerializer); + if (!ptr) { + return NULL; + } + + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_meta_op_t *ops = ecs_vec_first(&ptr->ops); + int32_t i, count = ecs_vec_count(&ptr->ops); + for (i = 0; i < count; i ++) { + ecs_meta_op_t *op = &ops[i]; + const char *kind_str = flecs_meta_op_kind_str(op->kind); + ecs_strbuf_append(&buf, "%s", kind_str); + ecs_strbuf_append(&buf, "%*s", 10 - ecs_os_strlen(kind_str), " "); + + if (op->type) { + char *type_str = ecs_id_str(world, op->type); + ecs_strbuf_append(&buf, "%s ", type_str); + ecs_os_free(type_str); + } + + if (op->name) { + ecs_strbuf_append(&buf, "%s: ", op->name); + } + + ecs_strbuf_appendstr(&buf, "{ "); + + ecs_strbuf_append(&buf, "offset: %d, size: %d ", + op->offset, op->type_info ? op->type_info->size : 0); + + ecs_strbuf_append(&buf, ", op_count = %d ", + op->op_count); + + ecs_strbuf_appendstr(&buf, "}"); + + ecs_strbuf_appendlit(&buf, "\n"); + } + + return ecs_strbuf_get(&buf); +} + +#endif + +/** + * @file addons/os_api_impl/os_api_impl.c + * @brief Builtin implementation for OS API. + */ + + +#ifdef FLECS_OS_API_IMPL +#ifdef ECS_TARGET_WINDOWS +/** + * @file addons/os_api_impl/posix_impl.inl + * @brief Builtin Windows implementation for OS API. + */ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include + +typedef struct ecs_win_thread_t { + HANDLE thread; + ecs_os_thread_callback_t callback; + void *arg; +} ecs_win_thread_t; + +static +DWORD flecs_win_thread(void *ptr) { + ecs_win_thread_t *thread = ptr; + thread->callback(thread->arg); + return 0; +} + +static +ecs_os_thread_t win_thread_new( + ecs_os_thread_callback_t callback, + void *arg) +{ + ecs_win_thread_t *thread = ecs_os_malloc_t(ecs_win_thread_t); + thread->arg= arg; + thread->callback = callback; + thread->thread = CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)flecs_win_thread, thread, 0, NULL); + return (ecs_os_thread_t)(uintptr_t)thread; +} + +static +void* win_thread_join( + ecs_os_thread_t thr) +{ + ecs_win_thread_t *thread = (ecs_win_thread_t*)(uintptr_t)thr; + DWORD r = WaitForSingleObject(thread->thread, INFINITE); + if (r == WAIT_FAILED) { + ecs_err("win_thread_join: WaitForSingleObject failed"); + } + ecs_os_free(thread); + return NULL; +} + +static +ecs_os_thread_id_t win_thread_self(void) +{ + return (ecs_os_thread_id_t)GetCurrentThreadId(); +} + +static +int32_t win_ainc( + int32_t *count) +{ + return InterlockedIncrement((volatile long*)count); +} + +static +int32_t win_adec( + int32_t *count) +{ + return InterlockedDecrement((volatile long*)count); +} + +static +int64_t win_lainc( + int64_t *count) +{ + return InterlockedIncrement64(count); +} + +static +int64_t win_ladec( + int64_t *count) +{ + return InterlockedDecrement64(count); +} + +static +ecs_os_mutex_t win_mutex_new(void) { + CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); + InitializeCriticalSection(mutex); + return (ecs_os_mutex_t)(uintptr_t)mutex; +} + +static +void win_mutex_free( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + DeleteCriticalSection(mutex); + ecs_os_free(mutex); +} + +static +void win_mutex_lock( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + EnterCriticalSection(mutex); +} + +static +void win_mutex_unlock( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + LeaveCriticalSection(mutex); +} + +static +ecs_os_cond_t win_cond_new(void) { + CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); + InitializeConditionVariable(cond); + return (ecs_os_cond_t)(uintptr_t)cond; +} + +static +void win_cond_free( + ecs_os_cond_t c) +{ + ecs_os_free((CONDITION_VARIABLE*)(uintptr_t)c); +} + +static +void win_cond_signal( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeConditionVariable(cond); +} + +static +void win_cond_broadcast( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeAllConditionVariable(cond); +} + +static +void win_cond_wait( + ecs_os_cond_t c, + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + SleepConditionVariableCS(cond, mutex, INFINITE); +} + +static bool win_time_initialized; +static double win_time_freq; +static LARGE_INTEGER win_time_start; +static ULONG win_current_resolution; + +static +void win_time_setup(void) { + if ( win_time_initialized) { + return; + } + + win_time_initialized = true; + + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&win_time_start); + win_time_freq = (double)freq.QuadPart / 1000000000.0; +} + +static +void win_sleep( + int32_t sec, + int32_t nanosec) +{ + HANDLE timer; + LARGE_INTEGER ft; + + ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); + + timer = CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +} + +static +void win_enable_high_timer_resolution(bool enable) +{ + HMODULE hntdll = GetModuleHandle(TEXT("ntdll.dll")); + if (!hntdll) { + return; + } + + union { + LONG (__stdcall *f)( + ULONG desired, BOOLEAN set, ULONG * current); + FARPROC p; + } func; + + func.p = GetProcAddress(hntdll, "NtSetTimerResolution"); + if(!func.p) { + return; + } + + ULONG current, resolution = 10000; /* 1 ms */ + + if (!enable && win_current_resolution) { + func.f(win_current_resolution, 0, ¤t); + win_current_resolution = 0; + return; + } else if (!enable) { + return; + } + + if (resolution == win_current_resolution) { + return; + } + + if (win_current_resolution) { + func.f(win_current_resolution, 0, ¤t); + } + + if (func.f(resolution, 1, ¤t)) { + /* Try setting a lower resolution */ + resolution *= 2; + if(func.f(resolution, 1, ¤t)) return; + } + + win_current_resolution = resolution; +} + +static +uint64_t win_time_now(void) { + uint64_t now; + + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t)((double)qpc_t.QuadPart / win_time_freq); + + return now; +} + +static +void win_fini(void) { + if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { + win_enable_high_timer_resolution(false); + } +} + +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); + + ecs_os_api_t api = ecs_os_api; + + api.thread_new_ = win_thread_new; + api.thread_join_ = win_thread_join; + api.thread_self_ = win_thread_self; + api.task_new_ = win_thread_new; + api.task_join_ = win_thread_join; + api.ainc_ = win_ainc; + api.adec_ = win_adec; + api.lainc_ = win_lainc; + api.ladec_ = win_ladec; + api.mutex_new_ = win_mutex_new; + api.mutex_free_ = win_mutex_free; + api.mutex_lock_ = win_mutex_lock; + api.mutex_unlock_ = win_mutex_unlock; + api.cond_new_ = win_cond_new; + api.cond_free_ = win_cond_free; + api.cond_signal_ = win_cond_signal; + api.cond_broadcast_ = win_cond_broadcast; + api.cond_wait_ = win_cond_wait; + api.sleep_ = win_sleep; + api.now_ = win_time_now; + api.fini_ = win_fini; + + win_time_setup(); + + if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { + win_enable_high_timer_resolution(true); + } + + ecs_os_set_api(&api); +} + +#else +/** + * @file addons/os_api_impl/posix_impl.inl + * @brief Builtin POSIX implementation for OS API. + */ + +#include "pthread.h" + +#if defined(__APPLE__) && defined(__MACH__) +#include +#elif defined(__EMSCRIPTEN__) +#include +#else +#include +#endif + +/* This mutex is used to emulate atomic operations when the gnu builtins are + * not supported. This is probably not very fast but if the compiler doesn't + * support the gnu built-ins, then speed is probably not a priority. */ +#ifndef __GNUC__ +static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +static +ecs_os_thread_t posix_thread_new( + ecs_os_thread_callback_t callback, + void *arg) +{ + pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); + + if (pthread_create (thread, NULL, callback, arg) != 0) { + ecs_os_abort(); + } + + return (ecs_os_thread_t)(uintptr_t)thread; +} + +static +void* posix_thread_join( + ecs_os_thread_t thread) +{ + void *arg; + pthread_t *thr = (pthread_t*)(uintptr_t)thread; + pthread_join(*thr, &arg); + ecs_os_free(thr); + return arg; +} + +static +ecs_os_thread_id_t posix_thread_self(void) +{ + return (ecs_os_thread_id_t)pthread_self(); +} + +static +int32_t posix_ainc( + int32_t *count) +{ + int value; +#ifdef __GNUC__ + value = __sync_add_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) += 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif +} + +static +int32_t posix_adec( + int32_t *count) +{ + int32_t value; +#ifdef __GNUC__ + value = __sync_sub_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) -= 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif +} + +static +int64_t posix_lainc( + int64_t *count) +{ + int64_t value; +#ifdef __GNUC__ + value = __sync_add_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) += 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif +} + +static +int64_t posix_ladec( + int64_t *count) +{ + int64_t value; +#ifdef __GNUC__ + value = __sync_sub_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) -= 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif +} + +static +ecs_os_mutex_t posix_mutex_new(void) { + pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); + if (pthread_mutex_init(mutex, NULL)) { + abort(); + } + return (ecs_os_mutex_t)(uintptr_t)mutex; +} + +static +void posix_mutex_free( + ecs_os_mutex_t m) +{ + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + pthread_mutex_destroy(mutex); + ecs_os_free(mutex); +} + +static +void posix_mutex_lock( + ecs_os_mutex_t m) +{ + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_lock(mutex)) { + abort(); + } +} + +static +void posix_mutex_unlock( + ecs_os_mutex_t m) +{ + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_unlock(mutex)) { + abort(); + } +} + +static +ecs_os_cond_t posix_cond_new(void) { + pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); + if (pthread_cond_init(cond, NULL)) { + abort(); + } + return (ecs_os_cond_t)(uintptr_t)cond; +} + +static +void posix_cond_free( + ecs_os_cond_t c) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_destroy(cond)) { + abort(); + } + ecs_os_free(cond); +} + +static +void posix_cond_signal( + ecs_os_cond_t c) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_signal(cond)) { + abort(); + } +} + +static +void posix_cond_broadcast( + ecs_os_cond_t c) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_broadcast(cond)) { + abort(); + } +} + +static +void posix_cond_wait( + ecs_os_cond_t c, + ecs_os_mutex_t m) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_cond_wait(cond, mutex)) { + abort(); + } +} + +static bool posix_time_initialized; + +#if defined(__APPLE__) && defined(__MACH__) +static mach_timebase_info_data_t posix_osx_timebase; +static uint64_t posix_time_start; +#else +static uint64_t posix_time_start; +#endif + +static +void posix_time_setup(void) { + if (posix_time_initialized) { + return; + } + + posix_time_initialized = true; + + #if defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&posix_osx_timebase); + posix_time_start = mach_absolute_time(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif +} + +static +void posix_sleep( + int32_t sec, + int32_t nanosec) +{ + struct timespec sleepTime; + ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); + + sleepTime.tv_sec = sec; + sleepTime.tv_nsec = nanosec; + if (nanosleep(&sleepTime, NULL)) { + ecs_err("nanosleep failed"); + } +} + +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(ECS_TARGET_DARWIN) +static +int64_t posix_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} +#endif + +static +uint64_t posix_time_now(void) { + ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t now; + + #if defined(ECS_TARGET_DARWIN) + now = (uint64_t) posix_int64_muldiv( + (int64_t)mach_absolute_time(), + (int64_t)posix_osx_timebase.numer, + (int64_t)posix_osx_timebase.denom); + #elif defined(__EMSCRIPTEN__) + now = (long long)(emscripten_get_now() * 1000.0 * 1000); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); + #endif + + return now; +} + +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); + + ecs_os_api_t api = ecs_os_api; + + api.thread_new_ = posix_thread_new; + api.thread_join_ = posix_thread_join; + api.thread_self_ = posix_thread_self; + api.task_new_ = posix_thread_new; + api.task_join_ = posix_thread_join; + api.ainc_ = posix_ainc; + api.adec_ = posix_adec; + api.lainc_ = posix_lainc; + api.ladec_ = posix_ladec; + api.mutex_new_ = posix_mutex_new; + api.mutex_free_ = posix_mutex_free; + api.mutex_lock_ = posix_mutex_lock; + api.mutex_unlock_ = posix_mutex_unlock; + api.cond_new_ = posix_cond_new; + api.cond_free_ = posix_cond_free; + api.cond_signal_ = posix_cond_signal; + api.cond_broadcast_ = posix_cond_broadcast; + api.cond_wait_ = posix_cond_wait; + api.sleep_ = posix_sleep; + api.now_ = posix_time_now; + + posix_time_setup(); + + ecs_os_set_api(&api); +} + +#endif +#endif + +/** + * @file addons/script/tokenizer.c + * @brief Script tokenizer. + */ + + +#ifdef FLECS_PARSER + + +static +bool flecs_is_comment( + const char *pos) +{ + return pos[0] == '/' && (pos[1] == '/' || pos[1] == '*'); +} + +static +bool flecs_keyword_boundary( + const char *pos) +{ + if (!pos[0]) { + return true; + } + + if (isspace(pos[0])) { + return true; + } + + if (flecs_is_comment(pos)) { + return true; + } + + return false; +} + +static +bool flecs_keyword_match( + const char *pos, + const char *keyword) +{ + ecs_size_t len = ecs_os_strlen(keyword); + if (ecs_os_strncmp(pos, keyword, len)) { + return false; + } + + return flecs_keyword_boundary(pos + len); +} + +#define Keyword(keyword, _kind)\ + } else if (flecs_keyword_match(pos, keyword)) {\ + out->value = keyword;\ + out->kind = _kind;\ + return pos + ecs_os_strlen(keyword); + +#define OperatorMultiChar(oper, _kind)\ + } else if (!ecs_os_strncmp(pos, oper, ecs_os_strlen(oper))) {\ + out->value = oper;\ + out->kind = _kind;\ + return pos + ecs_os_strlen(oper); + +#define Operator(oper, _kind)\ + } else if (pos[0] == oper[0]) {\ + out->value = oper;\ + out->kind = _kind;\ + return pos + 1; + +const char* flecs_token_kind_str( + ecs_token_kind_t kind) +{ + switch(kind) { + case EcsTokUnknown: + return "unknown token "; + case EcsTokColon: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokAnnotation: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokAssign: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokNot: + case EcsTokOptional: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokMatch: + case EcsTokRange: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokAddAssign: + case EcsTokMulAssign: + return ""; + case EcsTokKeywordWith: + case EcsTokKeywordUsing: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: + case EcsTokKeywordTemplate: + case EcsTokKeywordModule: + case EcsTokKeywordMatch: + case EcsTokKeywordNew: + case EcsTokKeywordExport: + return "keyword "; + case EcsTokIdentifier: + return "identifier "; + case EcsTokFunction: + return "function "; + case EcsTokString: + return "string "; + case EcsTokChar: + return "char "; + case EcsTokNumber: + return "number "; + case EcsTokNewline: + return "newline"; + case EcsTokMember: + return "member"; + case EcsTokEnd: + return "end of script"; + default: + return ""; + } +} + +const char* flecs_token_str( + ecs_token_kind_t kind) +{ + switch(kind) { + case EcsTokUnknown: return "unknown token"; + case EcsTokColon: return ":"; + case EcsTokScopeOpen: return "{"; + case EcsTokScopeClose: return "}"; + case EcsTokParenOpen: return "("; + case EcsTokParenClose: return ")"; + case EcsTokBracketOpen: return "["; + case EcsTokBracketClose: return "]"; + case EcsTokAnnotation: return "@"; + case EcsTokComma: return ","; + case EcsTokSemiColon: return ";"; + case EcsTokAssign: return "="; + case EcsTokAdd: return "+"; + case EcsTokSub: return "-"; + case EcsTokMul: return "*"; + case EcsTokDiv: return "/"; + case EcsTokMod: return "%%"; + case EcsTokBitwiseOr: return "|"; + case EcsTokBitwiseAnd: return "&"; + case EcsTokNot: return "!"; + case EcsTokOptional: return "?"; + case EcsTokEq: return "=="; + case EcsTokNeq: return "!="; + case EcsTokGt: return ">"; + case EcsTokGtEq: return ">="; + case EcsTokLt: return "<"; + case EcsTokLtEq: return "<="; + case EcsTokAnd: return "&&"; + case EcsTokOr: return "||"; + case EcsTokMatch: return "~="; + case EcsTokRange: return ".."; + case EcsTokShiftLeft: return "<<"; + case EcsTokShiftRight: return ">>"; + case EcsTokAddAssign: return "+="; + case EcsTokMulAssign: return "*="; + case EcsTokKeywordWith: return "with"; + case EcsTokKeywordUsing: return "using"; + case EcsTokKeywordProp: return "prop"; + case EcsTokKeywordConst: return "const"; + case EcsTokKeywordMatch: return "match"; + case EcsTokKeywordNew: return "new"; + case EcsTokKeywordExport: return "export"; + case EcsTokKeywordIf: return "if"; + case EcsTokKeywordElse: return "else"; + case EcsTokKeywordFor: return "for"; + case EcsTokKeywordIn: return "in"; + case EcsTokKeywordTemplate: return "template"; + case EcsTokKeywordModule: return "module"; + case EcsTokIdentifier: return "identifier"; + case EcsTokFunction: return "function"; + case EcsTokString: return "string"; + case EcsTokChar: return "char"; + case EcsTokNumber: return "number"; + case EcsTokNewline: return "newline"; + case EcsTokMember: return "member"; + case EcsTokEnd: return "end of script"; + default: + return ""; + } +} + +const char* flecs_scan_whitespace( + ecs_parser_t *parser, + const char *pos) +{ + ecs_assert(pos != NULL, ECS_INTERNAL_ERROR, NULL); + + if (parser->significant_newline) { + while (pos[0] && isspace(pos[0]) && pos[0] != '\n') { + pos ++; + } + } else { + while (pos[0] && isspace(pos[0])) { + pos ++; + } + } + + return pos; +} + +static +const char* flecs_scan_line_comment( + const char *pos) +{ + ecs_assert(pos[0] == '/' && pos[1] == '/', ECS_INTERNAL_ERROR, NULL); + + for (pos = pos + 2; pos[0] && pos[0] != '\n'; pos ++) { } + return pos; +} + +static +bool flecs_newline_followed_by_comment( + ecs_parser_t *parser, + const char *newline) +{ + ecs_assert(newline[0] == '\n', ECS_INTERNAL_ERROR, NULL); + const char *next = flecs_scan_whitespace(parser, newline + 1); + return flecs_is_comment(next); +} + +static +const char* flecs_scan_multiline_comment( + ecs_parser_t *parser, + const char *pos) +{ + ecs_assert(pos[0] == '/' && pos[1] == '*', ECS_INTERNAL_ERROR, NULL); + + for (pos = &pos[2]; pos[0] != 0; pos ++) { + if (pos[0] == '*' && pos[1] == '/') { + return pos + 2; + } + } + + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "missing */ for multiline comment"); + return NULL; +} + +static +const char* flecs_scan_significant_line_comment_newline_run( + ecs_parser_t *parser, + const char *comment_newline) +{ + ecs_assert(comment_newline[0] == '\n', ECS_INTERNAL_ERROR, NULL); + + const char *next = comment_newline + 1; + const char *last_newline = comment_newline; + bool collapse = false; + + while (next[0]) { + next = flecs_scan_whitespace(parser, next); + + if (next[0] == '\n') { + collapse = true; + last_newline = next; + next ++; + continue; + } + + if (next[0] == '/' && next[1] == '/') { + collapse = true; + next = flecs_scan_line_comment(next); + if (next[0] == '\n') { + last_newline = next; + next ++; + } + continue; + } + + if (next[0] == '/' && next[1] == '*') { + collapse = true; + const char *ml_end = &next[2]; + while (ml_end[0]) { + if (ml_end[0] == '*' && ml_end[1] == '/') { + next = ml_end + 2; + break; + } + ml_end ++; + } + + if (!ml_end[0]) { + /* Unterminated multiline comments are reported by the regular + * tokenizer path. Keep this pass non-fatal as it is only used + * to decide whether newlines can be collapsed. */ + break; + } + continue; + } + + break; + } + + return collapse ? last_newline : comment_newline; +} + +static +const char* flecs_scan_whitespace_and_comment( + ecs_parser_t *parser, + const char *pos) +{ + if (!pos) { + return NULL; + } + +repeat_skip_whitespace_comment: + pos = flecs_scan_whitespace(parser, pos); + + if (pos[0] == '/') { + if (pos[1] == '/') { + pos = flecs_scan_line_comment(pos); + if (pos[0] == '\n') { + if (parser->significant_newline) { + return flecs_scan_significant_line_comment_newline_run( + parser, pos); + } + pos ++; + goto repeat_skip_whitespace_comment; + } + } else if (pos[1] == '*') { + pos = flecs_scan_multiline_comment(parser, pos); + if (!pos) { + return NULL; + } + + if (parser->significant_newline && pos[0] == '\n' && + flecs_newline_followed_by_comment(parser, pos)) + { + return flecs_scan_significant_line_comment_newline_run( + parser, pos); + } + + goto repeat_skip_whitespace_comment; + } + } + + return pos; +} + +static +bool flecs_script_is_identifier( + char c) +{ + return isalpha(c) || (c == '_') || (c == '$') || (c == '#'); +} + +const char* flecs_tokenizer_identifier( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out) +{ + if (out) { + out->kind = EcsTokIdentifier; + out->value = parser->token_cur; + } + + bool is_var = pos[0] == '$'; + char *outpos = NULL; + const char *start = pos; + if (parser) { + outpos = parser->token_cur; + if (parser->merge_variable_members) { + is_var = false; + } + } + + const char *name = parser ? parser->name : NULL; + const char *code = parser ? parser->code : pos; + + if (!flecs_script_is_identifier(pos[0])) { + ecs_parser_error(name, code, pos - code, + "invalid start of identifier '%c'", + pos[0]); + return NULL; + } + + do { + char c = pos[0]; + + if ((c == '.') && (pos[1] == '.')) { + break; + } + + bool is_ident = flecs_script_is_identifier(c) || isdigit(c); + + if (!is_var) { + is_ident = is_ident || (c == '.'); + } + + /* Retain \. for name lookup operation */ + if (!is_ident && c == '\\' && pos[1] == '.') { + is_ident = true; + } + + /* Retain .* for using wildcard expressions */ + if (!is_ident && c == '*') { + if (pos != start && pos[-1] == '.') { + is_ident = true; + } + } + + if (!is_ident) { + if (c == '\\') { + pos ++; + } else if (c == '<') { + int32_t indent = 0; + do { + c = *pos; + + if (c == '<') { + indent ++; + } else if (c == '>') { + indent --; + } else if (!c) { + ecs_parser_error(name, code, pos - code, + "< without > in identifier"); + return NULL; + } + + if (outpos) { + *outpos = c; + outpos ++; + } + pos ++; + + if (!indent) { + break; + } + } while (true); + + goto done; + } else if (c == '>') { + ecs_parser_error(name, code, pos - code, + "> without < in identifier"); + return NULL; + } else { + goto done; + } + } + + if (outpos) { + *outpos = *pos; + outpos ++; + } + + pos ++; + } while (true); + +done: + if (outpos) { + *outpos = '\0'; + if (parser) { + parser->token_cur = outpos + 1; + } + } + + if (parser && parser->function_token) { + if (pos[0] == '(') { + out->kind = EcsTokFunction; + pos ++; + } + } + + return pos; +} + +static +bool flecs_script_is_number( + const char *c) +{ + return isdigit(c[0]) || ((c[0] == '-') && isdigit(c[1])); +} + +static +const char* flecs_script_number( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out) +{ + out->kind = EcsTokNumber; + out->value = parser->token_cur; + + bool dot_parsed = false; + bool e_parsed = false; + bool digit_parsed = false; + int base = 10; + + ecs_assert(flecs_script_is_number(pos), ECS_INTERNAL_ERROR, NULL); + char *outpos = parser->token_cur; + + if (pos[0] == '-') { + outpos[0] = pos[0]; + pos ++; + outpos ++; + } + + if (pos[0] == '0' && (pos[1] == 'x' || pos[1] == 'X')) { + base = 16; + outpos[0] = pos[0]; + outpos[1] = pos[1]; + outpos += 2; + pos += 2; + } else if (pos[0] == '0' && (pos[1] == 'b' || pos[1] == 'B')) { + base = 2; + outpos[0] = pos[0]; + outpos[1] = pos[1]; + outpos += 2; + pos += 2; + } + + do { + char c = pos[0]; + bool valid_number = false; + bool handled_char = false; + + if (c == '.') { + if (!dot_parsed && !e_parsed) { + if (isdigit(pos[1])) { + dot_parsed = true; + valid_number = true; + } + } + } else if ((c == 'e' || c == 'E') && base == 10) { + if (!e_parsed && digit_parsed) { + if (isdigit(pos[1])) { + e_parsed = true; + valid_number = true; + } else if ((pos[1] == '+' || pos[1] == '-') && isdigit(pos[2])) { + e_parsed = true; + valid_number = true; + handled_char = true; + + outpos[0] = c; + outpos[1] = pos[1]; + outpos += 2; + pos += 2; + } + } + } else if ((base == 10) && isdigit(c)) { + digit_parsed = true; + valid_number = true; + } else if ((base == 16) && isxdigit(c)) { + digit_parsed = true; + valid_number = true; + } else if ((base == 2) && (c == '0' || c == '1')) { + digit_parsed = true; + valid_number = true; + } + + if (!valid_number) { + if (!digit_parsed && base != 10) { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "missing digits in number literal"); + return NULL; + } + + *outpos = '\0'; + parser->token_cur = outpos + 1; + break; + } + + if (!handled_char) { + outpos[0] = pos[0]; + outpos ++; + pos ++; + } + } while (true); + + return pos; +} + +static +const char* flecs_script_skip_string( + ecs_parser_t *parser, + const char *pos, + char delim) +{ + char ch; + for (; (ch = pos[0]) && pos[0] != delim; pos ++) { + if (ch == '\\') { + pos ++; + } + } + + if (!pos[0]) { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "unterminated string"); + return NULL; + } + + return pos; +} + +static +const char* flecs_script_char( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out) +{ + const char *end = flecs_script_skip_string(parser, pos + 1, '\''); + if (!end) { + return NULL; + } + + ecs_assert(end[0] == '\'', ECS_INTERNAL_ERROR, NULL); + end --; + + int32_t len = flecs_ito(int32_t, end - pos); + if(len == 0) { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "Empty char"); + return NULL; + } else if ((len > 1) && !((len == 2) && (pos[1] == '\\'))) { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "only one char allowed"); + return NULL; + } + + ecs_os_memcpy(parser->token_cur, pos + 1, len); + parser->token_cur[len] = '\0'; + + out->kind = EcsTokChar; + out->value = parser->token_cur; + parser->token_cur += len + 1; + return end + 2; +} + +static +const char* flecs_script_string( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out) +{ + const char *end = flecs_script_skip_string(parser, pos + 1, '"'); + if (!end) { + return NULL; + } + + ecs_assert(end[0] == '"', ECS_INTERNAL_ERROR, NULL); + end --; + + int32_t len = flecs_ito(int32_t, end - pos); + ecs_os_memcpy(parser->token_cur, pos + 1, len); + parser->token_cur[len] = '\0'; + + out->kind = EcsTokString; + out->value = parser->token_cur; + parser->token_cur += len + 1; + return end + 2; +} + +static +const char* flecs_script_multiline_string( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out) +{ + char ch; + const char *end = pos + 1; + while ((ch = end[0]) && (ch != '`')) { + if (ch == '\\' && end[1] == '`') { + end ++; + } + end ++; + } + + if (ch != '`') { + ecs_parser_error(parser->name, parser->code, + end - parser->code, "unterminated string"); + return NULL; + } + + end --; + + int32_t len = flecs_ito(int32_t, end - pos); + ecs_os_memcpy(parser->token_cur, pos + 1, len); + parser->token_cur[len] = '\0'; + + out->kind = EcsTokString; + out->value = parser->token_cur; + parser->token_cur += len + 1; + return end + 2; +} + +const char* flecs_tokenizer_until( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out, + char until) +{ + parser->pos = pos; + + const char *start = pos = flecs_scan_whitespace(parser, pos); + char ch; + + for (; (ch = pos[0]); pos ++) { + if (ch == until) { + break; + } + } + + if (!pos[0]) { + if (until == '\0') { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "expected end of script"); + return NULL; + } else + if (until == '\n') { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "expected newline"); + return NULL; + } else { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "expected '%c'", until); + return NULL; + } + } + + int32_t len = flecs_ito(int32_t, pos - start); + ecs_os_memcpy(parser->token_cur, start, len); + char *token_start = parser->token_cur; + out->value = parser->token_cur; + parser->token_cur += len; + + while (parser->token_cur != token_start && isspace(parser->token_cur[-1])) { + parser->token_cur --; + } + + parser->token_cur[0] = '\0'; + parser->token_cur ++; + + return pos; +} + +const char* flecs_token( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out, + bool is_lookahead) +{ + if (!pos) { + if (!is_lookahead) { + ecs_parser_error(parser->name, parser->code, 0, + "unexpected end of parser state"); + } + return NULL; + } + + parser->pos = pos; + + pos = flecs_scan_whitespace_and_comment(parser, pos); + if (!pos) { + return NULL; + } + + out->kind = EcsTokUnknown; + out->value = NULL; + + if (pos[0] == '\0') { + out->kind = EcsTokEnd; + return pos; + } else if (pos[0] == '\n') { + out->kind = EcsTokNewline; + + pos = flecs_scan_whitespace_and_comment(parser, pos + 1); + if (!pos) { + return NULL; + } + + if (pos[0] == '\n') { + pos ++; + } + + return pos; + + } else if (flecs_script_is_number(pos)) { + return flecs_script_number(parser, pos, out); + + OperatorMultiChar ("+=", EcsTokAddAssign) + OperatorMultiChar ("*=", EcsTokMulAssign) + Operator (":", EcsTokColon) + Operator ("{", EcsTokScopeOpen) + Operator ("}", EcsTokScopeClose) + Operator ("(", EcsTokParenOpen) + Operator (")", EcsTokParenClose) + Operator ("[", EcsTokBracketOpen) + Operator ("]", EcsTokBracketClose) + Operator ("@", EcsTokAnnotation) + Operator (",", EcsTokComma) + Operator (";", EcsTokSemiColon) + Operator ("+", EcsTokAdd) + Operator ("-", EcsTokSub) + Operator ("*", EcsTokMul) + Operator ("/", EcsTokDiv) + Operator ("%%", EcsTokMod) + Operator ("?", EcsTokOptional) + + OperatorMultiChar ("..", EcsTokRange) + Operator (".", EcsTokMember) + + OperatorMultiChar ("==", EcsTokEq) + OperatorMultiChar ("!=", EcsTokNeq) + OperatorMultiChar ("<<", EcsTokShiftLeft) + OperatorMultiChar (">>", EcsTokShiftRight) + OperatorMultiChar (">=", EcsTokGtEq) + OperatorMultiChar ("<=", EcsTokLtEq) + + OperatorMultiChar ("&&", EcsTokAnd) + OperatorMultiChar ("||", EcsTokOr) + OperatorMultiChar ("~=", EcsTokMatch) + + Operator ("!", EcsTokNot) + Operator ("=", EcsTokAssign) + Operator ("&", EcsTokBitwiseAnd) + Operator ("|", EcsTokBitwiseOr) + Operator (">", EcsTokGt) + Operator ("<", EcsTokLt) + + Keyword ("with", EcsTokKeywordWith) + Keyword ("using", EcsTokKeywordUsing) + Keyword ("template", EcsTokKeywordTemplate) + Keyword ("prop", EcsTokKeywordProp) + Keyword ("const", EcsTokKeywordConst) + Keyword ("if", EcsTokKeywordIf) + Keyword ("else", EcsTokKeywordElse) + Keyword ("for", EcsTokKeywordFor) + Keyword ("in", EcsTokKeywordIn) + Keyword ("match", EcsTokKeywordMatch) + Keyword ("new", EcsTokKeywordNew) + Keyword ("export", EcsTokKeywordExport) + Keyword ("module", EcsTokKeywordModule) + + } else if (pos[0] == '\'') { + return flecs_script_char(parser, pos, out); + + } else if (pos[0] == '"') { + return flecs_script_string(parser, pos, out); + + } else if (pos[0] == '`') { + return flecs_script_multiline_string(parser, pos, out); + + } else if (flecs_script_is_identifier(pos[0])) { + return flecs_tokenizer_identifier(parser, pos, out); + } + + if (!is_lookahead) { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "unknown token '%c'", pos[0]); + } + + return NULL; +} + +#endif + + +#ifdef FLECS_PIPELINE + +static +ecs_ftime_t flecs_insert_sleep( + ecs_world_t *world, + ecs_time_t *stop) +{ + flecs_poly_assert(world, ecs_world_t); + + ecs_time_t start = *stop, now = start; + ecs_ftime_t delta_time = (ecs_ftime_t)ecs_time_measure(stop); + + if (ECS_EQZERO(world->info.target_fps)) { + return delta_time; + } + + ecs_os_perf_trace_push("flecs.insert_sleep"); + + ecs_ftime_t target_delta_time = + ((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); + + /* Calculate the time we need to sleep by taking the measured delta from the + * previous frame, and subtracting it from target_delta_time. */ + ecs_ftime_t sleep = target_delta_time - delta_time; + + /* Pick a sleep interval that is smaller than the time one frame should take + * which increases the sleep precision. */ + ecs_ftime_t sleep_time = sleep / (ecs_ftime_t)8.0; + if (sleep_time < 0) { + sleep_time = 0; + delta_time = (ecs_ftime_t)ecs_time_measure(&now); + } else { + do { + /* Only call sleep when sleep_time is not 0. On some platforms, even + * a sleep with a timeout of 0 can cause stutter. */ + if (ECS_NEQZERO(sleep_time)) { + ecs_sleepf((double)sleep_time); + } + + now = start; + delta_time = (ecs_ftime_t)ecs_time_measure(&now); + } while ((target_delta_time - delta_time) > + (sleep_time / (ecs_ftime_t)2.0)); + } + + ecs_os_perf_trace_pop("flecs.insert_sleep"); + + *stop = now; + return delta_time; +} + +static +ecs_ftime_t flecs_start_measure_frame( + ecs_world_t *world, + ecs_ftime_t user_delta_time) +{ + flecs_poly_assert(world, ecs_world_t); + + ecs_ftime_t delta_time = 0; + + if ((world->flags & EcsWorldMeasureFrameTime) || + (ECS_EQZERO(user_delta_time))) + { + ecs_time_t t = world->frame_start_time; + do { + if (world->frame_start_time.nanosec || world->frame_start_time.sec){ + delta_time = flecs_insert_sleep(world, &t); + } else { + ecs_time_measure(&t); + if (ECS_NEQZERO(world->info.target_fps)) { + delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; + } else { + /* Best guess */ + delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; + + if (ECS_EQZERO(delta_time)) { + delta_time = user_delta_time; + break; + } + } + } + + /* Keep trying while delta_time is zero */ + } while (ECS_EQZERO(delta_time)); + + world->frame_start_time = t; + + /* Keep track of total time passed in world */ + world->info.world_time_total_raw += (double)delta_time; + } + + return (ecs_ftime_t)delta_time; +} + +static +void flecs_stop_measure_frame( + ecs_world_t* world) +{ + flecs_poly_assert(world, ecs_world_t); + + if (world->flags & EcsWorldMeasureFrameTime) { + ecs_time_t t = world->frame_start_time; + world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); + } +} + +ecs_ftime_t ecs_frame_begin( + ecs_world_t *world, + ecs_ftime_t user_delta_time) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, + "cannot begin frame while world is in readonly mode"); + ecs_check(!(world->flags & EcsWorldFrameInProgress), ECS_INVALID_OPERATION, + "cannot begin frame while frame is already in progress"); + ecs_check(ECS_NEQZERO(user_delta_time) || ecs_os_has_time(), + ECS_MISSING_OS_API, "get_time"); + + /* Start measuring total frame time */ + ecs_ftime_t delta_time = flecs_start_measure_frame(world, user_delta_time); + if (ECS_EQZERO(user_delta_time)) { + user_delta_time = delta_time; + } + + world->info.delta_time_raw = user_delta_time; + world->info.delta_time = user_delta_time * world->info.time_scale; + + /* Keep track of total scaled time passed in world */ + world->info.world_time_total += (double)world->info.delta_time; + + /* Command buffer capturing */ + world->on_commands_active = world->on_commands; + world->on_commands = NULL; + + world->on_commands_ctx_active = world->on_commands_ctx; + world->on_commands_ctx = NULL; + + ecs_run_aperiodic(world, 0); + + world->flags |= EcsWorldFrameInProgress; + + return world->info.delta_time; +error: + return (ecs_ftime_t)0; +} + +void ecs_frame_end( + ecs_world_t *world) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, + "cannot end frame while world is in readonly mode"); + ecs_check((world->flags & EcsWorldFrameInProgress), ECS_INVALID_OPERATION, + "cannot end frame while frame is not in progress"); + + world->info.frame_count_total ++; + + int32_t i, count = world->stage_count; + for (i = 0; i < count; i ++) { + flecs_stage_merge_post_frame(world, world->stages[i]); + } + + flecs_stop_measure_frame(world); + + /* Reset command handler each frame */ + world->on_commands_active = NULL; + world->on_commands_ctx_active = NULL; + + world->flags &= ~EcsWorldFrameInProgress; + +error: + return; +} + +#endif + +/** + * @file addons/pipeline/pipeline.c + * @brief Functions for building and running pipelines. + */ + + +#ifdef FLECS_PIPELINE + +static void flecs_pipeline_free( + ecs_pipeline_state_t *p) +{ + if (p) { + ecs_world_t *world = p->query->world; + ecs_allocator_t *a = &world->allocator; + ecs_vec_fini_t(a, &p->ops, ecs_pipeline_op_t); + ecs_vec_fini_t(a, &p->systems, ecs_system_t*); + ecs_os_free(p->iters); + ecs_query_fini(p->query); + ecs_os_free(p); + } +} + +static ECS_MOVE(EcsPipeline, dst, src, { + flecs_pipeline_free(dst->state); + dst->state = src->state; + src->state = NULL; +}) + +static ECS_DTOR(EcsPipeline, ptr, { + flecs_pipeline_free(ptr->state); +}) + +typedef enum ecs_write_kind_t { + WriteStateNone = 0, + WriteStateToStage, +} ecs_write_kind_t; + +typedef struct ecs_write_state_t { + bool write_barrier; + ecs_map_t ids; + ecs_map_t wildcard_ids; +} ecs_write_state_t; + +static +ecs_write_kind_t flecs_pipeline_get_write_state( + ecs_write_state_t *write_state, + ecs_id_t id) +{ + ecs_write_kind_t result = WriteStateNone; + + if (write_state->write_barrier) { + /* Any component could have been written */ + return WriteStateToStage; + } + + if (id == EcsWildcard) { + /* Using a wildcard for id indicates read barrier. Return true if any + * components could have been staged */ + if (ecs_map_count(&write_state->ids) || + ecs_map_count(&write_state->wildcard_ids)) + { + return WriteStateToStage; + } + } + + if (!ecs_id_is_wildcard(id)) { + if (ecs_map_get(&write_state->ids, id)) { + result = WriteStateToStage; + } + } else { + ecs_map_iter_t it = ecs_map_iter(&write_state->ids); + while (ecs_map_next(&it)) { + if (ecs_id_match(ecs_map_key(&it), id)) { + return WriteStateToStage; + } + } + } + + if (ecs_map_count(&write_state->wildcard_ids)) { + ecs_map_iter_t it = ecs_map_iter(&write_state->wildcard_ids); + while (ecs_map_next(&it)) { + if (ecs_id_match(id, ecs_map_key(&it))) { + return WriteStateToStage; + } + } + } + + return result; +} + +static +void flecs_pipeline_set_write_state( + ecs_write_state_t *write_state, + ecs_id_t id) +{ + if (id == EcsWildcard) { + /* If writing to wildcard, flag all components as written */ + write_state->write_barrier = true; + return; + } + + ecs_map_t *ids; + if (ecs_id_is_wildcard(id)) { + ids = &write_state->wildcard_ids; + } else { + ids = &write_state->ids; + } + + ecs_map_ensure(ids, id)[0] = true; +} + +static +void flecs_pipeline_reset_write_state( + ecs_write_state_t *write_state) +{ + ecs_map_clear(&write_state->ids); + ecs_map_clear(&write_state->wildcard_ids); + write_state->write_barrier = false; +} + +static +bool flecs_pipeline_check_term( + ecs_world_t *world, + ecs_term_t *term, + bool is_active, + ecs_write_state_t *write_state) +{ + (void)world; + + ecs_term_ref_t *src = &term->src; + if (term->inout == EcsInOutFilter) { + return false; + } + + ecs_id_t id = term->id; + int16_t oper = term->oper; + int16_t inout = term->inout; + bool from_any = ecs_term_match_0(term); + bool from_this = ecs_term_match_this(term); + bool is_shared = !from_any && (!from_this || !(src->id & EcsSelf)); + + ecs_write_kind_t ws = flecs_pipeline_get_write_state(write_state, id); + + if (from_this && ws >= WriteStateToStage) { + /* A staged write could have happened for an id that's matched on the + * main storage. Even if the id isn't read, still insert a merge so that + * a write to the main storage after the staged write doesn't get + * overwritten. */ + return true; + } + + if (inout == EcsInOutDefault) { + if (from_any) { + /* If no inout kind is specified for terms without a source, this is + * not interpreted as a read/write annotation but just a (component) + * id that's passed to a system. */ + return false; + } else if (is_shared) { + inout = EcsIn; + } else { + /* Default for owned terms is InOut */ + inout = EcsInOut; + } + } + + if (oper == EcsNot && inout == EcsOut) { + /* If a Not term is combined with Out, it signals that the system + * intends to add a component that the entity doesn't yet have */ + from_any = true; + } + + if (from_any) { + switch(inout) { + case EcsOut: + case EcsInOut: + if (is_active) { + /* Only flag component as written if system is active */ + flecs_pipeline_set_write_state(write_state, id); + } + break; + case EcsInOutDefault: + case EcsInOutNone: + case EcsInOutFilter: + case EcsIn: + break; + } + + switch(inout) { + case EcsIn: + case EcsInOut: + if (ws == WriteStateToStage) { + /* If a system does a get/ensure, the component is fetched from + * the main store so it must be merged first */ + return true; + } + /* fall through */ + case EcsInOutDefault: + case EcsInOutNone: + case EcsInOutFilter: + case EcsOut: + break; + } + } + + return false; +} + +static +bool flecs_pipeline_check_terms( + ecs_world_t *world, + ecs_query_t *query, + bool is_active, + ecs_write_state_t *ws) +{ + bool needs_merge = false; + ecs_term_t *terms = query->terms; + int32_t t, term_count = query->term_count; + + /* Check This terms first. This way if a term indicating writing to a stage + * was added before the term, it won't cause merging. */ + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (ecs_term_match_this(term)) { + needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); + } + } + + /* Now check staged terms */ + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (!ecs_term_match_this(term)) { + needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); + } + } + + return needs_merge; +} + +static +EcsPoly* flecs_pipeline_term_system( + ecs_iter_t *it) +{ + int32_t index = ecs_table_get_column_index( + it->real_world, it->table, flecs_poly_id(EcsSystem)); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + EcsPoly *poly = ecs_table_get_column(it->table, index, it->offset); + ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); + return poly; +} + +static +bool flecs_pipeline_build( + ecs_world_t *world, + ecs_pipeline_state_t *pq) +{ + ecs_iter_t it = ecs_query_iter(world, pq->query); + + int32_t new_match_count = ecs_query_match_count(pq->query); + if (pq->match_count == new_match_count) { + /* No need to rebuild the pipeline */ + ecs_iter_fini(&it); + return false; + } + + world->info.pipeline_build_count_total ++; + pq->rebuild_count ++; + + ecs_allocator_t *a = &world->allocator; + ecs_pipeline_op_t *op = NULL; + ecs_write_state_t ws = {0}; + ecs_map_init(&ws.ids, a); + ecs_map_init(&ws.wildcard_ids, a); + + ecs_vec_reset_t(a, &pq->ops, ecs_pipeline_op_t); + ecs_vec_reset_t(a, &pq->systems, ecs_system_t*); + + bool multi_threaded = false; + bool immediate = false; + bool first = true; + + /* Iterate systems in pipeline, add ops for running / merging */ + while (ecs_query_next(&it)) { + EcsPoly *poly = flecs_pipeline_term_system(&it); + bool is_active = ecs_table_get_type_index( + world, it.table, EcsEmpty) == -1; + + int32_t i; + for (i = 0; i < it.count; i ++) { + flecs_poly_assert(poly[i].poly, ecs_system_t); + ecs_system_t *sys = (ecs_system_t*)poly[i].poly; + ecs_query_t *q = sys->query; + + bool needs_merge = false; + needs_merge = flecs_pipeline_check_terms( + world, q, is_active, &ws); + + if (is_active) { + if (first) { + multi_threaded = sys->multi_threaded; + immediate = sys->immediate; + first = false; + } + + if (sys->multi_threaded != multi_threaded) { + needs_merge = true; + multi_threaded = sys->multi_threaded; + } + if (sys->immediate != immediate) { + needs_merge = true; + immediate = sys->immediate; + } + } + + if (immediate) { + needs_merge = true; + } + + if (needs_merge) { + /* After merge all components will be merged, so reset state */ + flecs_pipeline_reset_write_state(&ws); + + /* An inactive system can insert a merge if one of its + * components got written, which could make the system + * active. If this is the only system in the pipeline operation, + * it results in an empty operation when we get here. If that's + * the case, reuse the empty operation for the next op. */ + if (op && op->count) { + op = NULL; + } + + /* Re-evaluate columns to set write flags if system is active. + * If system is inactive, it can't write anything and so it + * should not insert unnecessary merges. */ + needs_merge = false; + if (is_active) { + needs_merge = flecs_pipeline_check_terms( + world, q, true, &ws); + } + + /* The component states were just reset, so if we conclude that + * another merge is needed something is wrong. */ + ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); + } + + if (!op) { + op = ecs_vec_append_t(a, &pq->ops, ecs_pipeline_op_t); + op->offset = ecs_vec_count(&pq->systems); + op->count = 0; + op->multi_threaded = false; + op->immediate = false; + op->time_spent = 0; + op->commands_enqueued = 0; + } + + /* Don't increase count for inactive systems, as they are ignored by + * the query used to run the pipeline. */ + if (is_active) { + ecs_vec_append_t(a, &pq->systems, ecs_system_t*)[0] = sys; + if (!op->count) { + op->multi_threaded = multi_threaded; + op->immediate = immediate; + } + op->count ++; + } + } + } + + if (op && !op->count && ecs_vec_count(&pq->ops) > 1) { + ecs_vec_remove_last(&pq->ops); + } + + ecs_map_fini(&ws.ids); + ecs_map_fini(&ws.wildcard_ids); + + op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); + + if (!op) { + ecs_dbg("#[green]pipeline#[reset] is empty"); + return true; + } else { + /* Add schedule to debug tracing */ + ecs_dbg("#[bold]pipeline rebuild"); + ecs_log_push_1(); + + ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", + op->multi_threaded, !op->immediate); + ecs_log_push_1(); + + int32_t i, count = ecs_vec_count(&pq->systems); + int32_t op_index = 0, ran_since_merge = 0; + ecs_system_t **systems = ecs_vec_first_t(&pq->systems, ecs_system_t*); + for (i = 0; i < count; i ++) { + ecs_system_t *sys = systems[i]; + ecs_entity_t system = sys->query->entity; + ecs_assert(system != 0, ECS_INTERNAL_ERROR, NULL); + (void)system; + +#ifdef FLECS_LOG_1 + char *path = ecs_get_path(world, system); + const char *doc_name = NULL; +#ifdef FLECS_DOC + const EcsDocDescription *doc_name_id = ecs_get_pair(world, system, + EcsDocDescription, EcsName); + if (doc_name_id) { + doc_name = doc_name_id->value; + } +#endif + if (doc_name) { + ecs_dbg("#[green]system#[reset] %s (%s)", path, doc_name); + } else { + ecs_dbg("#[green]system#[reset] %s", path); + } + ecs_os_free(path); +#endif + + ecs_assert(op[op_index].offset + ran_since_merge == i, + ECS_INTERNAL_ERROR, NULL); + + ran_since_merge ++; + if (ran_since_merge == op[op_index].count) { + ecs_dbg("#[magenta]merge#[reset]"); + ecs_log_pop_1(); + ran_since_merge = 0; + op_index ++; + if (op_index < ecs_vec_count(&pq->ops)) { + ecs_dbg( + "#[green]schedule#[reset]: " + "threading: %d, staging: %d:", + op[op_index].multi_threaded, + !op[op_index].immediate); + } + ecs_log_push_1(); + } + + if (sys->last_frame == (world->info.frame_count_total + 1)) { + if (op_index < ecs_vec_count(&pq->ops)) { + pq->cur_op = &op[op_index]; + pq->cur_i = i; + } else { + pq->cur_op = NULL; + pq->cur_i = 0; + } + } + } + + ecs_log_pop_1(); + ecs_log_pop_1(); + } + + pq->match_count = new_match_count; + + if (ecs_vec_count(&pq->ops)) { + ecs_assert((pq->cur_op <= ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t)), + ECS_INTERNAL_ERROR, NULL); + } + + return true; +} + +static +void flecs_pipeline_next_system( + ecs_pipeline_state_t *pq) +{ + if (!pq->cur_op) { + return; + } + + pq->cur_i ++; + if (pq->cur_i >= (pq->cur_op->offset + pq->cur_op->count)) { + pq->cur_op ++; + if (pq->cur_op > ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t)) { + pq->cur_op = NULL; + } + } +} + +bool flecs_pipeline_update( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + bool start_of_frame) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, + "cannot update pipeline while world is in readonly mode"); + + /* If any entity mutations happened that could have affected query matching + * notify appropriate queries so caches are up to date. This includes the + * pipeline query. */ + if (start_of_frame) { + ecs_run_aperiodic(world, 0); + } + + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + + bool rebuilt = flecs_pipeline_build(world, pq); + if (start_of_frame) { + /* Initialize iterators */ + int32_t i, count = pq->iter_count; + for (i = 0; i < count; i ++) { + ecs_world_t *stage = ecs_get_stage(world, i); + pq->iters[i] = ecs_query_iter(stage, pq->query); + } + pq->cur_op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); + pq->cur_i = 0; + } else { + flecs_pipeline_next_system(pq); + } + + return rebuilt; +} + +void ecs_run_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline, + ecs_ftime_t delta_time) +{ + if (!pipeline) { + pipeline = world->pipeline; + } + + /* create any worker task threads request */ + if (ecs_using_task_threads(world)) { + flecs_create_worker_threads(world); + } + + EcsPipeline *p = + ECS_CONST_CAST(EcsPipeline*, ecs_get(world, pipeline, EcsPipeline)); + flecs_workers_progress(world, p->state, delta_time); + + if (ecs_using_task_threads(world)) { + /* task threads were temporary and may now be joined */ + flecs_join_worker_threads(world); + } +} + +int32_t flecs_run_pipeline_ops( + ecs_world_t* world, + ecs_stage_t* stage, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time) +{ + ecs_pipeline_state_t* pq = world->pq; + ecs_pipeline_op_t* op = pq->cur_op; + int32_t i = pq->cur_i; + + ecs_assert(!stage_index || op->multi_threaded, ECS_INTERNAL_ERROR, NULL); + + int32_t count = ecs_vec_count(&pq->systems); + ecs_system_t **systems = ecs_vec_first_t(&pq->systems, ecs_system_t*); + int32_t ran_since_merge = i - op->offset; + + for (; i < count; i++) { + ecs_system_t* sys = systems[i]; + + /* Keep track of the last frame for which the system has ran, so we + * know from where to resume the schedule in case the schedule + * changes during a merge. */ + if (stage_index == 0) { + sys->last_frame = world->info.frame_count_total + 1; + } + + ecs_stage_t* s = NULL; + if (!op->immediate) { + /* If system is immediate it operates on the actual world, not + * the stage. Only pass stage to system if it's readonly. */ + s = stage; + } + + flecs_run_system(world, s, sys->query->entity, sys, stage_index, + stage_count, delta_time, NULL); + + ecs_os_linc(&world->info.systems_ran_total); + ran_since_merge++; + + if (ran_since_merge == op->count) { + /* Merge */ + break; + } + } + + return i; +} + +void flecs_run_pipeline( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_poly_assert(world, ecs_stage_t); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + int32_t stage_index = ecs_stage_get_id(stage->thread_ctx); + int32_t stage_count = ecs_get_stage_count(world); + bool multi_threaded = world->worker_cond != 0; + + ecs_assert(!stage_index, ECS_INVALID_OPERATION, + "cannot run pipeline on stage"); + + // Update the pipeline the workers will execute + world->pq = pq; + + // Update the pipeline before waking the workers. + flecs_pipeline_update(world, pq, true); + + // If there are no operations to execute in the pipeline bail early, + // no need to wake the workers since they have nothing to do. + while (pq->cur_op != NULL) { + if (pq->cur_i == ecs_vec_count(&pq->systems)) { + flecs_pipeline_update(world, pq, false); + continue; + } + + bool immediate = pq->cur_op->immediate; + bool op_multi_threaded = multi_threaded && pq->cur_op->multi_threaded; + + pq->immediate = immediate; + + if (!immediate) { + ecs_readonly_begin(world, multi_threaded); + } else { + flecs_defer_begin(world, stage); + } + + ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, op_multi_threaded); + ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL); + + if (op_multi_threaded) { + flecs_signal_workers(world); + } + + ecs_time_t st = { 0 }; + bool measure_time = world->flags & EcsWorldMeasureSystemTime; + if (measure_time) { + ecs_time_measure(&st); + } + + const int32_t i = flecs_run_pipeline_ops( + world, stage, stage_index, stage_count, delta_time); + + if (measure_time) { + /* Don't include merge time in system time */ + world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); + } + + if (op_multi_threaded) { + flecs_wait_for_sync(world); + } + + if (!immediate) { + ecs_time_t mt = { 0 }; + if (measure_time) { + ecs_time_measure(&mt); + } + + int32_t si; + for (si = 0; si < stage_count; si ++) { + ecs_stage_t *s = world->stages[si]; + pq->cur_op->commands_enqueued += ecs_vec_count(&s->cmd->queue); + } + + ecs_readonly_end(world); + if (measure_time) { + pq->cur_op->time_spent += ecs_time_measure(&mt); + } + } else { + flecs_defer_end(world, stage); + } + + /* Store the current state of the schedule after we synchronized the + * threads, to avoid race conditions. */ + pq->cur_i = i; + + flecs_pipeline_update(world, pq, false); + } +} + +static +void flecs_run_startup_systems( + ecs_world_t *world) +{ + ecs_component_record_t *cr = flecs_components_get(world, + ecs_dependson(EcsOnStart)); + if (!cr || !flecs_table_cache_count(&cr->cache)) { + /* Don't bother creating startup pipeline if no systems exist */ + return; + } + + ecs_dbg_2("#[bold]startup#[reset]"); + ecs_log_push_2(); + int32_t stage_count = world->stage_count; + world->stage_count = 1; /* Prevents running startup systems on workers */ + + /* Creating a pipeline is relatively expensive, but this only happens + * for the first frame. The startup pipeline is deleted afterwards, which + * eliminates the overhead of keeping its query cache in sync. */ + ecs_dbg_2("#[bold]create startup pipeline#[reset]"); + ecs_log_push_2(); + ecs_entity_t start_pip = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ + .query = { + .terms = { + { .id = EcsSystem }, + { .id = EcsPhase, .src.id = EcsCascade, .trav = EcsDependsOn }, + { .id = ecs_dependson(EcsOnStart), .trav = EcsDependsOn }, + { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsDependsOn, .oper = EcsNot }, + { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsChildOf, .oper = EcsNot } + }, + .order_by_callback = flecs_entity_compare + } + }); + ecs_log_pop_2(); + + /* Run & delete pipeline */ + ecs_dbg_2("#[bold]run startup systems#[reset]"); + ecs_log_push_2(); + ecs_assert(start_pip != 0, ECS_INTERNAL_ERROR, NULL); + const EcsPipeline *p = ecs_get(world, start_pip, EcsPipeline); + ecs_check(p != NULL, ECS_INVALID_OPERATION, + "pipeline entity is missing flecs.pipeline.Pipeline component"); + flecs_workers_progress(world, p->state, 0); + ecs_log_pop_2(); + + ecs_dbg_2("#[bold]delete startup pipeline#[reset]"); + ecs_log_push_2(); + ecs_delete(world, start_pip); + ecs_log_pop_2(); + + world->stage_count = stage_count; + ecs_log_pop_2(); + +error: + return; +} + +bool ecs_progress( + ecs_world_t *world, + ecs_ftime_t user_delta_time) +{ + ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); + + /* If this is the first frame, run startup systems */ + if (world->info.frame_count_total == 0) { + flecs_run_startup_systems(world); + } + + /* create any worker task threads request */ + if (ecs_using_task_threads(world)) { + flecs_create_worker_threads(world); + } + + ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); + ecs_log_push_3(); + const EcsPipeline *p = ecs_get(world, world->pipeline, EcsPipeline); + ecs_check(p != NULL, ECS_INVALID_OPERATION, + "pipeline entity is missing flecs.pipeline.Pipeline component"); + flecs_workers_progress(world, p->state, delta_time); + ecs_log_pop_3(); + + ecs_frame_end(world); + + if (ecs_using_task_threads(world)) { + /* task threads were temporary and may now be joined */ + flecs_join_worker_threads(world); + } + + return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); +error: + return false; +} + +void ecs_set_time_scale( + ecs_world_t *world, + ecs_ftime_t scale) +{ + world->info.time_scale = scale; +} + +void ecs_reset_clock( + ecs_world_t *world) +{ + world->info.world_time_total = 0; + world->info.world_time_total_raw = 0; +} + +void ecs_set_pipeline( + ecs_world_t *world, + ecs_entity_t pipeline) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, + ECS_INVALID_PARAMETER, "not a pipeline"); + + world->pipeline = pipeline; +error: + return; +} + +ecs_entity_t ecs_get_pipeline( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + world = ecs_get_world(world); + return world->pipeline; +error: + return 0; +} + +ecs_entity_t ecs_pipeline_init( + ecs_world_t *world, + const ecs_pipeline_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = desc->entity; + if (!result) { + result = ecs_new(world); + } + + ecs_query_desc_t qd = desc->query; + if (!qd.order_by_callback) { + qd.order_by_callback = flecs_entity_compare; + } + qd.entity = result; + + ecs_query_t *query = ecs_query_init(world, &qd); + if (!query) { + ecs_delete(world, result); + return 0; + } + + ecs_check(query->terms != NULL, ECS_INVALID_PARAMETER, + "pipeline query cannot be empty"); + ecs_check(query->terms[0].id == EcsSystem, + ECS_INVALID_PARAMETER, "pipeline must start with System term"); + + ecs_pipeline_state_t *pq = ecs_os_calloc_t(ecs_pipeline_state_t); + pq->query = query; + pq->match_count = -1; + ecs_set(world, result, EcsPipeline, { pq }); + + return result; +error: + return 0; +} + +/* -- Module implementation -- */ + +static +void FlecsPipelineFini( + ecs_world_t *world, + void *ctx) +{ + (void)ctx; + if (ecs_get_stage_count(world)) { + ecs_set_threads(world, 0); + } + + ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); +} + +#define flecs_bootstrap_phase(world, phase, depends_on)\ + flecs_bootstrap_tag(world, phase);\ + flecs_bootstrap_phase_(world, phase, depends_on) +static +void flecs_bootstrap_phase_( + ecs_world_t *world, + ecs_entity_t phase, + ecs_entity_t depends_on) +{ + ecs_add_id(world, phase, EcsPhase); + if (depends_on) { + ecs_add_pair(world, phase, EcsDependsOn, depends_on); + } +} + +void FlecsPipelineImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsPipeline); + ECS_IMPORT(world, FlecsSystem); + + ecs_set_name_prefix(world, "Ecs"); + + flecs_bootstrap_component(world, EcsPipeline); + flecs_bootstrap_tag(world, EcsPhase); + + /* Create anonymous phases to which the builtin phases will have DependsOn + * relationships. This ensures that, for example, EcsOnUpdate doesn't have a + * direct DependsOn relationship on EcsPreUpdate, which ensures that when + * the EcsPreUpdate phase is disabled, EcsOnUpdate still runs. */ + ecs_entity_t phase_0 = ecs_entity(world, {0}); + ecs_entity_t phase_1 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_0)) }); + ecs_entity_t phase_2 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_1)) }); + ecs_entity_t phase_3 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_2)) }); + ecs_entity_t phase_4 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_3)) }); + ecs_entity_t phase_5 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_4)) }); + ecs_entity_t phase_6 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_5)) }); + ecs_entity_t phase_7 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_6)) }); + ecs_entity_t phase_8 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_7)) }); + + flecs_bootstrap_phase(world, EcsOnStart, 0); + flecs_bootstrap_phase(world, EcsPreFrame, 0); + flecs_bootstrap_phase(world, EcsOnLoad, phase_0); + flecs_bootstrap_phase(world, EcsPostLoad, phase_1); + flecs_bootstrap_phase(world, EcsPreUpdate, phase_2); + flecs_bootstrap_phase(world, EcsOnUpdate, phase_3); + flecs_bootstrap_phase(world, EcsOnValidate, phase_4); + flecs_bootstrap_phase(world, EcsPostUpdate, phase_5); + flecs_bootstrap_phase(world, EcsPreStore, phase_6); + flecs_bootstrap_phase(world, EcsOnStore, phase_7); + flecs_bootstrap_phase(world, EcsPostFrame, phase_8); + + ecs_set_hooks(world, EcsPipeline, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsPipeline), + .move = ecs_move(EcsPipeline) + }); + + world->pipeline = ecs_pipeline(world, { + .entity = ecs_entity(world, { .name = "BuiltinPipeline" }), + .query = { + .terms = { + { .id = EcsSystem }, + { .id = EcsPhase, .src.id = EcsCascade, .trav = EcsDependsOn }, + { .id = ecs_dependson(EcsOnStart), .trav = EcsDependsOn, .oper = EcsNot }, + { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsDependsOn, .oper = EcsNot }, + { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsChildOf, .oper = EcsNot } + }, + .order_by_callback = flecs_entity_compare + } + }); + + /* Cleanup thread administration when world is destroyed */ + ecs_atfini(world, FlecsPipelineFini, NULL); +} + +#endif + +/** + * @file addons/pipeline/worker.c + * @brief Functions for running pipelines on one or more threads. + */ + + +#ifdef FLECS_PIPELINE + +/* Synchronize workers */ +static +void flecs_sync_worker( + ecs_world_t* world) +{ + int32_t stage_count = ecs_get_stage_count(world); + if (stage_count <= 1) { + return; + } + + /* Signal that thread is waiting */ + ecs_os_mutex_lock(world->sync_mutex); + if (++world->workers_waiting == (stage_count - 1)) { + /* Only signal main thread when all threads are waiting */ + ecs_os_cond_signal(world->sync_cond); + } + + /* Wait until main thread signals that thread can continue */ + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + ecs_os_mutex_unlock(world->sync_mutex); +} + +/* Worker thread */ +static +void* flecs_worker(void *arg) { + ecs_stage_t *stage = arg; + ecs_world_t *world = stage->world; + + flecs_poly_assert(world, ecs_world_t); + flecs_poly_assert(stage, ecs_stage_t); + + ecs_dbg_2("worker %d: start", stage->id); + + /* Start worker, increase counter so main thread knows how many + * workers are ready */ + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running ++; + + if (!(world->flags & EcsWorldQuitWorkers)) { + ecs_os_cond_wait(world->worker_cond, world->sync_mutex); + } + + ecs_os_mutex_unlock(world->sync_mutex); + + while (!(world->flags & EcsWorldQuitWorkers)) { + ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); + + ecs_dbg_3("worker %d: run", stage->id); + flecs_run_pipeline_ops(world, stage, stage->id, world->stage_count, + world->info.delta_time); + + ecs_set_scope((ecs_world_t*)stage, old_scope); + + flecs_sync_worker(world); + } + + ecs_dbg_2("worker %d: finalizing", stage->id); + + ecs_os_mutex_lock(world->sync_mutex); + world->workers_running --; + ecs_os_mutex_unlock(world->sync_mutex); + + ecs_dbg_2("worker %d: stop", stage->id); + + return NULL; +} + +/* Start threads */ +void flecs_create_worker_threads( + ecs_world_t *world) +{ + flecs_poly_assert(world, ecs_world_t); + int32_t stages = ecs_get_stage_count(world); + + for (int32_t i = 1; i < stages; i ++) { + ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_poly_assert(stage, ecs_stage_t); + + ecs_assert(stage->thread == 0, ECS_INTERNAL_ERROR, NULL); + if (ecs_using_task_threads(world)) { + /* workers are using tasks in an external task manager provided to + * the OS API */ + stage->thread = ecs_os_task_new(flecs_worker, stage); + } else { + /* workers are using long-running os threads */ + stage->thread = ecs_os_thread_new(flecs_worker, stage); + } + ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, + "failed to create thread"); + } +} + +static +void flecs_start_workers( + ecs_world_t *world, + int32_t threads) +{ + ecs_set_stage_count(world, threads); + + ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); + + if (!ecs_using_task_threads(world)) { + flecs_create_worker_threads(world); + } +} + +/* Wait until all workers are running */ +static +void flecs_wait_for_workers( + ecs_world_t *world) +{ + flecs_poly_assert(world, ecs_world_t); + + int32_t stage_count = ecs_get_stage_count(world); + if (stage_count <= 1) { + return; + } + + bool wait = true; + do { + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_running == (stage_count - 1)) { + wait = false; + } + ecs_os_mutex_unlock(world->sync_mutex); + } while (wait); +} + +/* Wait until all threads are waiting on sync point */ +void flecs_wait_for_sync( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + if (stage_count <= 1) { + return; + } + + ecs_dbg_3("#[bold]pipeline: waiting for worker sync"); + + ecs_os_mutex_lock(world->sync_mutex); + if (world->workers_waiting != (stage_count - 1)) { + ecs_os_cond_wait(world->sync_cond, world->sync_mutex); + } + + /* We shouldn't have been signalled unless all workers are waiting on sync */ + ecs_assert(world->workers_waiting == (stage_count - 1), + ECS_INTERNAL_ERROR, NULL); + + world->workers_waiting = 0; + ecs_os_mutex_unlock(world->sync_mutex); + + ecs_dbg_3("#[bold]pipeline: workers synced"); +} + +/* Signal workers that they can start/resume work */ +void flecs_signal_workers( + ecs_world_t *world) +{ + int32_t stage_count = ecs_get_stage_count(world); + if (stage_count <= 1) { + return; + } + + ecs_dbg_3("#[bold]pipeline: signal workers"); + ecs_os_mutex_lock(world->sync_mutex); + ecs_os_cond_broadcast(world->worker_cond); + ecs_os_mutex_unlock(world->sync_mutex); +} + +void flecs_join_worker_threads( + ecs_world_t *world) +{ + flecs_poly_assert(world, ecs_world_t); + bool threads_active = false; + + /* Test if threads are created. Cannot use workers_running, since this is + * a potential race if threads haven't spun up yet. */ + int i, count = world->stage_count; + for (i = 1; i < count; i ++) { + ecs_stage_t *stage = world->stages[i]; + if (stage->thread) { + threads_active = true; + break; + } + }; + + /* If no threads are active, just return */ + if (!threads_active) { + return; + } + + /* Make sure all threads are running, to ensure they catch the signal */ + flecs_wait_for_workers(world); + + /* Signal threads should quit */ + world->flags |= EcsWorldQuitWorkers; + flecs_signal_workers(world); + + /* Join all threads with main */ + for (i = 1; i < count; i ++) { + ecs_stage_t *stage = world->stages[i]; + if (ecs_using_task_threads(world)) { + ecs_os_task_join(stage->thread); + } else { + ecs_os_thread_join(stage->thread); + } + stage->thread = 0; + } + + world->flags &= ~EcsWorldQuitWorkers; + ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); +} + +/* -- Private functions -- */ +void flecs_workers_progress( + ecs_world_t *world, + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, + "cannot call progress while world is deferred"); + + /* Make sure workers are running and ready */ + flecs_wait_for_workers(world); + + /* Run pipeline on main thread */ + ecs_world_t *stage = ecs_get_stage(world, 0); + ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); + flecs_run_pipeline(stage, pq, delta_time); + ecs_set_scope((ecs_world_t*)stage, old_scope); +} + +static +void flecs_set_threads_internal( + ecs_world_t *world, + int32_t threads, + bool use_task_api) +{ + ecs_assert(threads <= 1 || (use_task_api + ? ecs_os_has_task_support() + : ecs_os_has_threading()), + ECS_MISSING_OS_API, NULL); + + int32_t stage_count = ecs_get_stage_count(world); + bool worker_method_changed = (use_task_api != world->workers_use_task_api); + + if ((stage_count != threads) || worker_method_changed) { + /* Stop existing threads */ + if (stage_count > 1) { + flecs_join_worker_threads(world); + ecs_set_stage_count(world, 1); + + if (world->worker_cond) { + ecs_os_cond_free(world->worker_cond); + } + if (world->sync_cond) { + ecs_os_cond_free(world->sync_cond); + } + if (world->sync_mutex) { + ecs_os_mutex_free(world->sync_mutex); + } + } + + world->workers_use_task_api = use_task_api; + + /* Start threads if number of threads > 1 */ + if (threads > 1) { + world->worker_cond = ecs_os_cond_new(); + world->sync_cond = ecs_os_cond_new(); + world->sync_mutex = ecs_os_mutex_new(); + flecs_start_workers(world, threads); + } + } +} + +/* -- Public functions -- */ + +void ecs_set_threads( + ecs_world_t *world, + int32_t threads) +{ + flecs_set_threads_internal(world, threads, false /* use thread API */); +} + +void ecs_set_task_threads( + ecs_world_t *world, + int32_t task_threads) +{ + flecs_set_threads_internal(world, task_threads, true /* use task API */); +} + +bool ecs_using_task_threads( + ecs_world_t *world) +{ + return world->workers_use_task_api; +} + +#endif + +/** + * @file addons/script/query_parser.c + * @brief Script grammar parser. + */ + +/** + * @file addons/parser/grammar.h + * @brief Grammar parser. + * + * Macro utilities that facilitate a simple recursive descent parser. + */ + +#ifndef FLECS_PARSER_GRAMMAR_H +#define FLECS_PARSER_GRAMMAR_H + + +#if defined(ECS_TARGET_CLANG) +/* Ignore unused enum constants in switch as it would blow up the parser code */ +#pragma clang diagnostic ignored "-Wswitch-enum" +/* To allow for nested Parse statements */ +#pragma clang diagnostic ignored "-Wshadow" +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wshadow" +#elif defined(ECS_TARGET_MSVC) +/* Allow for variable shadowing */ +#pragma warning(disable : 4456) +#endif + +/* Create script & parser structs with static token buffer */ +#define EcsParserFixedBuffer(w, script_name, expr, tokens, tokens_len)\ + ecs_script_impl_t script = {\ + .pub.world = ECS_CONST_CAST(ecs_world_t*, w),\ + .pub.name = script_name,\ + .pub.code = expr\ + };\ + ecs_parser_t parser = {\ + .script = flecs_script_impl(&script),\ + .name = script_name,\ + .code = expr,\ + .pos = expr,\ + .token_cur = tokens\ + } + +/* Definitions for parser functions */ +#define ParserBegin\ + ecs_tokenizer_t _tokenizer;\ + ecs_os_zeromem(&_tokenizer);\ + _tokenizer.tokens = _tokenizer.stack.tokens;\ + ecs_tokenizer_t *tokenizer = &_tokenizer; + +#define ParserEnd\ + Error("unexpected end of rule (parser error)");\ + error:\ + return NULL + +/* Get token */ +#define Token(n) (tokenizer->tokens[n].value) + +/* Push/pop token frame (allows token stack reuse in recursive functions) */ +#define TokenFramePush() \ + tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; + +#define TokenFramePop() \ + tokenizer->tokens = tokenizer->stack.tokens; + +/* Error */ +#define Error(...)\ + ecs_parser_error(parser->name, parser->code,\ + (pos - parser->code) - 1, __VA_ARGS__);\ + goto error + +/* Warning */ +#define Warning(...)\ + ecs_parser_warning(parser->name, parser->code,\ + (pos - parser->code) - 1, __VA_ARGS__);\ + +/* Parse expression */ +#define Expr(until, ...)\ + {\ + ecs_expr_node_t *EXPR = NULL;\ + if (until == '}' || until == ']') {\ + pos --;\ + if (until == '}') {\ + ecs_assert(pos[0] == '{', ECS_INTERNAL_ERROR, NULL);\ + } else if (until == ']') {\ + ecs_assert(pos[0] == '[', ECS_INTERNAL_ERROR, NULL);\ + }\ + }\ + parser->significant_newline = false;\ + if (!(pos = flecs_script_parse_expr(parser, pos, 0, &EXPR))) {\ + goto error;\ + }\ + parser->significant_newline = true;\ + __VA_ARGS__\ + } + +/* Parse initializer */ +#define Initializer(until, ...)\ + {\ + ecs_expr_node_t *INITIALIZER = NULL;\ + ecs_expr_initializer_t *_initializer = NULL;\ + if (until != '\n') {\ + parser->significant_newline = false;\ + }\ + if (!(pos = flecs_script_parse_initializer(\ + parser, pos, until, &_initializer))) \ + {\ + flecs_expr_visit_free(\ + &parser->script->pub, (ecs_expr_node_t*)_initializer);\ + goto error;\ + }\ + parser->significant_newline = true;\ + if (pos[0] != until) {\ + if (until != '\n' || pos[0] != '\0') {\ + Error("expected '%c'", until);\ + }\ + if (pos[0] == '\0') {\ + pos --;\ + }\ + }\ + INITIALIZER = (ecs_expr_node_t*)_initializer;\ + pos ++;\ + __VA_ARGS__\ + } + +/* Parse token until character */ +#define Until(until, ...)\ + {\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ + if (!(pos = flecs_tokenizer_until(parser, pos, t, until))) {\ + goto error;\ + }\ + }\ + Parse_1(until, __VA_ARGS__) + +/* Parse next token */ +#define Parse(...)\ + {\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ + if (!(pos = flecs_token(parser, pos, t, false))) {\ + goto error;\ + }\ + switch(t->kind) {\ + __VA_ARGS__\ + default:\ + if (t->value) {\ + Error("unexpected %s'%s'", \ + flecs_token_kind_str(t->kind), t->value);\ + } else {\ + Error("unexpected %s", \ + flecs_token_kind_str(t->kind));\ + }\ + }\ + } + +/* Parse N consecutive tokens */ +#define Parse_1(tok, ...)\ + Parse(\ + case tok: {\ + __VA_ARGS__\ + }\ + ) + +#define Parse_2(tok1, tok2, ...)\ + Parse_1(tok1, \ + Parse(\ + case tok2: {\ + __VA_ARGS__\ + }\ + )\ + ) + +#define Parse_3(tok1, tok2, tok3, ...)\ + Parse_2(tok1, tok2, \ + Parse(\ + case tok3: {\ + __VA_ARGS__\ + }\ + )\ + ) + +#define Parse_4(tok1, tok2, tok3, tok4, ...)\ + Parse_3(tok1, tok2, tok3, \ + Parse(\ + case tok4: {\ + __VA_ARGS__\ + }\ + )\ + ) + +#define Parse_5(tok1, tok2, tok3, tok4, tok5, ...)\ + Parse_4(tok1, tok2, tok3, tok4, \ + Parse(\ + case tok5: {\ + __VA_ARGS__\ + }\ + )\ + ) + +#define LookAhead_Keep() \ + pos = lookahead;\ + parser->token_keep = parser->token_cur + +/* Same as Parse, but doesn't error out if token is not in handled cases */ +#define LookAhead(...)\ + const char *lookahead;\ + ecs_token_t lookahead_token;\ + const char *old_lh_token_cur = parser->token_cur;\ + if ((lookahead = flecs_token(parser, pos, &lookahead_token, true))) {\ + tokenizer->stack.tokens[tokenizer->stack.count ++] = lookahead_token;\ + switch(lookahead_token.kind) {\ + __VA_ARGS__\ + default:\ + tokenizer->stack.count --;\ + break;\ + }\ + if (old_lh_token_cur > parser->token_keep) {\ + parser->token_cur = ECS_CONST_CAST(char*, old_lh_token_cur);\ + } else {\ + parser->token_cur = parser->token_keep;\ + }\ + } else {\ + if (flecs_token(parser, pos, &lookahead_token, false)) {\ + if (lookahead_token.value) {\ + Error("unexpected %s'%s'", \ + flecs_token_kind_str(lookahead_token.kind), \ + lookahead_token.value);\ + } else {\ + Error("unexpected %s", \ + flecs_token_kind_str(lookahead_token.kind));\ + }\ + }\ + goto error;\ + } + +/* Lookahead N consecutive tokens */ +#define LookAhead_1(tok, ...)\ + LookAhead(\ + case tok: {\ + __VA_ARGS__\ + }\ + ) + +#define LookAhead_2(tok1, tok2, ...)\ + LookAhead_1(tok1, \ + const char *old_ptr = pos;\ + pos = lookahead;\ + LookAhead(\ + case tok2: {\ + __VA_ARGS__\ + }\ + )\ + if (pos != lookahead) {\ + pos = old_ptr;\ + }\ + ) + +#define LookAhead_3(tok1, tok2, tok3, ...)\ + LookAhead_2(tok1, tok2, \ + const char *old_ptr = pos;\ + pos = lookahead;\ + LookAhead(\ + case tok3: {\ + __VA_ARGS__\ + }\ + )\ + if (pos != lookahead) {\ + pos = old_ptr;\ + }\ + ) + +/* Open scope */ +#define Scope(s, ...) {\ + ecs_script_scope_t *old_scope = parser->scope;\ + parser->scope = s;\ + __VA_ARGS__\ + parser->scope = old_scope;\ + } + +/* Parser loop */ +#define Loop(...)\ + int32_t token_stack_count = tokenizer->stack.count;\ + do {\ + tokenizer->stack.count = token_stack_count;\ + __VA_ARGS__\ + } while (true); + +#define EndOfRule return pos + +#endif + + +#ifdef FLECS_QUERY_DSL + + +#define EcsTokTermIdentifier\ + EcsTokIdentifier:\ + case EcsTokNumber:\ + case EcsTokMul + +#define EcsTokEndOfTerm\ + '}':\ + pos --; /* Give token back to parser */\ + case EcsTokOr:\ + if (t->kind == EcsTokOr) {\ + if (parser->term->oper != EcsAnd) {\ + Error("cannot mix operators in || expression");\ + }\ + parser->term->oper = EcsOr;\ + }\ + case ',':\ + case '\n':\ + case '\0' + +// $this == +static +const char* flecs_term_parse_equality_pred( + ecs_parser_t *parser, + const char *pos, + ecs_entity_t pred) +{ + ParserBegin; + + if (parser->term->oper != EcsAnd) { + Error("cannot mix operator with equality expression"); + } + + parser->term->src = parser->term->first; + parser->term->first = (ecs_term_ref_t){0}; + parser->term->first.id = pred; + + Parse( + // $this == foo + // ^ + case EcsTokTermIdentifier: { + parser->term->second.name = Token(0); + Parse( case EcsTokEndOfTerm: EndOfRule; ) + } + + // $this == "foo" + // ^ + case EcsTokString: { + parser->term->second.name = Token(0); + parser->term->second.id = EcsIsName; + + if (pred == EcsPredMatch) { + if (Token(0)[0] == '!') { + /* If match expression starts with !, set Not operator. The + * reason the ! is embedded in the expression is because + * there is only a single match (~=) operator. */ + parser->term->second.name ++; + parser->term->oper = EcsNot; + } + } + + Parse( + case EcsTokEndOfTerm: + EndOfRule; + ) + } + ) + + ParserEnd; +} + +static +ecs_entity_t flecs_query_parse_trav_flags( + const char *tok) +{ + if (!ecs_os_strcmp(tok, "self")) return EcsSelf; + else if (!ecs_os_strcmp(tok, "up")) return EcsUp; + else if (!ecs_os_strcmp(tok, "cascade")) return EcsCascade; + else if (!ecs_os_strcmp(tok, "desc")) return EcsDesc; + else return 0; +} + +static +const char* flecs_term_parse_trav( + ecs_parser_t *parser, + ecs_term_ref_t *ref, + const char *pos) +{ + ParserBegin; + + Loop( + // self + Parse_1(EcsTokIdentifier, + ref->id |= flecs_query_parse_trav_flags(Token(0)); + + LookAhead( + // self| + case '|': + pos = lookahead; + continue; + + // self IsA + case EcsTokIdentifier: + pos = lookahead; + parser->term->trav = ecs_lookup( + parser->world, Token(1)); + if (!parser->term->trav) { + Error( + "unresolved traversal relationship '%s'", Token(1)); + goto error; + } + + EndOfRule; + ) + + EndOfRule; + ) + ) + + ParserEnd; +} + +// Position( +static +const char* flecs_term_parse_arg( + ecs_parser_t *parser, + const char *pos, + int32_t arg) +{ + ParserBegin; + + ecs_term_ref_t *ref = NULL; + + // Position(src + if (arg == 0) { + ref = &parser->term->src; + + // Position(src, tgt + } else if (arg == 1) { + ref = &parser->term->second; + } else { + if (arg > FLECS_TERM_ARG_COUNT_MAX) { + Error("too many arguments in term"); + } + ref = &parser->extra_args[arg - 2]; + } + + bool is_trav_flag = false; + + LookAhead_1(EcsTokIdentifier, + is_trav_flag = flecs_query_parse_trav_flags(Token(0)) != 0; + ) + + if (is_trav_flag) { + // Position(self|up + // ^ + pos = flecs_term_parse_trav(parser, ref, pos); + if (!pos) { + goto error; + } + } else { + // Position(src + // ^ + Parse( + case '@': { + parser->term->id = ECS_VALUE_PAIR; + Parse( + case '*': + ref->id = EcsWildcard; + break; + case EcsTokIdentifier: + ref->name = Token(1); + break; + case EcsTokNumber: { + char *end; + ref->id = strtoul(Token(1), &end, 10); + break; + } + ); + break; + } + case EcsTokTermIdentifier: { + ref->name = Token(0); + + // Position(src| + // ^ + { + LookAhead_1('|', + pos = lookahead; + pos = flecs_term_parse_trav(parser, ref, pos); + if (!pos) { + goto error; + } + + // Position(src|up IsA + // ^ + LookAhead_1(EcsTokIdentifier, + pos = lookahead; + parser->term->trav = ecs_lookup( + parser->world, Token(1)); + if (!parser->term->trav) { + Error( + "unresolved trav identifier '%s'", Token(1)); + } + ) + ) + } + + break; + } + ) + } + + Parse( + // Position(src, + // ^ + case ',': + if ((arg > 1) && parser->extra_oper != EcsAnd) { + Error("cannot mix operators in extra term arguments"); + } + parser->extra_oper = EcsAnd; + return flecs_term_parse_arg(parser, pos, arg + 1); + + // Position(src, second || + // ^ + case EcsTokOr: + if ((arg > 1) && parser->extra_oper != EcsOr) { + Error("cannot mix operators in extra term arguments"); + } + parser->extra_oper = EcsOr; + return flecs_term_parse_arg(parser, pos, arg + 1); + + // Position(src) + // ^ + case ')': + Parse( + case EcsTokEndOfTerm: + EndOfRule; + ) + ) + + ParserEnd; +} + +// Position +static +const char* flecs_term_parse_id( + ecs_parser_t *parser, + const char *pos) +{ + ParserBegin; + + Parse( + case EcsTokEq: + return flecs_term_parse_equality_pred( + parser, pos, EcsPredEq); + case EcsTokNeq: { + const char *ret = flecs_term_parse_equality_pred( + parser, pos, EcsPredEq); + if (ret) { + parser->term->oper = EcsNot; + } + return ret; + } + case EcsTokMatch: + return flecs_term_parse_equality_pred( + parser, pos, EcsPredMatch); + + // Position| + case '|': { + pos = flecs_term_parse_trav(parser, &parser->term->first, pos); + if (!pos) { + goto error; + } + + // Position|self( + Parse( + case '(': + return flecs_term_parse_arg(parser, pos, 0); + case EcsTokEndOfTerm: + EndOfRule; + ) + } + + // Position( + case '(': { + // Position() + LookAhead_1(')', + pos = lookahead; + parser->term->src.id = EcsIsEntity; + + Parse( + case EcsTokEndOfTerm: + EndOfRule; + ) + ) + + return flecs_term_parse_arg(parser, pos, 0); + } + + case EcsTokEndOfTerm: + EndOfRule; + ) + + ParserEnd; +} + +// ( +static const char* flecs_term_parse_pair( + ecs_parser_t *parser, + const char *pos) +{ + ParserBegin; + + // (Position + // ^ + Parse( + case EcsTokTermIdentifier: { + parser->term->first.name = Token(0); + + LookAhead_1('|', + // (Position|self + pos = lookahead; + pos = flecs_term_parse_trav( + parser, &parser->term->first, pos); + if (!pos) { + goto error; + } + ) + + // (Position, + Parse_1(',', + return flecs_term_parse_arg(parser, pos, 1); + ) + } + ) + + ParserEnd; +} + +// AND +static +const char* flecs_term_parse_flags( + ecs_parser_t *parser, + const char *token_0, + const char *pos) +{ + ecs_assert(token_0 != NULL, ECS_INTERNAL_ERROR, NULL); + + ParserBegin; + + ecs_id_t flag = 0; + int16_t oper = 0; + ecs_term_t *term = parser->term; + + // AND + if (!ecs_os_strcmp(token_0, "and")) oper = EcsAndFrom; + else if (!ecs_os_strcmp(token_0, "or")) oper = EcsOrFrom; + else if (!ecs_os_strcmp(token_0, "not")) oper = EcsNotFrom; + else if (!ecs_os_strcmp(token_0, "auto_override")) flag = ECS_AUTO_OVERRIDE; + else if (!ecs_os_strcmp(token_0, "toggle")) flag = ECS_TOGGLE; + else { + // Position + term->first.name = token_0; + return flecs_term_parse_id(parser, pos); + } + + if (oper || flag) { + // and | + // ^ + Parse_1('|', + Parse( + // and | Position + // ^ + case EcsTokTermIdentifier: { + if (oper) { + term->oper = oper; + } else if (flag) { + term->id = flag; + } + + term->first.name = Token(1); + + return flecs_term_parse_id(parser, pos); + } + + // and | ( + // ^ + case '(': { + return flecs_term_parse_pair(parser, pos); + } + ) + ) + } + + ParserEnd; +} + +// ! +static +const char* flecs_term_parse_unary( + ecs_parser_t *parser, + const char *pos) +{ + ParserBegin; + + Parse( + // !( + case '(': { + return flecs_term_parse_pair(parser, pos); + } + + // !{ + case '{': { + parser->term->first.id = EcsScopeOpen; + parser->term->src.id = EcsIsEntity; + parser->term->inout = EcsInOutNone; + EndOfRule; + } + + // !Position + // ^ + case EcsTokTermIdentifier: { + parser->term->first.name = Token(0); + return flecs_term_parse_id(parser, pos); + } + ) + + ParserEnd; +} + +// [ +static +const char* flecs_term_parse_inout( + ecs_parser_t *parser, + const char *pos) +{ + ParserBegin; + + ecs_term_t *term = parser->term; + + // [inout] + // ^ + Parse_2(EcsTokIdentifier, ']', + if (!ecs_os_strcmp(Token(0), "default")) term->inout = EcsInOutDefault; + else if (!ecs_os_strcmp(Token(0), "none")) term->inout = EcsInOutNone; + else if (!ecs_os_strcmp(Token(0), "filter")) term->inout = EcsInOutFilter; + else if (!ecs_os_strcmp(Token(0), "inout")) term->inout = EcsInOut; + else if (!ecs_os_strcmp(Token(0), "in")) term->inout = EcsIn; + else if (!ecs_os_strcmp(Token(0), "out")) term->inout = EcsOut; + + Parse( + // [inout] Position + // ^ + case EcsTokTermIdentifier: { + return flecs_term_parse_flags(parser, Token(2), pos); + } + + // [inout] !Position + // ^ + case '!': + term->oper = EcsNot; + return flecs_term_parse_unary(parser, pos); + case '?': + term->oper = EcsOptional; + return flecs_term_parse_unary(parser, pos); + + // [inout] ( + // ^ + case '(': { + return flecs_term_parse_pair(parser, pos); + } + ) + ) + + ParserEnd; +} + +static +const char* flecs_query_term_parse( + ecs_parser_t *parser, + const char *pos) +{ + ParserBegin; + + Parse( + case '[': + return flecs_term_parse_inout(parser, pos); + case EcsTokTermIdentifier: + return flecs_term_parse_flags(parser, Token(0), pos); + case '(': + return flecs_term_parse_pair(parser, pos); + case '!': + parser->term->oper = EcsNot; + return flecs_term_parse_unary(parser, pos); + case '?': + parser->term->oper = EcsOptional; + return flecs_term_parse_unary(parser, pos); + case '{': + parser->term->first.id = EcsScopeOpen; + parser->term->src.id = EcsIsEntity; + parser->term->inout = EcsInOutNone; + EndOfRule; + case '}': + parser->term->first.id = EcsScopeClose; + parser->term->src.id = EcsIsEntity; + parser->term->inout = EcsInOutNone; + LookAhead_1(',', + pos = lookahead; + ) + EndOfRule; + case '\n':\ + case '\0': + EndOfRule; + ); + + ParserEnd; +} + +int flecs_terms_parse( + ecs_world_t *world, + const char *name, + const char *code, + char *token_buffer, + ecs_term_t *terms, + int32_t *term_count_out) +{ + if (!ecs_os_strcmp(code, "0")) { + *term_count_out = 0; + return 0; + } + + ecs_parser_t parser = { + .name = name, + .code = code, + .world = world, + .pos = code, + .merge_variable_members = true + }; + + parser.token_cur = token_buffer; + + int32_t term_count = 0; + const char *ptr = code; + ecs_term_ref_t extra_args[FLECS_TERM_ARG_COUNT_MAX]; + ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, + FLECS_TERM_ARG_COUNT_MAX); + + parser.extra_args = extra_args; + parser.extra_oper = 0; + + do { + if (term_count == FLECS_TERM_COUNT_MAX) { + ecs_err("max number of terms (%d) reached, increase " + "FLECS_TERM_COUNT_MAX to support more", + FLECS_TERM_COUNT_MAX); + goto error; + } + + /* Parse next term */ + ecs_term_t *term = &terms[term_count]; + parser.term = term; + ecs_os_memset_t(term, 0, ecs_term_t); + ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, FLECS_TERM_ARG_COUNT_MAX); + parser.extra_oper = 0; + + ptr = flecs_query_term_parse(&parser, ptr); + if (!ptr) { + /* Parser error */ + goto error; + } + + if (!ecs_term_is_initialized(term)) { + /* Last term parsed */ + break; + } + + term_count ++; + + /* Unpack terms with more than two args into multiple terms so that: + * Rel(X, Y, Z) + * becomes: + * Rel(X, Y), Rel(Y, Z) */ + int32_t arg = 0; + while (ecs_term_ref_is_set(&extra_args[arg ++])) { + ecs_assert(arg <= FLECS_TERM_ARG_COUNT_MAX, + ECS_INTERNAL_ERROR, NULL); + + if (term_count == FLECS_TERM_COUNT_MAX) { + ecs_err("max number of terms (%d) reached, increase " + "FLECS_TERM_COUNT_MAX to support more", + FLECS_TERM_COUNT_MAX); + goto error; + } + + term = &terms[term_count ++]; + *term = term[-1]; + + if (parser.extra_oper == EcsAnd) { + term->src = term[-1].second; + term->second = extra_args[arg - 1]; + } else if (parser.extra_oper == EcsOr) { + term->src = term[-1].src; + term->second = extra_args[arg - 1]; + term[-1].oper = EcsOr; + } + + if (term->first.name != NULL) { + term->first.name = term->first.name; + } + + if (term->src.name != NULL) { + term->src.name = term->src.name; + } + } + + if (arg) { + ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, + FLECS_TERM_ARG_COUNT_MAX); + } + } while (ptr[0]); + + (*term_count_out) += term_count; + + return 0; +error: + return -1; +} + +const char* flecs_term_parse( + ecs_world_t *world, + const char *name, + const char *expr, + char *token_buffer, + ecs_term_t *term) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, name); + ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_parser_t parser = { + .name = name, + .code = expr, + .world = world, + .token_cur = token_buffer + }; + + parser.term = term; + + const char *result = flecs_query_term_parse(&parser, expr); + if (!result) { + return NULL; + } + + ecs_os_memset_t(term, 0, ecs_term_t); + + return flecs_query_term_parse(&parser, expr); +} + +const char* flecs_id_parse( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_id_t *id) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, name); + ecs_assert(id != NULL, ECS_INVALID_PARAMETER, NULL); + + char token_buffer[256]; + + ecs_parser_t parser = { + .name = name, + .code = expr, + .world = ECS_CONST_CAST(ecs_world_t*, world), /* Safe, won't modify */ + .token_cur = token_buffer + }; + + ecs_term_t term = {0}; + parser.term = &term; + + expr = flecs_scan_whitespace(&parser, expr); + if (!ecs_os_strcmp(expr, "#0")) { + *id = 0; + return &expr[1]; + } + + const char *result = flecs_query_term_parse(&parser, expr); + if (!result) { + return NULL; + } + + ecs_query_validator_ctx_t ctx = {0}; + ctx.world = world; + ctx.term = &term; + + if (flecs_term_finalize(world, &term, &ctx)) { + return NULL; + } + + if (term.oper != EcsAnd) { + ecs_parser_error(name, expr, (result - expr), + "invalid operator for add expression"); + return NULL; + } + + if ((term.src.id & ~EcsTraverseFlags) != (EcsThis|EcsIsVariable)) { + ecs_parser_error(name, expr, (result - expr), + "invalid source for add expression (must be $this)"); + return NULL; + } + + *id = term.id; + + return result; +} + +static +const char* flecs_query_arg_parse( + ecs_parser_t *parser, + ecs_query_t *q, + ecs_iter_t *it, + const char *pos) +{ + ParserBegin; + + Parse_3(EcsTokIdentifier, ':', EcsTokIdentifier, { + int var = ecs_query_find_var(q, Token(0)); + if (var == -1) { + Error("unknown variable '%s'", Token(0)); + } + + ecs_entity_t val = ecs_lookup(q->world, Token(2)); + if (!val) { + Error("unresolved entity '%s'", Token(2)); + } + + ecs_iter_set_var(it, var, val); + + EndOfRule; + }) + + ParserEnd; +} + +static +const char* flecs_query_args_parse( + ecs_parser_t *parser, + ecs_query_t *q, + ecs_iter_t *it, + const char *pos) +{ + ParserBegin; + + bool has_paren = false; + LookAhead( + case '\0': + pos = lookahead; + EndOfRule; + case '(': { + pos = lookahead; + has_paren = true; + LookAhead_1(')', + pos = lookahead; + EndOfRule; + ) + } + ) + + Loop( + pos = flecs_query_arg_parse(parser, q, it, pos); + if (!pos) { + goto error; + } + + Parse( + case ',': + continue; + case '\0': + EndOfRule; + case ')': + if (!has_paren) { + Error("unexpected ')' without opening '(')"); + } + EndOfRule; + ) + ) + + ParserEnd; +} + +const char* ecs_query_args_parse( + ecs_query_t *q, + ecs_iter_t *it, + const char *expr) +{ + flecs_poly_assert(q, ecs_query_t); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL); + + const char *q_name = q->entity ? ecs_get_name(q->world, q->entity) : NULL; + if (ecs_os_strlen(expr) > 512) { + ecs_parser_error(q_name, expr, 0, "query argument expression too long"); + return NULL; + } + + char token_buffer[1024]; + ecs_parser_t parser = { + .name = q_name, + .code = expr, + .world = q->real_world, + .token_cur = token_buffer + }; + + return flecs_query_args_parse(&parser, q, it, expr); +error: + return NULL; +} + +#endif + +/** + * @file addons/script/ast.c + * @brief Script AST implementation. + */ + + +#ifdef FLECS_SCRIPT + +#define flecs_ast_new(parser, T, kind)\ + (T*)flecs_ast_new_(parser, ECS_SIZEOF(T), kind) +#define flecs_ast_vec(parser, vec, T) \ + ecs_vec_init_t(&parser->script->allocator, &vec, T*, 0) +#define flecs_ast_append(parser, vec, T, node) \ + ecs_vec_append_t(&parser->script->allocator, &vec, T*)[0] = node + +static +void* flecs_ast_new_( + ecs_parser_t *parser, + ecs_size_t size, + ecs_script_node_kind_t kind) +{ + ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &parser->script->allocator; + ecs_script_node_t *result = flecs_calloc_w_dbg_info( + a, size, "ecs_script_node_t"); + result->kind = kind; + result->pos = parser->pos; + return result; +} + +ecs_script_scope_t* flecs_script_scope_new( + ecs_parser_t *parser) +{ + ecs_script_scope_t *result = flecs_ast_new( + parser, ecs_script_scope_t, EcsAstScope); + flecs_ast_vec(parser, result->stmts, ecs_script_node_t); + ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); + return result; +} + +bool flecs_scope_is_empty( + ecs_script_scope_t *scope) +{ + return ecs_vec_count(&scope->stmts) == 0; +} + +ecs_script_scope_t* flecs_script_insert_scope( + ecs_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_scope_t *result = flecs_script_scope_new(parser); + flecs_ast_append(parser, scope->stmts, ecs_script_scope_t, result); + ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); + return result; +} + +static +int flecs_script_name_to_expr( + ecs_parser_t *parser, + const char *name, + ecs_expr_node_t** out) +{ + if (!name || !flecs_string_is_interpolated(name)) { + return 0; + } + + parser->significant_newline = false; + + ecs_expr_node_t *result = (ecs_expr_node_t*) + flecs_expr_interpolated_string(parser, name); + if (!result) { + return -1; + } + + parser->significant_newline = true; + + *out = result; + + return 0; +} + +ecs_script_entity_t* flecs_script_insert_entity( + ecs_parser_t *parser, + const char *name) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_entity_t *result = flecs_ast_new( + parser, ecs_script_entity_t, EcsAstEntity); + + if (name && !ecs_os_strcmp(name, "_")) { + name = NULL; + } + + result->name = name; + + if (flecs_script_name_to_expr(parser, name, &result->name_expr)) { + goto error; + } + + ecs_script_scope_t *entity_scope = flecs_script_scope_new(parser); + ecs_assert(entity_scope != NULL, ECS_INTERNAL_ERROR, NULL); + result->scope = entity_scope; + + flecs_ast_append(parser, scope->stmts, ecs_script_entity_t, result); + return result; +error: + return NULL; +} + +static +int flecs_script_set_id( + ecs_parser_t *parser, + ecs_script_id_t *id, + const char *first, + const char *second) +{ + ecs_assert(first != NULL, ECS_INTERNAL_ERROR, NULL); + id->first = first; + id->second = second; + id->first_sp = -1; + id->second_sp = -1; + + if (flecs_script_name_to_expr(parser, first, &id->first_expr)) { + return -1; + } + + if (flecs_script_name_to_expr(parser, second, &id->second_expr)) { + return -1; + } + + if (id->first_expr || id->second_expr) { + id->dynamic = true; + } + + return 0; +} + +ecs_script_pair_scope_t* flecs_script_insert_pair_scope( + ecs_parser_t *parser, + const char *first, + const char *second) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_script_pair_scope_t *result = flecs_ast_new( + parser, ecs_script_pair_scope_t, EcsAstPairScope); + + if (flecs_script_set_id(parser, &result->id, first, second)) { + return NULL; + } + + result->scope = flecs_script_scope_new(parser); + + flecs_ast_append(parser, scope->stmts, ecs_script_pair_scope_t, result); + return result; +} + +ecs_script_tag_t* flecs_script_insert_pair_tag( + ecs_parser_t *parser, + const char *first, + const char *second) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_tag_t *result = flecs_ast_new( + parser, ecs_script_tag_t, EcsAstTag); + + if (flecs_script_set_id(parser, &result->id, first, second)) { + return NULL; + } + + flecs_ast_append(parser, scope->stmts, ecs_script_tag_t, result); + + return result; +} + +ecs_script_tag_t* flecs_script_insert_tag( + ecs_parser_t *parser, + const char *name) +{ + return flecs_script_insert_pair_tag(parser, name, NULL); +} + +ecs_script_component_t* flecs_script_insert_pair_component( + ecs_parser_t *parser, + const char *first, + const char *second) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_component_t *result = flecs_ast_new( + parser, ecs_script_component_t, EcsAstComponent); + + if (flecs_script_set_id(parser, &result->id, first, second)) { + return NULL; + } + + flecs_ast_append(parser, scope->stmts, ecs_script_component_t, result); + + return result; +} + +ecs_script_component_t* flecs_script_insert_component( + ecs_parser_t *parser, + const char *name) +{ + return flecs_script_insert_pair_component(parser, name, NULL); +} + +ecs_script_default_component_t* flecs_script_insert_default_component( + ecs_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_default_component_t *result = flecs_ast_new( + parser, ecs_script_default_component_t, EcsAstDefaultComponent); + + flecs_ast_append(parser, scope->stmts, + ecs_script_default_component_t, result); + + return result; +} + +ecs_script_var_component_t* flecs_script_insert_var_component( + ecs_parser_t *parser, + const char *var_name) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var_name != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_var_component_t *result = flecs_ast_new( + parser, ecs_script_var_component_t, EcsAstVarComponent); + result->name = var_name; + result->sp = -1; + + flecs_ast_append(parser, scope->stmts, + ecs_script_var_component_t, result); + return result; +} + +ecs_script_with_t* flecs_script_insert_with( + ecs_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_with_t *result = flecs_ast_new( + parser, ecs_script_with_t, EcsAstWith); + + result->expressions = flecs_script_scope_new(parser); + result->scope = flecs_script_scope_new(parser); + + flecs_ast_append(parser, scope->stmts, ecs_script_with_t, result); + return result; +} + +ecs_script_using_t* flecs_script_insert_using( + ecs_parser_t *parser, + const char *name) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_using_t *result = flecs_ast_new( + parser, ecs_script_using_t, EcsAstUsing); + + result->name = name; + + flecs_ast_append(parser, scope->stmts, ecs_script_using_t, result); + return result; +} + +ecs_script_module_t* flecs_script_insert_module( + ecs_parser_t *parser, + const char *name) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_module_t *result = flecs_ast_new( + parser, ecs_script_module_t, EcsAstModule); + + result->name = name; + + flecs_ast_append(parser, scope->stmts, ecs_script_module_t, result); + return result; +} + +ecs_script_annot_t* flecs_script_insert_annot( + ecs_parser_t *parser, + const char *name, + const char *expr) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_annot_t *result = flecs_ast_new( + parser, ecs_script_annot_t, EcsAstAnnotation); + + result->name = name; + result->expr = expr; + + flecs_ast_append(parser, scope->stmts, ecs_script_annot_t, result); + return result; +} + +ecs_script_template_node_t* flecs_script_insert_template( + ecs_parser_t *parser, + const char *name) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_template_node_t *result = flecs_ast_new( + parser, ecs_script_template_node_t, EcsAstTemplate); + result->name = name; + result->scope = flecs_script_scope_new(parser); + + flecs_ast_append(parser, scope->stmts, ecs_script_template_node_t, result); + return result; +} + +ecs_script_var_node_t* flecs_script_insert_var( + ecs_parser_t *parser, + const char *name) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_var_node_t *result = flecs_ast_new( + parser, ecs_script_var_node_t, EcsAstConst); + result->name = name; + + flecs_ast_append(parser, scope->stmts, ecs_script_var_node_t, result); + return result; +} + +ecs_script_if_t* flecs_script_insert_if( + ecs_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_if_t *result = flecs_ast_new( + parser, ecs_script_if_t, EcsAstIf); + result->if_true = flecs_script_scope_new(parser); + result->if_false = flecs_script_scope_new(parser); + + flecs_ast_append(parser, scope->stmts, ecs_script_if_t, result); + return result; +} + +ecs_script_for_range_t* flecs_script_insert_for_range( + ecs_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_for_range_t *result = flecs_ast_new( + parser, ecs_script_for_range_t, EcsAstFor); + result->scope = flecs_script_scope_new(parser); + + flecs_ast_append(parser, scope->stmts, ecs_script_for_range_t, result); + return result; +} + +#endif + +/** + * @file addons/script/function.c + * @brief Script function API. + */ + + +#ifdef FLECS_SCRIPT + +static +void ecs_script_params_free(ecs_vec_t *params) { + int32_t i, count = ecs_vec_count(params); + if (count) { + ecs_script_parameter_t *array = ecs_vec_first(params); + for (i = 0; i < count; i ++) { + /* Safe, component owns string */ + ecs_os_free(ECS_CONST_CAST(char*, array[i].name)); + } + } + + ecs_vec_fini_t(NULL, params, ecs_script_parameter_t); + ecs_os_zeromem(params); +} + +static +void ecs_script_params_copy( + ecs_vec_t *dst, + const ecs_vec_t *src) +{ + int32_t i, count = ecs_vec_count(src); + if (!count) { + ecs_os_zeromem(dst); + return; + } + + const ecs_script_parameter_t *src_array = ecs_vec_first(src); + if (!src_array) { + ecs_os_zeromem(dst); + return; + } + + ecs_vec_init_t(NULL, dst, ecs_script_parameter_t, count); + for (i = 0; i < count; i ++) { + ecs_script_parameter_t *p = ecs_vec_append_t( + NULL, dst, ecs_script_parameter_t); + p->type = src_array[i].type; + p->name = src_array[i].name ? ecs_os_strdup(src_array[i].name) : NULL; + } +} + +static +void ecs_script_const_var_fini( + EcsScriptConstVar *ptr) +{ + if (!ptr->value.ptr) { + return; + } + + ecs_assert(ptr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + if (ptr->type_info->hooks.dtor) { + ptr->type_info->hooks.dtor(ptr->value.ptr, 1, ptr->type_info); + } + + ecs_os_free(ptr->value.ptr); + ptr->value.ptr = NULL; + ptr->value.type = 0; + ptr->type_info = NULL; +} + +static +ECS_COPY(EcsScriptConstVar, dst, src, { + ecs_script_const_var_fini(dst); + dst->value.type = src->value.type; + dst->type_info = src->type_info; + + if (src->value.ptr) { + ecs_assert(src->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + dst->value.ptr = ecs_os_malloc(src->type_info->size); + if (src->type_info->hooks.copy) { + src->type_info->hooks.copy( + dst->value.ptr, src->value.ptr, 1, src->type_info); + } else { + ecs_os_memcpy(dst->value.ptr, src->value.ptr, src->type_info->size); + } + } +}) + +static +ECS_MOVE(EcsScriptConstVar, dst, src, { + ecs_script_const_var_fini(dst); + + *dst = *src; + + src->value.ptr = NULL; + src->value.type = 0; + src->type_info = NULL; +}) + +static +ECS_DTOR(EcsScriptConstVar, ptr, { + ecs_script_const_var_fini(ptr); +}) + +static +ECS_COPY(EcsScriptFunction, dst, src, { + ecs_script_params_free(&dst->params); + dst->return_type = src->return_type; + dst->callback = src->callback; + ecs_os_memcpy_n(dst->vector_callbacks, src->vector_callbacks, + ecs_vector_function_callback_t, FLECS_SCRIPT_VECTOR_FUNCTION_COUNT); + dst->ctx = src->ctx; + ecs_script_params_copy(&dst->params, &src->params); +}) + +static +ECS_MOVE(EcsScriptFunction, dst, src, { + ecs_script_params_free(&dst->params); + *dst = *src; + ecs_os_zeromem(src); +}) + +static +ECS_DTOR(EcsScriptFunction, ptr, { + ecs_script_params_free(&ptr->params); +}) + +static +ECS_COPY(EcsScriptMethod, dst, src, { + ecs_script_params_free(&dst->params); + dst->return_type = src->return_type; + dst->callback = src->callback; + ecs_os_memcpy_n(dst->vector_callbacks, src->vector_callbacks, + ecs_vector_function_callback_t, FLECS_SCRIPT_VECTOR_FUNCTION_COUNT); + dst->ctx = src->ctx; + ecs_script_params_copy(&dst->params, &src->params); +}) + +static +ECS_MOVE(EcsScriptMethod, dst, src, { + ecs_script_params_free(&dst->params); + *dst = *src; + ecs_os_zeromem(src); +}) + +static +ECS_DTOR(EcsScriptMethod, ptr, { + ecs_script_params_free(&ptr->params); +}) + +ecs_entity_t ecs_const_var_init( + ecs_world_t *world, + ecs_const_var_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->value != NULL, ECS_INVALID_PARAMETER, NULL); + + if (ecs_lookup_child(world, desc->parent, desc->name) != 0) { + ecs_err("cannot redeclare const variable entity '%s' in parent '%s'", + desc->name, flecs_errstr(ecs_get_path(world, desc->parent))); + return 0; + } + + const ecs_type_info_t *ti = ecs_get_type_info(world, desc->type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "ecs_const_var_desc_t::type is not a valid type"); + + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); + + if (!result) { + goto error; + } + + EcsScriptConstVar *v = ecs_ensure(world, result, EcsScriptConstVar); + v->value.ptr = ecs_os_malloc(ti->size); + v->value.type = desc->type; + v->type_info = ti; + ecs_value_init(world, desc->type, v->value.ptr); + ecs_value_copy(world, desc->type, v->value.ptr, desc->value); + ecs_modified(world, result, EcsScriptConstVar); + + return result; +error: + return 0; +} + +ecs_value_t ecs_const_var_get( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const EcsScriptConstVar *v = ecs_get(world, entity, EcsScriptConstVar); + if (!v) { + goto error; + } + + return v->value; +error: + return (ecs_value_t){0}; +} + +#ifdef FLECS_DEBUG +static +bool flecs_script_function_has_vector_args( + const ecs_function_desc_t *desc) +{ + int32_t i; + for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { + if (!desc->params[i].name) { + break; + } + + if (desc->params[i].type == EcsScriptVectorType) { + return true; + } + } + + return false; +} + +static +int flecs_script_function_validate_desc( + const ecs_function_desc_t *desc) +{ + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); + + if (!flecs_script_function_has_vector_args(desc)) { + ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, desc->name); + ecs_check(desc->return_type != EcsScriptVectorType, ECS_INVALID_PARAMETER, + "function '%s' cannot have flecs.script.vector return type unless " + "at least one argument is of type flecs.script.vector", + desc->name); + } else { + int32_t i; + for (i = 0; i < EcsPrimitiveKindLast; i ++) { + if (desc->vector_callbacks[i]) { + break; + } + } + + if (i == EcsPrimitiveKindLast) { + ecs_throw(ECS_INVALID_PARAMETER, "function '%s' has vector " + "arguments, must implement at least one element of " + ".vector_callbacks", desc->name); + } + } + + return 0; +error: + return -1; +} +#endif + +static +void flecs_script_function_parse_args( + const ecs_function_desc_t *desc, + ecs_vec_t *params) +{ + int32_t i; + for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { + if (!desc->params[i].name) { + break; + } + + if (!i) { + ecs_vec_init_t(NULL, params, ecs_script_parameter_t, 0); + } + + ecs_script_parameter_t *p = ecs_vec_append_t( + NULL, params, ecs_script_parameter_t); + p->type = desc->params[i].type; + p->name = ecs_os_strdup(desc->params[i].name); + } +} + +ecs_entity_t ecs_function_init( + ecs_world_t *world, + const ecs_function_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_dbg_assert(!flecs_script_function_validate_desc(desc), + ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); + + if (!result) { + goto error; + } + + EcsScriptFunction *f = ecs_ensure(world, result, EcsScriptFunction); + f->return_type = desc->return_type; + f->callback = desc->callback; + ecs_os_memcpy_n(f->vector_callbacks, desc->vector_callbacks, + ecs_vector_function_callback_t, FLECS_SCRIPT_VECTOR_FUNCTION_COUNT); + f->ctx = desc->ctx; + + flecs_script_function_parse_args(desc, &f->params); + + ecs_modified(world, result, EcsScriptFunction); + + return result; +error: + return 0; +} + +ecs_entity_t ecs_method_init( + ecs_world_t *world, + const ecs_function_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_dbg_assert(!flecs_script_function_validate_desc(desc), + ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); + + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); + + if (!result) { + goto error; + } + + EcsScriptMethod *f = ecs_ensure(world, result, EcsScriptMethod); + f->return_type = desc->return_type; + f->callback = desc->callback; + ecs_os_memcpy_n(f->vector_callbacks, desc->vector_callbacks, + ecs_vector_function_callback_t, FLECS_SCRIPT_VECTOR_FUNCTION_COUNT); + f->ctx = desc->ctx; + + flecs_script_function_parse_args(desc, &f->params); + + ecs_modified(world, result, EcsScriptMethod); + + return result; +error: + return 0; +} + +void flecs_function_import( + ecs_world_t *world) +{ + ecs_set_name_prefix(world, "EcsScript"); + ECS_COMPONENT_DEFINE(world, EcsScriptConstVar); + ECS_COMPONENT_DEFINE(world, EcsScriptFunction); + ECS_COMPONENT_DEFINE(world, EcsScriptMethod); + + ecs_struct(world, { + .entity = ecs_id(EcsScriptFunction), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_struct(world, { + .entity = ecs_id(EcsScriptMethod), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); + + ecs_set_hooks(world, EcsScriptConstVar, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptConstVar), + .copy = ecs_copy(EcsScriptConstVar), + .move = ecs_move(EcsScriptConstVar), + }); + + ecs_set_hooks(world, EcsScriptFunction, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptFunction), + .copy = ecs_copy(EcsScriptFunction), + .move = ecs_move(EcsScriptFunction), + }); + + ecs_set_hooks(world, EcsScriptMethod, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptMethod), + .copy = ecs_copy(EcsScriptMethod), + .move = ecs_move(EcsScriptMethod), + }); + + flecs_script_register_builtin_functions(world); +} + +#endif + +/** + * @file addons/script/builtin_functions.c + * @brief Flecs functions for flecs script. + */ + + +#ifdef FLECS_SCRIPT + +static +void flecs_meta_entity_name( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); +} + +static +void flecs_meta_entity_path( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_get_path(ctx->world, entity); +} + +static +void flecs_meta_entity_parent( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); +} + +static +void flecs_meta_entity_has( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + ecs_id_t id = *(ecs_id_t*)argv[1].ptr; + *(ecs_bool_t*)result->ptr = ecs_has_id(ctx->world, entity, id); +} + +static +void flecs_meta_entity_lookup( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + (void)ctx; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + const char *path = *(char**)argv[1].ptr; + if (entity) { + *(ecs_entity_t*)result->ptr = ecs_lookup_child(ctx->world, entity, path); + } else { + *(ecs_entity_t*)result->ptr = ecs_lookup(ctx->world, path); + } +} + +static +void flecs_meta_core_pair( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + (void)ctx; + ecs_entity_t first = *(ecs_entity_t*)argv[0].ptr; + ecs_entity_t second = *(ecs_entity_t*)argv[1].ptr; + *(ecs_id_t*)result->ptr = ecs_pair(first, second); +} + +#ifdef FLECS_DOC + +#define FLECS_DOC_FUNC(name)\ + static\ + void flecs_meta_entity_doc_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)argc;\ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr;\ + *(char**)result->ptr = \ + ecs_os_strdup(ecs_doc_get_##name(ctx->world, entity));\ + } + +FLECS_DOC_FUNC(name) +FLECS_DOC_FUNC(uuid) +FLECS_DOC_FUNC(brief) +FLECS_DOC_FUNC(detail) +FLECS_DOC_FUNC(link) +FLECS_DOC_FUNC(color) + +#undef FLECS_DOC_FUNC + +static +void flecs_script_register_builtin_doc_functions( + ecs_world_t *world) +{ + ecs_method(world, { + .name = "doc_name", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_name + }); + + ecs_method(world, { + .name = "doc_uuid", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_uuid + }); + + ecs_method(world, { + .name = "doc_brief", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_brief + }); + + ecs_method(world, { + .name = "doc_detail", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_detail + }); + + ecs_method(world, { + .name = "doc_link", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_link + }); + + ecs_method(world, { + .name = "doc_color", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_color + }); +} + +#else + +static +void flecs_script_register_builtin_doc_functions( + ecs_world_t *world) +{ + (void)world; +} + +#endif + +void flecs_script_register_builtin_functions( + ecs_world_t *world) +{ + ecs_method(world, { + .name = "name", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_name + }); + + ecs_method(world, { + .name = "path", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_path + }); + + ecs_method(world, { + .name = "parent", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_entity_t), + .callback = flecs_meta_entity_parent + }); + + ecs_method(world, { + .name = "has", + .parent = ecs_id(ecs_entity_t), + .params = { + { .name = "component", .type = ecs_id(ecs_id_t) } + }, + .return_type = ecs_id(ecs_bool_t), + .callback = flecs_meta_entity_has + }); + + ecs_method(world, { + .name = "lookup", + .parent = ecs_id(ecs_entity_t), + .params = { + { .name = "path", .type = ecs_id(ecs_string_t) } + }, + .return_type = ecs_id(ecs_entity_t), + .callback = flecs_meta_entity_lookup + }); + + ecs_function(world, { + .name = "pair", + .parent = ecs_entity(world, { .name = "core" }), + .params = { + { .name = "first", .type = ecs_id(ecs_entity_t) }, + { .name = "second", .type = ecs_id(ecs_entity_t) } + }, + .return_type = ecs_id(ecs_id_t), + .callback = flecs_meta_core_pair + }); + + flecs_script_register_builtin_doc_functions(world); +} + +#endif + +/** + * @file addons/script/functions_math.c + * @brief Math functions for flecs script. + */ + + +#ifdef FLECS_SCRIPT_MATH +#include + +typedef struct ecs_script_rng_t { + uint64_t x; /* Current state (initialize with seed) */ + uint64_t w; /* Weyl sequence increment */ + uint64_t s; /* Constant for Weyl sequence */ + int32_t refcount; /* Necessary as flecs script doesn't have ref types */ + bool initialized; +} ecs_script_rng_t; + +static +ecs_script_rng_t* flecs_script_rng_new(void) { + ecs_script_rng_t *result = ecs_os_calloc_t(ecs_script_rng_t); + result->x = 0; + result->w = 0; + result->s = 0xb5ad4eceda1ce2a9; /* Constant for the Weyl sequence */ + result->refcount = 1; + result->initialized = false; + return result; +} + +static +void flecs_script_rng_keep(ecs_script_rng_t *rng) { + if (!rng) { + return; + } + rng->refcount ++; +} + +static +void flecs_script_rng_free(ecs_script_rng_t *rng) { + if (!rng) { + return; + } + ecs_assert(rng->refcount > 0, ECS_INTERNAL_ERROR, NULL); + if (!--rng->refcount) { + ecs_os_free(rng); + } +} + +static +uint64_t flecs_script_rng_next(ecs_script_rng_t *rng) { + rng->x *= rng->x; + rng->x += (rng->w += rng->s); + rng->x = (rng->x >> 32) | (rng->x << 32); + return rng->x; +} + +ECS_COMPONENT_DECLARE(EcsScriptRng); + +static +ECS_CTOR(EcsScriptRng, ptr, { + ptr->seed = 0; + ptr->impl = flecs_script_rng_new(); +}) + +static +ECS_COPY(EcsScriptRng, dst, src, { + flecs_script_rng_keep(src->impl); + if (dst->impl != src->impl) { + flecs_script_rng_free(dst->impl); + } + dst->seed = src->seed; + dst->impl = src->impl; +}) + +static +ECS_MOVE(EcsScriptRng, dst, src, { + flecs_script_rng_free(dst->impl); + dst->seed = src->seed; + dst->impl = src->impl; + src->impl = NULL; +}) + +static +ECS_DTOR(EcsScriptRng, ptr, { + flecs_script_rng_free(ptr->impl); +}) + +void flecs_script_rng_get_float( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)ctx; + (void)argc; + EcsScriptRng *rng = argv[0].ptr; + ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_rng_t *impl = rng->impl; + if (!impl->initialized) { + impl->x = rng->seed; + impl->initialized = true; + } + uint64_t x = flecs_script_rng_next(rng->impl); + double max = *(double*)argv[1].ptr; + double *r = result->ptr; + + if (ECS_EQZERO(max)) { + ecs_err("flecs.script.math.Rng.f(): invalid division by zero"); + } else { + *r = (double)x / ((double)UINT64_MAX / max); + } +} + +void flecs_script_rng_get_uint( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)ctx; + (void)argc; + EcsScriptRng *rng = argv[0].ptr; + ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_rng_t *impl = rng->impl; + if (!impl->initialized) { + impl->x = rng->seed; + impl->initialized = true; + } + uint64_t x = flecs_script_rng_next(rng->impl); + uint64_t max = *(uint64_t*)argv[1].ptr; + uint64_t *r = result->ptr; + if (!max) { + ecs_err("flecs.script.math.Rng.u(): invalid division by zero"); + } else { + *r = x % max; + } +} + +double flecs_lerp( + double a, + double b, + double t) +{ + return a + t * (b - a); +} + +void flecs_script_min( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)ctx; + (void)argc; + double a = *(double*)argv[0].ptr; + double b = *(double*)argv[1].ptr; + *(double*)result->ptr = (a < b) ? a : b; +} + +void flecs_script_max( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)ctx; + (void)argc; + double a = *(double*)argv[0].ptr; + double b = *(double*)argv[1].ptr; + *(double*)result->ptr = (a > b) ? a : b; +} + +#define FLECS_SCRIPT_LERP(type)\ + static void flecs_script_lerp_##type(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result,\ + int32_t elem_count)\ + {\ + (void)ctx; (void)argc;\ + type *a = argv[0].ptr;\ + type *b = argv[1].ptr;\ + type t = (type)*(double*)argv[2].ptr;\ + type *r = result->ptr;\ + for (int i = 0; i < elem_count; i ++) {\ + r[i] = a[i] + t * (b[i] - a[i]);\ + }\ + } + +FLECS_SCRIPT_LERP(float) +FLECS_SCRIPT_LERP(double) + +#define FLECS_SCRIPT_SMOOTHSTEP(type)\ + void flecs_script_smoothstep_##type(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result,\ + int32_t elem_count)\ + {\ + (void)ctx; (void)argc;\ + type *a = argv[0].ptr;\ + type *b = argv[1].ptr;\ + type t = (type)*(double*)argv[2].ptr;\ + type *r = result->ptr;\ + for (int i = 0; i < elem_count; i ++) {\ + type v = (t - a[i]) / (b[i] - a[i]);\ + type x = (v < (type)0) ? (type)0\ + : (v > (type)1) ? (type)1\ + : v;\ + r[i] = x * x * (3 - 2 * x);\ + }\ + } + +FLECS_SCRIPT_SMOOTHSTEP(float) +FLECS_SCRIPT_SMOOTHSTEP(double) + +#define FLECS_SCRIPT_CLAMP(type)\ + static void flecs_script_clamp_##type(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result,\ + int32_t elem_count)\ + {\ + (void)ctx; (void)argc;\ + type *v = argv[0].ptr;\ + type min = (type)*(double*)argv[1].ptr;\ + type max = (type)*(double*)argv[2].ptr;\ + type *r = result->ptr;\ + for (int i = 0; i < elem_count; i ++) {\ + r[i] = (v[i] < min) ? min\ + : (v[i] > max) ? max\ + : v[i];\ + }\ + } + +FLECS_SCRIPT_CLAMP(int8_t) +FLECS_SCRIPT_CLAMP(int16_t) +FLECS_SCRIPT_CLAMP(int32_t) +FLECS_SCRIPT_CLAMP(int64_t) +FLECS_SCRIPT_CLAMP(uint8_t) +FLECS_SCRIPT_CLAMP(uint16_t) +FLECS_SCRIPT_CLAMP(uint32_t) +FLECS_SCRIPT_CLAMP(uint64_t) +FLECS_SCRIPT_CLAMP(float) +FLECS_SCRIPT_CLAMP(double) + +#define FLECS_SCRIPT_DOT(type)\ + static void flecs_script_dot_##type(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result,\ + int32_t elem_count)\ + {\ + (void)ctx; (void)argc;\ + type *a = argv[0].ptr;\ + type *b = argv[1].ptr;\ + double *r = result->ptr;\ + for (int i = 0; i < elem_count; i ++) {\ + double x = (double)a[i], y = (double)b[i];\ + *r += x * y;\ + }\ + } + +FLECS_SCRIPT_DOT(int8_t) +FLECS_SCRIPT_DOT(int16_t) +FLECS_SCRIPT_DOT(int32_t) +FLECS_SCRIPT_DOT(int64_t) +FLECS_SCRIPT_DOT(uint8_t) +FLECS_SCRIPT_DOT(uint16_t) +FLECS_SCRIPT_DOT(uint32_t) +FLECS_SCRIPT_DOT(uint64_t) +FLECS_SCRIPT_DOT(float) +FLECS_SCRIPT_DOT(double) + +#define FLECS_SCRIPT_LENGTH(type)\ + static void flecs_script_length_##type(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result,\ + int32_t elem_count)\ + {\ + (void)ctx; (void)argc;\ + type *v = argv[0].ptr;\ + double *r = result->ptr;\ + double sum = 0;\ + for (int i = 0; i < elem_count; i ++) {\ + double x = (double)v[i];\ + sum += x * x;\ + }\ + *r = sqrt(sum);\ + } + +FLECS_SCRIPT_LENGTH(int8_t) +FLECS_SCRIPT_LENGTH(int16_t) +FLECS_SCRIPT_LENGTH(int32_t) +FLECS_SCRIPT_LENGTH(int64_t) +FLECS_SCRIPT_LENGTH(uint8_t) +FLECS_SCRIPT_LENGTH(uint16_t) +FLECS_SCRIPT_LENGTH(uint32_t) +FLECS_SCRIPT_LENGTH(uint64_t) +FLECS_SCRIPT_LENGTH(float) +FLECS_SCRIPT_LENGTH(double) + +#define FLECS_SCRIPT_LENGTH_SQ(type)\ + static void flecs_script_length_sq_##type(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result,\ + int32_t elem_count)\ + {\ + (void)ctx; (void)argc;\ + type *v = argv[0].ptr;\ + double *r = result->ptr;\ + for (int i = 0; i < elem_count; i ++) {\ + double x = (double)v[i];\ + *r += x * x;\ + }\ + } + +FLECS_SCRIPT_LENGTH_SQ(int8_t) +FLECS_SCRIPT_LENGTH_SQ(int16_t) +FLECS_SCRIPT_LENGTH_SQ(int32_t) +FLECS_SCRIPT_LENGTH_SQ(int64_t) +FLECS_SCRIPT_LENGTH_SQ(uint8_t) +FLECS_SCRIPT_LENGTH_SQ(uint16_t) +FLECS_SCRIPT_LENGTH_SQ(uint32_t) +FLECS_SCRIPT_LENGTH_SQ(uint64_t) +FLECS_SCRIPT_LENGTH_SQ(float) +FLECS_SCRIPT_LENGTH_SQ(double) + +#define FLECS_SCRIPT_NORMALIZE(type)\ + static void flecs_script_normalize_##type(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result,\ + int32_t elem_count)\ + {\ + (void)ctx; (void)argc;\ + const type *v = argv[0].ptr;\ + type *r = result->ptr;\ + double sum_sq = 0.0;\ + for (int i = 0; i < elem_count; i++) {\ + double x = (double)v[i];\ + sum_sq += x * x;\ + }\ + if (ECS_EQZERO(sum_sq)) {\ + for (int i = 0; i < elem_count; i++) r[i] = (type)0;\ + return;\ + }\ + double inv_len = 1.0 / sqrt(sum_sq);\ + for (int i = 0; i < elem_count; i++) {\ + r[i] = (type)((double)v[i] * inv_len);\ + }\ + } + +FLECS_SCRIPT_NORMALIZE(float) +FLECS_SCRIPT_NORMALIZE(double) + +#define FLECS_MATH_FUNC_F64(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx; (void)argc;\ + ecs_assert(argc == 1, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } + +#define FLECS_MATH_FUNC_F64_F64(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx; (void)argc;\ + ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + double y = *(double*)argv[1].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } + +#define FLECS_MATH_FUNC_F64_I32(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx; (void)argc;\ + ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + ecs_i32_t y = *(ecs_i32_t*)argv[1].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } + +#define FLECS_MATH_FUNC_DEF_F64(_name, brief)\ + {\ + ecs_entity_t f = ecs_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {{ .name = "x", .type = ecs_id(ecs_f64_t) }},\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } + +#define FLECS_MATH_FUNC_DEF_F64_F64(_name, brief)\ + {\ + ecs_entity_t f = ecs_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {\ + { .name = "x", .type = ecs_id(ecs_f64_t) },\ + { .name = "y", .type = ecs_id(ecs_f64_t) }\ + },\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } + +#define FLECS_MATH_FUNC_DEF_F64_F32(_name, brief)\ + {\ + ecs_entity_t f = ecs_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {\ + { .name = "x", .type = ecs_id(ecs_f64_t) },\ + { .name = "y", .type = ecs_id(ecs_i32_t) }\ + },\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } + +/* Trigonometric functions */ +FLECS_MATH_FUNC_F64(cos, cos(x)) +FLECS_MATH_FUNC_F64(sin, sin(x)) +FLECS_MATH_FUNC_F64(tan, tan(x)) +FLECS_MATH_FUNC_F64(acos, acos(x)) +FLECS_MATH_FUNC_F64(asin, asin(x)) +FLECS_MATH_FUNC_F64(atan, atan(x)) +FLECS_MATH_FUNC_F64_F64(atan2, atan2(x, y)) + +/* Hyperbolic functions */ +FLECS_MATH_FUNC_F64(cosh, cosh(x)) +FLECS_MATH_FUNC_F64(sinh, sinh(x)) +FLECS_MATH_FUNC_F64(tanh, tanh(x)) +FLECS_MATH_FUNC_F64(acosh, acosh(x)) +FLECS_MATH_FUNC_F64(asinh, asinh(x)) +FLECS_MATH_FUNC_F64(atanh, atanh(x)) + +/* Exponential and logarithmic functions */ +FLECS_MATH_FUNC_F64(exp, exp(x)) +FLECS_MATH_FUNC_F64_I32(ldexp, ldexp(x, y)) +FLECS_MATH_FUNC_F64(log, log(x)) +FLECS_MATH_FUNC_F64(log10, log10(x)) +FLECS_MATH_FUNC_F64(exp2, exp2(x)) +FLECS_MATH_FUNC_F64(log2, log2(x)) + +/* Power functions */ +FLECS_MATH_FUNC_F64_F64(pow, pow(x, y)) +FLECS_MATH_FUNC_F64(sqrt, sqrt(x)) +FLECS_MATH_FUNC_F64(sqr, x * x) + +/* Rounding functions */ +FLECS_MATH_FUNC_F64(ceil, ceil(x)) +FLECS_MATH_FUNC_F64(floor, floor(x)) +FLECS_MATH_FUNC_F64(round, round(x)) + +FLECS_MATH_FUNC_F64(abs, fabs(x)) + +void FlecsScriptMathImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsScriptMath); + + ECS_IMPORT(world, FlecsScript); + + /* Constants */ + double E = 2.71828182845904523536028747135266250; + ecs_const_var(world, { + .name = "E", + .parent = ecs_id(FlecsScriptMath), + .type = ecs_id(ecs_f64_t), + .value = &E + }); + + double PI = 3.14159265358979323846264338327950288; + ecs_const_var(world, { + .name = "PI", + .parent = ecs_id(FlecsScriptMath), + .type = ecs_id(ecs_f64_t), + .value = &PI + }); + + /* Trigonometric functions */ + FLECS_MATH_FUNC_DEF_F64(cos, "Compute cosine"); + FLECS_MATH_FUNC_DEF_F64(sin, "Compute sine"); + FLECS_MATH_FUNC_DEF_F64(tan, "Compute tangent"); + FLECS_MATH_FUNC_DEF_F64(acos, "Compute arc cosine"); + FLECS_MATH_FUNC_DEF_F64(asin, "Compute arc sine"); + FLECS_MATH_FUNC_DEF_F64(atan, "Compute arc tangent"); + FLECS_MATH_FUNC_DEF_F64_F64(atan2, "Compute arc tangent with two parameters"); + + /* Hyperbolic functions */ + FLECS_MATH_FUNC_DEF_F64(cosh, "Compute hyperbolic cosine"); + FLECS_MATH_FUNC_DEF_F64(sinh, "Compute hyperbolic sine"); + FLECS_MATH_FUNC_DEF_F64(tanh, "Compute hyperbolic tangent"); + FLECS_MATH_FUNC_DEF_F64(acosh, "Compute area hyperbolic cosine"); + FLECS_MATH_FUNC_DEF_F64(asinh, "Compute area hyperbolic sine"); + FLECS_MATH_FUNC_DEF_F64(atanh, "Compute area hyperbolic tangent"); + + /* Exponential and logarithmic functions */ + FLECS_MATH_FUNC_DEF_F64(exp, "Compute exponential function"); + FLECS_MATH_FUNC_DEF_F64_F32(ldexp, "Generate value from significant and exponent"); + FLECS_MATH_FUNC_DEF_F64(log, "Compute natural logarithm"); + FLECS_MATH_FUNC_DEF_F64(log10, "Compute common logarithm"); + FLECS_MATH_FUNC_DEF_F64(exp2, "Compute binary exponential function"); + FLECS_MATH_FUNC_DEF_F64(log2, "Compute binary logarithm"); + + /* Power functions */ + FLECS_MATH_FUNC_DEF_F64_F64(pow, "Raise to power"); + FLECS_MATH_FUNC_DEF_F64(sqrt, "Compute square root"); + FLECS_MATH_FUNC_DEF_F64(sqr, "Compute square"); + + /* Rounding functions */ + FLECS_MATH_FUNC_DEF_F64(ceil, "Round up value"); + FLECS_MATH_FUNC_DEF_F64(floor, "Round down value"); + FLECS_MATH_FUNC_DEF_F64(round, "Round to nearest"); + + FLECS_MATH_FUNC_DEF_F64(abs, "Compute absolute value"); + + ecs_set_name_prefix(world, "EcsScript"); + + ECS_COMPONENT_DEFINE(world, EcsScriptRng); + + ecs_set_hooks(world, EcsScriptRng, { + .ctor = ecs_ctor(EcsScriptRng), + .move = ecs_move(EcsScriptRng), + .copy = ecs_copy(EcsScriptRng), + .dtor = ecs_dtor(EcsScriptRng), + }); + + ecs_struct(world, { + .entity = ecs_id(EcsScriptRng), + .members = { + { .name = "seed", .type = ecs_id(ecs_u64_t) } + } + }); + + ecs_method(world, { + .parent = ecs_id(EcsScriptRng), + .name = "f", + .return_type = ecs_id(ecs_f64_t), + .params = { + { .name = "max", .type = ecs_id(ecs_f64_t) } + }, + .callback = flecs_script_rng_get_float + }); + + ecs_method(world, { + .parent = ecs_id(EcsScriptRng), + .name = "u", + .return_type = ecs_id(ecs_u64_t), + .params = { + { .name = "max", .type = ecs_id(ecs_u64_t) } + }, + .callback = flecs_script_rng_get_uint + }); + + ecs_function(world, { + .name = "min", + .parent = ecs_id(FlecsScriptMath), + .return_type = ecs_id(ecs_f64_t), + .params = { + { .name = "a", .type = ecs_id(ecs_f64_t) }, + { .name = "b", .type = ecs_id(ecs_f64_t) }, + }, + .callback = flecs_script_min + }); + + ecs_function(world, { + .name = "max", + .parent = ecs_id(FlecsScriptMath), + .return_type = ecs_id(ecs_f64_t), + .params = { + { .name = "a", .type = ecs_id(ecs_f64_t) }, + { .name = "b", .type = ecs_id(ecs_f64_t) }, + }, + .callback = flecs_script_max + }); + + ecs_function(world, { + .name = "dot", + .parent = ecs_id(FlecsScriptMath), + .return_type = ecs_id(ecs_f64_t), + .params = { + { .name = "a", .type = EcsScriptVectorType }, + { .name = "b", .type = EcsScriptVectorType }, + }, + .vector_callbacks = { + [EcsI8] = flecs_script_dot_int8_t, + [EcsI16] = flecs_script_dot_int16_t, + [EcsI32] = flecs_script_dot_int32_t, + [EcsI64] = flecs_script_dot_int64_t, + [EcsU8] = flecs_script_dot_uint8_t, + [EcsU16] = flecs_script_dot_uint16_t, + [EcsU32] = flecs_script_dot_uint32_t, + [EcsU64] = flecs_script_dot_uint64_t, + [EcsF32] = flecs_script_dot_float, + [EcsF64] = flecs_script_dot_double + } + }); + + ecs_function(world, { + .name = "length", + .parent = ecs_id(FlecsScriptMath), + .return_type = ecs_id(ecs_f64_t), + .params = { + { .name = "v", .type = EcsScriptVectorType } + }, + .vector_callbacks = { + [EcsI8] = flecs_script_length_int8_t, + [EcsI16] = flecs_script_length_int16_t, + [EcsI32] = flecs_script_length_int32_t, + [EcsI64] = flecs_script_length_int64_t, + [EcsU8] = flecs_script_length_uint8_t, + [EcsU16] = flecs_script_length_uint16_t, + [EcsU32] = flecs_script_length_uint32_t, + [EcsU64] = flecs_script_length_uint64_t, + [EcsF32] = flecs_script_length_float, + [EcsF64] = flecs_script_length_double + } + }); + + ecs_function(world, { + .name = "length_sq", + .parent = ecs_id(FlecsScriptMath), + .return_type = ecs_id(ecs_f64_t), + .params = { + { .name = "v", .type = EcsScriptVectorType } + }, + .vector_callbacks = { + [EcsI8] = flecs_script_length_sq_int8_t, + [EcsI16] = flecs_script_length_sq_int16_t, + [EcsI32] = flecs_script_length_sq_int32_t, + [EcsI64] = flecs_script_length_sq_int64_t, + [EcsU8] = flecs_script_length_sq_uint8_t, + [EcsU16] = flecs_script_length_sq_uint16_t, + [EcsU32] = flecs_script_length_sq_uint32_t, + [EcsU64] = flecs_script_length_sq_uint64_t, + [EcsF32] = flecs_script_length_sq_float, + [EcsF64] = flecs_script_length_sq_double + } + }); + + ecs_function(world, { + .name = "normalize", + .parent = ecs_id(FlecsScriptMath), + .return_type = EcsScriptVectorType, + .params = { + { .name = "v", .type = EcsScriptVectorType } + }, + .vector_callbacks = { + [EcsF32] = flecs_script_normalize_float, + [EcsF64] = flecs_script_normalize_double + } + }); + + ecs_function(world, { + .name = "lerp", + .parent = ecs_id(FlecsScriptMath), + .return_type = EcsScriptVectorType, + .params = { + { .name = "a", .type = EcsScriptVectorType }, + { .name = "b", .type = EcsScriptVectorType }, + { .name = "t", .type = ecs_id(ecs_f64_t) } + }, + .vector_callbacks = { + [EcsF32] = flecs_script_lerp_float, + [EcsF64] = flecs_script_lerp_double + } + }); + + ecs_function(world, { + .name = "smoothstep", + .parent = ecs_id(FlecsScriptMath), + .return_type = EcsScriptVectorType, + .params = { + { .name = "a", .type = EcsScriptVectorType }, + { .name = "b", .type = EcsScriptVectorType }, + { .name = "t", .type = ecs_id(ecs_f64_t) } + }, + .vector_callbacks = { + [EcsF32] = flecs_script_smoothstep_float, + [EcsF64] = flecs_script_smoothstep_double + } + }); + + ecs_function(world, { + .name = "clamp", + .parent = ecs_id(FlecsScriptMath), + .return_type = EcsScriptVectorType, + .params = { + { .name = "v", .type = EcsScriptVectorType }, + { .name = "min", .type = ecs_id(ecs_f64_t) }, + { .name = "max", .type = ecs_id(ecs_f64_t) } + }, + .vector_callbacks = { + [EcsI8] = flecs_script_clamp_int8_t, + [EcsI16] = flecs_script_clamp_int16_t, + [EcsI32] = flecs_script_clamp_int32_t, + [EcsI64] = flecs_script_clamp_int64_t, + [EcsU8] = flecs_script_clamp_uint8_t, + [EcsU16] = flecs_script_clamp_uint16_t, + [EcsU32] = flecs_script_clamp_uint32_t, + [EcsU64] = flecs_script_clamp_uint64_t, + [EcsF32] = flecs_script_clamp_float, + [EcsF64] = flecs_script_clamp_double + } + }); + + FlecsScriptMathPerlinImport(world); +} + +#endif + +/** + * @file addons/script/functions_math_perlin.c + * @brief Perlin noise functions for flecs script. + * + * - flecs_perlin2(x, y): returns noise in ~[-1, 1] + * - flecs_perlin_seed(seed): initializes permutation table for repeatable noise + */ + + +#ifdef FLECS_SCRIPT_MATH +#include + +static uint8_t flecs_perlin_perm[512]; + +static +uint32_t flecs_perlin_xs32( + uint32_t *state) +{ + uint32_t x = *state; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *state = x; + return x; +} + +void flecs_perlin_seed( + uint32_t seed) +{ + uint8_t p[256]; + for (int i = 0; i < 256; i++) p[i] = (uint8_t)i; + + uint32_t st = (seed ? seed : 0xA341316Cu); + + for (int i = 255; i > 0; i--) { + uint32_t r = flecs_perlin_xs32(&st); + int j = (int)(r % (uint32_t)(i + 1)); + uint8_t tmp = p[i]; + p[i] = p[j]; + p[j] = tmp; + } + + for (int i = 0; i < 256; i++) { + flecs_perlin_perm[i] = p[i]; + flecs_perlin_perm[i + 256] = p[i]; + } +} + +static +double flecs_perlin_fade( + double t) +{ + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +} + +static +double flecs_perlin_grad2( + int hash, + double x, + double y) +{ + switch (hash & 7) { + case 0: return x + y; + case 1: return -x + y; + case 2: return x - y; + case 3: return -x - y; + case 4: return x; + case 5: return -x; + case 6: return y; + default: return -y; + } +} + +static +double flecs_perlin2( + double x, + double y) +{ + int X = (int)floor(x) & 255; + int Y = (int)floor(y) & 255; + + double xf = x - floor(x); + double yf = y - floor(y); + + double u = flecs_perlin_fade(xf); + double v = flecs_perlin_fade(yf); + + int aa = flecs_perlin_perm[flecs_perlin_perm[X] + Y]; + int ab = flecs_perlin_perm[flecs_perlin_perm[X] + Y + 1]; + int ba = flecs_perlin_perm[flecs_perlin_perm[X + 1] + Y]; + int bb = flecs_perlin_perm[flecs_perlin_perm[X + 1] + Y + 1]; + + double x1 = flecs_lerp( + flecs_perlin_grad2(aa, xf, yf), + flecs_perlin_grad2(ba, xf - 1, yf), + u); + + double x2 = flecs_lerp( + flecs_perlin_grad2(ab, xf, yf - 1), + flecs_perlin_grad2(bb, xf - 1, yf - 1), + u); + + return flecs_lerp(x1, x2, v); +} + +static +void flecs_script_perlin2( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + (void)ctx; + double x = *(double*)argv[0].ptr; + double y = *(double*)argv[1].ptr; + *(double*)result->ptr = flecs_perlin2(x, y); +} + +void FlecsScriptMathPerlinImport( + ecs_world_t *world) +{ + ecs_entity_t FlecsScriptMath = ecs_lookup(world, "flecs.script.math"); + ecs_assert(FlecsScriptMath != 0, ECS_INTERNAL_ERROR, NULL); + + flecs_perlin_seed(1234); + + ecs_function(world, { + .name = "perlin2", + .parent = FlecsScriptMath, + .return_type = ecs_id(ecs_f64_t), + .params = { + { .name = "x", .type = ecs_id(ecs_f64_t) }, + { .name = "y", .type = ecs_id(ecs_f64_t) }, + }, + .callback = flecs_script_perlin2 + }); +} + +#endif + +/** + * @file addons/script/parser.c + * @brief Script grammar parser. + */ + + + +#ifdef FLECS_SCRIPT + +#define EcsTokEndOfStatement\ + case ';':\ + case '\n':\ + case '\0' + +/* Parse scope (statements inside {}) */ +static +const char* flecs_script_scope( + ecs_parser_t *parser, + ecs_script_scope_t *scope, + const char *pos) +{ + ParserBegin; + + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pos[-1] == '{', ECS_INTERNAL_ERROR, NULL); + + if (parser->scope_depth >= ECS_PARSER_MAX_RECURSION_DEPTH) { + ecs_parser_error(parser->name, parser->code, pos - parser->code, + "maximum scope nesting depth exceeded"); + return NULL; + } + + parser->scope_depth ++; + + ecs_script_scope_t *prev = parser->scope; + parser->scope = scope; + + Loop( + LookAhead( + case EcsTokScopeClose: + pos = lookahead; + goto scope_close; + case EcsTokEnd: + Error("unexpected end of script"); + goto error; + ) + + pos = flecs_script_stmt(parser, pos); + if (!pos) { + goto error; + } + ) + +scope_close: + parser->scope = prev; + parser->scope_depth --; + + ecs_assert(pos[-1] == '}', ECS_INTERNAL_ERROR, NULL); + return pos; + + Error("unexpected end of rule (parser error)"); +error: + parser->scope = prev; + parser->scope_depth --; + return NULL; +} + +/* Parse comma expression (expressions separated by ',') */ +static +const char* flecs_script_comma_expr( + ecs_parser_t *parser, + const char *pos, + bool is_base_list) +{ + ParserBegin; + + Loop( + LookAhead( + case '\n': + pos = lookahead; + continue; + + case EcsTokIdentifier: + LookAhead_Keep(); + + if (is_base_list) { + flecs_script_insert_pair_tag(parser, "IsA", Token(0)); + } else { + flecs_script_insert_entity(parser, Token(0)); + } + + LookAhead_1(',', + pos = lookahead; + continue; + ) + ) + + break; + ) + + return pos; +error: + return NULL; +} + +/* Parse with expression (expression after 'with' keyword) */ +static +const char* flecs_script_with_expr( + ecs_parser_t *parser, + const char *pos) +{ + ParserBegin; + + Parse( + // Position + case EcsTokIdentifier: { + // Position ( + LookAhead_1('(', + pos = lookahead; + + // Position ( expr ) + Initializer(')', + ecs_script_component_t *component = + flecs_script_insert_component(parser, Token(0)); + component->node.kind = EcsAstWithComponent; + component->expr = INITIALIZER; + EndOfRule; + ) + ) + + if (Token(0)[0] == '$') { + ecs_script_var_component_t *var = + flecs_script_insert_var_component(parser, &Token(0)[1]); + var->node.kind = EcsAstWithVar; + } else { + ecs_script_tag_t *tag = + flecs_script_insert_tag(parser, Token(0)); + tag->node.kind = EcsAstWithTag; + } + + EndOfRule; + } + + // ( + case '(': + // (Eats, Apples) + Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', + // (Eats, Apples) ( expr + LookAhead_1('(', + pos = lookahead; + + // (Eats, Apples) ( expr ) + Initializer(')', + ecs_script_component_t *component = + flecs_script_insert_pair_component(parser, + Token(1), Token(3)); + component->node.kind = EcsAstWithComponent; + component->expr = INITIALIZER; + EndOfRule; + ) + ) + + ecs_script_tag_t *tag = + flecs_script_insert_pair_tag(parser, Token(1), Token(3)); + tag->node.kind = EcsAstWithTag; + EndOfRule; + ) + ) + + ParserEnd; +} + +/* Parse with expression list (expression list after 'with' keyword) */ +static +const char* flecs_script_with( + ecs_parser_t *parser, + ecs_script_with_t *with, + const char *pos) +{ + ParserBegin; + + bool has_next; + do { + Scope(with->expressions, + pos = flecs_script_with_expr(parser, pos); + ) + + if (!pos) { + goto error; + } + + Parse( + case ',': { + has_next = true; + break; + } + case '{': { + return flecs_script_scope(parser, with->scope, pos); + } + ) + } while (has_next); + + ParserEnd; +} + +/* Parenthesis expression */ +static +const char* flecs_script_paren_expr( + ecs_parser_t *parser, + const char *kind, + ecs_script_entity_t *entity, + const char *pos) +{ + ParserBegin; + + Initializer(')', + entity->kind_w_expr = true; + + Scope(entity->scope, + ecs_script_component_t *component = + flecs_script_insert_component(parser, kind); + component->expr = INITIALIZER; + ) + + Parse( + // Position spaceship(expr) } + // This can happen when used as new expression. + case '}': + pos --; + + // Position spaceship (expr)\n + EcsTokEndOfStatement: { + EndOfRule; + } + + // Position spaceship (expr) { + case '{': { + return flecs_script_scope(parser, entity->scope, pos); + } + ) + ) + + ParserEnd; +} + +/* Parse a single statement */ +static +const char* flecs_script_if_stmt( + ecs_parser_t *parser, + const char *pos) +{ + ParserBegin; + + // if expr + Expr('\0', + // if expr { + Parse_1('{', { + ecs_script_if_t *stmt = flecs_script_insert_if(parser); + stmt->expr = EXPR; + pos = flecs_script_scope(parser, stmt->if_true, pos); + if (!pos) { + goto error; + } + + // if expr { } else + LookAhead_1(EcsTokKeywordElse, + pos = lookahead; + + Parse( + // if expr { } else if + case EcsTokKeywordIf: { + Scope(stmt->if_false, + return flecs_script_if_stmt(parser, pos); + ) + } + + // if expr { } else\n if + case EcsTokNewline: { + Parse( + case EcsTokKeywordIf: { + Scope(stmt->if_false, + return flecs_script_if_stmt(parser, pos); + ) + } + + // if expr { } else\n { + case '{': { + return flecs_script_scope( + parser, stmt->if_false, pos); + } + ) + } + + // if expr { } else { + case '{': { + return flecs_script_scope(parser, stmt->if_false, pos); + } + ) + ) + + EndOfRule; + }); + ) + + ParserEnd; +} + +static +const char* flecs_script_parse_var( + ecs_parser_t *parser, + const char *pos, + ecs_tokenizer_t *tokenizer, + ecs_script_node_kind_t kind) +{ + int token_offset = kind != EcsAstExportConst ? 0 : 1; + + Parse_1(EcsTokIdentifier, + ecs_script_var_node_t *var = flecs_script_insert_var( + parser, Token(1 + token_offset)); + var->node.kind = kind; + + bool is_prop = kind == EcsAstProp; + + Parse( + // const color = + case '=': { + // const color = Color : + LookAhead_2(EcsTokIdentifier, ':', + pos = lookahead; + + var->type = Token(3 + token_offset); + + { + // Position: { + LookAhead_1('{', + pos = lookahead; + Expr('}', { + var->expr = EXPR; + EndOfRule; + }) + ) + } + + { + // Position: [ + LookAhead_1('[', + pos = lookahead; + Expr(']', { + var->expr = EXPR; + EndOfRule; + }) + ) + } + + // const color = Color: expr\n + Initializer('\n', + var->expr = INITIALIZER; + EndOfRule; + ) + ) + + // const PI = expr\n + Expr('\n', + Warning("'%s var = expr' syntax is deprecated" + ", use '%s var: expr' instead", + is_prop ? "prop" : "const", + is_prop ? "prop" : "const"); + var->expr = EXPR; + EndOfRule; + ) + } + + case ':': { + // const PI: expr\n + Expr('\n', + var->expr = EXPR; + EndOfRule; + ) + } + ) + ) + +error: + return NULL; +} + +static +const char* flecs_script_parse_const( + ecs_parser_t *parser, + const char *pos, + ecs_tokenizer_t *tokenizer) +{ + return flecs_script_parse_var(parser, pos, tokenizer, EcsAstConst); +} + +static +const char* flecs_script_parse_export_const( + ecs_parser_t *parser, + const char *pos, + ecs_tokenizer_t *tokenizer) +{ + return flecs_script_parse_var(parser, pos, tokenizer, EcsAstExportConst); +} + +static +const char* flecs_script_parse_prop( + ecs_parser_t *parser, + const char *pos, + ecs_tokenizer_t *tokenizer) +{ + return flecs_script_parse_var(parser, pos, tokenizer, EcsAstProp); +} + +/* Parse a single statement */ +const char* flecs_script_stmt( + ecs_parser_t *parser, + const char *pos) +{ + ParserBegin; + + bool name_is_expr_0 = false; + + Parse( + case EcsTokIdentifier: goto identifier; + case EcsTokString: goto string_name; + case '{': goto anonymous_entity; + case '(': goto paren; + case '@': goto annotation; + case EcsTokKeywordWith: goto with_stmt; + case EcsTokKeywordModule: goto module_stmt; + case EcsTokKeywordUsing: goto using_stmt; + case EcsTokKeywordTemplate: goto template_stmt; + case EcsTokKeywordProp: goto prop_var; + case EcsTokKeywordConst: goto const_var; + case EcsTokKeywordExport: goto export_var; + case EcsTokKeywordIf: goto if_stmt; + case EcsTokKeywordFor: goto for_stmt; + EcsTokEndOfStatement: EndOfRule; + ); + +anonymous_entity: { + return flecs_script_scope(parser, + flecs_script_insert_entity(parser, "_")->scope, pos); +} + +string_name: + /* If this is an interpolated string, we need to evaluate it as expression + * at evaluation time. Otherwise we can just use the string as name. The + * latter is useful if an entity name contains special characters that are + * not allowed in identifier tokens. */ + if (flecs_string_is_interpolated(Token(0))) { + name_is_expr_0 = true; + } + +identifier: { + // enterprise } (end of scope) + LookAhead_1('}', + goto insert_tag; + ) + + Parse( + // enterprise { + case '{': { + return flecs_script_scope(parser, + flecs_script_insert_entity(parser, Token(0))->scope, pos); + } + + // Red, + case ',': { + if (name_is_expr_0) { + Error("expression not allowed as entity name here"); + } + + flecs_script_insert_entity(parser, Token(0)); + pos = flecs_script_comma_expr(parser, pos, false); + EndOfRule; + } + + // Npc\n + EcsTokEndOfStatement: { + // Npc\n{ + LookAhead_1('{', + pos = lookahead; + return flecs_script_scope(parser, + flecs_script_insert_entity(parser, Token(0))->scope, pos); + ) + + goto insert_tag; + } + + // auto_override | + case '|': { + goto identifier_flag; + } + + // Position: + case ':': { + goto identifier_colon; + } + + // x = + case '=': { + goto identifier_assign; + } + + // SpaceShip( + case '(': { + goto identifier_paren; + } + + // Spaceship enterprise + case EcsTokIdentifier: { + goto identifier_identifier; + } + + // Spaceship "enterprise" + case EcsTokString: { + goto identifier_string; + } + ) +} + +insert_tag: { + if (Token(0)[0] == '$') { + if (!flecs_script_insert_var_component(parser, &Token(0)[1])) { + Error( + "invalid context for variable component '%s': must be " + "part of entity", tokenizer->tokens[0].value); + } + } else { + if (!flecs_script_insert_tag(parser, Token(0))) { + Error( + "invalid context for tag '%s': must be part of entity", + tokenizer->tokens[0].value); + } + } + + EndOfRule; +} + +// @ +annotation: { + // @brief + Parse_1(EcsTokIdentifier, + // $brief expr + Until('\n', + flecs_script_insert_annot(parser, Token(1), Token(2)); + EndOfRule; + ) + ) +} + +// with +with_stmt: { + ecs_script_with_t *with = flecs_script_insert_with(parser); + pos = flecs_script_with(parser, with, pos); + EndOfRule; +} + +// using +using_stmt: { + // using flecs.meta\n + Parse_1(EcsTokIdentifier, + flecs_script_insert_using(parser, Token(1)); + + Parse( + EcsTokEndOfStatement: + EndOfRule; + ) + ) +} + +// module +module_stmt: { + // using flecs.meta\n + Parse_2(EcsTokIdentifier, '\n', + flecs_script_insert_module(parser, Token(1)); + EndOfRule; + ) +} + +// template +template_stmt: { + // template SpaceShip + Parse_1(EcsTokIdentifier, + ecs_script_template_node_t *template = flecs_script_insert_template( + parser, Token(1)); + + Parse( + // template SpaceShip { + case '{': + return flecs_script_scope(parser, template->scope, pos); + + // template SpaceShip\n + EcsTokEndOfStatement: + EndOfRule; + ) + ) +} + +// prop +prop_var: { + // prop color = Color: + return flecs_script_parse_prop(parser, pos, tokenizer); +} + +// export +export_var: { + // export const + Parse_1(EcsTokKeywordConst, + return flecs_script_parse_export_const(parser, pos, tokenizer); + ) +} + +// const +const_var: { + // const color + return flecs_script_parse_const(parser, pos, tokenizer); +} + +// if +if_stmt: { + return flecs_script_if_stmt(parser, pos); +} + +// for +for_stmt: { + // for i + Parse_2(EcsTokIdentifier, EcsTokKeywordIn, { + Expr(0, { + ecs_expr_node_t *from = EXPR; + Parse_1(EcsTokRange, { + Expr(0, { + ecs_expr_node_t *to = EXPR; + ecs_script_for_range_t *stmt = + flecs_script_insert_for_range(parser); + stmt->loop_var = Token(1); + stmt->from = from; + stmt->to = to; + + Parse_1('{', { + return flecs_script_scope(parser, stmt->scope, pos); + }); + }); + }); + }); + + }); +} + +// ( +paren: { + Parse( + case EcsTokIdentifier: + case EcsTokString: + Parse_1(',', + Parse( + case EcsTokIdentifier: + case EcsTokString: + Parse_1(')', + goto pair; + ) + ) + ) + ) +} + +// (Likes, Apples) +pair: { + // (Likes, Apples) } (end of scope) + LookAhead_1('}', + flecs_script_insert_pair_tag(parser, Token(1), Token(3)); + EndOfRule; + ) + + Parse( + // (Likes, Apples)\n + EcsTokEndOfStatement: { + flecs_script_insert_pair_tag(parser, Token(1), Token(3)); + EndOfRule; + } + + // (Eats, Apples): + case ':': { + // Use lookahead so that expression parser starts at "match" + LookAhead_1(EcsTokKeywordMatch, { + // (Eats, Apples): match expr + Expr('\n', { + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = EXPR; + EndOfRule; + }) + }) + + // (Eats, Apples): { + Parse_1('{', { + // (Eats, Apples): { expr } + Initializer('}', + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = INITIALIZER; + EndOfRule; + ) + } + ) + } + + // (IsA, Machine) { + case '{': { + ecs_script_pair_scope_t *ps = flecs_script_insert_pair_scope( + parser, Token(1), Token(3)); + return flecs_script_scope(parser, ps->scope, pos); + } + ) +} + +// auto_override | +identifier_flag: { + ecs_id_t flag; + if (!ecs_os_strcmp(Token(0), "auto_override")) { + flag = ECS_AUTO_OVERRIDE; + } else { + Error("invalid flag '%s'", Token(0)); + } + + Parse( + // auto_override | ( + case '(': + // auto_override | (Rel, Tgt) + Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', + ecs_script_tag_t *tag = flecs_script_insert_pair_tag( + parser, Token(3), Token(5)); + tag->id.flag = flag; + + Parse( + // auto_override | (Rel, Tgt)\n + EcsTokEndOfStatement: { + EndOfRule; + } + + // auto_override | (Rel, Tgt): + case ':': { + Parse_1('{', + // auto_override | (Rel, Tgt): {expr} + Expr('}', { + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(3), Token(5)); + comp->expr = EXPR; + EndOfRule; + }) + ) + } + ) + ) + + // auto_override | Position + case EcsTokIdentifier: { + ecs_script_tag_t *tag = flecs_script_insert_tag( + parser, Token(2)); + tag->id.flag = flag; + + Parse( + // auto_override | Position\n + EcsTokEndOfStatement: { + EndOfRule; + } + + // auto_override | Position: + case ':': { + Parse_1('{', + // auto_override | Position: {expr} + Expr('}', { + ecs_script_component_t *comp = + flecs_script_insert_component( + parser, Token(2)); + comp->expr = EXPR; + EndOfRule; + }) + ) + } + ) + } + ) +} + +// Position: +identifier_colon: { + { + // Position: { + LookAhead_1('{', + pos = lookahead; + goto component_expr_scope; + ) + } + + { + // Position: [ + LookAhead_1('[', + pos = lookahead; + goto component_expr_collection; + ) + } + + { + // Position: match + LookAhead_1(EcsTokKeywordMatch, + goto component_expr_match; + ) + } + + // enterprise : SpaceShip + Parse_1(EcsTokIdentifier, { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, Token(0)); + + Scope(entity->scope, + flecs_script_insert_pair_tag(parser, "IsA", Token(2)); + + LookAhead_1(',', { + pos = lookahead; + pos = flecs_script_comma_expr(parser, pos, true); + }) + ) + + Parse( + // enterprise : SpaceShip { + case '{': + return flecs_script_scope(parser, entity->scope, pos); + ) + }) +} + +// x = +identifier_assign: { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, Token(0)); + + // x = Position: + LookAhead_2(EcsTokIdentifier, ':', + pos = lookahead; + + // Use lookahead so that expression parser starts at "match" + LookAhead_1(EcsTokKeywordMatch, { + // (Eats, Apples): match expr + Expr('\n', { + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = EXPR; + EndOfRule; + }) + }) + + // x = Position: { + Parse_1('{', { + // x = Position: {expr} + Expr('}', + Scope(entity->scope, + ecs_script_component_t *comp = + flecs_script_insert_component(parser, Token(2)); + comp->expr = EXPR; + ) + + // x = Position: {expr}\n + Parse( + EcsTokEndOfStatement: + EndOfRule; + ) + ) + }) + ) + + // x = f32\n + Initializer('\n', + Scope(entity->scope, + ecs_script_default_component_t *comp = + flecs_script_insert_default_component(parser); + comp->expr = INITIALIZER; + ) + + EndOfRule; + ) +} + +// Spaceship enterprise +identifier_string: +identifier_identifier: { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, Token(1)); + entity->kind = Token(0); + + // Spaceship enterprise : + LookAhead_1(':', + pos = lookahead; + + Parse_1(EcsTokIdentifier, { + Scope(entity->scope, + flecs_script_insert_pair_tag(parser, "IsA", Token(3)); + + LookAhead_1(',', { + pos = lookahead; + pos = flecs_script_comma_expr(parser, pos, true); + }) + ) + + goto identifier_identifier_x; + }) + ) + +identifier_identifier_x: + Parse( + // Spaceship enterprise\n + EcsTokEndOfStatement: { + EndOfRule; + } + + // Spaceship enterprise { + case '{': { + return flecs_script_scope(parser, entity->scope, pos); + } + + // Spaceship enterprise( + case '(': { + return flecs_script_paren_expr(parser, Token(0), entity, pos); + } + ) +} + +// SpaceShip( +identifier_paren: { + // SpaceShip() + Initializer(')', + Parse( + // SpaceShip(expr) } + // This can happen when used as new expression. + case '}': + pos --; + + // SpaceShip(expr)\n + EcsTokEndOfStatement: { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, NULL); + + Scope(entity->scope, + ecs_script_component_t *comp = + flecs_script_insert_component(parser, Token(0)); + comp->expr = INITIALIZER; + ) + + EndOfRule; + } + + // SpaceShip(expr) { + case '{': { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, NULL); + + Scope(entity->scope, + ecs_script_component_t *comp = + flecs_script_insert_component(parser, Token(0)); + comp->expr = INITIALIZER; + ) + + return flecs_script_scope(parser, entity->scope, pos); + } + ) + ) +} + +// Position: { +component_expr_scope: { + + // Position: {expr} + Expr('}', { + ecs_script_component_t *comp = flecs_script_insert_component( + parser, Token(0)); + comp->expr = EXPR; + EndOfRule; + }) +} + +// Points: [ +component_expr_collection: { + // Position: [expr] + Expr(']', { + ecs_script_component_t *comp = flecs_script_insert_component( + parser, Token(0)); + comp->expr = EXPR; + comp->is_collection = true; + EndOfRule; + }) +} + +// Position: match +component_expr_match: { + + // Position: match expr + Expr('\n', { + ecs_script_component_t *comp = flecs_script_insert_component( + parser, Token(0)); + comp->expr = EXPR; + EndOfRule; + }) +} + + ParserEnd; +} + +/* Parse script */ +ecs_script_t* ecs_script_parse( + ecs_world_t *world, + const char *name, + const char *code, + const ecs_script_eval_desc_t *desc, + ecs_script_eval_result_t *result) +{ + (void)desc; /* Will be used in future to expand type checking features */ + + if (!code) { + code = ""; + } + + if (result) { + ecs_log_start_capture(true); + } + + ecs_script_t *script = flecs_script_new(world); + script->name = ecs_os_strdup(name); + script->code = ecs_os_strdup(code); + + ecs_script_impl_t *impl = flecs_script_impl(script); + + ecs_parser_t parser = { + .name = script->name, + .code = script->code, + .script = impl, + .scope = impl->root, + .significant_newline = true + }; + + /* Allocate a buffer that is able to store all parsed tokens. Multiply the + * size of the script by two so that there is enough space to add \0 + * terminators and expression deliminators ('""') + * The token buffer will exist for as long as the script object exists, and + * ensures that AST nodes don't need to do separate allocations for the data + * they contain. */ + impl->token_buffer_size = ecs_os_strlen(code) * 2 + 1; + impl->token_buffer = flecs_alloc_w_dbg_info( + &impl->allocator, impl->token_buffer_size, "token buffer"); + parser.token_cur = impl->token_buffer; + + /* Start parsing code */ + const char *pos = script->code; + + do { + pos = flecs_script_stmt(&parser, pos); + if (!pos) { + /* NULL means error */ + goto error; + } + + if (!pos[0]) { + /* \0 means end of input */ + break; + } + } while (true); + + impl->token_remaining = parser.token_cur; + + if (result) { + ecs_log_stop_capture(); + } + + return script; +error: + if (result) { + result->error = ecs_log_stop_capture(); + } + + ecs_script_free(script); + return NULL; +} + +#endif + +/** + * @file addons/script/script.c + * @brief Script API. + */ + + +#ifdef FLECS_SCRIPT + +ECS_COMPONENT_DECLARE(EcsScript); +ECS_COMPONENT_DECLARE(EcsScriptConstVar); +ECS_COMPONENT_DECLARE(EcsScriptFunction); +ECS_COMPONENT_DECLARE(EcsScriptMethod); +ECS_DECLARE(EcsScriptVectorType); + +static +ECS_MOVE(EcsScript, dst, src, { + if (dst->script && (dst->script != src->script)) { + if (dst->template_ && (dst->template_ != src->template_)) { + flecs_script_template_fini( + flecs_script_impl(dst->script), dst->template_); + } + ecs_script_free(dst->script); + } + + dst->filename = src->filename; + dst->code = src->code; + dst->error = src->error; + dst->script = src->script; + dst->template_ = src->template_; + + src->filename = NULL; + src->code = NULL; + src->error = NULL; + src->script = NULL; + src->template_ = NULL; +}) + +static +ECS_DTOR(EcsScript, ptr, { + if (ptr->template_) { + flecs_script_template_fini( + flecs_script_impl(ptr->script), ptr->template_); + } + + if (ptr->script) { + ecs_script_free(ptr->script); + } + + ecs_os_free(ptr->filename); + ecs_os_free(ptr->code); + ecs_os_free(ptr->error); +}) + +static +ecs_id_t flecs_script_tag( + ecs_entity_t script, + ecs_entity_t instance) +{ + if (!instance) { + return ecs_pair_t(EcsScript, script); + } else { + return ecs_pair(EcsChildOf, instance); + } +} + +ecs_script_t* flecs_script_new( + ecs_world_t *world) +{ + ecs_script_impl_t *result = ecs_os_calloc_t(ecs_script_impl_t); + flecs_allocator_init(&result->allocator); + ecs_parser_t parser = { .script = result }; + result->root = flecs_script_scope_new(&parser); + result->pub.world = world; + result->refcount = 1; + return &result->pub; +} + +void ecs_script_clear( + ecs_world_t *world, + ecs_entity_t script, + ecs_entity_t instance) +{ + if (!instance) { + ecs_delete_with(world, ecs_pair_t(EcsScript, script)); + } else { + ecs_assert(ecs_is_alive(world, instance), ECS_INTERNAL_ERROR, NULL); + ecs_vec_t to_delete = {0}; + ecs_vec_init_t(&world->allocator, &to_delete, ecs_entity_t, 0); + + ecs_iter_t it = ecs_children(world, instance); + while (ecs_children_next(&it)) { + if (it.table) { + if (ecs_table_has_id(world, it.table, ecs_pair(EcsScriptTemplate, script))) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_vec_append_t( + &world->allocator, &to_delete, ecs_entity_t)[0] = + it.entities[i]; + } + } + } else { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it.entities[i]; + if (ecs_has_pair(world, e, EcsScriptTemplate, script)) { + ecs_vec_append_t( + &world->allocator, &to_delete, ecs_entity_t)[0] = e; + } + } + } + } + + ecs_entity_t *to_delete_ids = ecs_vec_first(&to_delete); + int32_t i, count = ecs_vec_count(&to_delete); + for (i = 0; i < count; i ++) { + ecs_delete(world, to_delete_ids[i]); + } + ecs_vec_fini_t(&world->allocator, &to_delete, ecs_entity_t); + } +} + +int ecs_script_run( + ecs_world_t *world, + const char *name, + const char *code, + ecs_script_eval_result_t *result) +{ + ecs_script_t *script = ecs_script_parse(world, name, code, NULL, result); + if (!script) { + goto error; + } + + ecs_entity_t prev_scope = ecs_set_scope(world, 0); + + if (ecs_script_eval(script, NULL, result)) { + goto error_free; + } + + ecs_set_scope(world, prev_scope); + + ecs_script_free(script); + return 0; +error_free: + ecs_script_free(script); +error: + return -1; +} + +int ecs_script_run_file( + ecs_world_t *world, + const char *filename) +{ + char *script = flecs_load_from_file(filename); + if (!script) { + return -1; + } + + int result = ecs_script_run(world, filename, script, NULL); + ecs_os_free(script); + return result; +} + +void ecs_script_free( + ecs_script_t *script) +{ + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_impl_t *impl = flecs_script_impl(script); + ecs_check(impl->refcount > 0, ECS_INVALID_OPERATION, NULL); + if (!--impl->refcount) { + flecs_script_visit_free(script); + flecs_expr_visit_free(script, impl->expr); + flecs_free(&impl->allocator, + impl->token_buffer_size, impl->token_buffer); + flecs_allocator_fini(&impl->allocator); + ecs_os_free(ECS_CONST_CAST(char*, impl->pub.name)); /* safe, owned value */ + ecs_os_free(ECS_CONST_CAST(char*, impl->pub.code)); /* safe, owned value */ + ecs_os_free(impl); + } +error: + return; +} + +int ecs_script_update( + ecs_world_t *world, + ecs_entity_t e, + ecs_entity_t instance, + const char *code) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(code != NULL, ECS_INTERNAL_ERROR, NULL); + + const char *name = ecs_get_name(world, e); + EcsScript *s = ecs_ensure(world, e, EcsScript); + if (s->template_) { + char *template_name = ecs_get_path(world, s->template_->entity); + ecs_err("cannot update scripts for individual templates, " + "update parent script instead (tried to update '%s')", + template_name); + ecs_os_free(template_name); + return -1; + } + + if (s->code) { + ecs_os_free(s->code); + } + + s->code = ecs_os_strdup(code); + + if (s->error) { + ecs_os_free(s->error); + s->error = NULL; + } + + if (s->script) { + ecs_script_free(s->script); + } + + ecs_script_eval_result_t eval_result = {NULL}; + + s->script = ecs_script_parse(world, name, code, NULL, &eval_result); + if (s->script == NULL) { + s->error = eval_result.error; + ecs_log_(-3, NULL, 0, "%s: %s", name ? name : "script", s->error); + return -1; + } + + int result = 0; + bool is_defer = ecs_is_deferred(world); + ecs_suspend_readonly_state_t srs; + ecs_world_t *real_world = NULL; + if (is_defer) { + ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); + real_world = flecs_suspend_readonly(world, &srs); + ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); + } + + ecs_script_clear(world, e, instance); + +#ifdef FLECS_DEBUG + { + ecs_iter_t it = ecs_each_pair_t(world, EcsScript, e); + ecs_assert(!ecs_iter_is_true(&it), ECS_INTERNAL_ERROR, NULL); + } +#endif + + ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); + + if (ecs_script_eval(s->script, NULL, &eval_result)) { + s->error = eval_result.error; + ecs_script_free(s->script); + s->script = NULL; + ecs_delete_with(world, ecs_pair_t(EcsScript, e)); + result = -1; + } + + ecs_set_with(world, prev); + + if (is_defer) { + flecs_resume_readonly(real_world, &srs); + } + + return result; +} + +ecs_entity_t ecs_script_init( + ecs_world_t *world, + const ecs_script_desc_t *desc) +{ + const char *script = NULL; + ecs_entity_t e = desc->entity; + + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(desc != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!e) { + if (desc->filename) { + e = ecs_new_from_path_w_sep(world, 0, desc->filename, "/", NULL); + } else { + e = ecs_new(world); + } + } + + script = desc->code; + if (!script && desc->filename) { + script = flecs_load_from_file(desc->filename); + if (!script) { + goto error; + } + + EcsScript *comp = ecs_ensure(world, e, EcsScript); + comp->filename = ecs_os_strdup(desc->filename); + } + + if (ecs_script_update(world, e, 0, script)) { + goto code_error; + } + + if (script != desc->code) { + /* Safe cast, only happens when script is loaded from file */ + ecs_os_free(ECS_CONST_CAST(char*, script)); + } + +code_error: + return e; +error: + if (script != desc->code) { + /* Safe cast, only happens when script is loaded from file */ + ecs_os_free(ECS_CONST_CAST(char*, script)); + } + if (!desc->entity) { + ecs_delete(world, e); + } + return 0; +} + +ecs_script_runtime_t* ecs_script_runtime_new(void) +{ + ecs_script_runtime_t *r = ecs_os_calloc_t(ecs_script_runtime_t); + flecs_expr_stack_init(&r->expr_stack); + flecs_allocator_init(&r->allocator); + flecs_stack_init(&r->stack); + ecs_vec_init_t(&r->allocator, &r->using, ecs_entity_t, 0); + ecs_vec_init_t(&r->allocator, &r->with, ecs_value_t, 0); + ecs_vec_init_t(&r->allocator, &r->with_type_info, ecs_type_info_t*, 0); + ecs_vec_init_t(&r->allocator, &r->annot, ecs_script_annot_t*, 0); + return r; +} + +void ecs_script_runtime_free( + ecs_script_runtime_t *r) +{ + flecs_expr_stack_fini(&r->expr_stack); + ecs_vec_fini_t(&r->allocator, &r->annot, ecs_script_annot_t*); + ecs_vec_fini_t(&r->allocator, &r->with, ecs_value_t); + ecs_vec_fini_t(&r->allocator, &r->with_type_info, ecs_type_info_t*); + ecs_vec_fini_t(&r->allocator, &r->using, ecs_entity_t); + flecs_allocator_fini(&r->allocator); + flecs_stack_fini(&r->stack); + ecs_os_free(r); +} + +void ecs_script_runtime_clear( + ecs_script_runtime_t *r) +{ + ecs_vec_clear(&r->annot); + ecs_vec_clear(&r->with); + ecs_vec_clear(&r->with_type_info); + ecs_vec_clear(&r->using); +} + +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world) +{ + ecs_stage_t *stage; + if (flecs_poly_is(world, ecs_stage_t)) { + stage = (ecs_stage_t*)world; + } else { + stage = world->stages[0]; + } + + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!stage->runtime) { + stage->runtime = ecs_script_runtime_new(); + } + + return stage->runtime; +} + +static +int EcsScript_serialize( + const ecs_serializer_t *ser, + const void *ptr) +{ + const EcsScript *data = ptr; + ser->member(ser, "filename"); + ser->value(ser, ecs_id(ecs_string_t), &data->filename); + ser->member(ser, "code"); + ser->value(ser, ecs_id(ecs_string_t), &data->code); + ser->member(ser, "error"); + ser->value(ser, ecs_id(ecs_string_t), &data->error); + + if (data->script) { + char *ast = ecs_script_ast_to_str(data->script, true); + ser->member(ser, "ast"); + ser->value(ser, ecs_id(ecs_string_t), &ast); + ecs_os_free(ast); + } else { + char *nullString = NULL; + ser->member(ser, "ast"); + ser->value(ser, ecs_id(ecs_string_t), &nullString); + } + return 0; +} + +void FlecsScriptImport( + ecs_world_t *world) +{ + ecs_assert(FLECS_SCRIPT_VECTOR_FUNCTION_COUNT == EcsPrimitiveKindLast, + ECS_INTERNAL_ERROR, NULL); + + ECS_MODULE(world, FlecsScript); + ECS_IMPORT(world, FlecsMeta); + + ecs_set_name_prefix(world, "Ecs"); + ECS_COMPONENT_DEFINE(world, EcsScript); + ECS_TAG_DEFINE(world, EcsScriptVectorType); + + ecs_set_hooks(world, EcsScript, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsScript), + .dtor = ecs_dtor(EcsScript), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); + + ECS_COMPONENT(world, ecs_script_t); + + ecs_entity_t opaque_view = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "ecs_script_view_t" }), + .members = { + { .name = "filename", .type = ecs_id(ecs_string_t) }, + { .name = "code", .type = ecs_id(ecs_string_t) }, + { .name = "error", .type = ecs_id(ecs_string_t) }, + { .name = "ast", .type = ecs_id(ecs_string_t) } + } + }); + + ecs_opaque(world, { + .entity = ecs_id(EcsScript), + .type.as_type = opaque_view, + .type.serialize = EcsScript_serialize + }); + + ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); + ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); + + flecs_script_template_import(world); + flecs_function_import(world); +} + +#endif + +/** + * @file addons/script/serialize.c + * @brief Serialize values to string. + */ + +#include + +#ifdef FLECS_SCRIPT + +static +int flecs_expr_ser_type( + const ecs_world_t *world, + const ecs_vec_t *ser, + const void *base, + ecs_strbuf_t *str, + bool is_expr); + +static +int flecs_expr_ser_type_ops( + const ecs_world_t *world, + ecs_meta_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str, + bool is_expr); + +static +ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_op_kind_t kind) { + return kind - EcsOpPrimitive; +} + +/* Serialize enumeration */ +static +int flecs_expr_ser_enum( + const ecs_world_t *world, + ecs_meta_op_t *op, + const void *base, + ecs_strbuf_t *str) +{ + ecs_map_key_t value; + ecs_meta_op_kind_t kind = op->underlying_kind; + + if (kind == EcsOpU8 || kind == EcsOpI8) { + value = *(const uint8_t*)base; + } else if (kind == EcsOpU16 || kind == EcsOpI16) { + value = *(const uint16_t*)base; + } else if (kind == EcsOpU32 || kind == EcsOpI32) { + value = *(const uint32_t*)base; + } else if (kind == EcsOpUPtr || kind == EcsOpIPtr) { + value = *(const uintptr_t*)base; + } else if (kind == EcsOpU64 || kind == EcsOpI64) { + value = *(const uint64_t*)base; + } else { + ecs_abort(ECS_INTERNAL_ERROR, "invalid underlying type"); + } + + /* Enumeration constants are stored in a map that is keyed on the + * enumeration value. */ + ecs_enum_constant_t *c = ecs_map_get_deref(op->is.constants, + ecs_enum_constant_t, value); + if (!c) { + char *path = ecs_get_path(world, op->type); + ecs_err("value %" PRIu64 " is not valid for enum type '%s'", + value, path); + ecs_os_free(path); + goto error; + } + + ecs_strbuf_appendstr(str, ecs_get_name(world, c->constant)); + + return 0; +error: + return -1; +} + +/* Serialize bitmask */ +static +int flecs_expr_ser_bitmask( + const ecs_world_t *world, + ecs_meta_op_t *op, + const void *ptr, + ecs_strbuf_t *str) +{ + uint32_t value = *(const uint32_t*)ptr; + + ecs_strbuf_list_push(str, "", "|"); + + /* Multiple flags can be set at a given time. Iterate through all the flags + * and append the ones that are set. */ + ecs_map_iter_t it = ecs_map_iter(op->is.constants); + int count = 0; + while (ecs_map_next(&it)) { + ecs_bitmask_constant_t *c = ecs_map_ptr(&it); + ecs_map_key_t key = ecs_map_key(&it); + if ((value & key) == key) { + ecs_strbuf_list_appendstr(str, ecs_get_name(world, c->constant)); + count ++; + value -= (uint32_t)key; + } + } + + if (value != 0) { + /* All bits must have been matched by a constant */ + char *path = ecs_get_path(world, op->type); + ecs_err( + "value for bitmask %s contains bits (%u) that cannot be mapped to constant", + path, value); + ecs_os_free(path); + goto error; + } + + if (!count) { + ecs_strbuf_list_appendstr(str, "0"); + } + + ecs_strbuf_list_pop(str, ""); + + return 0; +error: + return -1; +} + +static +int flecs_expr_ser_scope( + const ecs_world_t *world, + ecs_meta_op_t *ops, + const void *base, + ecs_strbuf_t *str, + bool is_expr) +{ + if (flecs_expr_ser_type_ops( + world, ops + 1, ops->op_count - 2, base, str, is_expr)) + { + return -1; + } + return 0; +} + +static +int flecs_expr_ser_array( + const ecs_world_t *world, + ecs_meta_op_t *ops, + const void *array, + int32_t count, + ecs_strbuf_t *str, + bool is_expr) +{ + ecs_strbuf_list_push(str, "[", ", "); + + int32_t i; + for (i = 0; i < count; i ++) { + ecs_strbuf_list_next(str); + void *ptr = ECS_ELEM(array, ops->elem_size, i); + if (flecs_expr_ser_scope(world, ops, ptr, str, is_expr)) { + goto error; + } + } + + ecs_strbuf_list_pop(str, "]"); + + return 0; +error: + return -1; +} + +static +int flecs_expr_ser_struct( + const ecs_world_t *world, + ecs_meta_op_t *ops, + const void *base, + ecs_strbuf_t *str, + bool is_expr) +{ + ecs_strbuf_list_push(str, "{", ", "); + + if (flecs_expr_ser_scope(world, ops, base, str, is_expr)) { + return -1; + } + + ecs_strbuf_list_pop(str, "}"); + + return 0; +} + +static +int flecs_expr_ser_forward( + const ecs_world_t *world, + ecs_entity_t type, + const void *base, + ecs_strbuf_t *str, + bool is_expr) +{ + const EcsTypeSerializer *ts = ecs_get(world, type, EcsTypeSerializer); + if (!ts) { + ecs_err("missing type serializer for '%s'", + flecs_errstr(ecs_get_path(world, type))); + return -1; + } + + return flecs_expr_ser_type_ops(world, ecs_vec_first(&ts->ops), + ecs_vec_count(&ts->ops), base, str, is_expr); +} + +typedef struct flecs_expr_serializer_ctx_t { + ecs_strbuf_t *str; + bool is_collection; +} flecs_expr_serializer_ctx_t; + +static +int flecs_expr_ser_opaque_value( + const ecs_serializer_t *ser, + ecs_entity_t type, + const void *value) +{ + flecs_expr_serializer_ctx_t *expr_ser = ser->ctx; + if (expr_ser->is_collection) { + ecs_strbuf_list_next(expr_ser->str); + } + return ecs_ptr_to_expr_buf(ser->world, type, value, expr_ser->str); +} + +static +int flecs_expr_ser_opaque_member( + const ecs_serializer_t *ser, + const char *name) +{ + flecs_expr_serializer_ctx_t *expr_ser = ser->ctx; + ecs_strbuf_list_next(expr_ser->str); + ecs_strbuf_append(expr_ser->str, "%s: ", name); + return 0; +} + +static +int flecs_expr_ser_opaque( + const ecs_world_t *world, + ecs_meta_op_t *op, + const void *base, + ecs_strbuf_t *str, + ecs_meta_op_kind_t kind) +{ + bool is_struct = kind == EcsOpOpaqueStruct; + bool is_collection = kind == EcsOpOpaqueVector || kind == EcsOpOpaqueArray; + + if (is_struct) { + ecs_strbuf_list_push(str, "{", ", "); + } else if (is_collection) { + ecs_strbuf_list_push(str, "[", ", "); + } + + flecs_expr_serializer_ctx_t expr_ser = { + .str = str, .is_collection = is_collection + }; + + ecs_serializer_t ser = { + .world = world, + .value = flecs_expr_ser_opaque_value, + .member = is_struct ? flecs_expr_ser_opaque_member : NULL, + .ctx = &expr_ser + }; + + ecs_assert(op->is.opaque != NULL, ECS_INTERNAL_ERROR, NULL); + if (op->is.opaque(&ser, base)) { + return -1; + } + + if (is_struct) { + ecs_strbuf_list_pop(str, "}"); + } else if (is_collection) { + ecs_strbuf_list_pop(str, "]"); + } + + return 0; +} + +/* Iterate over a slice of the type ops array */ +static +int flecs_expr_ser_type_ops( + const ecs_world_t *world, + ecs_meta_op_t *ops, + int32_t op_count, + const void *base, + ecs_strbuf_t *str, + bool is_expr) +{ + for (int i = 0; i < op_count; i ++) { + ecs_meta_op_t *op = &ops[i]; + + const void *ptr = ECS_OFFSET(base, op->offset); + + if (op->name) { + ecs_strbuf_list_next(str); + ecs_strbuf_append(str, "%s: ", op->name); + } + + switch(op->kind) { + case EcsOpPushStruct: { + if (flecs_expr_ser_struct(world, op, ptr, str, is_expr)) { + goto error; + } + break; + } + case EcsOpPushArray: { + if (flecs_expr_ser_array(world, op, ptr, + ecs_meta_op_get_elem_count(op, ptr), str, is_expr)) + { + goto error; + } + break; + } + case EcsOpPushVector: { + ecs_vec_t *vec = ECS_OFFSET(base, op->offset); + + if (flecs_expr_ser_array(world, op, + vec->array, vec->count, str, is_expr)) + { + goto error; + } + break; + } + case EcsOpForward: { + if (flecs_expr_ser_forward(world, op->type, ptr, str, is_expr)) { + goto error; + } + break; + } + case EcsOpOpaqueStruct: + case EcsOpOpaqueArray: + case EcsOpOpaqueVector: + case EcsOpOpaqueValue: + if (flecs_expr_ser_opaque(world, op, ptr, str, op->kind)) { + goto error; + } + break; + case EcsOpEnum: + if (flecs_expr_ser_enum(world, op, ptr, str)) { + goto error; + } + break; + case EcsOpBitmask: + if (flecs_expr_ser_bitmask(world, op, ptr, str)) { + goto error; + } + break; + case EcsOpBool: + case EcsOpChar: + case EcsOpByte: + case EcsOpU8: + case EcsOpU16: + case EcsOpU32: + case EcsOpU64: + case EcsOpI8: + case EcsOpI16: + case EcsOpI32: + case EcsOpI64: + case EcsOpF32: + case EcsOpF64: + case EcsOpUPtr: + case EcsOpIPtr: + case EcsOpEntity: + case EcsOpId: + case EcsOpString: + if (flecs_expr_ser_primitive(world, + flecs_expr_op_to_primitive_kind(op->kind), ptr, str, is_expr)) + { + /* Unknown operation */ + ecs_err("unknown serializer operation kind (%d)", op->kind); + goto error; + } + break; + case EcsOpScope: + case EcsOpPrimitive: + case EcsOpPop: + default: + ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); + } + + i += op->op_count - 1; // Skip over already processed instructions + } + + return 0; +error: + return -1; +} + +/* Iterate over the type ops of a type */ +static +int flecs_expr_ser_type( + const ecs_world_t *world, + const ecs_vec_t *v_ops, + const void *base, + ecs_strbuf_t *str, + bool is_expr) +{ + ecs_meta_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_op_t); + int32_t count = ecs_vec_count(v_ops); + return flecs_expr_ser_type_ops(world, ops, count, base, str, is_expr); +} + +int ecs_ptr_to_expr_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf_out) +{ + const EcsTypeSerializer *ser = ecs_get( + world, type, EcsTypeSerializer); + if (ser == NULL) { + char *path = ecs_get_path(world, type); + ecs_err("cannot serialize value for type '%s'", path); + ecs_os_free(path); + goto error; + } + + if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, true)) { + goto error; + } + + return 0; +error: + return -1; +} + +char* ecs_ptr_to_expr( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } + + return ecs_strbuf_get(&str); +} + +int ecs_ptr_to_str_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *ptr, + ecs_strbuf_t *buf_out) +{ + const EcsTypeSerializer *ser = ecs_get( + world, type, EcsTypeSerializer); + if (ser == NULL) { + char *path = ecs_get_path(world, type); + ecs_err("cannot serialize value for type '%s'", path); + ecs_os_free(path); + goto error; + } + + if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, false)) { + goto error; + } + + return 0; +error: + return -1; +} + +char* ecs_ptr_to_str( + const ecs_world_t *world, + ecs_entity_t type, + const void* ptr) +{ + ecs_strbuf_t str = ECS_STRBUF_INIT; + + if (ecs_ptr_to_str_buf(world, type, ptr, &str) != 0) { + ecs_strbuf_reset(&str); + return NULL; + } + + return ecs_strbuf_get(&str); +} + +#endif + +/** + * @file addons/script/template.c + * @brief Script template implementation. + */ + + +#ifdef FLECS_SCRIPT + +ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); +ECS_DECLARE(EcsScriptTemplate); + +static +void flecs_template_set_event_free(EcsScriptTemplateSetEvent *ptr) { + if (ptr->entities != &ptr->entity_storage) { + ecs_os_free(ptr->entities); + } + if (ptr->data != ptr->data_storage) { + ecs_os_free(ptr->data); + } +} + +static +ECS_MOVE(EcsScriptTemplateSetEvent, dst, src, { + flecs_template_set_event_free(dst); + + *dst = *src; + + if (src->entities == &src->entity_storage) { + dst->entities = &dst->entity_storage; + } + + if (src->data == src->data_storage) { + dst->data = &dst->data_storage; + } + + src->entities = NULL; + src->data = NULL; +}) + +static +ECS_DTOR(EcsScriptTemplateSetEvent, ptr, { + flecs_template_set_event_free(ptr); +}) + +/* Template ctor to initialize with default property values */ +static +void flecs_script_template_ctor( + void *ptr, + int32_t count, + const ecs_type_info_t *ti) +{ + ecs_world_t *world = ti->hooks.ctx; + ecs_entity_t template_entity = ti->component; + + /* Initialize object so copy hooks can safely overwrite members with dtors. */ + flecs_default_ctor(ptr, count, ti); + + const EcsStruct *st = ecs_get(world, template_entity, EcsStruct); + if (!st) { + return; + } + + const EcsScript *script = ecs_get(world, template_entity, EcsScript); + if (!script) { + ecs_err("template '%s' is not a script, cannot construct", ti->name); + return; + } + + ecs_script_template_t *template = script->template_; + ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); + if (st->members.count != template->prop_defaults.count) { + ecs_err("number of props (%d) of template '%s' does not match members" + " (%d), cannot construct", template->prop_defaults.count, + ti->name, st->members.count); + return; + } + + const ecs_member_t *members = st->members.array; + int32_t i, m, member_count = st->members.count; + ecs_script_var_t *values = template->prop_defaults.array; + for (m = 0; m < member_count; m ++) { + const ecs_member_t *member = &members[m]; + ecs_script_var_t *value = &values[m]; + const ecs_type_info_t *mti = value->type_info; + ecs_assert(mti != NULL, ECS_INTERNAL_ERROR, NULL); + + for (i = 0; i < count; i ++) { + void *el = ECS_ELEM(ptr, ti->size, i); + ecs_value_copy_w_type_info(world, mti, + ECS_OFFSET(el, member->offset), value->value.ptr); + } + } +} + +/* Defer template instantiation if we're in deferred mode. */ +static +void flecs_script_template_defer_on_set( + ecs_iter_t *it, + ecs_entity_t template_entity, + const ecs_type_info_t *ti, + void *data) +{ + EcsScriptTemplateSetEvent evt; + + if ((it->count == 1) && ti->size <= ECS_TEMPLATE_SMALL_SIZE && !ti->hooks.dtor) { + /* This should be true for the vast majority of templates */ + evt.entities = &evt.entity_storage; + evt.data = evt.data_storage; + evt.entity_storage = it->entities[0]; + ecs_os_memcpy(evt.data, data, ti->size); + } else { + evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); + evt.data = ecs_os_memdup(data, ti->size * it->count); + } + + evt.count = it->count; + evt.template_entity = template_entity; + + ecs_enqueue(it->world, &(ecs_event_desc_t){ + .event = ecs_id(EcsScriptTemplateSetEvent), + .entity = EcsAny, + .param = &evt + }); +} + +static +void flecs_script_template_instantiate( + ecs_world_t *world, + ecs_entity_t template_entity, + const ecs_entity_t *entities, + void *data, + int32_t count) +{ + ecs_assert(!ecs_is_deferred(world), ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *r = ecs_record_find(world, template_entity); + if (!r) { + ecs_err("template entity is empty (should never happen)"); + return; + } + + const EcsScript *script = ecs_record_get(world, r, EcsScript); + if (!script) { + ecs_err("template is missing script component"); + return; + } + + ecs_script_template_t *template = script->template_; + ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = template->type_info; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + const EcsStruct *st = ecs_record_get(world, r, EcsStruct); + + ecs_script_eval_visitor_t v; + ecs_script_eval_desc_t desc = { + .runtime = flecs_script_runtime_get(world) + }; + + flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, &desc); + ecs_vec_t prev_using = v.r->using; + ecs_vec_t prev_with = desc.runtime->with; + ecs_vec_t prev_with_type_info = desc.runtime->with_type_info; + v.r->using = template->using_; + v.template_entity = template_entity; + ecs_vec_init_t(NULL, &desc.runtime->with, ecs_value_t, 0); + ecs_vec_init_t(NULL, &desc.runtime->with_type_info, ecs_type_info_t*, 0); + + ecs_script_scope_t *scope = template->node->scope; + + /* Dummy entity node for instance */ + ecs_script_entity_t instance_node = { + .node = { + .kind = EcsAstEntity, + .pos = template->node->node.pos + }, + .scope = scope + }; + + v.entity = &instance_node; + + int32_t i, m, a; + for (i = 0; i < count; i ++) { + v.parent = entities[i]; + ecs_assert(ecs_is_alive(world, v.parent), ECS_INTERNAL_ERROR, NULL); + + instance_node.eval = entities[i]; + + /* Apply annotations, if any */ + for (a = 0; a < ecs_vec_count(&template->annot); a ++) { + ecs_script_annot_t *annot = ecs_vec_get_t( + &template->annot, ecs_script_annot_t*, a)[0]; + flecs_script_apply_annot(&v, &instance_node, annot); + } + + /* Create variables to hold template properties */ + ecs_script_vars_t *vars = flecs_script_vars_push( + NULL, &v.r->stack, &v.r->allocator); + vars->parent = template->vars; /* Include hoisted variables */ + vars->sp = ecs_vec_count(&template->vars->vars); + + /* Allocate enough space for variables */ + ecs_script_vars_set_size(vars, (st ? st->members.count : 0) + 1); + + /* Populate $this variable with instance entity */ + ecs_entity_t instance = entities[i]; + ecs_script_var_t *this_var = ecs_script_vars_declare( + vars, NULL /* $this */); + this_var->value.type = ecs_id(ecs_entity_t); + this_var->value.ptr = &instance; + + /* Populate properties from template members */ + if (st) { + const ecs_member_t *members = st->members.array; + for (m = 0; m < st->members.count; m ++) { + const ecs_member_t *member = &members[m]; + + /* Assign template property from template instance. Don't + * set name as variables will be resolved by frame offset. */ + ecs_script_var_t *var = ecs_script_vars_declare( + vars, NULL /* member->name */); + var->value.type = member->type; + var->value.ptr = ECS_OFFSET(data, member->offset); + } + } + + ecs_script_clear(world, template_entity, instance); + + /* Run template code */ + v.vars = vars; + + flecs_script_eval_scope(&v, scope); + + /* Pop variable scope */ + ecs_script_vars_pop(vars); + + data = ECS_OFFSET(data, ti->size); + } + + ecs_vec_fini_t(&desc.runtime->allocator, + &desc.runtime->with, ecs_value_t); + ecs_vec_fini_t(&desc.runtime->allocator, + &desc.runtime->with_type_info, ecs_type_info_t*); + + v.r->with = prev_with; + v.r->with_type_info = prev_with_type_info; + v.r->using = prev_using; + flecs_script_eval_visit_fini(&v, &desc); +} + +static +void flecs_on_template_set_event( + ecs_iter_t *it) +{ + ecs_assert(ecs_is_deferred(it->world), ECS_INTERNAL_ERROR, NULL); + + EcsScriptTemplateSetEvent *evt = it->param; + ecs_world_t *world = it->real_world; + ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); + + ecs_defer_suspend(world); + + flecs_script_template_instantiate( + world, evt->template_entity, evt->entities, evt->data, evt->count); + + ecs_defer_resume(world); +} + +/* Template on_set handler to update contents for new property values */ +static +void flecs_script_template_on_set( + ecs_iter_t *it) +{ + if (it->table->flags & EcsTableIsPrefab) { + /* Don't instantiate templates for prefabs */ + return; + } + + ecs_world_t *world = it->world; + ecs_entity_t template_entity = ecs_field_id(it, 0); + ecs_record_t *r = ecs_record_find(world, template_entity); + if (!r) { + ecs_err("template entity is empty (should never happen)"); + return; + } + + const EcsScript *script = ecs_record_get(world, r, EcsScript); + if (!script) { + ecs_err("template is missing script component"); + return; + } + + ecs_script_template_t *template = script->template_; + ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = template->type_info; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); + + if (ecs_is_deferred(it->world)) { + flecs_script_template_defer_on_set(it, template_entity, ti, data); + return; + } + + flecs_script_template_instantiate( + world, template_entity, it->entities, data, it->count); + return; +} + +static +int flecs_script_template_eval_prop( + ecs_script_visit_t *_v, + ecs_script_var_node_t *node) +{ + ecs_script_eval_visitor_t *v = (ecs_script_eval_visitor_t*)_v; + ecs_script_template_t *template = v->template; + if (ecs_vec_count(&v->vars->vars) > + ecs_vec_count(&template->prop_defaults)) + { + flecs_script_eval_error(v, node, + "const variables declared before prop '%s' (props must come first)", + node->name); + return -1; + } + + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "variable '%s' redeclared", node->name); + return -1; + } + + ecs_entity_t type; + const ecs_type_info_t *ti; + + if (node->type) { + if (flecs_script_find_entity(v, 0, node->type, NULL, NULL, + &type, NULL) || !type) + { + flecs_script_eval_error(v, node, + "unresolved type '%s' for prop '%s'", + node->type, node->name); + return -1; + } + + ti = flecs_script_get_type_info(v, node, type); + if (!ti) { + return -1; + } + + var->value.type = type; + var->value.ptr = flecs_stack_alloc( + &v->r->stack, ti->size, ti->alignment); + var->type_info = ti; + + if (flecs_script_eval_expr(v, &node->expr, &var->value)) { + return -1; + } + } else { + /* We don't know the type yet, so we can't create a storage for it yet. + * Run the expression first to deduce the type. */ + ecs_value_t value = {0}; + if (flecs_script_eval_expr(v, &node->expr, &value)) { + flecs_script_eval_error(v, node, + "failed to evaluate expression for const variable '%s'", + node->name); + return -1; + } + + ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); + ti = ecs_get_type_info(v->world, value.type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + var->value.ptr = flecs_stack_calloc( + &v->r->stack, ti->size, ti->alignment); + type = var->value.type = value.type; + var->type_info = ti; + + if (ti->hooks.ctor) { + ti->hooks.ctor(var->value.ptr, 1, ti); + } + + ecs_value_copy_w_type_info(v->world, ti, var->value.ptr, value.ptr); + ecs_value_fini_w_type_info(v->world, ti, value.ptr); + flecs_free(&v->world->allocator, ti->size, value.ptr); + } + + ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, + &template->prop_defaults, ecs_script_var_t); + value->value.ptr = flecs_calloc_w_dbg_info( + &v->base.script->allocator, ti->size, ti->name); + value->value.type = type; + value->type_info = ti; + ecs_value_copy_w_type_info( + v->world, ti, value->value.ptr, var->value.ptr); + + ecs_entity_t mbr = ecs_entity(v->world, { + .name = node->name, + .parent = template->entity + }); + + ecs_set(v->world, mbr, EcsMember, { .type = var->value.type }); + + return 0; +} + +static +int flecs_script_template_eval( + ecs_script_visit_t *v, + ecs_script_node_t *node) +{ + if (node->kind == EcsAstTemplate) { + flecs_script_eval_error((ecs_script_eval_visitor_t*)v, node, + "nested templates are not allowed"); + return -1; + } else if (node->kind == EcsAstProp) { + return flecs_script_template_eval_prop( + v, (ecs_script_var_node_t*)node); + } + + return flecs_script_check_node(v, node); +} + +static +int flecs_script_template_preprocess( + ecs_script_eval_visitor_t *v, + ecs_script_template_t *template) +{ + ecs_visit_action_t prev_visit = v->base.visit; + v->template = template; + + /* Dummy entity node for instance */ + ecs_script_entity_t instance_node = { + .node = { + .kind = EcsAstEntity, + .pos = template->node->node.pos + } + }; + + v->entity = &instance_node; + + v->base.visit = flecs_script_template_eval; + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, "this"); + var->value.type = ecs_id(ecs_entity_t); + int result = flecs_script_check_scope(v, template->node->scope); + v->vars = ecs_script_vars_pop(v->vars); + v->base.visit = prev_visit; + v->template = NULL; + v->entity = NULL; + + return result; +} + +static +int flecs_script_template_hoist_using( + ecs_script_eval_visitor_t *v, + ecs_script_template_t *template) +{ + ecs_allocator_t *a = &v->base.script->allocator; + if (v->module) { + ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = v->module; + } + + int i, count = ecs_vec_count(&v->r->using); + for (i = 0; i < count; i ++) { + ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = + ecs_vec_get_t(&v->r->using, ecs_entity_t, i)[0]; + } + + return 0; +} + +static +int flecs_script_template_hoist_vars( + ecs_script_eval_visitor_t *v, + ecs_script_template_t *template, + ecs_script_vars_t *vars) +{ + int32_t i, count = ecs_vec_count(&vars->vars); + ecs_script_var_t *src_vars = ecs_vec_first(&vars->vars); + for (i = 0; i < count; i ++) { + ecs_script_var_t *src = &src_vars[i]; + if (ecs_script_vars_lookup(template->vars, src->name)) { + /* If variable is masked, don't declare it twice */ + continue; + } + ecs_script_var_t *dst = ecs_script_vars_define_id( + template->vars, src->name, src->value.type); + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_value_copy(v->world, + src->value.type, dst->value.ptr, src->value.ptr); + } + + if (vars->parent) { + flecs_script_template_hoist_vars(v, template, vars->parent); + } + + return 0; +} + +ecs_script_template_t* flecs_script_template_init( + ecs_script_impl_t *script) +{ + ecs_allocator_t *a = &script->allocator; + ecs_script_template_t *result = flecs_alloc_t(a, ecs_script_template_t); + ecs_vec_init_t(NULL, &result->prop_defaults, ecs_script_var_t, 0); + ecs_vec_init_t(NULL, &result->using_, ecs_entity_t, 0); + ecs_vec_init_t(NULL, &result->annot, ecs_script_annot_t*, 0); + + result->vars = ecs_script_vars_init(script->pub.world); + return result; +} + +void flecs_script_template_fini( + ecs_script_impl_t *script, + ecs_script_template_t *template) +{ + ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &script->allocator; + + int32_t i, count = ecs_vec_count(&template->prop_defaults); + ecs_script_var_t *values = ecs_vec_first(&template->prop_defaults); + for (i = 0; i < count; i ++) { + ecs_script_var_t *value = &values[i]; + const ecs_type_info_t *ti = value->type_info; + if (ti->hooks.dtor) { + ti->hooks.dtor(value->value.ptr, 1, ti); + } + flecs_free(a, ti->size, value->value.ptr); + } + + ecs_vec_fini_t(a, &template->prop_defaults, ecs_script_var_t); + + ecs_vec_fini_t(a, &template->using_, ecs_entity_t); + ecs_vec_fini_t(a, &template->annot, ecs_script_annot_t*); + ecs_script_vars_fini(template->vars); + flecs_free_t(a, ecs_script_template_t, template); +} + +/* Create new template */ +int flecs_script_eval_template( + ecs_script_eval_visitor_t *v, + ecs_script_template_node_t *node) +{ + ecs_entity_t template_entity = flecs_script_create_entity(v, node->name); + if (!template_entity) { + return -1; + } + + ecs_script_template_t *template = flecs_script_template_init(v->base.script); + template->entity = template_entity; + template->node = node; + + /* Variables are always presented to a template in a well defined order, so + * we don't need dynamic variable binding. */ + bool old_dynamic_variable_binding = v->dynamic_variable_binding; + v->dynamic_variable_binding = false; + + if (flecs_script_template_preprocess(v, template)) { + goto error; + } + + if (flecs_script_template_hoist_using(v, template)) { + goto error; + } + + if (flecs_script_template_hoist_vars(v, template, v->vars)) { + goto error; + } + + v->dynamic_variable_binding = old_dynamic_variable_binding; + + /* If template has no props, give template dummy size so we can register + * hooks for it. */ + if (!ecs_has(v->world, template_entity, EcsComponent)) { + ecs_set(v->world, template_entity, EcsComponent, {1, 1}); + } + + /* Consume annotations, if any */ + int32_t i, count = ecs_vec_count(&v->r->annot); + if (count) { + ecs_script_annot_t **annots = ecs_vec_first(&v->r->annot); + for (i = 0; i < count ; i ++) { + ecs_vec_append_t(&v->base.script->allocator, + &template->annot, ecs_script_annot_t*)[0] = annots[i]; + } + ecs_vec_clear(&v->r->annot); + } + + template->type_info = ecs_get_type_info(v->world, template_entity); + + ecs_add_pair(v->world, template_entity, EcsOnInstantiate, EcsOverride); + + EcsScript *script = ecs_ensure(v->world, template_entity, EcsScript); + if (script->script) { + if (script->template_) { + flecs_script_template_fini( + flecs_script_impl(script->script), script->template_); + } + ecs_script_free(script->script); + } + + script->script = &v->base.script->pub; + script->template_ = template; + ecs_modified(v->world, template_entity, EcsScript); + + ecs_set_hooks_id(v->world, template_entity, &(ecs_type_hooks_t) { + .ctor = flecs_script_template_ctor, + .on_set = flecs_script_template_on_set, + .ctx = v->world + }); + + /* Keep script alive for as long as template is alive */ + v->base.script->refcount ++; + + return 0; +error: + flecs_script_template_fini(v->base.script, template); + return -1; +} + +void flecs_script_template_import( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsScriptTemplateSetEvent); + ECS_TAG_DEFINE(world, EcsScriptTemplate); + + ecs_add_id(world, EcsScriptTemplate, EcsPairIsTag); + + ecs_set_hooks(world, EcsScriptTemplateSetEvent, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsScriptTemplateSetEvent), + .dtor = ecs_dtor(EcsScriptTemplateSetEvent), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); + + ecs_observer(world, { + .entity = ecs_entity(world, { .name = "TemplateSetObserver" }), + .query.terms = {{ .id = EcsAny }}, + .events = { ecs_id(EcsScriptTemplateSetEvent) }, + .callback = flecs_on_template_set_event + }); +} + +#endif + +/** + * @file addons/script/vars.c + * @brief Script variables. + */ + + +#ifdef FLECS_SCRIPT + +ecs_script_vars_t* flecs_script_vars_push( + ecs_script_vars_t *parent, + ecs_stack_t *stack, + ecs_allocator_t *allocator) +{ + ecs_check(stack || parent, ECS_INVALID_PARAMETER, + "must provide either parent scope or stack allocator"); + ecs_check(allocator || parent, ECS_INVALID_PARAMETER, + "must provide either parent scope or allocator"); + + if (!stack) { + stack = parent->stack; + } else if (parent) { + ecs_check(stack == parent->stack, ECS_INVALID_PARAMETER, + "provided stack allocator is different from parent scope"); + } + if (!allocator) { + allocator = parent->allocator; + } else if (parent) { + ecs_check(allocator == parent->allocator, ECS_INVALID_PARAMETER, + "provided allocator is different from parent scope"); + } + + ecs_stack_cursor_t *cursor = flecs_stack_get_cursor(stack); + ecs_script_vars_t *result = flecs_stack_calloc_t(stack, ecs_script_vars_t); + ecs_vec_init_t(allocator, &result->vars, ecs_script_var_t, 0); + result->parent = parent; + if (parent) { + result->world = parent->world; + result->sp = + parent->sp + ecs_vec_count(&parent->vars); + } else { + result->sp = 0; + } + result->stack = stack; + result->allocator = allocator; + result->cursor = cursor; + return result; +error: + return NULL; +} + +ecs_script_vars_t* ecs_script_vars_init( + ecs_world_t *world) +{ + ecs_script_vars_t *result = flecs_script_vars_push(NULL, + flecs_stage_get_stack_allocator(world), + flecs_stage_get_allocator(world)); + result->world = ecs_get_world(world); /* Provided world can be stage */ + return result; +} + +void ecs_script_vars_fini( + ecs_script_vars_t *vars) +{ + ecs_check(vars->parent == NULL, ECS_INVALID_PARAMETER, + "ecs_script_vars_fini can only be called on the roots cope"); + ecs_script_vars_pop(vars); +error: + return; +} + +ecs_script_vars_t* ecs_script_vars_push( + ecs_script_vars_t *parent) +{ + ecs_check(parent != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stack_t *stack = parent->stack; + ecs_allocator_t *allocator = parent->allocator; + ecs_check(stack != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(allocator != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_script_vars_push(parent, stack, allocator); +error: + return NULL; +} + +ecs_script_vars_t* ecs_script_vars_pop( + ecs_script_vars_t *vars) +{ + ecs_script_vars_t *parent = vars->parent; + ecs_stack_cursor_t *cursor = vars->cursor; + int32_t i, count = ecs_vec_count(&vars->vars); + if (count) { + ecs_script_var_t *var_array = ecs_vec_first(&vars->vars); + for (i = 0; i < count; i ++) { + ecs_script_var_t *var = &var_array[i]; + if (!var->value.ptr) { + continue; + } + + if (!var->type_info || !var->type_info->hooks.dtor) { + continue; + } + + var->type_info->hooks.dtor(var->value.ptr, 1, var->type_info); + } + + flecs_name_index_fini(&vars->var_index); + } + + ecs_vec_fini_t(vars->allocator, &vars->vars, ecs_script_var_t); + flecs_stack_restore_cursor(vars->stack, cursor); + return parent; +} + +ecs_script_var_t* ecs_script_vars_declare( + ecs_script_vars_t *vars, + const char *name) +{ + if (name) { + if (flecs_name_index_is_init(&vars->var_index)) { + if (flecs_name_index_find(&vars->var_index, name, 0, 0) != 0) { + goto error; + } + } else { + flecs_name_index_init(&vars->var_index, vars->allocator); + } + } + + ecs_script_var_t *var = ecs_vec_append_t( + vars->allocator, &vars->vars, ecs_script_var_t); + var->name = name; + var->value.ptr = NULL; + var->value.type = 0; + var->type_info = NULL; + var->sp = ecs_vec_count(&vars->vars) + vars->sp - 1; + var->is_const = false; + + if (name) { + flecs_name_index_ensure(&vars->var_index, + flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); + } + + return var; +error: + return NULL; +} + +void ecs_script_vars_set_size( + ecs_script_vars_t *vars, + int32_t count) +{ + ecs_assert(!ecs_vec_count(&vars->vars), ECS_INVALID_OPERATION, + "variable scope must be empty for resize operation"); + ecs_vec_set_size_t(vars->allocator, &vars->vars, ecs_script_var_t, count); +} + +ecs_script_var_t* ecs_script_vars_define_id( + ecs_script_vars_t *vars, + const char *name, + ecs_entity_t type) +{ + ecs_check(vars->world != NULL, ECS_INVALID_OPERATION, "variable scope is " + "not associated with world, create scope with ecs_script_vars_init"); + + const ecs_type_info_t *ti = ecs_get_type_info(vars->world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "the entity provided for the type parameter is not a type"); + + ecs_script_var_t *result = ecs_script_vars_declare(vars, name); + if (!result) { + return NULL; + } + + result->value.type = type; + result->value.ptr = flecs_stack_alloc(vars->stack, ti->size, ti->alignment); + result->type_info = ti; + + if (ti->hooks.ctor) { + ti->hooks.ctor(result->value.ptr, 1, ti); + } + + return result; +error: + return NULL; +} + +ecs_script_var_t* ecs_script_vars_lookup( + const ecs_script_vars_t *vars, + const char *name) +{ + if (!vars) { + return NULL; + } + + uint64_t var_id = 0; + if (ecs_vec_count(&vars->vars)) { + if (flecs_name_index_is_init(&vars->var_index)) { + var_id = flecs_name_index_find(&vars->var_index, name, 0, 0); + } + } + + if (!var_id) { + if (vars->parent) { + return ecs_script_vars_lookup(vars->parent, name); + } + return NULL; + } + + return ecs_vec_get_t(&vars->vars, ecs_script_var_t, + flecs_uto(int32_t, var_id - 1)); +} + +ecs_script_var_t* ecs_script_vars_from_sp( + const ecs_script_vars_t *vars, + int32_t sp) +{ + ecs_check(sp >= 0, ECS_INVALID_PARAMETER, NULL); + + if (sp < vars->sp) { + ecs_assert(vars->parent != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_script_vars_from_sp(vars->parent, sp); + } + + sp -= vars->sp; + ecs_check(sp < ecs_vec_count(&vars->vars), + ECS_INVALID_PARAMETER, NULL); + + return ecs_vec_get_t(&vars->vars, ecs_script_var_t, sp); +error: + return NULL; +} + +void ecs_script_vars_print( + const ecs_script_vars_t *vars) +{ + if (vars->parent) { + ecs_script_vars_print(vars->parent); + } + + int32_t i, count = ecs_vec_count(&vars->vars); + ecs_script_var_t *array = ecs_vec_first(&vars->vars); + for (i = 0; i < count; i ++) { + ecs_script_var_t *var = &array[i]; + if (!i) { + printf("FRAME "); + } else { + printf(" "); + } + + printf("%2d: %s\n", var->sp, var->name); + } +} + +/* Static names for iterator fields */ +static const char* flecs_script_iter_field_names[] = { + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", "10", "11", "12", "13", "14", "15" +}; + +void ecs_script_vars_from_iter( + const ecs_iter_t *it, + ecs_script_vars_t *vars, + int offset) +{ + ecs_check(vars != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!offset || offset < it->count, ECS_INVALID_PARAMETER, NULL); + + /* Set variable for $this */ + if (it->count) { + ecs_script_var_t *var = ecs_script_vars_lookup(vars, "this"); + if (!var) { + var = ecs_script_vars_declare(vars, "this"); + var->value.type = ecs_id(ecs_entity_t); + } + + /* Safe, variable value will never be written */ + var->value.ptr = ECS_CONST_CAST(ecs_entity_t*, &it->entities[offset]); + } + + /* Set variables for fields */ + { + int8_t i, field_count = it->field_count; + for (i = 0; i < field_count; i ++) { + ecs_size_t size = it->sizes[i]; + if (!size) { + continue; + } + + void *ptr = ecs_field_w_size(it, flecs_itosize(size), i); + if (!ptr) { + continue; + } + + ptr = ECS_OFFSET(ptr, offset * size); + + const char *name = flecs_script_iter_field_names[i]; + ecs_script_var_t *var = ecs_script_vars_lookup(vars, name); + if (!var) { + var = ecs_script_vars_declare(vars, name); + ecs_assert(ecs_script_vars_lookup(vars, name) != NULL, + ECS_INTERNAL_ERROR, NULL); + var->value.type = it->ids[i]; + } else { + ecs_check(var->value.type == it->ids[i], + ECS_INVALID_PARAMETER, NULL); + } + var->value.ptr = ptr; + } + } + + /* Set variables for query variables */ + { + if (it->query) { + char **variable_names = it->query->vars; + int32_t i, var_count = ecs_iter_get_var_count(it); + for (i = 1 /* skip this variable */ ; i < var_count; i ++) { + const ecs_entity_t *e_ptr = NULL; + ecs_var_t *query_var = &ecs_iter_get_vars(it)[i]; + if (query_var->entity) { + e_ptr = &query_var->entity; + } else { + ecs_table_range_t *range = &query_var->range; + if (range->count == 1) { + const ecs_entity_t *entities = + ecs_table_entities(range->table); + e_ptr = &entities[range->offset]; + } + } + if (!e_ptr) { + continue; + } + + ecs_script_var_t *var = ecs_script_vars_lookup( + vars, variable_names[i]); + if (!var) { + var = ecs_script_vars_declare(vars, variable_names[i]); + var->value.type = ecs_id(ecs_entity_t); + } else { + ecs_check(var->value.type == ecs_id(ecs_entity_t), + ECS_INVALID_PARAMETER, NULL); + } + + /* Safe, variable value will never be written */ + var->value.ptr = ECS_CONST_CAST(ecs_entity_t*, e_ptr); + } + } + } + +error: + return; +} + +#endif + +/** + * @file addons/script/visit.c + * @brief Script AST visitor utilities. + */ + + +#ifdef FLECS_SCRIPT + +static +int flecs_script_visit_push_checked( + ecs_script_visit_t *v, + ecs_script_node_t *node) +{ + if (v->depth >= ECS_SCRIPT_VISIT_MAX_DEPTH) { + ecs_err("maximum script nesting depth exceeded"); + return -1; + } + + v->nodes[v->depth ++] = node; + return 0; +} + +ecs_script_node_t* ecs_script_parent_node_( + ecs_script_visit_t *v) +{ + if (v->depth > 1) { + return v->nodes[v->depth - 2]; /* Last node is current node */ + } else { + return NULL; + } +} + +ecs_script_scope_t* ecs_script_current_scope_( + ecs_script_visit_t *v) +{ + int32_t depth; + for(depth = v->depth - 1; depth >= 0; depth --) { + ecs_script_node_t *node = v->nodes[depth]; + if (node->kind == EcsAstScope) { + return (ecs_script_scope_t*)node; + } + } + + return NULL; +} + +ecs_script_node_t* ecs_script_parent_( + ecs_script_visit_t *v, + ecs_script_node_t *child) +{ + int32_t depth; + for(depth = v->depth - 1; depth >= 0; depth --) { + ecs_script_node_t *node = v->nodes[depth]; + if (node == child && depth) { + return v->nodes[depth - 1]; + } + } + + return NULL; +} + +int32_t ecs_script_node_line_number_( + ecs_script_impl_t *script, + ecs_script_node_t *node) +{ + const char *ptr; + int32_t line_count = 1; + for (ptr = script->pub.code; ptr < node->pos; ptr ++) { + ecs_assert(ptr[0] != 0, ECS_INTERNAL_ERROR, NULL); + if (ptr[0] == '\n') { + line_count ++; + } + } + + return line_count; +} + +void ecs_script_visit_push_( + ecs_script_visit_t *v, + ecs_script_node_t *node) +{ + v->nodes[v->depth ++] = node; +} + +void ecs_script_visit_pop_( + ecs_script_visit_t *v, + ecs_script_node_t *node) +{ + v->depth --; + ecs_assert(v->nodes[v->depth] == node, ECS_INTERNAL_ERROR, NULL); + (void)node; +} + +int ecs_script_visit_scope_( + ecs_script_visit_t *v, + ecs_script_scope_t *scope) +{ + int32_t depth = v->depth; + ecs_script_node_t **nodes = ecs_vec_first_t( + &scope->stmts, ecs_script_node_t*); + + if (flecs_script_visit_push_checked(v, (ecs_script_node_t*)scope)) { + return -1; + } + + int32_t i, count = ecs_vec_count(&scope->stmts); + for (i = 0; i < count; i ++) { + if (!i) { + v->prev = NULL; + } else { + v->prev = nodes[i - 1]; + } + + if (i != (count - 1)) { + v->next = nodes[i + 1]; + } else { + v->next = NULL; + } + + if (flecs_script_visit_push_checked(v, nodes[i])) { + v->depth = depth; + return -1; + } + + ecs_assert(v->visit != NULL, ECS_INTERNAL_ERROR, NULL); + + if (v->visit(v, nodes[i])) { + v->depth = depth; + return -1; + } + + ecs_script_visit_pop(v, nodes[i]); + } + + v->depth = depth; + + return 0; +} + +int ecs_script_visit_node_( + ecs_script_visit_t *v, + ecs_script_node_t *node) +{ + int32_t depth = v->depth; + if (flecs_script_visit_push_checked(v, node)) { + return -1; + } + + if (v->visit(v, node)) { + v->depth = depth; + return -1; + } + + v->depth = depth; + + return 0; +} + +int ecs_script_visit_from_( + ecs_script_visit_t *visitor, + ecs_visit_action_t visit, + ecs_script_impl_t *script, + ecs_script_node_t *node, + int32_t depth) +{ + if (!script->root) { + return -1; + } + + visitor->script = script; + visitor->visit = visit; + visitor->depth = depth; + int result = ecs_script_visit_node(visitor, node); + if (result) { + return -1; + } + + if (visitor->depth != depth) { + ecs_parser_error(script->pub.name, NULL, 0, "unexpected end of script"); + return -1; + } + + return 0; +} + +int ecs_script_visit_( + ecs_script_visit_t *visitor, + ecs_visit_action_t visit, + ecs_script_impl_t *script) +{ + if (!script->root) { + return -1; + } + + return ecs_script_visit_from_(visitor, visit, script, + (ecs_script_node_t*)script->root, 0); +} + +#endif + +/** + * @file addons/script/visit_validate.c + * @brief Script AST validation. + */ + + +#ifdef FLECS_SCRIPT + +static +int flecs_script_check_expr( + ecs_script_eval_visitor_t *v, + ecs_expr_node_t **expr_ptr, + ecs_entity_t *type) +{ + ecs_expr_node_t *expr = *expr_ptr; + ecs_script_impl_t *impl = v->base.script; + ecs_script_t *script = &impl->pub; + + ecs_expr_eval_desc_t desc = { + .name = script->name, + .lookup_action = flecs_script_find_entity_action, + .lookup_ctx = v, + .vars = v->vars, + .type = type ? type[0] : 0, + .runtime = v->r, + .allow_unresolved_identifiers = true + }; + + ecs_assert(expr->type_info == NULL, ECS_INTERNAL_ERROR, NULL); + + if (flecs_expr_visit_type(script, expr, &desc)) { + goto error; + } + + if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { + goto error; + } + + if (type) { + type[0] = expr_ptr[0]->type; + } + + return 0; +error: + return -1; +} + +int flecs_script_check_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node) +{ + int ret = flecs_script_eval_scope(v, node); + if (ret) { + return -1; + } + + /* Gather all resolved components in scope so we can add them in one bulk + * operation to entities. */ + ecs_allocator_t *a = &v->base.script->allocator; + int32_t i, count = ecs_vec_count(&node->stmts); + ecs_script_node_t **stmts = ecs_vec_first(&node->stmts); + for (i = 0; i < count; i ++) { + ecs_script_node_t *stmt = stmts[i]; + ecs_id_t id = 0; + if (stmt->kind == EcsAstComponent) { + ecs_script_component_t *cmp = (ecs_script_component_t*)stmt; + id = cmp->id.eval; + } else if (stmt->kind == EcsAstTag) { + ecs_script_tag_t *cmp = (ecs_script_tag_t*)stmt; + id = cmp->id.eval; + } + + if (id) { + ecs_vec_append_t(a, &node->components, ecs_id_t)[0] = id; + } + } + + return 0; +} + +static +int flecs_script_check_entity( + ecs_script_eval_visitor_t *v, + ecs_script_entity_t *node) +{ + if (node->kind) { + ecs_script_id_t id = { + .first = node->kind + }; + + if (!ecs_os_strcmp(node->kind, "prefab")) { + id.eval = EcsPrefab; + } else if (!ecs_os_strcmp(node->kind, "slot")) { + } else if (flecs_script_eval_id(v, node, &id)) { + return -1; + } + + node->eval_kind = id.eval; + } else { + /* Inherit kind from parent kind's DefaultChildComponent, if it exists */ + ecs_script_scope_t *scope = ecs_script_current_scope(v); + if (scope && scope->default_component_eval) { + node->eval_kind = scope->default_component_eval; + } + } + + ecs_script_entity_t *old_entity = v->entity; + v->entity = node; + + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = false; + + if (ecs_script_visit_node(v, node->scope)) { + return -1; + } + + v->is_with_scope = old_is_with_scope; + v->entity = old_entity; + + return 0; +} + +static +int flecs_script_check_tag( + ecs_script_eval_visitor_t *v, + ecs_script_tag_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + + if (!v->entity) { + if (node->id.second) { + flecs_script_eval_error( + v, node, "missing entity for pair (%s, %s)", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, "missing entity for tag %s", + node->id.first); + } + return -1; + } + + return 0; +} + +static +int flecs_script_check_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (!v->entity) { + if (node->id.second) { + flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, "missing entity for component %s", + node->id.first); + } + return -1; + } + + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + + if (node->expr) { + const ecs_type_info_t *ti = ecs_get_type_info(v->world, node->id.eval); + if (!ti) { + return 0; + } + + const EcsType *type = ecs_get(v->world, ti->component, EcsType); + if (type) { + bool is_collection = false; + + switch(type->kind) { + case EcsPrimitiveType: + case EcsBitmaskType: + case EcsEnumType: + case EcsStructType: + case EcsOpaqueType: + break; + case EcsArrayType: + case EcsVectorType: + is_collection = true; + break; + } + + if (node->is_collection != is_collection) { + char *id_str = ecs_id_str(v->world, ti->component); + if (node->is_collection && !is_collection) { + flecs_script_eval_error(v, node, + "type %s is not a collection (use '%s: {...}')", + id_str, id_str); + } else { + flecs_script_eval_error(v, node, + "type %s is a collection (use '%s: [...]')", + id_str, id_str); + } + ecs_os_free(id_str); + return -1; + } + } + + ecs_entity_t expr_type = ti->component; + if (flecs_script_check_expr(v, &node->expr, &expr_type)) { + return -1; + } + } + + return 0; +} + +static +int flecs_script_check_var_component( + ecs_script_eval_visitor_t *v, + ecs_script_var_component_t *node) +{ + ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + + node->sp = var->sp; + + return 0; +} + +static +int flecs_script_check_default_component( + ecs_script_eval_visitor_t *v, + ecs_script_default_component_t *node) +{ + if (!v->entity) { + flecs_script_eval_error(v, node, + "missing entity for default component"); + return -1; + } + + return 0; +} + +static +int flecs_script_check_with_var( + ecs_script_eval_visitor_t *v, + ecs_script_var_component_t *node) +{ + ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + + node->sp = var->sp; + + return 0; +} + +static +int flecs_script_check_with_tag( + ecs_script_eval_visitor_t *v, + ecs_script_tag_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + return 0; +} + +static +int flecs_script_check_with_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (node->expr) { + ecs_entity_t type = node->id.eval; + + if (flecs_script_check_expr(v, &node->expr, &type)) { + return -1; + } + } + + return 0; +} + +static +int flecs_script_check_with( + ecs_script_eval_visitor_t *v, + ecs_script_with_t *node) +{ + if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->expressions)) { + return -1; + } + + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = true; + + if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->scope)) { + return -1; + } + + v->is_with_scope = old_is_with_scope; + + return 0; +} + +static +int flecs_script_check_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node) +{ + return flecs_script_eval_using(v, node); +} + +static +int flecs_script_check_const( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node) +{ + return flecs_script_eval_const(v, node, false); +} + +static +int flecs_script_check_pair_scope( + ecs_script_eval_visitor_t *v, + ecs_script_pair_scope_t *node) +{ + ecs_entity_t dummy; + + if (flecs_script_find_entity( + v, 0, node->id.first, NULL, &node->id.first_sp, &dummy, NULL)) + { + return -1; + } + + if (flecs_script_find_entity( + v, 0, node->id.second, NULL, &node->id.second_sp, &dummy, NULL)) + { + return -1; + } + + if (ecs_script_visit_scope(v, node->scope)) { + return -1; + } + + return 0; +} + +static +int flecs_script_check_if( + ecs_script_eval_visitor_t *v, + ecs_script_if_t *node) +{ + if (flecs_script_check_expr(v, &node->expr, NULL)) { + return -1; + } + + if (flecs_script_check_scope(v, node->if_true)) { + return -1; + } + + if (flecs_script_check_scope(v, node->if_false)) { + return -1; + } + + return 0; +} + +static +int flecs_script_check_for_range( + ecs_script_eval_visitor_t *v, + ecs_script_for_range_t *node) +{ + ecs_entity_t type = ecs_id(ecs_i32_t); + if (flecs_script_check_expr(v, &node->from, &type)) { + return -1; + } + + type = ecs_id(ecs_i32_t); + if (flecs_script_check_expr(v, &node->to, &type)) { + return -1; + } + + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); + + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); + const ecs_type_info_t *ti = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); + int32_t dummy = 0; + var->value.ptr = &dummy; + var->value.type = ecs_id(ecs_i32_t); + var->type_info = ti; + + if (flecs_script_eval_scope(v, node->scope)) { + return -1; + } + + var->value.ptr = NULL; + + v->vars = ecs_script_vars_pop(v->vars); + + return 0; +} + +static +int flecs_script_check_annot( + ecs_script_eval_visitor_t *v, + ecs_script_annot_t *node) +{ + if (!v->base.next) { + flecs_script_eval_error(v, node, + "annotation '%s' is not applied to anything", node->name); + return -1; + } + + ecs_script_node_kind_t kind = v->base.next->kind; + if (kind != EcsAstEntity && kind != EcsAstAnnotation) { + flecs_script_eval_error(v, node, + "annotation must be applied to an entity"); + return -1; + } + + return 0; +} + +int flecs_script_check_node( + ecs_script_visit_t *_v, + ecs_script_node_t *node) +{ + ecs_script_eval_visitor_t *v = (ecs_script_eval_visitor_t*)_v; + switch(node->kind) { + case EcsAstScope: + return flecs_script_check_scope( + v, (ecs_script_scope_t*)node); + case EcsAstTag: + return flecs_script_check_tag( + v, (ecs_script_tag_t*)node); + case EcsAstComponent: + return flecs_script_check_component( + v, (ecs_script_component_t*)node); + case EcsAstVarComponent: + return flecs_script_check_var_component( + v, (ecs_script_var_component_t*)node); + case EcsAstDefaultComponent: + return flecs_script_check_default_component( + v, (ecs_script_default_component_t*)node); + case EcsAstWithVar: + return flecs_script_check_with_var( + v, (ecs_script_var_component_t*)node); + case EcsAstWithTag: + return flecs_script_check_with_tag( + v, (ecs_script_tag_t*)node); + case EcsAstWithComponent: + return flecs_script_check_with_component( + v, (ecs_script_component_t*)node); + case EcsAstWith: + return flecs_script_check_with( + v, (ecs_script_with_t*)node); + case EcsAstUsing: + return flecs_script_check_using( + v, (ecs_script_using_t*)node); + case EcsAstModule: + return 0; + case EcsAstAnnotation: + return flecs_script_check_annot( + v, (ecs_script_annot_t*)node); + case EcsAstTemplate: + return 0; + case EcsAstProp: + return 0; + case EcsAstConst: + return flecs_script_check_const( + v, (ecs_script_var_node_t*)node); + case EcsAstExportConst: + return 0; + case EcsAstEntity: + return flecs_script_check_entity( + v, (ecs_script_entity_t*)node); + case EcsAstPairScope: + return flecs_script_check_pair_scope( + v, (ecs_script_pair_scope_t*)node); + case EcsAstIf: + return flecs_script_check_if( + v, (ecs_script_if_t*)node); + case EcsAstFor: + return flecs_script_check_for_range( + v, (ecs_script_for_range_t*)node); + } + + ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); +} + +int flecs_script_check( + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc) +{ + ecs_script_eval_visitor_t v; + ecs_script_impl_t *impl = flecs_script_impl( + /* Safe, script will only be used for reading by visitor */ + ECS_CONST_CAST(ecs_script_t*, script)); + + ecs_script_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.runtime) { + priv_desc.runtime = flecs_script_runtime_get(script->world); + } + + flecs_script_eval_visit_init(impl, &v, &priv_desc); + int result = ecs_script_visit(impl, &v, flecs_script_check_node); + flecs_script_eval_visit_fini(&v, &priv_desc); + + return result; +} + +#endif + +/** + * @file addons/script/visit_eval.c + * @brief Script evaluation visitor. + */ + + +#ifdef FLECS_SCRIPT + +static +bool flecs_script_valid_lookup_path( + const char *path) +{ + int32_t template_nesting = 0; + char ch; + for (; (ch = path[0]); path ++) { + if (ch == '<') { + template_nesting ++; + } else if (ch == '>') { + template_nesting --; + if (template_nesting < 0) { + return false; + } + } + } + + return template_nesting == 0; +} + +void flecs_script_eval_error_( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node, + const char *fmt, + ...) +{ + va_list args; + va_start(args, fmt); + char *msg = flecs_vasprintf(fmt, args); + va_end(args); + + if (node) { + int32_t line = ecs_script_node_line_number(v->base.script, node); + ecs_parser_error(v->base.script->pub.name, NULL, 0, "%d: %s", line, msg); + } else { + ecs_parser_error(v->base.script->pub.name, NULL, 0, "%s", msg); + } + + ecs_os_free(msg); +} + +static +ecs_value_t* flecs_script_with_append( + ecs_allocator_t *a, + ecs_script_eval_visitor_t *v, + const ecs_type_info_t *ti) +{ + if (ecs_vec_count(&v->r->with)) { + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->type == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->ptr == NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_vec_remove_last(&v->r->with); + } + + ecs_vec_append_t(a, &v->r->with_type_info, const ecs_type_info_t*)[0] = ti; + + ecs_vec_append_t(a, &v->r->with, ecs_value_t); + ecs_value_t *last = ecs_vec_append_t(a, &v->r->with, ecs_value_t); + ecs_os_memset_t(last, 0, ecs_value_t); + return ecs_vec_get_t(&v->r->with, ecs_value_t, ecs_vec_count(&v->r->with) - 2); +} + +static +void flecs_script_with_set_count( + ecs_allocator_t *a, + ecs_script_eval_visitor_t *v, + int32_t count) +{ + int32_t i = count, until = ecs_vec_count(&v->r->with) - 1; + for (; i < until; i ++) { + ecs_value_t *val = ecs_vec_get_t(&v->r->with, ecs_value_t, i); + ecs_type_info_t *ti = ecs_vec_get_t( + &v->r->with_type_info, ecs_type_info_t*, i)[0]; + if (ti) { + if (ti->hooks.dtor) { + ti->hooks.dtor(val->ptr, 1, ti); + } + flecs_stack_free(val->ptr, ti->size); + } + } + + if (count) { + ecs_value_t *last = ecs_vec_get_t(&v->r->with, ecs_value_t, count); + ecs_os_memset_t(last, 0, ecs_value_t); + ecs_vec_set_count_t(a, &v->r->with, ecs_value_t, count + 1); + } else { + ecs_vec_set_count_t(a, &v->r->with, ecs_value_t, 0); + } + + ecs_vec_set_count_t(a, &v->r->with_type_info, ecs_type_info_t*, count); +} + +static +ecs_value_t* flecs_script_with_last( + ecs_script_eval_visitor_t *v) +{ + int32_t count = ecs_vec_count(&v->r->with); + if (count) { + return ecs_vec_get_t(&v->r->with, ecs_value_t, count - 2); + } + return NULL; +} + +static +int32_t flecs_script_with_count( + ecs_script_eval_visitor_t *v) +{ + if (ecs_vec_count(&v->r->with)) { + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->type == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->ptr == NULL, + ECS_INTERNAL_ERROR, NULL); + return ecs_vec_count(&v->r->with) - 1; + } + return 0; +} + +const ecs_type_info_t* flecs_script_get_type_info( + ecs_script_eval_visitor_t *v, + void *node, + ecs_id_t id) +{ + ecs_component_record_t *cr = flecs_components_ensure(v->world, id); + if (!cr) { + goto error; + } + + if (!cr->type_info) { + goto error; + } + + return cr->type_info; +error: + { + char *idstr = ecs_id_str(v->world, id); + flecs_script_eval_error(v, node, + "cannot set value of '%s': not a component", idstr); + ecs_os_free(idstr); + } + return NULL; +} + +ecs_script_var_t* flecs_script_find_var( + const ecs_script_vars_t *vars, + const char *name, + int32_t *sp) +{ + if (sp && sp[0] != -1) { + return ecs_script_vars_from_sp(vars, sp[0]); + } else { + ecs_script_var_t *var = ecs_script_vars_lookup(vars, name); + if (var && sp) { + sp[0] = var->sp; + } + return var; + } +} + +static +ecs_entity_t flecs_script_eval_name_expr( + ecs_script_eval_visitor_t *v, + ecs_script_entity_t *node, + ecs_expr_node_t **expr_ptr, + bool find) +{ + ecs_script_t *script = &v->base.script->pub; + ecs_expr_eval_desc_t desc = { + .name = script->name, + .lookup_action = flecs_script_find_entity_action, + .lookup_ctx = v, + .vars = v->vars, + .type = ecs_id(ecs_string_t), + .runtime = v->r + }; + + ecs_expr_node_t *expr = *expr_ptr; + + if (!expr->type_info) { + if (flecs_expr_visit_type(script, expr, &desc)) { + return 0; + } - int32_t t = s->t = t_next(s->t); + if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { + return 0; + } - ecs_iter_t it = ecs_query_iter(world, (ecs_query_t*)query); - record_gauge(&s->matched_entity_count, t, ecs_iter_count(&it)); - record_gauge(&s->matched_table_count, t, ecs_query_table_count(query)); - record_gauge(&s->matched_empty_table_count, t, - ecs_query_empty_table_count(query)); -error: - return; + expr = *expr_ptr; + } + + ecs_value_t value = { .type = ecs_id(ecs_string_t) }; + if (flecs_expr_visit_eval(script, expr, &desc, &value)) { + return 0; + } + + char *name = *(char**)value.ptr; + if (!name) { + flecs_script_eval_error(v, node, "failed to evaluate entity name"); + return 0; + } + + ecs_entity_t result; + if (!find) { + result = flecs_script_create_entity(v, name); + } else { + if (flecs_script_find_entity(v, 0, name, NULL, NULL, &result, NULL)) { + return 0; + } + } + + ecs_value_free(script->world, value.type, value.ptr); + + return result; } -#ifdef FLECS_SYSTEM -bool ecs_get_system_stats( - const ecs_world_t *world, - ecs_entity_t system, - ecs_system_stats_t *s) +int flecs_script_find_entity( + ecs_script_eval_visitor_t *v, + ecs_entity_t from, + const char *path, + ecs_expr_node_t **name_expr, + int32_t *sp, + ecs_entity_t *out, + bool *is_var) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + if (!path) { + goto error; + } - world = ecs_get_world(world); + ecs_entity_t result = 0; - const EcsSystem *ptr = ecs_get(world, system, EcsSystem); - if (!ptr) { - return false; - } + if (path[0] != '$') { + bool valid_path = flecs_script_valid_lookup_path(path); - ecs_get_query_stats(world, ptr->query, &s->query_stats); - int32_t t = s->query_stats.t; + if (name_expr && *name_expr) { + result = flecs_script_eval_name_expr(v, NULL, name_expr, true); + if (!result) { + return -1; + } + } else if (from && valid_path) { + result = ecs_lookup_path_w_sep( + v->world, from, path, NULL, NULL, false); + } else { + int32_t i, using_count = ecs_vec_count(&v->r->using); + if (using_count && valid_path) { + ecs_entity_t *using = ecs_vec_first(&v->r->using); + for (i = using_count - 1; i >= 0; i --) { + ecs_entity_t e = ecs_lookup_path_w_sep( + v->world, using[i], path, NULL, NULL, false); + if (e) { + result = e; + } + } + } - record_counter(&s->time_spent, t, ptr->time_spent); - record_counter(&s->invoke_count, t, ptr->invoke_count); - record_gauge(&s->active, t, !ecs_has_id(world, system, EcsInactive)); - record_gauge(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); + if (!result && valid_path) { + result = ecs_lookup_path_w_sep( + v->world, v->parent, path, NULL, NULL, true); + } + } - return true; -error: - return false; -} -#endif + if (result) { + *out = result; + return 0; + } + if (!sp) { + return -1; + } + } else { + if (!sp) { + flecs_script_eval_error(v, NULL, + "variable identifier '%s' not allowed here", path); + goto error; + } -#ifdef FLECS_PIPELINE + path ++; + } -static -ecs_system_stats_t* get_system_stats( - ecs_map_t *systems, - ecs_entity_t system) -{ - ecs_check(systems != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); + const ecs_script_var_t *var = flecs_script_find_var( + v->vars, path, v->dynamic_variable_binding ? NULL : sp); + if (!var) { + goto error; + } - ecs_system_stats_t *s = ecs_map_get(systems, ecs_system_stats_t, system); - if (!s) { - s = ecs_map_ensure(systems, ecs_system_stats_t, system); + if (var->value.type != ecs_id(ecs_entity_t)) { + char *type_str = ecs_id_str(v->world, var->value.type); + flecs_script_eval_error(v, NULL, + "variable '%s' must be of type entity, got '%s'", + path, type_str); + ecs_os_free(type_str); + goto error; + } + + if (v->template) { + return 0; + } + + if (var->value.ptr == NULL) { + flecs_script_eval_error(v, NULL, + "variable '%s' is not initialized", path); + goto error; + } + + result = *(ecs_entity_t*)var->value.ptr; + if (!result) { + flecs_script_eval_error(v, NULL, + "variable '%s' contains invalid entity id (0)", path); + goto error; } - return s; + *out = result; + if (is_var) *is_var = true; + + return 0; error: - return NULL; + return -1; } -bool ecs_get_pipeline_stats( - ecs_world_t *stage, - ecs_entity_t pipeline, - ecs_pipeline_stats_t *s) +ecs_entity_t flecs_script_create_entity( + ecs_script_eval_visitor_t *v, + const char *name) { - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); + ecs_value_t *with = NULL; + if (flecs_script_with_count(v)) { + with = ecs_vec_first_t(&v->r->with, ecs_value_t); + } - const ecs_world_t *world = ecs_get_world(stage); + ecs_entity_desc_t desc = {0}; - const EcsPipelineQuery *pq = ecs_get(world, pipeline, EcsPipelineQuery); - if (!pq) { - return false; + if (v->entity && v->entity->non_fragmenting_parent) { + desc.id = ecs_new_w_parent(v->world, v->parent, name); + ecs_id_t world_with = ecs_get_with(v->world); + if (world_with) { + ecs_add_id(v->world, desc.id, world_with); + } + } else { + desc.parent = v->parent; + desc.name = name; } - int32_t sys_count = 0, active_sys_count = 0; + desc.set = with; + return ecs_entity_init(v->world, &desc); +} - /* Count number of active systems */ - ecs_iter_t it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - active_sys_count += it.count; +ecs_entity_t flecs_script_find_entity_action( + const ecs_world_t *world, + const char *path, + void *ctx) +{ + (void)world; + ecs_script_eval_visitor_t *v = ctx; + ecs_entity_t result; + if (!flecs_script_find_entity(v, 0, path, NULL, NULL, &result, NULL)) { + return result; } + return 0; +} - /* Count total number of systems in pipeline */ - it = ecs_query_iter(stage, pq->build_query); - while (ecs_query_next(&it)) { - sys_count += it.count; - } +static +int flecs_script_find_template_entity( + ecs_script_eval_visitor_t *v, + void *node, + const char *name) +{ + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); - /* Also count synchronization points */ - ecs_vector_t *ops = pq->ops; - ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); - ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); - int32_t pip_count = active_sys_count + ecs_vector_count(ops); + /* Loop template scope to see if it declares an entity with requested name */ + ecs_script_template_t *t = v->template; + ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t->node != NULL, ECS_INTERNAL_ERROR, NULL); - if (!sys_count) { - return false; + ecs_script_scope_t *scope = t->node->scope; + ecs_script_node_t **nodes = ecs_vec_first_t( + &scope->stmts, ecs_script_node_t*); + + int32_t i, count = ecs_vec_count(&scope->stmts); + for (i = 0; i < count; i ++) { + ecs_script_node_t *elem = nodes[i]; + if (elem->kind == EcsAstEntity) { + ecs_script_entity_t *entity_node = (ecs_script_entity_t*)elem; + if (!entity_node->name) { + continue; + } + + if (!ecs_os_strcmp(entity_node->name, name)) { + return 0; + } + } } - if (s->system_stats && !sys_count) { - ecs_map_free(s->system_stats); + flecs_script_eval_error(v, node, "unresolved reference to '%s'", name); + + return -1; +} + +int flecs_script_eval_id( + ecs_script_eval_visitor_t *v, + void *node, + ecs_script_id_t *id) +{ + ecs_entity_t second_from = 0; + + if (id->eval && !id->dynamic) { + /* Already resolved */ + return 0; } - if (!s->system_stats && sys_count) { - s->system_stats = ecs_map_new(ecs_system_stats_t, sys_count); + + if (!id->first) { + flecs_script_eval_error(v, node, + "invalid component/tag identifier"); + return -1; } - if (!sys_count) { - s->system_stats = NULL; + + if (v->template) { + /* Can't resolve variables while preprocessing template scope */ + if (id->first[0] == '$') { + if (flecs_script_find_var(v->vars, &id->first[1], + v->dynamic_variable_binding ? NULL : &id->first_sp)) + { + return 0; + } else { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", &id->first[1]); + return -1; + } + } + if (id->second && id->second[0] == '$') { + if (flecs_script_find_var(v->vars, &id->second[1], + v->dynamic_variable_binding ? NULL : &id->second_sp)) + { + return 0; + } else { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", &id->second[1]); + return -1; + } + } } - /* Make sure vector is large enough to store all systems & sync points */ - ecs_entity_t *systems = NULL; - if (pip_count) { - ecs_vector_set_count(&s->systems, ecs_entity_t, pip_count); - systems = ecs_vector_first(s->systems, ecs_entity_t); + ecs_entity_t first = 0; + if (flecs_script_find_entity( + v, 0, id->first, &id->first_expr, &id->first_sp, &first, NULL) || !first) + { + if (id->first[0] == '$') { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", id->first); + return -1; + } + + flecs_script_eval_error(v, node, + "unresolved identifier '%s'", id->first); - /* Populate systems vector, keep track of sync points */ - it = ecs_query_iter(stage, pq->query); - - int32_t i, i_system = 0, ran_since_merge = 0; - while (ecs_query_next(&it)) { - for (i = 0; i < it.count; i ++) { - systems[i_system ++] = it.entities[i]; - ran_since_merge ++; - if (op != op_last && ran_since_merge == op->count) { - ran_since_merge = 0; - op++; - systems[i_system ++] = 0; /* 0 indicates a merge point */ + return -1; + } else if (id->second) { + second_from = flecs_get_oneof(v->world, first); + } + + if (id->second) { + ecs_entity_t second = 0; + if (flecs_script_find_entity( + v, second_from, id->second, &id->second_expr, &id->second_sp, + &second, &id->dynamic) || !second) + { + if (id->second[0] == '$') { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", id->second); + return -1; + } + + /* Targets may be defined by the template */ + if (v->template) { + if (!flecs_script_find_template_entity(v, node, id->second)) { + id->dynamic = true; + return 0; + } else { + return -1; } } + + if (second_from) { + char *parent_str = ecs_id_str(v->world, second_from); + flecs_script_eval_error(v, node, "target '%s' not found in " + "parent '%s'", id->second, parent_str); + ecs_os_free(parent_str); + return -1; + } + + flecs_script_eval_error(v, node, + "unresolved identifier '%s'", id->second); + + return -1; + } + + if (first == EcsAny || second == EcsAny) { + flecs_script_eval_error(v, node, + "cannot use anonymous entity as element of pair"); + return -1; } - systems[i_system ++] = 0; /* Last merge */ - ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); + id->eval = id->flag | ecs_pair(first, second); } else { - ecs_vector_free(s->systems); - s->systems = NULL; + if (first == EcsAny) { + flecs_script_eval_error(v, node, + "cannot use anonymous entity as component or tag"); + return -1; + } + + id->eval = id->flag | first; } - /* Separately populate system stats map from build query, which includes - * systems that aren't currently active */ - it = ecs_query_iter(stage, pq->build_query); - while (ecs_query_next(&it)) { - int i; - for (i = 0; i < it.count; i ++) { - ecs_system_stats_t *sys_stats = get_system_stats( - s->system_stats, it.entities[i]); - ecs_get_system_stats(world, it.entities[i], sys_stats); + return 0; +} + +int flecs_script_eval_expr( + ecs_script_eval_visitor_t *v, + ecs_expr_node_t **expr_ptr, + ecs_value_t *value) +{ + ecs_expr_node_t *expr = *expr_ptr; + ecs_assert(expr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_impl_t *impl = v->base.script; + ecs_script_t *script = &impl->pub; + + ecs_expr_eval_desc_t desc = { + .name = script->name, + .lookup_action = flecs_script_find_entity_action, + .lookup_ctx = v, + .vars = v->vars, + .type = value->type, + .runtime = v->r, + .disable_dynamic_variable_binding = !v->dynamic_variable_binding, + .script_visitor = v + }; + + if (expr->type_info == NULL) { + if (flecs_expr_visit_type(script, expr, &desc)) { + goto error; + } + if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { + goto error; } } - return true; + if (flecs_expr_visit_eval(script, *expr_ptr, &desc, value)) { + goto error; + } + + return 0; error: - return false; + return -1; } -void ecs_pipeline_stats_fini( - ecs_pipeline_stats_t *stats) +int flecs_script_eval_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node) { - ecs_map_free(stats->system_stats); - ecs_vector_free(stats->systems); + ecs_script_node_t *scope_parent = ecs_script_parent_node(v); + ecs_entity_t prev_eval_parent = v->parent; + int32_t prev_using_count = ecs_vec_count(&v->r->using); + + for (int i = v->base.depth - 2; i >= 0; i --) { + if (v->base.nodes[i]->kind == EcsAstScope) { + node->parent = (ecs_script_scope_t*)v->base.nodes[i]; + break; + } + } + + ecs_allocator_t *a = &v->r->allocator; + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); + + if (scope_parent && (scope_parent->kind == EcsAstEntity)) { + if (!v->template) { + v->parent = ecs_script_node(entity, scope_parent)->eval; + } + } + + if (v->entity) { + ecs_entity_t src = v->entity->eval; + int32_t count = ecs_vec_count(&node->components); + if (src != EcsVariable && count) { + flecs_add_ids( + v->world, src, ecs_vec_first(&node->components), count); + } + } + + int result = ecs_script_visit_scope(v, node); + + ecs_vec_set_count_t(a, &v->r->using, ecs_entity_t, prev_using_count); + v->vars = ecs_script_vars_pop(v->vars); + v->parent = prev_eval_parent; + + return result; } -#endif +static +void flecs_script_apply_non_fragmenting_childof( + ecs_world_t *world, + ecs_script_entity_t *node, + bool enabled); -void ecs_dump_world_stats( - const ecs_world_t *world, - const ecs_world_stats_t *s) +static +void flecs_script_apply_non_fragmenting_childof_to_scope( + ecs_world_t *world, + ecs_script_scope_t *scope, + bool enabled) { - int32_t t = s->t; + if (!scope) { + return; + } - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t i, count = ecs_vec_count(&scope->stmts); + ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); + for (i = 0; i < count; i ++) { + ecs_script_node_t *stmt = stmts[i]; + switch(stmt->kind) { + case EcsAstScope: + flecs_script_apply_non_fragmenting_childof_to_scope( + world, (ecs_script_scope_t*)stmt, enabled); + break; + case EcsAstEntity: + flecs_script_apply_non_fragmenting_childof( + world, (ecs_script_entity_t*)stmt, enabled); + break; + case EcsAstIf: + flecs_script_apply_non_fragmenting_childof_to_scope( + world, ((ecs_script_if_t*)stmt)->if_false, enabled); + flecs_script_apply_non_fragmenting_childof_to_scope( + world, ((ecs_script_if_t*)stmt)->if_true, enabled); + break; + case EcsAstFor: + flecs_script_apply_non_fragmenting_childof_to_scope( + world, ((ecs_script_for_range_t*)stmt)->scope, enabled); + break; + case EcsAstWith: + case EcsAstWithVar: + case EcsAstWithTag: + case EcsAstWithComponent: + flecs_script_apply_non_fragmenting_childof_to_scope( + world, ((ecs_script_with_t*)stmt)->scope, enabled); + break; + case EcsAstPairScope: + flecs_script_apply_non_fragmenting_childof_to_scope( + world, ((ecs_script_pair_scope_t*)stmt)->scope, enabled); + break; + case EcsAstTag: + case EcsAstComponent: + case EcsAstDefaultComponent: + case EcsAstVarComponent: + case EcsAstUsing: + case EcsAstModule: + case EcsAstAnnotation: + case EcsAstTemplate: + case EcsAstProp: + case EcsAstConst: + case EcsAstExportConst: + break; + } + } +} - world = ecs_get_world(world); - - print_counter("Frame", t, &s->frame_count_total); - ecs_trace("-------------------------------------"); - print_counter("pipeline rebuilds", t, &s->pipeline_build_count_total); - print_counter("systems invocations", t, &s->systems_ran_frame); - ecs_trace(""); - print_value("target FPS", world->info.target_fps); - print_value("time scale", world->info.time_scale); - ecs_trace(""); - print_gauge("actual FPS", t, &s->fps); - print_counter("frame time", t, &s->frame_time_total); - print_counter("system time", t, &s->system_time_total); - print_counter("merge time", t, &s->merge_time_total); - print_counter("simulation time elapsed", t, &s->world_time_total); - ecs_trace(""); - print_gauge("id count", t, &s->id_count); - print_gauge("tag id count", t, &s->tag_id_count); - print_gauge("component id count", t, &s->component_id_count); - print_gauge("pair id count", t, &s->pair_id_count); - print_gauge("wildcard id count", t, &s->wildcard_id_count); - print_gauge("component count", t, &s->component_count); - ecs_trace(""); - print_gauge("alive entity count", t, &s->entity_count); - print_gauge("not alive entity count", t, &s->entity_not_alive_count); - ecs_trace(""); - print_gauge("query count", t, &s->query_count); - print_gauge("trigger count", t, &s->trigger_count); - print_gauge("observer count", t, &s->observer_count); - print_gauge("system count", t, &s->system_count); - ecs_trace(""); - print_gauge("table count", t, &s->table_count); - print_gauge("empty table count", t, &s->empty_table_count); - print_gauge("tag table count", t, &s->tag_table_count); - print_gauge("trivial table count", t, &s->trivial_table_count); - print_gauge("table storage count", t, &s->table_storage_count); - print_gauge("table cache record count", t, &s->table_record_count); - print_gauge("singleton table count", t, &s->singleton_table_count); - ecs_trace(""); - print_counter("table create count", t, &s->table_create_count); - print_counter("table delete count", t, &s->table_delete_count); - print_counter("id create count", t, &s->id_create_count); - print_counter("id delete count", t, &s->id_delete_count); - ecs_trace(""); - print_counter("deferred new operations", t, &s->new_count); - print_counter("deferred bulk_new operations", t, &s->bulk_new_count); - print_counter("deferred delete operations", t, &s->delete_count); - print_counter("deferred clear operations", t, &s->clear_count); - print_counter("deferred add operations", t, &s->add_count); - print_counter("deferred remove operations", t, &s->remove_count); - print_counter("deferred set operations", t, &s->set_count); - print_counter("discarded operations", t, &s->discard_count); - ecs_trace(""); +static +void flecs_script_apply_non_fragmenting_childof( + ecs_world_t *world, + ecs_script_entity_t *node, + bool enabled) +{ + node->non_fragmenting_parent = enabled; + + flecs_script_apply_non_fragmenting_childof_to_scope( + world, node->scope, enabled); +} + +int flecs_script_apply_annot( + ecs_script_eval_visitor_t *v, + ecs_script_entity_t *node, + ecs_script_annot_t *annot) +{ + ecs_entity_t e = node->eval; + + if (!ecs_os_strcmp(annot->name, "name")) { + ecs_doc_set_name(v->world, e, annot->expr); + } else + if (!ecs_os_strcmp(annot->name, "brief")) { + ecs_doc_set_brief(v->world, e, annot->expr); + } else + if (!ecs_os_strcmp(annot->name, "detail")) { + ecs_doc_set_detail(v->world, e, annot->expr); + } else + if (!ecs_os_strcmp(annot->name, "link")) { + ecs_doc_set_link(v->world, e, annot->expr); + } else + if (!ecs_os_strcmp(annot->name, "color")) { + ecs_doc_set_color(v->world, e, annot->expr); + } else + if (!ecs_os_strcmp(annot->name, "tree")) { + if (!ecs_os_strcmp(annot->expr, "Parent")) { + flecs_script_apply_non_fragmenting_childof(v->world, node, true); + } else if (!ecs_os_strcmp(annot->expr, "ChildOf")) { + flecs_script_apply_non_fragmenting_childof(v->world, node, false); + } else { + flecs_script_eval_error(v, annot, + "invalid value for tree annotation: '%s' (expected 'Parent' or 'ChildOf')", + annot->expr); + return -1; + } + } else { + flecs_script_eval_error(v, annot, "unknown annotation '%s'", + annot->name); + return -1; + } -error: - return; + return 0; } -#endif +static +int flecs_script_eval_entity( + ecs_script_eval_visitor_t *v, + ecs_script_entity_t *node) +{ + bool is_slot = false; + if (node->kind) { + ecs_script_id_t id = { + .first = node->kind, + .first_sp = -1 + }; + if (!ecs_os_strcmp(node->kind, "prefab")) { + id.eval = EcsPrefab; + } else if (!ecs_os_strcmp(node->kind, "slot")) { + is_slot = true; + } else if (flecs_script_eval_id(v, node, &id)) { + return -1; + } + node->eval_kind = id.eval; + } else { + /* Inherit kind from parent kind's DefaultChildComponent, if it exists */ + ecs_script_scope_t *scope = ecs_script_current_scope(v); + if (scope && scope->default_component_eval) { + node->eval_kind = scope->default_component_eval; + } + } -#ifdef FLECS_UNITS + ecs_expr_node_t *name_expr = node->name_expr; + if (name_expr) { + node->eval = flecs_script_eval_name_expr( + v, node, &node->name_expr, false); + if (!node->eval) { + return -1; + } + } else { + node->eval = flecs_script_create_entity(v, node->name); + } -ECS_DECLARE(EcsUnitPrefixes); - -ECS_DECLARE(EcsYocto); -ECS_DECLARE(EcsZepto); -ECS_DECLARE(EcsAtto); -ECS_DECLARE(EcsFemto); -ECS_DECLARE(EcsPico); -ECS_DECLARE(EcsNano); -ECS_DECLARE(EcsMicro); -ECS_DECLARE(EcsMilli); -ECS_DECLARE(EcsCenti); -ECS_DECLARE(EcsDeci); -ECS_DECLARE(EcsDeca); -ECS_DECLARE(EcsHecto); -ECS_DECLARE(EcsKilo); -ECS_DECLARE(EcsMega); -ECS_DECLARE(EcsGiga); -ECS_DECLARE(EcsTera); -ECS_DECLARE(EcsPeta); -ECS_DECLARE(EcsExa); -ECS_DECLARE(EcsZetta); -ECS_DECLARE(EcsYotta); - -ECS_DECLARE(EcsKibi); -ECS_DECLARE(EcsMebi); -ECS_DECLARE(EcsGibi); -ECS_DECLARE(EcsTebi); -ECS_DECLARE(EcsPebi); -ECS_DECLARE(EcsExbi); -ECS_DECLARE(EcsZebi); -ECS_DECLARE(EcsYobi); - -ECS_DECLARE(EcsDuration); - ECS_DECLARE(EcsPicoSeconds); - ECS_DECLARE(EcsNanoSeconds); - ECS_DECLARE(EcsMicroSeconds); - ECS_DECLARE(EcsMilliSeconds); - ECS_DECLARE(EcsSeconds); - ECS_DECLARE(EcsMinutes); - ECS_DECLARE(EcsHours); - ECS_DECLARE(EcsDays); - -ECS_DECLARE(EcsTime); - ECS_DECLARE(EcsDate); - -ECS_DECLARE(EcsMass); - ECS_DECLARE(EcsGrams); - ECS_DECLARE(EcsKiloGrams); - -ECS_DECLARE(EcsElectricCurrent); - ECS_DECLARE(EcsAmpere); - -ECS_DECLARE(EcsAmount); - ECS_DECLARE(EcsMole); - -ECS_DECLARE(EcsLuminousIntensity); - ECS_DECLARE(EcsCandela); - -ECS_DECLARE(EcsForce); - ECS_DECLARE(EcsNewton); - -ECS_DECLARE(EcsLength); - ECS_DECLARE(EcsMeters); - ECS_DECLARE(EcsPicoMeters); - ECS_DECLARE(EcsNanoMeters); - ECS_DECLARE(EcsMicroMeters); - ECS_DECLARE(EcsMilliMeters); - ECS_DECLARE(EcsCentiMeters); - ECS_DECLARE(EcsKiloMeters); - ECS_DECLARE(EcsMiles); - -ECS_DECLARE(EcsPressure); - ECS_DECLARE(EcsPascal); - ECS_DECLARE(EcsBar); - -ECS_DECLARE(EcsSpeed); - ECS_DECLARE(EcsMetersPerSecond); - ECS_DECLARE(EcsKiloMetersPerSecond); - ECS_DECLARE(EcsKiloMetersPerHour); - ECS_DECLARE(EcsMilesPerHour); - -ECS_DECLARE(EcsAcceleration); - -ECS_DECLARE(EcsTemperature); - ECS_DECLARE(EcsKelvin); - ECS_DECLARE(EcsCelsius); - ECS_DECLARE(EcsFahrenheit); - -ECS_DECLARE(EcsData); - ECS_DECLARE(EcsBits); - ECS_DECLARE(EcsKiloBits); - ECS_DECLARE(EcsMegaBits); - ECS_DECLARE(EcsGigaBits); - ECS_DECLARE(EcsBytes); - ECS_DECLARE(EcsKiloBytes); - ECS_DECLARE(EcsMegaBytes); - ECS_DECLARE(EcsGigaBytes); - ECS_DECLARE(EcsKibiBytes); - ECS_DECLARE(EcsGibiBytes); - ECS_DECLARE(EcsMebiBytes); - -ECS_DECLARE(EcsDataRate); - ECS_DECLARE(EcsBitsPerSecond); - ECS_DECLARE(EcsKiloBitsPerSecond); - ECS_DECLARE(EcsMegaBitsPerSecond); - ECS_DECLARE(EcsGigaBitsPerSecond); - ECS_DECLARE(EcsBytesPerSecond); - ECS_DECLARE(EcsKiloBytesPerSecond); - ECS_DECLARE(EcsMegaBytesPerSecond); - ECS_DECLARE(EcsGigaBytesPerSecond); - -ECS_DECLARE(EcsPercentage); - -ECS_DECLARE(EcsAngle); - ECS_DECLARE(EcsRadians); - ECS_DECLARE(EcsDegrees); - -ECS_DECLARE(EcsBel); -ECS_DECLARE(EcsDeciBel); + node->parent = v->entity; -void FlecsUnitsImport( - ecs_world_t *world) + if (v->template_entity) { + ecs_add_pair( + v->world, node->eval, EcsScriptTemplate, v->template_entity); + } + + if (is_slot) { + ecs_entity_t parent = ecs_get_target( + v->world, node->eval, EcsChildOf, 0); + if (!parent) { + flecs_script_eval_error(v, node, + "slot entity must have a parent"); + return -1; + } + + ecs_add_pair(v->world, node->eval, EcsSlotOf, parent); + } + + const EcsDefaultChildComponent *default_comp = NULL; + ecs_script_entity_t *old_entity = v->entity; + v->entity = node; + + if (node->eval_kind) { + ecs_add_id(v->world, node->eval, node->eval_kind); + + default_comp = + ecs_get(v->world, node->eval_kind, EcsDefaultChildComponent); + if (default_comp) { + if (!default_comp->component) { + flecs_script_eval_error(v, node, "entity '%s' has kind '%s' " + "with uninitialized DefaultChildComponent", + node->name, node->kind); + return -1; + } + + node->scope->default_component_eval = default_comp->component; + } + } + + int32_t i, count = ecs_vec_count(&v->r->annot); + if (count) { + ecs_script_annot_t **annots = ecs_vec_first(&v->r->annot); + for (i = 0; i < count ; i ++) { + flecs_script_apply_annot(v, node, annots[i]); + } + ecs_vec_clear(&v->r->annot); + } + + bool old_is_with_scope = v->is_with_scope; + ecs_entity_t old_template_entity = v->template_entity; + v->is_with_scope = false; + v->template_entity = 0; + if (ecs_script_visit_node(v, node->scope)) { + return -1; + } + v->template_entity = old_template_entity; + v->is_with_scope = old_is_with_scope; + + if (node->eval_kind) { + if (!node->kind_w_expr) { + if (ecs_get_type_info(v->world, node->eval_kind) != NULL) { + ecs_modified_id(v->world, node->eval, node->eval_kind); + } + } + } + + v->entity = old_entity; + + return 0; +} + +static +ecs_entity_t flecs_script_get_src( + ecs_script_eval_visitor_t *v, + ecs_entity_t entity, + ecs_id_t id) { - ECS_MODULE(world, FlecsUnits); + if (entity == EcsVariable) { + if (ECS_IS_PAIR(id)) { + return ecs_pair_first(v->world, id); + } else { + return id & ECS_COMPONENT_MASK; + } + } + return entity; +} - ecs_set_name_prefix(world, "Ecs"); +static +bool flecs_script_can_default_ctor( + ecs_world_t *world, + ecs_id_t component) +{ + /* Check if tag is a component, and if so, if it can be default + * constructed. */ + ecs_entity_t type = ecs_get_typeid(world, component); + if (type) { + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + if (ti->hooks.flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) { + return false; + } + } + return true; +} - EcsUnitPrefixes = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = "prefixes", - .add = { EcsModule } - }); +static +int flecs_script_eval_tag( + ecs_script_eval_visitor_t *v, + ecs_script_tag_t *node) +{ + bool resolved = node->id.eval != 0; - /* Initialize unit prefixes */ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } - ecs_entity_t prev_scope = ecs_set_scope(world, EcsUnitPrefixes); + if (node->id.eval == ecs_id(EcsParent)) { + flecs_script_eval_error(v, node, + "Parent component cannot be added as tag (set to valid parent)", + node->id.first, node->id.second); + return -1; + } - EcsYocto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Yocto", - .symbol = "y", - .translation = { .factor = 10, .power = -24 } - }); - EcsZepto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Zepto", - .symbol = "z", - .translation = { .factor = 10, .power = -21 } - }); - EcsAtto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Atto", - .symbol = "a", - .translation = { .factor = 10, .power = -18 } - }); - EcsFemto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Femto", - .symbol = "a", - .translation = { .factor = 10, .power = -15 } - }); - EcsPico = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Pico", - .symbol = "p", - .translation = { .factor = 10, .power = -12 } - }); - EcsNano = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Nano", - .symbol = "n", - .translation = { .factor = 10, .power = -9 } - }); - EcsMicro = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Micro", - .symbol = "μ", - .translation = { .factor = 10, .power = -6 } - }); - EcsMilli = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Milli", - .symbol = "m", - .translation = { .factor = 10, .power = -3 } - }); - EcsCenti = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Centi", - .symbol = "c", - .translation = { .factor = 10, .power = -2 } - }); - EcsDeci = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Deci", - .symbol = "d", - .translation = { .factor = 10, .power = -1 } - }); - EcsDeca = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Deca", - .symbol = "da", - .translation = { .factor = 10, .power = 1 } - }); - EcsHecto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Hecto", - .symbol = "h", - .translation = { .factor = 10, .power = 2 } - }); - EcsKilo = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Kilo", - .symbol = "k", - .translation = { .factor = 10, .power = 3 } - }); - EcsMega = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Mega", - .symbol = "M", - .translation = { .factor = 10, .power = 6 } - }); - EcsGiga = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Giga", - .symbol = "G", - .translation = { .factor = 10, .power = 9 } - }); - EcsTera = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Tera", - .symbol = "T", - .translation = { .factor = 10, .power = 12 } - }); - EcsPeta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Peta", - .symbol = "P", - .translation = { .factor = 10, .power = 15 } - }); - EcsExa = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Exa", - .symbol = "E", - .translation = { .factor = 10, .power = 18 } - }); - EcsZetta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Zetta", - .symbol = "Z", - .translation = { .factor = 10, .power = 21 } - }); - EcsYotta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Yotta", - .symbol = "Y", - .translation = { .factor = 10, .power = 24 } - }); + if (!resolved) { + if (!flecs_script_can_default_ctor(v->world, node->id.eval)) { + if (node->id.second) { + flecs_script_eval_error(v, node, + "cannot add (%s, %s), " + "type is not default constructible", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, + "cannot add %s, " + "type is not default constructible", + node->id.first); + } + return -1; + } + } + + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + + if (!v->entity) { + if (node->id.second) { + flecs_script_eval_error( + v, node, "missing entity for pair (%s, %s)", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, "missing entity for tag %s", + node->id.first); + } + return -1; + } + + ecs_entity_t src = flecs_script_get_src( + v, v->entity->eval, node->id.eval); + ecs_add_id(v->world, src, node->id.eval); + + return 0; +} + +static +int flecs_script_eval_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + bool resolved = node->id.eval != 0; + + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } + + if (!resolved) { + if (!flecs_script_can_default_ctor(v->world, node->id.eval)) { + if (node->id.second) { + flecs_script_eval_error(v, node, + "cannot add (%s, %s), " + "type is not default constructible", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, + "cannot add %s, " + "type is not default constructible", + node->id.first); + } + return -1; + } + } + + if (!v->entity) { + if (node->id.second) { + flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", + node->id.first, node->id.second); + } else { + flecs_script_eval_error(v, node, "missing entity for component %s", + node->id.first); + } + return -1; + } + + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + + ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); + + if (node->expr) { + const ecs_type_info_t *ti = flecs_script_get_type_info( + v, node, node->id.eval); + if (!ti) { + return -1; + } + + ecs_record_t *r = flecs_entities_get(v->world, src); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + + ecs_value_t value = { + .ptr = ecs_ensure_id(v->world, src, node->id.eval, + flecs_ito(size_t, ti->size)), + .type = ti->component + }; + + /* Assign entire value, including members not set by expression. This + * prevents uninitialized or unexpected values. */ + if (r->table != table) { + if (!ti->hooks.ctor) { + ecs_os_memset(value.ptr, 0, ti->size); + } else if (ti->hooks.ctor) { + if (ti->hooks.dtor) { + ti->hooks.dtor(value.ptr, 1, ti); + } + ti->hooks.ctor(value.ptr, 1, ti); + } + } + + if (flecs_script_eval_expr(v, &node->expr, &value)) { + return -1; + } + + ecs_modified_id(v->world, src, node->id.eval); + } else { + ecs_add_id(v->world, src, node->id.eval); + } + + return 0; +} + +static +int flecs_script_eval_var_component( + ecs_script_eval_visitor_t *v, + ecs_script_var_component_t *node) +{ + ecs_script_var_t *var = flecs_script_find_var( + v->vars, node->name, v->dynamic_variable_binding ? NULL : &node->sp); + ecs_value_t var_value = {0}; + if (!var) { + /* If we cannot find local variable, try find as const var */ + ecs_entity_t var_entity = 0; + if (flecs_script_find_entity( + v, 0, node->name, NULL, NULL, &var_entity, NULL)) + { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + + if (var_entity) { + var_value = ecs_const_var_get(v->world, var_entity); + } + + if (!var_value.ptr) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } + } else { + var_value = var->value; + } + + if (v->is_with_scope) { + flecs_script_eval_error(v, node, "invalid component in with scope"); + return -1; + } + + if (!v->entity) { + flecs_script_eval_error(v, node, "missing entity for variable component"); + return -1; + } + + ecs_id_t var_id = var_value.type; + + if (var_value.ptr) { + const ecs_type_info_t *ti = flecs_script_get_type_info( + v, node, var_id); + if (!ti) { + return -1; + } + + ecs_value_t value = { + .ptr = ecs_ensure_id(v->world, v->entity->eval, var_id, + flecs_ito(size_t, ti->size)), + .type = var_id + }; + + ecs_value_copy_w_type_info(v->world, ti, value.ptr, var_value.ptr); + + ecs_modified_id(v->world, v->entity->eval, var_id); + } else { + ecs_add_id(v->world, v->entity->eval, var_id); + } + + return 0; +} + +static +int flecs_script_eval_default_component( + ecs_script_eval_visitor_t *v, + ecs_script_default_component_t *node) +{ + if (!v->entity) { + flecs_script_eval_error(v, node, + "missing entity for default component"); + return -1; + } + + ecs_script_scope_t *scope = ecs_script_current_scope(v); + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(scope->node.kind == EcsAstScope, ECS_INTERNAL_ERROR, NULL); + scope = scope->parent; + + if (!scope) { + flecs_script_eval_error(v, node, + "entity '%s' is in root scope which cannot have a default type", + v->entity->name); + return -1; + } + + ecs_id_t default_type = scope->default_component_eval; + if (!default_type) { + flecs_script_eval_error(v, node, + "scope for entity '%s' does not have a default type", + v->entity->name); + return -1; + } - EcsKibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Kibi", - .symbol = "Ki", - .translation = { .factor = 1024, .power = 1 } - }); - EcsMebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Mebi", - .symbol = "Mi", - .translation = { .factor = 1024, .power = 2 } - }); - EcsGibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Gibi", - .symbol = "Gi", - .translation = { .factor = 1024, .power = 3 } - }); - EcsTebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Tebi", - .symbol = "Ti", - .translation = { .factor = 1024, .power = 4 } - }); - EcsPebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Pebi", - .symbol = "Pi", - .translation = { .factor = 1024, .power = 5 } - }); - EcsExbi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Exbi", - .symbol = "Ei", - .translation = { .factor = 1024, .power = 6 } - }); - EcsZebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Zebi", - .symbol = "Zi", - .translation = { .factor = 1024, .power = 7 } - }); - EcsYobi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) { - .entity.name = "Yobi", - .symbol = "Yi", - .translation = { .factor = 1024, .power = 8 } - }); + const ecs_type_info_t *ti = ecs_get_type_info(v->world, default_type); + if (ti == NULL) { + char *id_str = ecs_id_str(v->world, default_type); + flecs_script_eval_error(v, node, + "cannot use tag '%s' as default type in assignment", + id_str); + ecs_os_free(id_str); + return -1; + } - ecs_set_scope(world, prev_scope); + ecs_value_t value = { + .ptr = ecs_ensure_id(v->world, v->entity->eval, default_type, + flecs_ito(size_t, ti->size)), + .type = default_type + }; - /* Duration units */ + if (flecs_script_eval_expr(v, &node->expr, &value)) { + return -1; + } - EcsDuration = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Duration" }); - prev_scope = ecs_set_scope(world, EcsDuration); + ecs_modified_id(v->world, v->entity->eval, default_type); - EcsSeconds = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Seconds", - .quantity = EcsDuration, - .symbol = "s" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsSeconds, - .kind = EcsF32 - }); - EcsPicoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "PicoSeconds", - .quantity = EcsDuration, - .base = EcsSeconds, - .prefix = EcsPico }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsPicoSeconds, - .kind = EcsF32 - }); + return 0; +} +static +int flecs_script_eval_with_var( + ecs_script_eval_visitor_t *v, + ecs_script_var_component_t *node) +{ + ecs_script_var_t *var = flecs_script_find_var( + v->vars, node->name, v->dynamic_variable_binding ? NULL : &node->sp); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + return -1; + } - EcsNanoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "NanoSeconds", - .quantity = EcsDuration, - .base = EcsSeconds, - .prefix = EcsNano }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsNanoSeconds, - .kind = EcsF32 - }); + ecs_allocator_t *a = &v->r->allocator; + ecs_value_t *value = flecs_script_with_append(a, v, NULL); // TODO: vars of non trivial types + *value = var->value; - EcsMicroSeconds = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MicroSeconds", - .quantity = EcsDuration, - .base = EcsSeconds, - .prefix = EcsMicro }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMicroSeconds, - .kind = EcsF32 - }); + return 0; +} - EcsMilliSeconds = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MilliSeconds", - .quantity = EcsDuration, - .base = EcsSeconds, - .prefix = EcsMilli }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMilliSeconds, - .kind = EcsF32 - }); +static +int flecs_script_eval_with_tag( + ecs_script_eval_visitor_t *v, + ecs_script_tag_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } - EcsMinutes = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Minutes", - .quantity = EcsDuration, - .base = EcsSeconds, - .symbol = "min", - .translation = { .factor = 60, .power = 1 } }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMinutes, - .kind = EcsU32 - }); + ecs_allocator_t *a = &v->r->allocator; + ecs_value_t *value = flecs_script_with_append(a, v, NULL); + value->type = node->id.eval; + value->ptr = NULL; - EcsHours = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Hours", - .quantity = EcsDuration, - .base = EcsMinutes, - .symbol = "h", - .translation = { .factor = 60, .power = 1 } }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsHours, - .kind = EcsU32 - }); + return 0; +} - EcsDays = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Days", - .quantity = EcsDuration, - .base = EcsHours, - .symbol = "d", - .translation = { .factor = 24, .power = 1 } }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsDays, - .kind = EcsU32 - }); - ecs_set_scope(world, prev_scope); +static +int flecs_script_eval_with_component( + ecs_script_eval_visitor_t *v, + ecs_script_component_t *node) +{ + if (flecs_script_eval_id(v, node, &node->id)) { + return -1; + } - /* Time units */ + ecs_allocator_t *a = &v->r->allocator; + const ecs_type_info_t *ti = flecs_script_get_type_info( + v, node, node->id.eval); - EcsTime = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Time" }); - prev_scope = ecs_set_scope(world, EcsTime); + ecs_value_t *value = flecs_script_with_append(a, v, ti); + value->type = node->id.eval; + value->ptr = NULL; - EcsDate = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Date", - .quantity = EcsTime }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsDate, - .kind = EcsU32 - }); - ecs_set_scope(world, prev_scope); + if (node->expr) { + if (!ti) { + return -1; + } - /* Mass units */ + value->ptr = flecs_stack_alloc(&v->r->stack, ti->size, ti->alignment); + value->type = ti->component; // Expression parser needs actual type - EcsMass = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Mass" }); - prev_scope = ecs_set_scope(world, EcsMass); - EcsGrams = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Grams", - .quantity = EcsMass, - .symbol = "g" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsGrams, - .kind = EcsF32 - }); - EcsKiloGrams = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "KiloGrams", - .quantity = EcsMass, - .prefix = EcsKilo, - .base = EcsGrams }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsKiloGrams, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + if (ti->hooks.ctor) { + ti->hooks.ctor(value->ptr, 1, ti); + } - /* Electric current units */ + if (flecs_script_eval_expr(v, &node->expr, value)) { + return -1; + } - EcsElectricCurrent = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "ElectricCurrent" }); - prev_scope = ecs_set_scope(world, EcsElectricCurrent); - EcsAmpere = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Ampere", - .quantity = EcsElectricCurrent, - .symbol = "A" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsAmpere, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + value->type = node->id.eval; // Restore so we're adding actual id + } - /* Amount of substance units */ + return 0; +} - EcsAmount = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Amount" }); - prev_scope = ecs_set_scope(world, EcsAmount); - EcsMole = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Mole", - .quantity = EcsAmount, - .symbol = "mol" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMole, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); +static +int flecs_script_eval_with( + ecs_script_eval_visitor_t *v, + ecs_script_with_t *node) +{ + ecs_allocator_t *a = &v->r->allocator; + int32_t prev_with_count = flecs_script_with_count(v); + ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->r->stack); + int result = 0; - /* Luminous intensity units */ + if (ecs_script_visit_scope(v, node->expressions)) { + result = -1; + goto error; + } - EcsLuminousIntensity = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "LuminousIntensity" }); - prev_scope = ecs_set_scope(world, EcsLuminousIntensity); - EcsCandela = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Candela", - .quantity = EcsLuminousIntensity, - .symbol = "cd" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsCandela, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + ecs_value_t *value = flecs_script_with_last(v); + if (!value->ptr) { + if (ecs_is_valid(v->world, value->type)) { + node->scope->default_component_eval = value->type; + } + } - /* Force units */ + bool old_is_with_scope = v->is_with_scope; + v->is_with_scope = true; - EcsForce = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Force" }); - prev_scope = ecs_set_scope(world, EcsForce); - EcsNewton = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Newton", - .quantity = EcsForce, - .symbol = "N" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsNewton, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + if (ecs_script_visit_scope(v, node->scope)) { + result = -1; + goto error; + } - /* Length units */ + v->is_with_scope = old_is_with_scope; - EcsLength = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Length" }); - prev_scope = ecs_set_scope(world, EcsLength); - EcsMeters = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Meters", - .quantity = EcsLength, - .symbol = "m" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMeters, - .kind = EcsF32 - }); +error: + flecs_script_with_set_count(a, v, prev_with_count); + flecs_stack_restore_cursor(&v->r->stack, prev_stack_cursor); + return result; +} - EcsPicoMeters = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "PicoMeters", - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsPico }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsPicoMeters, - .kind = EcsF32 - }); +int flecs_script_eval_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node) +{ + ecs_allocator_t *a = &v->r->allocator; + int32_t len = ecs_os_strlen(node->name); - EcsNanoMeters = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "NanoMeters", - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsNano }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsNanoMeters, - .kind = EcsF32 - }); + if (len > 2 && !ecs_os_strcmp(&node->name[len - 2], ".*")) { + char *path = flecs_strdup(a, node->name); + path[len - 2] = '\0'; - EcsMicroMeters = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MicroMeters", - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsMicro }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMicroMeters, - .kind = EcsF32 - }); + ecs_entity_t from = ecs_lookup(v->world, path); + if (!from) { + flecs_script_eval_error(v, node, + "unresolved path '%s' in using statement", path); + flecs_strfree(a, path); + return -1; + } - EcsMilliMeters = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MilliMeters", - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsMilli }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMilliMeters, - .kind = EcsF32 - }); + /* Add each child of the scope to using stack */ + ecs_iter_t it = ecs_children(v->world, from); + while (ecs_children_next(&it)) { + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_vec_append_t( + a, &v->r->using, ecs_entity_t)[0] = it.entities[i]; + } + } - EcsCentiMeters = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "CentiMeters", - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsCenti }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsCentiMeters, - .kind = EcsF32 + flecs_strfree(a, path); + } else { + ecs_entity_t from = ecs_lookup_path_w_sep( + v->world, 0, node->name, NULL, NULL, false); + if (!from) { + from = ecs_entity(v->world, { + .name = node->name, + .root_sep = "" }); - EcsKiloMeters = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "KiloMeters", - .quantity = EcsLength, - .base = EcsMeters, - .prefix = EcsKilo }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsKiloMeters, - .kind = EcsF32 - }); - - EcsMiles = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Miles", - .quantity = EcsLength, - .symbol = "mi" - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMiles, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + if (!from) { + return -1; + } + } - /* Pressure units */ + ecs_vec_append_t(a, &v->r->using, ecs_entity_t)[0] = from; + } - EcsPressure = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Pressure" }); - prev_scope = ecs_set_scope(world, EcsPressure); - EcsPascal = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Pascal", - .quantity = EcsPressure, - .symbol = "Pa" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsPascal, - .kind = EcsF32 - }); - EcsBar = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Bar", - .quantity = EcsPressure, - .symbol = "bar" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsBar, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + return 0; +} - /* Speed units */ +static +int flecs_script_eval_module( + ecs_script_eval_visitor_t *v, + ecs_script_module_t *node) +{ + /* Always create modules in root */ + ecs_entity_t old_scope = ecs_set_scope(v->world, 0); + v->parent = 0; - EcsSpeed = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Speed" }); - prev_scope = ecs_set_scope(world, EcsSpeed); - EcsMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MetersPerSecond", - .quantity = EcsSpeed, - .base = EcsMeters, - .over = EcsSeconds }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMetersPerSecond, - .kind = EcsF32 - }); - EcsKiloMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "KiloMetersPerSecond", - .quantity = EcsSpeed, - .base = EcsKiloMeters, - .over = EcsSeconds }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsKiloMetersPerSecond, - .kind = EcsF32 - }); - EcsKiloMetersPerHour = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "KiloMetersPerHour", - .quantity = EcsSpeed, - .base = EcsKiloMeters, - .over = EcsHours }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsKiloMetersPerHour, - .kind = EcsF32 - }); - EcsMilesPerHour = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MilesPerHour", - .quantity = EcsSpeed, - .base = EcsMiles, - .over = EcsHours }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMilesPerHour, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); - - /* Acceleration */ - EcsAcceleration = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Acceleration", - .base = EcsMetersPerSecond, - .over = EcsSeconds }); - ecs_quantity_init(world, &(ecs_entity_desc_t) { - .entity = EcsAcceleration - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsAcceleration, - .kind = EcsF32 - }); + ecs_entity_t m = flecs_script_create_entity(v, node->name); + if (!m) { + return -1; + } - /* Temperature units */ + ecs_set_scope(v->world, old_scope); - EcsTemperature = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Temperature" }); - prev_scope = ecs_set_scope(world, EcsTemperature); - EcsKelvin = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Kelvin", - .quantity = EcsTemperature, - .symbol = "K" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsKelvin, - .kind = EcsF32 - }); - EcsCelsius = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Celsius", - .quantity = EcsTemperature, - .symbol = "°C" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsCelsius, - .kind = EcsF32 - }); - EcsFahrenheit = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Fahrenheit", - .quantity = EcsTemperature, - .symbol = "F" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsFahrenheit, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + ecs_add_id(v->world, m, EcsModule); - /* Data units */ + v->module = m; + v->parent = m; - EcsData = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Data" }); - prev_scope = ecs_set_scope(world, EcsData); + return 0; +} - EcsBits = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Bits", - .quantity = EcsData, - .symbol = "bit" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsBits, - .kind = EcsU64 - }); +int flecs_script_eval_const( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node, + bool export) +{ + /* Declare variable. If this variable is declared while instantiating a + * template, the variable sp has already been resolved in all expressions + * that used it, so we don't need to create the variable with a name. */ + ecs_script_var_t *var = NULL; + if (!export) { + ecs_entity_t e = ecs_lookup_child(v->world, v->parent, node->name); + if (e) { + ecs_value_t existing = ecs_const_var_get(v->world, e); + if (existing.ptr != NULL) { + flecs_script_eval_error(v, node, + "local variable '%s' shadows an exported variable", + node->name); + return -1; + } + } - EcsKiloBits = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "KiloBits", - .quantity = EcsData, - .base = EcsBits, - .prefix = EcsKilo }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsKiloBits, - .kind = EcsU64 - }); + var = ecs_script_vars_declare(v->vars, + v->template_entity ? NULL : node->name); + if (!var) { + flecs_script_eval_error(v, node, + "variable '%s' redeclared", node->name); + return -1; + } + } else { + var = ecs_script_vars_lookup(v->vars, + v->template_entity ? NULL : node->name); + if (var) { + flecs_script_eval_error(v, node, + "exported variable '%s' shadows a local variable", node->name); + return -1; + } + } - EcsMegaBits = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MegaBits", - .quantity = EcsData, - .base = EcsBits, - .prefix = EcsMega }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMegaBits, - .kind = EcsU64 - }); + ecs_entity_t type = 0; + const ecs_type_info_t *ti = NULL; + if (node->expr) { + type = node->expr->type; + ti = node->expr->type_info; + } - EcsGigaBits = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "GigaBits", - .quantity = EcsData, - .base = EcsBits, - .prefix = EcsGiga }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsGigaBits, - .kind = EcsU64 - }); + if (!type && node->type) { + if (flecs_script_find_entity(v, 0, node->type, NULL, NULL, &type, NULL) || !type) { + flecs_script_eval_error(v, node, + "unresolved type '%s' for const variable '%s'", + node->type, node->name); + return -1; + } - EcsBytes = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Bytes", - .quantity = EcsData, - .symbol = "B", - .base = EcsBits, - .translation = { .factor = 8, .power = 1 } }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsBytes, - .kind = EcsU64 - }); + ti = flecs_script_get_type_info(v, node, type); + if (!ti) { + flecs_script_eval_error(v, node, + "failed to retrieve type info for '%s' for const variable '%s'", + node->type, node->name); + return -1; + } + } - EcsKiloBytes = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "KiloBytes", - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsKilo }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsKiloBytes, - .kind = EcsU64 - }); + ecs_value_t result = { + .type = type + }; - EcsMegaBytes = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MegaBytes", - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsMega }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMegaBytes, - .kind = EcsU64 - }); + if (type && ti) { + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - EcsGigaBytes = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "GigaBytes", - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsGiga }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsGigaBytes, - .kind = EcsU64 - }); + result.ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment); - EcsKibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "KibiBytes", - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsKibi }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsKibiBytes, - .kind = EcsU64 - }); + if (ti->hooks.ctor) { + ti->hooks.ctor(result.ptr, 1, ti); + } - EcsMebiBytes = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MebiBytes", - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsMebi }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMebiBytes, - .kind = EcsU64 - }); + if (flecs_script_eval_expr(v, &node->expr, &result)) { + flecs_script_eval_error(v, node, + "failed to evaluate expression for const variable '%s'", + node->name); + return -1; + } + } else { + /* We don't know the type yet, so we can't create a storage for it yet. + * Run the expression first to deduce the type. */ + ecs_value_t value = {0}; + if (flecs_script_eval_expr(v, &node->expr, &value)) { + flecs_script_eval_error(v, node, + "failed to evaluate expression for const variable '%s'", + node->name); + return -1; + } - EcsGibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "GibiBytes", - .quantity = EcsData, - .base = EcsBytes, - .prefix = EcsGibi }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsGibiBytes, - .kind = EcsU64 - }); + ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); + ti = ecs_get_type_info(v->world, value.type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_set_scope(world, prev_scope); + result.ptr = flecs_stack_calloc( + &v->r->stack, ti->size, ti->alignment); + result.type = value.type; - /* DataRate units */ + if (ti->hooks.ctor) { + ti->hooks.ctor(result.ptr, 1, ti); + } - EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "DataRate" }); - prev_scope = ecs_set_scope(world, EcsDataRate); + ecs_value_copy_w_type_info(v->world, ti, result.ptr, value.ptr); + ecs_value_fini_w_type_info(v->world, ti, value.ptr); + flecs_free(&v->world->allocator, ti->size, value.ptr); + } - EcsBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "BitsPerSecond", - .quantity = EcsDataRate, - .base = EcsBits, - .over = EcsSeconds }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsBitsPerSecond, - .kind = EcsU64 + /* If variable resolves to a constant expression, mark it as const so that + * its value can be folded. */ + if (!export) { + if (node->expr->kind == EcsExprValue) { + var->is_const = true; + } + var->type_info = ti; + var->value = result; + } else { + ecs_entity_t const_var = ecs_const_var(v->world, { + .parent = v->parent, + .name = node->name, + .type = result.type, + .value = result.ptr }); - EcsKiloBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "KiloBitsPerSecond", - .quantity = EcsDataRate, - .base = EcsKiloBits, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsKiloBitsPerSecond, - .kind = EcsU64 - }); + /* Clean up value since it'll have been copied into the const var. */ + if (ti->hooks.dtor) { + ti->hooks.dtor(result.ptr, 1, ti); + } - EcsMegaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MegaBitsPerSecond", - .quantity = EcsDataRate, - .base = EcsMegaBits, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMegaBitsPerSecond, - .kind = EcsU64 - }); + flecs_stack_free(result.ptr, ti->size); - EcsGigaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "GigaBitsPerSecond", - .quantity = EcsDataRate, - .base = EcsGigaBits, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsGigaBitsPerSecond, - .kind = EcsU64 - }); + if (!const_var) { + flecs_script_eval_error(v, node, + "failed to create exported const variable '%s'", + node->name); + return -1; + } + } - EcsBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "BytesPerSecond", - .quantity = EcsDataRate, - .base = EcsBytes, - .over = EcsSeconds }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsBytesPerSecond, - .kind = EcsU64 - }); + return 0; +} - EcsKiloBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "KiloBytesPerSecond", - .quantity = EcsDataRate, - .base = EcsKiloBytes, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsKiloBytesPerSecond, - .kind = EcsU64 - }); +static +int flecs_script_eval_pair_scope( + ecs_script_eval_visitor_t *v, + ecs_script_pair_scope_t *node) +{ + ecs_entity_t first; + if (flecs_script_find_entity( + v, 0, node->id.first, &node->id.first_expr, &node->id.first_sp, + &first, NULL) || !first) + { + first = flecs_script_create_entity(v, node->id.first); + if (!first) { + return -1; + } + } - EcsMegaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "MegaBytesPerSecond", - .quantity = EcsDataRate, - .base = EcsMegaBytes, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsMegaBytesPerSecond, - .kind = EcsU64 - }); + ecs_entity_t second = 0; + if (node->id.second) { + if (node->id.second[0] == '$') { + if (flecs_script_find_entity(v, 0, node->id.second, + &node->id.second_expr, &node->id.second_sp, &second, NULL)) + { + return -1; + } + } else { + second = flecs_script_create_entity(v, node->id.second); + } + + } + + if (!second) { + return -1; + } - EcsGigaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "GigaBytesPerSecond", - .quantity = EcsDataRate, - .base = EcsGigaBytes, - .over = EcsSeconds - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsGigaBytesPerSecond, - .kind = EcsU64 - }); + ecs_allocator_t *a = &v->r->allocator; + ecs_entity_t prev_first = v->with_relationship; + ecs_entity_t prev_second = 0; + int32_t prev_with_relationship_sp = v->with_relationship_sp; - ecs_set_scope(world, prev_scope); + v->with_relationship = first; - /* Percentage */ + if (prev_first != first) { + /* Append new element to with stack */ + ecs_value_t *value = flecs_script_with_append(a, v, NULL); + value->type = ecs_pair(first, second); + value->ptr = NULL; + v->with_relationship_sp = flecs_script_with_count(v) - 1; + } else { + /* Get existing with element for current relationhip stack */ + ecs_value_t *value = ecs_vec_get_t( + &v->r->with, ecs_value_t, v->with_relationship_sp); + ecs_assert(ECS_PAIR_FIRST(value->type) == (uint32_t)first, + ECS_INTERNAL_ERROR, NULL); + prev_second = ECS_PAIR_SECOND(value->type); + value->type = ecs_pair(first, second); + value->ptr = NULL; + } - EcsPercentage = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Percentage" }); - ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.entity = EcsPercentage, - .symbol = "%" - }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsPercentage, - .kind = EcsF32 - }); + if (ecs_script_visit_scope(v, node->scope)) { + return -1; + } - /* Angles */ + if (prev_second) { + ecs_value_t *value = ecs_vec_get_t( + &v->r->with, ecs_value_t, v->with_relationship_sp); + value->type = ecs_pair(first, prev_second); + } else { + flecs_script_with_set_count(a, v, v->with_relationship_sp); + } - EcsAngle = ecs_quantity_init(world, &(ecs_entity_desc_t) { - .name = "Angle" }); - prev_scope = ecs_set_scope(world, EcsAngle); - EcsRadians = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Radians", - .quantity = EcsAngle, - .symbol = "rad" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsRadians, - .kind = EcsF32 - }); + v->with_relationship = prev_first; + v->with_relationship_sp = prev_with_relationship_sp; - EcsDegrees = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Degrees", - .quantity = EcsAngle, - .symbol = "°" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsDegrees, - .kind = EcsF32 - }); - ecs_set_scope(world, prev_scope); + return 0; +} + +static +int flecs_script_eval_if( + ecs_script_eval_visitor_t *v, + ecs_script_if_t *node) +{ + ecs_value_t condval = { .type = 0, .ptr = NULL }; + if (flecs_script_eval_expr(v, &node->expr, &condval)) { + return -1; + } + + bool cond; + if (condval.type == ecs_id(ecs_bool_t)) { + cond = *(bool*)(condval.ptr); + } else { + ecs_meta_cursor_t cur = ecs_meta_cursor( + v->world, condval.type, condval.ptr); + cond = ecs_meta_get_bool(&cur); + } + + ecs_value_free(v->world, condval.type, condval.ptr); + + if (flecs_script_eval_scope(v, cond ? node->if_true : node->if_false)) { + return -1; + } + + return 0; +} + +static +int flecs_script_eval_for_range( + ecs_script_eval_visitor_t *v, + ecs_script_for_range_t *node) +{ + int32_t from; + int32_t to; + ecs_value_t from_val = { .type = ecs_id(ecs_i32_t), .ptr = &from }; + ecs_value_t to_val = { .type = ecs_id(ecs_i32_t), .ptr = &to }; + + if (flecs_script_eval_expr(v, &node->from, &from_val)) { + return -1; + } + + if (flecs_script_eval_expr(v, &node->to, &to_val)) { + return -1; + } + + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); + + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); + var->value.ptr = flecs_stack_calloc(&v->r->stack, 4, 4); + var->value.type = ecs_id(ecs_i32_t); + var->type_info = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); + + int32_t i; + for (i = from; i < to; i ++) { + *(int32_t*)var->value.ptr = i; + if (flecs_script_eval_scope(v, node->scope)) { + v->vars = ecs_script_vars_pop(v->vars); + return -1; + } + } + + v->vars = ecs_script_vars_pop(v->vars); + + return 0; +} - /* DeciBel */ +static +int flecs_script_eval_annot( + ecs_script_eval_visitor_t *v, + ecs_script_annot_t *node) +{ + if (!v->base.next) { + flecs_script_eval_error(v, node, + "missing target for @%s annotation", node->name); + return -1; + } + + ecs_script_node_kind_t next_kind = v->base.next->kind; + if (next_kind != EcsAstEntity && next_kind != EcsAstTemplate) { + if (next_kind != EcsAstAnnotation) { + flecs_script_eval_error(v, node, + "target of @%s annotation must be an entity or template", + node->name); + return -1; + } + } - EcsBel = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "Bel", - .symbol = "B" }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsBel, - .kind = EcsF32 - }); - EcsDeciBel = ecs_unit_init(world, &(ecs_unit_desc_t) { - .entity.name = "DeciBel", - .prefix = EcsDeci, - .base = EcsBel }); - ecs_primitive_init(world, &(ecs_primitive_desc_t) { - .entity.entity = EcsDeciBel, - .kind = EcsF32 - }); + ecs_allocator_t *a = &v->r->allocator; + ecs_vec_append_t(a, &v->r->annot, ecs_script_annot_t*)[0] = node; - /* Documentation */ -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); + return 0; +} - ecs_doc_set_brief(world, EcsDuration, - "Time amount (e.g. \"20 seconds\", \"2 hours\")"); - ecs_doc_set_brief(world, EcsSeconds, "Time amount in seconds"); - ecs_doc_set_brief(world, EcsMinutes, "60 seconds"); - ecs_doc_set_brief(world, EcsHours, "60 minutes"); - ecs_doc_set_brief(world, EcsDays, "24 hours"); +int flecs_script_eval_node( + ecs_script_visit_t *_v, + ecs_script_node_t *node) +{ + ecs_script_eval_visitor_t *v = (ecs_script_eval_visitor_t*)_v; + ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v->template == NULL, ECS_INTERNAL_ERROR, NULL); + + switch(node->kind) { + case EcsAstScope: + return flecs_script_eval_scope( + v, (ecs_script_scope_t*)node); + case EcsAstTag: + return flecs_script_eval_tag( + v, (ecs_script_tag_t*)node); + case EcsAstComponent: + return flecs_script_eval_component( + v, (ecs_script_component_t*)node); + case EcsAstVarComponent: + return flecs_script_eval_var_component( + v, (ecs_script_var_component_t*)node); + case EcsAstDefaultComponent: + return flecs_script_eval_default_component( + v, (ecs_script_default_component_t*)node); + case EcsAstWithVar: + return flecs_script_eval_with_var( + v, (ecs_script_var_component_t*)node); + case EcsAstWithTag: + return flecs_script_eval_with_tag( + v, (ecs_script_tag_t*)node); + case EcsAstWithComponent: + return flecs_script_eval_with_component( + v, (ecs_script_component_t*)node); + case EcsAstWith: + return flecs_script_eval_with( + v, (ecs_script_with_t*)node); + case EcsAstUsing: + return flecs_script_eval_using( + v, (ecs_script_using_t*)node); + case EcsAstModule: + return flecs_script_eval_module( + v, (ecs_script_module_t*)node); + case EcsAstAnnotation: + return flecs_script_eval_annot( + v, (ecs_script_annot_t*)node); + case EcsAstTemplate: + return flecs_script_eval_template( + v, (ecs_script_template_node_t*)node); + case EcsAstProp: + return 0; + case EcsAstConst: + return flecs_script_eval_const( + v, (ecs_script_var_node_t*)node, false); + case EcsAstExportConst: + return flecs_script_eval_const( + v, (ecs_script_var_node_t*)node, true); + case EcsAstEntity: + return flecs_script_eval_entity( + v, (ecs_script_entity_t*)node); + case EcsAstPairScope: + return flecs_script_eval_pair_scope( + v, (ecs_script_pair_scope_t*)node); + case EcsAstIf: + return flecs_script_eval_if( + v, (ecs_script_if_t*)node); + case EcsAstFor: + return flecs_script_eval_for_range( + v, (ecs_script_for_range_t*)node); + } + + ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); +} + +void flecs_script_eval_visit_init( + const ecs_script_impl_t *script, + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc) +{ + *v = (ecs_script_eval_visitor_t){ + .base = { + .visit = (ecs_visit_action_t)flecs_script_eval_node, + .script = ECS_CONST_CAST(ecs_script_impl_t*, script) + }, + .world = script->pub.world, + .r = desc ? desc->runtime : NULL + }; - ecs_doc_set_brief(world, EcsTime, - "Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")"); - ecs_doc_set_brief(world, EcsDate, - "Seconds passed since January 1st 1970"); + if (!v->r) { + v->r = ecs_script_runtime_new(); + } - ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); + if (desc && desc->vars) { + ecs_allocator_t *a = &v->r->allocator; + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); + v->vars->parent = desc->vars; + v->vars->sp = ecs_vec_count(&desc->vars->vars); - ecs_doc_set_brief(world, EcsElectricCurrent, - "Units of electrical current (e.g. \"2 ampere\")"); + /* When variables are provided to script, don't use cached variable + * stack pointers, as the order in which the application provides + * variables may not be the same across evaluations. */ + v->dynamic_variable_binding = true; + } - ecs_doc_set_brief(world, EcsAmount, - "Units of amount of substance (e.g. \"2 mole\")"); + /* Always include flecs.meta */ + ecs_vec_append_t(&v->r->allocator, &v->r->using, ecs_entity_t)[0] = + ecs_lookup(v->world, "flecs.meta"); +} - ecs_doc_set_brief(world, EcsLuminousIntensity, - "Units of luminous intensity (e.g. \"1 candela\")"); +void flecs_script_eval_visit_fini( + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc) +{ + if (desc && desc->vars) { + v->vars = ecs_script_vars_pop(v->vars); + } - ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); + if (!desc || (v->r != desc->runtime)) { + ecs_script_runtime_free(v->r); + } +} - ecs_doc_set_brief(world, EcsLength, - "Units of length (e.g. \"5 meters\", \"20 miles\")"); +int ecs_script_eval( + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc, + ecs_script_eval_result_t *result) +{ + ecs_script_eval_visitor_t v; + ecs_script_impl_t *impl = flecs_script_impl( + /* Safe, script will only be used for reading by visitor */ + ECS_CONST_CAST(ecs_script_t*, script)); - ecs_doc_set_brief(world, EcsPressure, - "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); + ecs_script_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } - ecs_doc_set_brief(world, EcsSpeed, - "Units of movement (e.g. \"5 meters/second\")"); + if (!priv_desc.runtime) { + priv_desc.runtime = flecs_script_runtime_get(script->world); + } - ecs_doc_set_brief(world, EcsAcceleration, - "Unit of speed increase (e.g. \"5 meters/second/second\")"); + if (result) { + ecs_log_start_capture(true); + } - ecs_doc_set_brief(world, EcsTemperature, - "Units of temperature (e.g. \"5 degrees Celsius\")"); + flecs_script_eval_visit_init(impl, &v, &priv_desc); + int r = ecs_script_visit(impl, &v, flecs_script_eval_node); + flecs_script_eval_visit_fini(&v, &priv_desc); - ecs_doc_set_brief(world, EcsData, - "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); + // printf("%s\n", ecs_script_ast_to_str(script, true)); - ecs_doc_set_brief(world, EcsDataRate, - "Units of data transmission (e.g. \"100 megabits/second\")"); + if (result) { + result->error = ecs_log_stop_capture(); + } - ecs_doc_set_brief(world, EcsAngle, - "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); + if (r) { + ecs_script_runtime_clear(priv_desc.runtime); + } -#endif + return r; } #endif - -#ifdef FLECS_SNAPSHOT +/** + * @file addons/script/visit_free.c + * @brief Script free visitor (frees AST resources). + */ -/* World snapshot */ -struct ecs_snapshot_t { - ecs_world_t *world; - ecs_sparse_t *entity_index; - ecs_vector_t *tables; - ecs_entity_t last_id; - ecs_filter_t filter; -}; +#ifdef FLECS_SCRIPT -/** Small footprint data structure for storing data associated with a table. */ -typedef struct ecs_table_leaf_t { - ecs_table_t *table; - ecs_vector_t *type; - ecs_data_t *data; -} ecs_table_leaf_t; +static +void flecs_script_scope_free( + ecs_script_visit_t *v, + ecs_script_scope_t *node) +{ + ecs_script_visit_scope(v, node); + ecs_vec_fini_t(&v->script->allocator, &node->stmts, ecs_script_node_t*); + ecs_vec_fini_t(&v->script->allocator, &node->components, ecs_id_t); + flecs_free_t(&v->script->allocator, ecs_script_scope_t, node); +} static -ecs_data_t* duplicate_data( - ecs_table_t *table, - ecs_data_t *main_data) +void flecs_script_id_free( + ecs_script_visit_t *v, + ecs_script_id_t *id) { - if (!ecs_table_count(table)) { - return NULL; + if (id->first_expr) { + flecs_expr_visit_free(&v->script->pub, id->first_expr); } + if (id->second_expr) { + flecs_expr_visit_free(&v->script->pub, id->second_expr); + } +} - ecs_data_t *result = ecs_os_calloc(ECS_SIZEOF(ecs_data_t)); - int32_t i, column_count = table->storage_count; - result->columns = ecs_os_memdup_n( - main_data->columns, ecs_column_t, column_count); - - /* Copy entities */ - result->entities = ecs_vector_copy(main_data->entities, ecs_entity_t); - - /* Copy record ptrs */ - result->record_ptrs = ecs_vector_copy( - main_data->record_ptrs, ecs_record_t*); +static +void flecs_script_with_free( + ecs_script_visit_t *v, + ecs_script_with_t *node) +{ + flecs_script_scope_free(v, node->expressions); + flecs_script_scope_free(v, node->scope); +} - ecs_size_t to_alloc = ecs_vector_size(result->entities); +static +void flecs_script_template_free( + ecs_script_visit_t *v, + ecs_script_template_node_t *node) +{ + flecs_script_scope_free(v, node->scope); +} - /* Copy each column */ - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &result->columns[i]; - ecs_type_info_t *ti = &table->type_info[i]; - int32_t size = ti->size; - int32_t alignment = ti->alignment; - ecs_copy_t copy = ti->lifecycle.copy; - if (copy) { - int32_t count = ecs_vector_count(column->data); - ecs_vector_t *dst_vec = ecs_vector_new_t(size, alignment, to_alloc); - ecs_vector_set_count_t(&dst_vec, size, alignment, count); - void *dst_ptr = ecs_vector_first_t(dst_vec, size, alignment); - - ecs_xtor_t ctor = ti->lifecycle.ctor; - if (ctor) { - ctor(dst_ptr, count, ti); - } +static +void flecs_script_entity_free( + ecs_script_visit_t *v, + ecs_script_entity_t *node) +{ + flecs_script_scope_free(v, node->scope); + if (node->name_expr) { + flecs_expr_visit_free(&v->script->pub, node->name_expr); + } +} - void *src_ptr = ecs_vector_first_t(column->data, size, alignment); - copy(dst_ptr, src_ptr, count, ti); +static +void flecs_script_pair_scope_free( + ecs_script_visit_t *v, + ecs_script_pair_scope_t *node) +{ + flecs_script_scope_free(v, node->scope); + flecs_script_id_free(v, &node->id); +} - column->data = dst_vec; - } else { - column->data = ecs_vector_copy_t(column->data, size, alignment); - } - } +static +void flecs_script_if_free( + ecs_script_visit_t *v, + ecs_script_if_t *node) +{ + flecs_script_scope_free(v, node->if_true); + flecs_script_scope_free(v, node->if_false); + flecs_expr_visit_free(&v->script->pub, node->expr); +} - return result; +static +void flecs_script_for_range_free( + ecs_script_visit_t *v, + ecs_script_for_range_t *node) +{ + flecs_expr_visit_free(&v->script->pub, node->from); + flecs_expr_visit_free(&v->script->pub, node->to); + flecs_script_scope_free(v, node->scope); } static -void snapshot_table( - ecs_snapshot_t *snapshot, - ecs_table_t *table) +void flecs_script_tag_free( + ecs_script_visit_t *v, + ecs_script_tag_t *node) { - if (table->flags & EcsTableHasBuiltins) { - return; - } - - ecs_table_leaf_t *l = ecs_vector_get( - snapshot->tables, ecs_table_leaf_t, (int32_t)table->id); - ecs_assert(l != NULL, ECS_INTERNAL_ERROR, NULL); - - l->table = table; - l->type = ecs_vector_copy(table->type, ecs_id_t); - l->data = duplicate_data(table, &table->storage); + flecs_script_id_free(v, &node->id); } static -ecs_snapshot_t* snapshot_create( - const ecs_world_t *world, - const ecs_sparse_t *entity_index, - ecs_iter_t *iter, - ecs_iter_next_action_t next) +void flecs_script_component_free( + ecs_script_visit_t *v, + ecs_script_component_t *node) { - ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); + flecs_expr_visit_free(&v->script->pub, node->expr); + flecs_script_id_free(v, &node->id); +} - ecs_force_aperiodic((ecs_world_t*)world); +static +void flecs_script_default_component_free( + ecs_script_visit_t *v, + ecs_script_default_component_t *node) +{ + flecs_expr_visit_free(&v->script->pub, node->expr); +} - result->world = (ecs_world_t*)world; +static +void flecs_script_var_node_free( + ecs_script_visit_t *v, + ecs_script_var_node_t *node) +{ + flecs_expr_visit_free(&v->script->pub, node->expr); +} - /* If no iterator is provided, the snapshot will be taken of the entire - * world, and we can simply copy the entity index as it will be restored - * entirely upon snapshote restore. */ - if (!iter && entity_index) { - result->entity_index = flecs_sparse_copy(entity_index); +static +int flecs_script_stmt_free( + ecs_script_visit_t *v, + ecs_script_node_t *node) +{ + ecs_allocator_t *a = &v->script->allocator; + switch(node->kind) { + case EcsAstScope: + flecs_script_scope_free(v, (ecs_script_scope_t*)node); + break; + case EcsAstWith: + flecs_script_with_free(v, (ecs_script_with_t*)node); + flecs_free_t(a, ecs_script_with_t, node); + break; + case EcsAstTemplate: + flecs_script_template_free(v, (ecs_script_template_node_t*)node); + flecs_free_t(a, ecs_script_template_node_t, node); + break; + case EcsAstEntity: + flecs_script_entity_free(v, (ecs_script_entity_t*)node); + flecs_free_t(a, ecs_script_entity_t, node); + break; + case EcsAstPairScope: + flecs_script_pair_scope_free(v, (ecs_script_pair_scope_t*)node); + flecs_free_t(a, ecs_script_pair_scope_t, node); + break; + case EcsAstIf: + flecs_script_if_free(v, (ecs_script_if_t*)node); + flecs_free_t(a, ecs_script_if_t, node); + break; + case EcsAstFor: + flecs_script_for_range_free(v, (ecs_script_for_range_t*)node); + flecs_free_t(a, ecs_script_for_range_t, node); + break; + case EcsAstTag: + flecs_script_tag_free(v, (ecs_script_tag_t*)node); + flecs_free_t(a, ecs_script_tag_t, node); + break; + case EcsAstComponent: + case EcsAstWithComponent: + flecs_script_component_free(v, (ecs_script_component_t*)node); + flecs_free_t(a, ecs_script_component_t, node); + break; + case EcsAstDefaultComponent: + flecs_script_default_component_free(v, + (ecs_script_default_component_t*)node); + flecs_free_t(a, ecs_script_default_component_t, node); + break; + case EcsAstVarComponent: + flecs_free_t(a, ecs_script_var_component_t, node); + break; + case EcsAstWithVar: + flecs_free_t(a, ecs_script_var_component_t, node); + break; + case EcsAstWithTag: + flecs_free_t(a, ecs_script_tag_t, node); + break; + case EcsAstUsing: + flecs_free_t(a, ecs_script_using_t, node); + break; + case EcsAstModule: + flecs_free_t(a, ecs_script_module_t, node); + break; + case EcsAstAnnotation: + flecs_free_t(a, ecs_script_annot_t, node); + break; + case EcsAstProp: + case EcsAstConst: + case EcsAstExportConst: + flecs_script_var_node_free(v, (ecs_script_var_node_t*)node); + flecs_free_t(a, ecs_script_var_node_t, node); + break; } - /* Create vector with as many elements as tables, so we can store the - * snapshot tables at their element ids. When restoring a snapshot, the code - * will run a diff between the tables in the world and the snapshot, to see - * which of the world tables still exist, no longer exist, or need to be - * deleted. */ - uint64_t t, table_count = flecs_sparse_last_id(&world->store.tables) + 1; - result->tables = ecs_vector_new(ecs_table_leaf_t, (int32_t)table_count); - ecs_vector_set_count(&result->tables, ecs_table_leaf_t, (int32_t)table_count); - ecs_table_leaf_t *arr = ecs_vector_first(result->tables, ecs_table_leaf_t); + return 0; +} - /* Array may have holes, so initialize with 0 */ - ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count); +int flecs_script_visit_free_node( + ecs_script_t *script, + ecs_script_node_t *node) +{ + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_impl_t *impl = flecs_script_impl(script); - /* Iterate tables in iterator */ - if (iter) { - while (next(iter)) { - ecs_table_t *table = iter->table; - snapshot_table(result, table); - } - } else { - for (t = 0; t < table_count; t ++) { - ecs_table_t *table = flecs_sparse_get( - &world->store.tables, ecs_table_t, t); - snapshot_table(result, table); - } + ecs_script_visit_t v = { + .script = impl + }; + + if (ecs_script_visit_from( + flecs_script_impl(script), &v, flecs_script_stmt_free, node, 0)) + { + goto error; } - return result; + return 0; +error: + return -1; } -/** Create a snapshot */ -ecs_snapshot_t* ecs_snapshot_take( - ecs_world_t *stage) +int flecs_script_visit_free( + ecs_script_t *script) { - const ecs_world_t *world = ecs_get_world(stage); + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_impl_t *impl = flecs_script_impl(script); + if (!impl->expr && !impl->root) { + return 0; + } - ecs_snapshot_t *result = snapshot_create( - world, ecs_eis(world), NULL, NULL); + ecs_script_visit_t v = { + .script = impl + }; - result->last_id = world->info.last_id; + if (ecs_script_visit( + flecs_script_impl(script), &v, flecs_script_stmt_free)) + { + goto error; + } - return result; + return 0; +error: + return - 1; } -/** Create a filtered snapshot */ -ecs_snapshot_t* ecs_snapshot_take_w_iter( - ecs_iter_t *iter) -{ - ecs_world_t *world = iter->world; - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); +#endif - ecs_snapshot_t *result = snapshot_create( - world, ecs_eis(world), iter, iter ? iter->next : NULL); +/** + * @file addons/script/visit_to_str.c + * @brief Script AST to string visitor. + */ - result->last_id = world->info.last_id; - return result; +#ifdef FLECS_SCRIPT + +typedef struct ecs_script_str_visitor_t { + ecs_script_visit_t base; + ecs_strbuf_t *buf; + int32_t depth; + bool newline; + bool colors; +} ecs_script_str_visitor_t; + +static +int flecs_script_scope_to_str( + ecs_script_str_visitor_t *v, + ecs_script_scope_t *scope); + +static +void flecs_script_color_to_str( + ecs_script_str_visitor_t *v, + const char *color) +{ + if (v->colors) ecs_strbuf_appendstr(v->buf, color); } -/* Restoring an unfiltered snapshot restores the world to the exact state it was - * when the snapshot was taken. */ static -void restore_unfiltered( - ecs_world_t *world, - ecs_snapshot_t *snapshot) +void flecs_scriptbuf_append( + ecs_script_str_visitor_t *v, + const char *fmt, + ...) { - flecs_sparse_restore(ecs_eis(world), snapshot->entity_index); - flecs_sparse_free(snapshot->entity_index); - - world->info.last_id = snapshot->last_id; + if (v->newline) { + ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); + v->newline = false; + } - ecs_table_leaf_t *leafs = ecs_vector_first( - snapshot->tables, ecs_table_leaf_t); - int32_t i, count = (int32_t)flecs_sparse_last_id(&world->store.tables); - int32_t snapshot_count = ecs_vector_count(snapshot->tables); + va_list args; + va_start(args, fmt); + ecs_strbuf_vappend(v->buf, fmt, args); + va_end(args); - for (i = 0; i <= count; i ++) { - ecs_table_t *world_table = flecs_sparse_get( - &world->store.tables, ecs_table_t, (uint32_t)i); + if (fmt[strlen(fmt) - 1] == '\n') { + v->newline = true; + } +} - if (world_table && (world_table->flags & EcsTableHasBuiltins)) { - continue; - } +static +void flecs_scriptbuf_appendstr( + ecs_script_str_visitor_t *v, + const char *str) +{ + if (v->newline) { + ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); + v->newline = false; + } - ecs_table_leaf_t *snapshot_table = NULL; - if (i < snapshot_count) { - snapshot_table = &leafs[i]; - if (!snapshot_table->table) { - snapshot_table = NULL; - } - } + ecs_strbuf_appendstr(v->buf, str); - /* If the world table no longer exists but the snapshot table does, - * reinsert it */ - if (!world_table && snapshot_table) { - ecs_ids_t type = { - .array = ecs_vector_first(snapshot_table->type, ecs_id_t), - .count = ecs_vector_count(snapshot_table->type) - }; + if (str[strlen(str) - 1] == '\n') { + v->newline = true; + } +} - ecs_table_t *table = flecs_table_find_or_create(world, &type); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); +static +void flecs_script_id_to_str( + ecs_script_str_visitor_t *v, + ecs_script_id_t *id) +{ + if (id->flag) { + if (id->flag == ECS_AUTO_OVERRIDE) { + flecs_scriptbuf_appendstr(v, "auto_override | "); + } else { + flecs_scriptbuf_appendstr(v, "??? | "); + } + } - if (snapshot_table->data) { - flecs_table_replace_data(world, table, snapshot_table->data); - } - - /* If the world table still exists, replace its data */ - } else if (world_table && snapshot_table) { - ecs_assert(snapshot_table->table == world_table, - ECS_INTERNAL_ERROR, NULL); + if (id->second) { + flecs_scriptbuf_append(v, "(%s, %s)", + id->first, id->second); + } else { + flecs_scriptbuf_appendstr(v, id->first); + } +} - if (snapshot_table->data) { - flecs_table_replace_data( - world, world_table, snapshot_table->data); - } else { - flecs_table_clear_data( - world, world_table, &world_table->storage); - flecs_table_init_data(world, world_table); - } - - /* If the snapshot table doesn't exist, this table was created after the - * snapshot was taken and needs to be deleted */ - } else if (world_table && !snapshot_table) { - /* Deleting a table invokes OnRemove triggers & updates the entity - * index. That is not what we want, since entities may no longer be - * valid (if they don't exist in the snapshot) or may have been - * restored in a different table. Therefore first clear the data - * from the table (which doesn't invoke triggers), and then delete - * the table. */ - flecs_table_clear_data(world, world_table, &world_table->storage); - flecs_delete_table(world, world_table); - - /* If there is no world & snapshot table, nothing needs to be done */ - } else { } +static +void flecs_expr_to_str( + ecs_script_str_visitor_t *v, + const ecs_expr_node_t *expr) +{ + if (expr) { + flecs_expr_to_str_buf( + &v->base.script->pub, expr, v->buf, v->colors); + } else { + flecs_scriptbuf_appendstr(v, "{}"); + } +} - if (snapshot_table) { - ecs_os_free(snapshot_table->data); - ecs_os_free(snapshot_table->type); - } +static +const char* flecs_script_node_to_str( + ecs_script_node_t *node) +{ + switch(node->kind) { + case EcsAstScope: return "scope"; + case EcsAstWithTag: + case EcsAstTag: return "tag"; + case EcsAstWithComponent: + case EcsAstComponent: return "component"; + case EcsAstWithVar: + case EcsAstVarComponent: return "var"; + case EcsAstDefaultComponent: return "default_component"; + case EcsAstWith: return "with"; + case EcsAstUsing: return "using"; + case EcsAstModule: return "module"; + case EcsAstAnnotation: return "annot"; + case EcsAstTemplate: return "template"; + case EcsAstProp: return "prop"; + case EcsAstConst: return "const"; + case EcsAstExportConst: return "export const"; + case EcsAstEntity: return "entity"; + case EcsAstPairScope: return "pair_scope"; + case EcsAstIf: return "if"; + case EcsAstFor: return "for"; } + return "???"; +} - /* Now that all tables have been restored and world is in a consistent - * state, run OnSet systems */ - int32_t world_count = flecs_sparse_count(&world->store.tables); - for (i = 0; i < world_count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense( - &world->store.tables, ecs_table_t, i); - if (table->flags & EcsTableHasBuiltins) { - continue; - } +static +void flecs_scriptbuf_node( + ecs_script_str_visitor_t *v, + ecs_script_node_t *node) +{ + flecs_script_color_to_str(v, ECS_BLUE); + flecs_scriptbuf_append(v, "%s: ", flecs_script_node_to_str(node)); + flecs_script_color_to_str(v, ECS_NORMAL); +} - int32_t tcount = ecs_table_count(table); - if (tcount) { - flecs_notify_on_set(world, table, 0, tcount, NULL, true); - } - } +static +void flecs_script_tag_to_str( + ecs_script_str_visitor_t *v, + ecs_script_tag_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_script_id_to_str(v, &node->id); + flecs_scriptbuf_appendstr(v, "\n"); } -/* Restoring a filtered snapshots only restores the entities in the snapshot - * to their previous state. */ static -void restore_filtered( - ecs_world_t *world, - ecs_snapshot_t *snapshot) +void flecs_script_component_to_str( + ecs_script_str_visitor_t *v, + ecs_script_component_t *node) { - ecs_table_leaf_t *leafs = ecs_vector_first( - snapshot->tables, ecs_table_leaf_t); - int32_t l = 0, snapshot_count = ecs_vector_count(snapshot->tables); + flecs_scriptbuf_node(v, &node->node); + flecs_script_id_to_str(v, &node->id); + if (node->expr) { + flecs_scriptbuf_appendstr(v, ": "); + flecs_expr_to_str(v, node->expr); + } + flecs_scriptbuf_appendstr(v, "\n"); +} - for (l = 0; l < snapshot_count; l ++) { - ecs_table_leaf_t *snapshot_table = &leafs[l]; - ecs_table_t *table = snapshot_table->table; +static +void flecs_script_default_component_to_str( + ecs_script_str_visitor_t *v, + ecs_script_default_component_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + if (node->expr) { + flecs_expr_to_str(v, node->expr); + } + flecs_scriptbuf_appendstr(v, "\n"); +} - if (!table) { - continue; - } +static +void flecs_script_with_var_to_str( + ecs_script_str_visitor_t *v, + ecs_script_var_component_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s ", node->name); + flecs_scriptbuf_appendstr(v, "\n"); +} - ecs_data_t *data = snapshot_table->data; - if (!data) { - ecs_vector_free(snapshot_table->type); - continue; - } +static +void flecs_script_with_to_str( + ecs_script_str_visitor_t *v, + ecs_script_with_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + + flecs_scriptbuf_appendstr(v, "{\n"); + v->depth ++; + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_appendstr(v, "expressions: "); + flecs_script_color_to_str(v, ECS_NORMAL); + flecs_script_scope_to_str(v, node->expressions); + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_append(v, "scope: "); + flecs_script_color_to_str(v, ECS_NORMAL); + flecs_script_scope_to_str(v, node->scope); + v->depth --; + flecs_scriptbuf_appendstr(v, "}\n"); +} - /* Delete entity from storage first, so that when we restore it to the - * current table we can be sure that there won't be any duplicates */ - int32_t i, entity_count = ecs_vector_count(data->entities); - ecs_entity_t *entities = ecs_vector_first( - snapshot_table->data->entities, ecs_entity_t); - for (i = 0; i < entity_count; i ++) { - ecs_entity_t e = entities[i]; - ecs_record_t *r = ecs_eis_get(world, e); - if (r && r->table) { - flecs_table_delete(world, r->table, &r->table->storage, - ECS_RECORD_TO_ROW(r->row), true); - } else { - /* Make sure that the entity has the same generation count */ - ecs_eis_set_generation(world, e); - } - } +static +void flecs_script_using_to_str( + ecs_script_str_visitor_t *v, + ecs_script_using_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s\n", node->name); +} - /* Merge data from snapshot table with world table */ - int32_t old_count = ecs_table_count(snapshot_table->table); - int32_t new_count = flecs_table_data_count(snapshot_table->data); +static +void flecs_script_module_to_str( + ecs_script_str_visitor_t *v, + ecs_script_module_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s\n", node->name); +} - flecs_table_merge(world, table, table, &table->storage, snapshot_table->data); +static +void flecs_script_annot_to_str( + ecs_script_str_visitor_t *v, + ecs_script_annot_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_script_color_to_str(v, ECS_GREEN); + flecs_scriptbuf_append(v, "%s = \"%s\"", node->name, node->expr); + flecs_script_color_to_str(v, ECS_NORMAL); + flecs_scriptbuf_appendstr(v, "\n"); +} - /* Run OnSet systems for merged entities */ - if (new_count) { - flecs_notify_on_set( - world, table, old_count, new_count, NULL, true); - } +static +void flecs_script_template_to_str( + ecs_script_str_visitor_t *v, + ecs_script_template_node_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_append(v, "%s ", node->name); + flecs_script_scope_to_str(v, node->scope); +} - ecs_os_free(snapshot_table->data->columns); - ecs_os_free(snapshot_table->data); - ecs_vector_free(snapshot_table->type); +static +void flecs_script_var_node_to_str( + ecs_script_str_visitor_t *v, + ecs_script_var_node_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + if (node->type) { + flecs_scriptbuf_append(v, "%s : %s = ", + node->name, + node->type); + } else { + flecs_scriptbuf_append(v, "%s = ", + node->name); } + flecs_expr_to_str(v, node->expr); + flecs_scriptbuf_appendstr(v, "\n"); } -/** Restore a snapshot */ -void ecs_snapshot_restore( - ecs_world_t *world, - ecs_snapshot_t *snapshot) +static +void flecs_script_entity_to_str( + ecs_script_str_visitor_t *v, + ecs_script_entity_t *node) { - ecs_force_aperiodic(world); - - if (snapshot->entity_index) { - /* Unfiltered snapshots have a copy of the entity index which is - * copied back entirely when the snapshot is restored */ - restore_unfiltered(world, snapshot); + flecs_scriptbuf_node(v, &node->node); + if (node->kind) { + flecs_scriptbuf_append(v, "%s ", node->kind); + } + if (node->name) { + flecs_scriptbuf_append(v, "%s ", node->name); } else { - restore_filtered(world, snapshot); + flecs_scriptbuf_appendstr(v, " "); } - ecs_vector_free(snapshot->tables); + if (!flecs_scope_is_empty(node->scope)) { + flecs_script_scope_to_str(v, node->scope); + } else { + flecs_scriptbuf_appendstr(v, "\n"); + } +} - ecs_os_free(snapshot); +static +void flecs_script_pair_scope_to_str( + ecs_script_str_visitor_t *v, + ecs_script_pair_scope_t *node) +{ + flecs_scriptbuf_node(v, &node->node); + flecs_script_id_to_str(v, &node->id); + flecs_scriptbuf_appendstr(v, " "); + flecs_script_scope_to_str(v, node->scope); } -ecs_iter_t ecs_snapshot_iter( - ecs_snapshot_t *snapshot) +static +void flecs_script_if_to_str( + ecs_script_str_visitor_t *v, + ecs_script_if_t *node) { - ecs_snapshot_iter_t iter = { - .tables = snapshot->tables, - .index = 0 - }; + flecs_scriptbuf_node(v, &node->node); + flecs_expr_to_str(v, node->expr); - return (ecs_iter_t){ - .world = snapshot->world, - .table_count = ecs_vector_count(snapshot->tables), - .priv.iter.snapshot = iter, - .next = ecs_snapshot_next - }; + flecs_scriptbuf_appendstr(v, " {\n"); + v->depth ++; + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_appendstr(v, "true: "); + flecs_script_color_to_str(v, ECS_NORMAL); + flecs_script_scope_to_str(v, node->if_true); + flecs_script_color_to_str(v, ECS_CYAN); + flecs_scriptbuf_appendstr(v, "false: "); + flecs_script_color_to_str(v, ECS_NORMAL); + flecs_script_scope_to_str(v, node->if_false); + v->depth --; + flecs_scriptbuf_appendstr(v, "}\n"); } -bool ecs_snapshot_next( - ecs_iter_t *it) +static +void flecs_script_for_range_to_str( + ecs_script_str_visitor_t *v, + ecs_script_for_range_t *node) { - ecs_snapshot_iter_t *iter = &it->priv.iter.snapshot; - ecs_table_leaf_t *tables = ecs_vector_first(iter->tables, ecs_table_leaf_t); - int32_t count = ecs_vector_count(iter->tables); - int32_t i; + flecs_scriptbuf_node(v, &node->node); + flecs_scriptbuf_appendstr(v, node->loop_var); + flecs_script_color_to_str(v, ECS_BLUE); + flecs_scriptbuf_appendstr(v, " in "); + flecs_script_color_to_str(v, ECS_NORMAL); + flecs_expr_to_str(v, node->from); + flecs_scriptbuf_appendstr(v, " .. "); + flecs_expr_to_str(v, node->to); - for (i = iter->index; i < count; i ++) { - ecs_table_t *table = tables[i].table; - if (!table) { - continue; - } + flecs_scriptbuf_appendstr(v, " {\n"); + v->depth ++; + flecs_script_scope_to_str(v, node->scope); + v->depth --; + flecs_scriptbuf_appendstr(v, "}\n"); +} - ecs_data_t *data = tables[i].data; +static +int flecs_script_scope_to_str( + ecs_script_str_visitor_t *v, + ecs_script_scope_t *scope) +{ + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - it->table = table; - it->count = ecs_table_count(table); - if (data) { - it->entities = ecs_vector_first(data->entities, ecs_entity_t); - } else { - it->entities = NULL; - } + if (!ecs_vec_count(&scope->stmts)) { + flecs_scriptbuf_appendstr(v, "{}\n"); + return 0; + } - ECS_BIT_SET(it->flags, EcsIterIsValid); - iter->index = i + 1; - - goto yield; + flecs_scriptbuf_appendstr(v, "{\n"); + + v->depth ++; + + if (ecs_script_visit_scope(v, scope)) { + return -1; } - ECS_BIT_CLEAR(it->flags, EcsIterIsValid); - return false; + v->depth --; -yield: - ECS_BIT_CLEAR(it->flags, EcsIterIsValid); - return true; + flecs_scriptbuf_appendstr(v, "}\n"); + + return 0; } -/** Cleanup snapshot */ -void ecs_snapshot_free( - ecs_snapshot_t *snapshot) +static +int flecs_script_stmt_to_str( + ecs_script_visit_t *_v, + ecs_script_node_t *node) { - flecs_sparse_free(snapshot->entity_index); - - ecs_table_leaf_t *tables = ecs_vector_first(snapshot->tables, ecs_table_leaf_t); - int32_t i, count = ecs_vector_count(snapshot->tables); - for (i = 0; i < count; i ++) { - ecs_table_leaf_t *snapshot_table = &tables[i]; - ecs_table_t *table = snapshot_table->table; - if (table) { - ecs_data_t *data = snapshot_table->data; - if (data) { - flecs_table_clear_data(snapshot->world, table, data); - ecs_os_free(data); - } - ecs_vector_free(snapshot_table->type); + ecs_script_str_visitor_t *v = (ecs_script_str_visitor_t*)_v; + switch(node->kind) { + case EcsAstScope: + if (flecs_script_scope_to_str(v, (ecs_script_scope_t*)node)) { + return -1; } - } + break; + case EcsAstTag: + case EcsAstWithTag: + flecs_script_tag_to_str(v, (ecs_script_tag_t*)node); + break; + case EcsAstComponent: + case EcsAstWithComponent: + flecs_script_component_to_str(v, (ecs_script_component_t*)node); + break; + case EcsAstVarComponent: + case EcsAstWithVar: + flecs_script_with_var_to_str(v, + (ecs_script_var_component_t*)node); + break; + case EcsAstDefaultComponent: + flecs_script_default_component_to_str(v, + (ecs_script_default_component_t*)node); + break; + case EcsAstWith: + flecs_script_with_to_str(v, (ecs_script_with_t*)node); + break; + case EcsAstUsing: + flecs_script_using_to_str(v, (ecs_script_using_t*)node); + break; + case EcsAstModule: + flecs_script_module_to_str(v, (ecs_script_module_t*)node); + break; + case EcsAstAnnotation: + flecs_script_annot_to_str(v, (ecs_script_annot_t*)node); + break; + case EcsAstTemplate: + flecs_script_template_to_str(v, (ecs_script_template_node_t*)node); + break; + case EcsAstConst: + case EcsAstExportConst: + case EcsAstProp: + flecs_script_var_node_to_str(v, (ecs_script_var_node_t*)node); + break; + case EcsAstEntity: + flecs_script_entity_to_str(v, (ecs_script_entity_t*)node); + break; + case EcsAstPairScope: + flecs_script_pair_scope_to_str(v, (ecs_script_pair_scope_t*)node); + break; + case EcsAstIf: + flecs_script_if_to_str(v, (ecs_script_if_t*)node); + break; + case EcsAstFor: + flecs_script_for_range_to_str(v, (ecs_script_for_range_t*)node); + break; + } - ecs_vector_free(snapshot->tables); - ecs_os_free(snapshot); + return 0; } -#endif - +int ecs_script_ast_node_to_buf( + const ecs_script_t *script, + ecs_script_node_t *node, + ecs_strbuf_t *buf, + bool colors, + int32_t depth) +{ + ecs_script_str_visitor_t v = { + .buf = buf, .colors = colors, .depth = depth + }; -#ifdef FLECS_SYSTEM + /* Safe, string visitor doesn't modify script */ + if (ecs_script_visit_from( + (ecs_script_impl_t*)ECS_CONST_CAST(ecs_script_t*, script), + &v, flecs_script_stmt_to_str, node, depth)) + { + goto error; + } + return 0; +error: + ecs_strbuf_reset(buf); + return -1; +} -static -void invoke_status_action( - ecs_world_t *world, - ecs_entity_t system, - const EcsSystem *system_data, - ecs_system_status_t status) +int ecs_script_ast_to_buf( + ecs_script_t *script, + ecs_strbuf_t *buf, + bool colors) { - ecs_system_status_action_t action = system_data->status_action; - if (action) { - action(world, system, status, system_data->status_ctx); + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_str_visitor_t v = { .buf = buf, .colors = colors }; + if (ecs_script_visit(flecs_script_impl(script), &v, flecs_script_stmt_to_str)) { + goto error; } + + return 0; +error: + ecs_strbuf_reset(buf); + return -1; } -/* Invoked when system becomes active or inactive */ -void ecs_system_activate( - ecs_world_t *world, - ecs_entity_t system, - bool activate, - const EcsSystem *system_data) +char* ecs_script_ast_to_str( + ecs_script_t *script, + bool colors) { - ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_script_impl_t *impl = flecs_script_impl(script); - if (activate) { - /* If activating system, ensure that it doesn't have the Inactive tag. - * Systems are implicitly activated so they are kept out of the main - * loop as long as they aren't used. They are not implicitly deactivated - * to prevent overhead in case of oscillating app behavior. - * After activation, systems that aren't matched with anything can be - * deactivated again by explicitly calling ecs_deactivate_systems. - */ - ecs_remove_id(world, system, EcsInactive); + if (!impl->expr && !impl->root) { + return NULL; } - if (!system_data) { - system_data = ecs_get(world, system, EcsSystem); - } - if (!system_data || !system_data->query) { - return; + if (flecs_script_impl(script)->expr) { + flecs_expr_to_str_buf( + script, flecs_script_impl(script)->expr, &buf, colors); + } else { + if (ecs_script_ast_to_buf(script, &buf, colors)) { + goto error; + } } - if (!activate) { - if (ecs_has_id(world, system, EcsDisabled)) { - if (!ecs_query_table_count(system_data->query)) { - /* If deactivating a disabled system that isn't matched with - * any active tables, there is nothing to deactivate. */ - return; - } - } - } + return ecs_strbuf_get(&buf); +error: + return NULL; +} - /* Invoke system status action */ - invoke_status_action(world, system, system_data, - activate ? EcsSystemActivated : EcsSystemDeactivated); +#endif - ecs_dbg_1("#[green]system#[reset] %s %s", - ecs_get_name(world, system), - activate ? "activated" : "deactivated"); -} +/** + * @file addons/stats/memory.c + */ -/* Actually enable or disable system */ -static -void ecs_enable_system( +/** + * @file addons/stats/stats.h + * @brief Internal functions/types for stats addon. + */ + +#ifndef FLECS_STATS_PRIVATE_H +#define FLECS_STATS_PRIVATE_H + + +typedef struct { + /* Statistics API interface */ + void (*copy_last)(void *stats, void *src); + void (*get)(ecs_world_t *world, ecs_entity_t res, void *stats); + void (*reduce)(void *stats, void *src); + void (*reduce_last)(void *stats, void *last, int32_t reduce_count); + void (*repeat_last)(void* stats); + void (*set_t)(void *stats, int32_t t); + void (*fini)(void *stats); + + /* Size of statistics type */ + ecs_size_t stats_size; + + /* Id of component that contains the statistics */ + ecs_entity_t monitor_component_id; + + /* Id of component used to query for monitored resources (optional) */ + ecs_id_t query_component_id; + + /* Is the stats object inlined or a pointer */ + bool is_pointer; +} ecs_stats_api_t; + +void flecs_stats_api_import( ecs_world_t *world, - ecs_entity_t system, - EcsSystem *system_data, - bool enabled) + ecs_stats_api_t *api); + +void FlecsWorldSummaryImport( + ecs_world_t *world); + +void FlecsWorldMonitorImport( + ecs_world_t *world); + +void FlecsSystemMonitorImport( + ecs_world_t *world); + +void FlecsPipelineMonitorImport( + ecs_world_t *world); + +void flecs_stats_memory_register_reflection( + ecs_world_t *world); + +#endif + + +#ifdef FLECS_REST +#endif + +#ifdef FLECS_STATS + +ECS_COMPONENT_DECLARE(ecs_entities_memory_t); +ECS_COMPONENT_DECLARE(ecs_component_index_memory_t); +ECS_COMPONENT_DECLARE(ecs_query_memory_t); +ECS_COMPONENT_DECLARE(ecs_component_memory_t); +ECS_COMPONENT_DECLARE(ecs_table_memory_t); +ECS_COMPONENT_DECLARE(ecs_misc_memory_t); +ECS_COMPONENT_DECLARE(ecs_table_histogram_t); +ECS_COMPONENT_DECLARE(ecs_allocator_memory_t); +ECS_COMPONENT_DECLARE(EcsWorldMemory); + +static +ecs_size_t flecs_ballocator_memory_get( + const ecs_block_allocator_t *allocator) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + ecs_size_t result = 0; + (void)allocator; - ecs_query_t *query = system_data->query; - if (!query) { - return; - } +#ifndef FLECS_USE_OS_ALLOC + ecs_block_allocator_chunk_header_t *chunk = allocator->head; - if (ecs_query_table_count(query)) { - /* Only (de)activate system if it has non-empty tables. */ - ecs_system_activate(world, system, enabled, system_data); - system_data = ecs_get_mut(world, system, EcsSystem, NULL); + while (chunk) { + result += allocator->data_size; + chunk = chunk->next; } - - /* Invoke action for enable/disable status */ - invoke_status_action( - world, system, system_data, - enabled ? EcsSystemEnabled : EcsSystemDisabled); -} +#endif -/* -- Public API -- */ + return result; +} -ecs_entity_t ecs_run_intern( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t system, - EcsSystem *system_data, - int32_t stage_current, - int32_t stage_count, - FLECS_FLOAT delta_time, - int32_t offset, - int32_t limit, - void *param) +static +ecs_size_t flecs_map_memory_get( + const ecs_map_t *map, + ecs_size_t element_size) { - FLECS_FLOAT time_elapsed = delta_time; - ecs_entity_t tick_source = system_data->tick_source; - - /* Support legacy behavior */ - if (!param) { - param = system_data->ctx; + ecs_size_t result = 0; + if (map && map->bucket_count > 0) { + result += map->bucket_count * ECS_SIZEOF(ecs_bucket_t); + result += ecs_map_count(map) * ECS_SIZEOF(ecs_bucket_entry_t); + result += ecs_map_count(map) * element_size; } + return result; +} - if (tick_source) { - const EcsTickSource *tick = ecs_get( - world, tick_source, EcsTickSource); +static +ecs_size_t flecs_hashmap_memory_get( + const ecs_hashmap_t *name_index) +{ + const ecs_map_t *map = &name_index->impl; - if (tick) { - time_elapsed = tick->time_elapsed; + ecs_size_t key_size = name_index->key_size; + ecs_size_t value_size = name_index->value_size; + ecs_size_t result = flecs_map_memory_get(map, ECS_SIZEOF(ecs_hm_bucket_t)); - /* If timer hasn't fired we shouldn't run the system */ - if (!tick->tick) { - return 0; - } - } else { - /* If a timer has been set but the timer entity does not have the - * EcsTimer component, don't run the system. This can be the result - * of a single-shot timer that has fired already. Not resetting the - * timer field of the system will ensure that the system won't be - * ran after the timer has fired. */ - return 0; - } + ecs_map_iter_t it = ecs_map_iter(map); + while (ecs_map_next(&it)) { + ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); + result += ecs_vec_size(&bucket->keys) * key_size; + result += ecs_vec_size(&bucket->values) * value_size; } - ecs_time_t time_start; - bool measure_time = world->measure_system_time; - if (measure_time) { - ecs_os_get_time(&time_start); + return result; +} + +static +ecs_size_t flecs_sparse_memory_get( + const ecs_sparse_t *sparse, + ecs_size_t element_size) +{ + if (!sparse) { + return 0; } - ecs_world_t *thread_ctx = world; - if (stage) { - thread_ctx = stage->thread_ctx; + ecs_size_t result = 0; + + result += ecs_vec_size(&sparse->dense) * ECS_SIZEOF(uint64_t); + result += ecs_vec_size(&sparse->pages) * ECS_SIZEOF(ecs_sparse_page_t); + + int32_t i, pages_count = ecs_vec_count(&sparse->pages); + ecs_sparse_page_t *pages = ecs_vec_first_t( + &sparse->pages, ecs_sparse_page_t); + int32_t page_count = 0; + for (i = 0; i < pages_count; i++) { + ecs_sparse_page_t *page = &pages[i]; + if (page->sparse) { + page_count ++; + } } - ecs_defer_begin(thread_ctx); + result += page_count * ECS_SIZEOF(int32_t) * FLECS_SPARSE_PAGE_SIZE; + result += page_count * element_size * FLECS_SPARSE_PAGE_SIZE; - /* Prepare the query iterator */ - ecs_iter_t pit, wit, qit = ecs_query_iter(thread_ctx, system_data->query); - ecs_iter_t *it = &qit; + return result; +} - if (offset || limit) { - pit = ecs_page_iter(it, offset, limit); - it = &pit; +static +ecs_size_t flecs_allocator_memory_get( + const ecs_allocator_t *allocator) +{ + ecs_size_t result = 0; + (void)allocator; + +#ifndef FLECS_USE_OS_ALLOC + int32_t i, count = flecs_sparse_count(&allocator->sizes); + for (i = 0; i < count; i++) { + ecs_block_allocator_t *ba = flecs_sparse_get_dense_t( + &allocator->sizes, ecs_block_allocator_t, i); + result += flecs_ballocator_memory_get(ba); } + result += flecs_ballocator_memory_get(&allocator->chunks); + + result += flecs_sparse_memory_get(&allocator->sizes, + ECS_SIZEOF(ecs_block_allocator_t)); +#endif - if (stage_count > 1 && system_data->multi_threaded) { - wit = ecs_worker_iter(it, stage_current, stage_count); - it = &wit; - } + return result; +} - qit.system = system; - qit.self = system_data->self; - qit.delta_time = delta_time; - qit.delta_system_time = time_elapsed; - qit.frame_offset = offset; - qit.param = param; - qit.ctx = system_data->ctx; - qit.binding_ctx = system_data->binding_ctx; +static +ecs_size_t flecs_stack_memory_get( + const ecs_stack_t *stack) +{ + ecs_size_t result = 0; + ecs_stack_page_t *page = stack->first; + while (page) { + result += + FLECS_STACK_PAGE_OFFSET + FLECS_STACK_PAGE_SIZE; + page = page->next; + } + return result; +} - ecs_iter_action_t action = system_data->action; - it->callback = action; - - ecs_run_action_t run = system_data->run; - if (run) { - run(it); - } else if (system_data->query->filter.term_count) { - if (it == &qit) { - while (ecs_query_next(&qit)) { - action(&qit); - } - } else { - while (ecs_iter_next(it)) { - action(it); +static +ecs_size_t flecs_identifier_memory_get( + const ecs_world_t *world, + ecs_entity_t tag) +{ + ecs_size_t result = 0; + ecs_iter_t it = ecs_each_id(world, ecs_pair_t(EcsIdentifier, tag)); + while (ecs_each_next(&it)) { + EcsIdentifier *ptr = ecs_field(&it, EcsIdentifier, 0); + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + if (ptr[i].value) { + result += ecs_os_strlen(ptr[i].value) + 1; } } - } else { - action(&qit); } + return result; +} - if (measure_time) { - system_data->time_spent += (float)ecs_time_measure(&time_start); +#ifdef FLECS_DOC +static +ecs_size_t flecs_doc_string_memory_get( + const ecs_world_t *world, + ecs_entity_t tag) +{ + ecs_size_t result = 0; + ecs_iter_t it = ecs_each_id(world, ecs_pair_t(EcsDocDescription, tag)); + while (ecs_each_next(&it)) { + EcsDocDescription *ptr = ecs_field(&it, EcsDocDescription, 0); + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + if (ptr[i].value) { + result += ecs_os_strlen(ptr[i].value) + 1; + } + } } + return result; +} +#endif - system_data->invoke_count ++; +ecs_entities_memory_t ecs_entity_memory_get( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_entity_index_t *index = &world->store.entity_index; + ecs_entities_memory_t result = {0}; - ecs_defer_end(thread_ctx); + result.alive_count = index->alive_count - 1; + result.not_alive_count = ecs_vec_count(&index->dense) - index->alive_count; - return it->interrupted_by; -} + result.bytes_entity_index = + ecs_vec_size(&index->dense) * ECS_SIZEOF(uint64_t); + result.bytes_entity_index += ecs_vec_size(&index->pages) * + ECS_SIZEOF(ecs_entity_index_page_t*); -/* -- Public API -- */ + ecs_entity_index_page_t **pages = ecs_vec_first(&index->pages); + int32_t i, page_count = 0, pages_count = ecs_vec_count(&index->pages); + for (i = 0; i < pages_count; i++) { + if (pages[i]) { + page_count ++; + } + } -ecs_entity_t ecs_run_w_filter( - ecs_world_t *world, - ecs_entity_t system, - FLECS_FLOAT delta_time, - int32_t offset, - int32_t limit, - void *param) -{ - ecs_stage_t *stage = flecs_stage_from_world(&world); + result.bytes_entity_index += page_count * + ECS_SIZEOF(ecs_entity_index_page_t); + + /* Entity names, symbols, aliases */ + result.bytes_names += flecs_identifier_memory_get(world, EcsName); + result.bytes_names += flecs_identifier_memory_get(world, EcsSymbol); + result.bytes_names += flecs_identifier_memory_get(world, EcsAlias); - EcsSystem *system_data = (EcsSystem*)ecs_get( - world, system, EcsSystem); - assert(system_data != NULL); +#ifdef FLECS_DOC + /* Doc strings */ + result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsName); + result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsDocBrief); + result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsDocDetail); + result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsDocUuid); + result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsDocLink); + result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsDocColor); +#endif - return ecs_run_intern(world, stage, system, system_data, 0, 0, delta_time, - offset, limit, param); +error: + return result; } -ecs_entity_t ecs_run_worker( - ecs_world_t *world, - ecs_entity_t system, - int32_t stage_current, - int32_t stage_count, - FLECS_FLOAT delta_time, - void *param) +void ecs_component_record_memory_get( + const ecs_component_record_t *cr, + ecs_component_index_memory_t *result) { - ecs_stage_t *stage = flecs_stage_from_world(&world); + result->count++; - EcsSystem *system_data = (EcsSystem*)ecs_get( - world, system, EcsSystem); - assert(system_data != NULL); + result->bytes_component_record += ECS_SIZEOF(ecs_component_record_t); + + const ecs_table_cache_t *cache = &cr->cache; + result->bytes_table_cache += ECS_SIZEOF(ecs_table_cache_t); + + const ecs_map_t *map = &cache->index; + result->bytes_table_cache += flecs_map_memory_get(map, 0); - return ecs_run_intern( - world, stage, system, system_data, stage_current, stage_count, - delta_time, 0, 0, param); -} +#ifdef FLECS_DEBUG_INFO + if (cr->str) { + result->bytes_component_record += ecs_os_strlen(cr->str); + } +#endif -ecs_entity_t ecs_run( - ecs_world_t *world, - ecs_entity_t system, - FLECS_FLOAT delta_time, - void *param) -{ - return ecs_run_w_filter(world, system, delta_time, 0, 0, param); -} + if (cr->pair) { + ecs_pair_record_t *pair = cr->pair; -ecs_query_t* ecs_system_get_query( - const ecs_world_t *world, - ecs_entity_t system) -{ - const EcsQuery *q = ecs_get(world, system, EcsQuery); - if (q) { - return q->query; - } else { - const EcsSystem *s = ecs_get(world, system, EcsSystem); - if (s) { - return s->query; - } else { - return NULL; + result->bytes_component_record += ECS_SIZEOF(ecs_pair_record_t); + + if (pair->name_index) { + result->bytes_name_index += ECS_SIZEOF(ecs_hashmap_t); + result->bytes_name_index += flecs_hashmap_memory_get( + pair->name_index); } + + result->bytes_ordered_children += + ecs_vec_size(&pair->ordered_children) * ECS_SIZEOF(ecs_entity_t); + + result->bytes_children_table_map += flecs_map_memory_get( + &pair->children_tables, 0); + + const ecs_reachable_cache_t *reachable = &pair->reachable; + result->bytes_reachable_cache += + ecs_vec_size(&reachable->ids) * ECS_SIZEOF(ecs_reachable_elem_t); } } -void* ecs_get_system_ctx( - const ecs_world_t *world, - ecs_entity_t system) +ecs_component_index_memory_t ecs_component_index_memory_get( + const ecs_world_t *world) { - const EcsSystem *s = ecs_get(world, system, EcsSystem); - if (s) { - return s->ctx; - } else { - return NULL; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_component_index_memory_t result = {0}; + + FLECS_EACH_COMPONENT_RECORD(cr, { + ecs_component_record_memory_get(cr, &result); + }) + +error: + return result; } -void* ecs_get_system_binding_ctx( - const ecs_world_t *world, - ecs_entity_t system) +void ecs_query_memory_get( + const ecs_query_t *query, + ecs_query_memory_t *result) { - const EcsSystem *s = ecs_get(world, system, EcsSystem); - if (s) { - return s->binding_ctx; - } else { - return NULL; - } -} + const ecs_query_impl_t *impl = flecs_query_impl(query); -/* System deinitialization */ -static -void ecs_on_remove(EcsSystem)(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsSystem *ptr = ecs_term(it, EcsSystem, 1); + result->count++; + result->bytes_query += ECS_SIZEOF(ecs_query_impl_t); + result->bytes_terms += query->term_count * ECS_SIZEOF(ecs_term_t); + + if (impl->ops) { + result->bytes_plan += + impl->op_count * ECS_SIZEOF(ecs_query_op_t); + } - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - EcsSystem *sys = &ptr[i]; - ecs_entity_t entity = it->entities[i]; + if (impl->vars) { + result->bytes_misc += + impl->var_size * ECS_SIZEOF(ecs_query_var_t); + } - if (!ecs_is_alive(world, entity)) { - /* This can happen when a set is deferred while a system is being - * cleaned up. The operation will be discarded, but the destructor - * still needs to be invoked for the value */ - continue; - } + if (impl->src_vars) { + result->bytes_misc += + query->field_count * ECS_SIZEOF(ecs_var_id_t); + } - /* Invoke Deactivated action for active systems */ - if (sys->query && ecs_query_table_count(sys->query)) { - invoke_status_action(world, entity, sys, EcsSystemDeactivated); - } + if (impl->tokens) { + result->bytes_misc += impl->tokens_len; + } - /* Invoke Disabled action for enabled systems */ - if (!ecs_has_id(world, entity, EcsDisabled)) { - invoke_status_action(world, entity, sys, EcsSystemDisabled); - } + if (impl->monitor) { + result->bytes_misc += query->field_count * ECS_SIZEOF(int32_t); + } + + /* Query cache memory */ + if (impl->cache) { + ecs_query_cache_t *cache = impl->cache; + + result->cached_count++; + + result->bytes_cache += ECS_SIZEOF(ecs_query_cache_t); + result->bytes_cache += + flecs_map_memory_get(&cache->tables, + ECS_SIZEOF(ecs_query_cache_table_t)); + result->bytes_group_by += + flecs_map_memory_get(&cache->groups, + ECS_SIZEOF(ecs_query_cache_group_t)); + + ecs_size_t cache_elem_size = flecs_query_cache_elem_size(cache); + ecs_query_cache_group_t *cur = cache->first_group; + do { + result->bytes_cache += ecs_vec_size(&cur->tables) * cache_elem_size; + + if (!(cache->query->flags & EcsQueryTrivialCache)) { + int32_t group_table_count = ecs_vec_count(&cur->tables); + for (int32_t j = 0; j < group_table_count; j++) { + ecs_query_cache_match_t *match = + ecs_vec_get(&cur->tables, cache_elem_size, j); + ecs_size_t elem_arrays = 0; + elem_arrays += query->field_count * ECS_SIZEOF(ecs_id_t); + elem_arrays += query->field_count * ECS_SIZEOF(ecs_entity_t); + elem_arrays += query->field_count * ECS_SIZEOF(ecs_table_t*); + elem_arrays += query->field_count * ECS_SIZEOF(int32_t); + result->bytes_cache += elem_arrays; + + if (match->wildcard_matches) { + int32_t wildcard_count = + ecs_vec_size(match->wildcard_matches); + result->bytes_cache += + (cache_elem_size + elem_arrays) * wildcard_count; + } + } + } + + cur = cur->next; + } while (cur && cur != cache->first_group); + + result->bytes_order_by += + ecs_vec_size(&cache->table_slices) * + ECS_SIZEOF(ecs_table_range_t); - if (sys->ctx_free) { - sys->ctx_free(sys->ctx); + if (cache->sources) { + result->bytes_cache += + query->field_count * ECS_SIZEOF(ecs_entity_t); } - if (sys->status_ctx_free) { - sys->status_ctx_free(sys->status_ctx); + if (cache->field_map) { + result->bytes_cache += query->field_count * ECS_SIZEOF(int8_t); } - if (sys->binding_ctx_free) { - sys->binding_ctx_free(sys->binding_ctx); - } + result->bytes_cache += flecs_ballocator_memory_get(&cache->allocators.ids); + result->bytes_cache += flecs_ballocator_memory_get(&cache->allocators.monitors); + result->bytes_cache += flecs_ballocator_memory_get(&cache->allocators.pointers); - if (sys->query) { - ecs_query_fini(sys->query); - } + ecs_query_memory_get(cache->query, result); + result->count --; /* Don't double count query */ } } static -void EnableMonitor( - ecs_iter_t *it) +ecs_size_t flecs_query_total_memory_get( + const ecs_query_t *query) +{ + ecs_query_memory_t memory = {0}; + ecs_query_memory_get(query, &memory); + + ecs_size_t result = 0; + result += memory.bytes_query; + result += memory.bytes_cache; + result += memory.bytes_group_by; + result += memory.bytes_order_by; + result += memory.bytes_plan; + result += memory.bytes_terms; + result += memory.bytes_misc; + return result; +} + +ecs_query_memory_t ecs_queries_memory_get( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_query_memory_t result = (ecs_query_memory_t){0}; + + FLECS_EACH_QUERY(query, { + ecs_query_memory_get(query, &result); + }) + +error: + return result; +} + +static +void flecs_component_memory_get_sparse( + const ecs_component_record_t *cr, + ecs_component_memory_t *result) { - if (ecs_is_fini(it->world)) { + ecs_sparse_t *sparse = cr->sparse; + if (!sparse) { return; } - EcsSystem *sys = ecs_term(it, EcsSystem, 1); + ecs_size_t component_size = cr->type_info ? cr->type_info->size : 0; + result->bytes_sparse_components += flecs_sparse_memory_get( + sparse, component_size); - int32_t i; - for (i = 0; i < it->count; i ++) { - if (it->event == EcsOnAdd) { - ecs_enable_system(it->world, it->entities[i], &sys[i], true); - } else if (it->event == EcsOnRemove) { - ecs_enable_system(it->world, it->entities[i], &sys[i], false); - } + if (component_size) { + result->instances += flecs_sparse_count(sparse); } } -ecs_entity_t ecs_system_init( - ecs_world_t *world, - const ecs_system_desc_t *desc) +void ecs_table_component_memory_get( + const ecs_table_t *table, + ecs_component_memory_t *result) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(!world->is_readonly, ECS_INVALID_WHILE_ITERATING, NULL); + int32_t count = ecs_table_count(table); + int32_t size = ecs_table_size(table); + ecs_column_t *columns = table->data.columns; - ecs_entity_t result = ecs_entity_init(world, &desc->entity); - if (!result) { - return 0; + int32_t col; + for (col = 0; col < table->column_count; col++) { + ecs_column_t *column = &columns[col]; + + ecs_size_t component_size = column->ti->size; + ecs_size_t used = count * component_size; + ecs_size_t allocated = size * component_size; + + result->bytes_table_components += used; + result->bytes_table_components_unused += allocated - used; } - bool added = false; - EcsSystem *system = ecs_get_mut(world, result, EcsSystem, &added); - if (added) { - ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + result->instances += count * table->column_count; - memset(system, 0, sizeof(EcsSystem)); + if (table->_) { + result->bytes_toggle_bitsets += + table->_->bs_count * ECS_SIZEOF(ecs_bitset_t); + result->bytes_toggle_bitsets += + (count / 64) * ECS_SIZEOF(uint64_t) * table->_->bs_count; + } +} - ecs_query_desc_t query_desc = desc->query; - query_desc.filter.name = desc->entity.name; - query_desc.system = result; +ecs_component_memory_t ecs_component_memory_get( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_component_memory_t result = {0}; + + /* Iterate through all tables to calculate table component memory */ + const ecs_sparse_t *tables = &world->store.tables; + int32_t i, table_count = flecs_sparse_count(tables); + + for (i = 0; i < table_count; i++) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + ecs_table_component_memory_get(table, &result); + } - ecs_query_t *query = ecs_query_init(world, &query_desc); - if (!query) { - ecs_delete(world, result); - return 0; + /* Sparse components */ + FLECS_EACH_COMPONENT_RECORD(cr, { + flecs_component_memory_get_sparse(cr, &result); + }) + +error: + return result; +} + +static +void flecs_table_graph_edge_memory_get( + const ecs_graph_edge_t *edge, + ecs_table_memory_t *result) +{ + result->bytes_edges += ECS_SIZEOF(ecs_graph_edge_t); + if (edge->diff) { + result->bytes_edges += ECS_SIZEOF(ecs_table_diff_t); + result->bytes_edges += + edge->diff->added.count * ECS_SIZEOF(ecs_id_t); + result->bytes_edges += + edge->diff->removed.count * ECS_SIZEOF(ecs_id_t); + } +} + +static +void flecs_table_graph_edges_memory_get( + const ecs_graph_edges_t *edges, + ecs_table_memory_t *result) +{ + if (edges->lo) { + int32_t i; + for (i = 0; i < FLECS_HI_COMPONENT_ID; i++) { + ecs_graph_edge_t *edge = &edges->lo[i]; + flecs_table_graph_edge_memory_get(edge, result); } + } + if (edges->hi) { + result->bytes_edges += ECS_SIZEOF(ecs_map_t); + result->bytes_edges += flecs_map_memory_get(edges->hi, 0); - /* Re-obtain pointer, as query may have added components */ - system = ecs_get_mut(world, result, EcsSystem, &added); - ecs_assert(added == false, ECS_INTERNAL_ERROR, NULL); + ecs_map_iter_t it = ecs_map_iter(edges->hi); + while (ecs_map_next(&it)) { + ecs_graph_edge_t *edge = ecs_map_ptr(&it); + flecs_table_graph_edge_memory_get(edge, result); + } + } +} - /* Prevent the system from moving while we're initializing */ - ecs_defer_begin(world); +void ecs_table_memory_get( + const ecs_table_t *table, + ecs_table_memory_t *result) +{ + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); - system->entity = result; - system->query = query; + result->bytes_table += ECS_SIZEOF(ecs_table_t); + + int32_t entity_count = ecs_table_count(table); + int32_t entity_size = ecs_table_size(table); + int32_t type_count = table->type.count; + int32_t column_count = table->column_count; + + result->column_count += column_count; - system->run = desc->run; - system->action = desc->callback; - system->status_action = desc->status_callback; + if (entity_count == 0) { + result->empty_count++; + } + + result->bytes_type += type_count * ECS_SIZEOF(ecs_id_t); - system->self = desc->self; - system->ctx = desc->ctx; - system->status_ctx = desc->status_ctx; - system->binding_ctx = desc->binding_ctx; + result->bytes_entities += + entity_size * ECS_SIZEOF(ecs_entity_t); + + if (table->data.overrides) { + result->bytes_overrides += ECS_SIZEOF(ecs_table_overrides_t); + result->bytes_overrides += column_count * ECS_SIZEOF(ecs_ref_t); + } + + result->bytes_type += column_count * ECS_SIZEOF(ecs_column_t); - system->ctx_free = desc->ctx_free; - system->status_ctx_free = desc->status_ctx_free; - system->binding_ctx_free = desc->binding_ctx_free; + if (table->_) { + result->bytes_table += ECS_SIZEOF(ecs_table__t); + result->bytes_type += + table->_->record_count * ECS_SIZEOF(ecs_table_record_t); + } - system->tick_source = desc->tick_source; + result->bytes_column_map += + (type_count + column_count) * ECS_SIZEOF(int16_t); + if (table->component_map) { + result->bytes_component_map += + FLECS_HI_COMPONENT_ID * ECS_SIZEOF(int16_t); + } - system->multi_threaded = desc->multi_threaded; - system->no_staging = desc->no_staging; + if (table->dirty_state) { + result->bytes_dirty_state += + (column_count + 1) * ECS_SIZEOF(int32_t); + } + + flecs_table_graph_edges_memory_get(&table->node.add, result); + flecs_table_graph_edges_memory_get(&table->node.remove, result); +} - /* If tables have been matched with this system it is active, and we - * should activate the in terms, if any. This will ensure that any - * OnDemand systems get enabled. */ - if (ecs_query_table_count(query)) { - ecs_system_activate(world, result, true, system); - } else if (query->filter.term_count) { - /* If system isn't matched with any tables, mark it as inactive. This - * causes it to be ignored by the main loop. When the system matches - * with a table it will be activated. */ - ecs_add_id(world, result, EcsInactive); - } +ecs_table_memory_t ecs_tables_memory_get( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_table_memory_t result = {0}; + const ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); - if (!ecs_has_id(world, result, EcsDisabled)) { - /* If system is already enabled, generate enable status. The API - * should guarantee that it exactly matches enable-disable - * notifications and activate-deactivate notifications. */ - invoke_status_action(world, result, system, EcsSystemEnabled); + result.count = count; - /* If column system has active (non-empty) tables, also generate the - * activate status. */ - if (ecs_query_table_count(system->query)) { - invoke_status_action(world, result, system, EcsSystemActivated); - } - } + result.bytes_table += + flecs_sparse_memory_get(tables, ECS_SIZEOF(ecs_table_t)); - if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { -#ifdef FLECS_TIMER - if (desc->interval != 0) { - ecs_set_interval(world, result, desc->interval); - } + for (i = 0; i < count; i++) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + ecs_table_memory_get(table, &result); + /* Already accounted for by flecs_sparse_memory_get */ + result.bytes_table -= ECS_SIZEOF(ecs_table_t); + } - if (desc->rate) { - ecs_set_rate(world, result, desc->rate, desc->tick_source); - } else if (desc->tick_source) { - ecs_set_tick_source(world, result, desc->tick_source); - } -#else - ecs_abort(ECS_UNSUPPORTED, "timer module not available"); -#endif - } +error: + return result; +} - ecs_modified(world, result, EcsSystem); +ecs_table_histogram_t ecs_table_histogram_get( + const ecs_world_t *world) +{ + ecs_table_histogram_t result = {0}; - if (desc->entity.name) { - ecs_trace("#[green]system#[reset] %s created", - ecs_get_name(world, result)); - } + const ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + for (i = 0; i < count; i++) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t entity_count = ecs_table_count(table); - ecs_defer_end(world); - } else { - if (desc->run) { - system->run = desc->run; - } - if (desc->callback) { - system->action = desc->callback; - } - if (desc->ctx) { - system->ctx = desc->ctx; - } - if (desc->binding_ctx) { - system->binding_ctx = desc->binding_ctx; - } - if (desc->query.filter.instanced) { - system->query->filter.instanced = true; - } - if (desc->multi_threaded) { - system->multi_threaded = desc->multi_threaded; - } - if (desc->no_staging) { - system->no_staging = desc->no_staging; + if (entity_count == 0) { + result.entity_counts[0]++; /* Bucket 0: 0 entities */ + } else if (entity_count == 1) { + result.entity_counts[1]++; /* Bucket 1: 1 entity */ + } else if (entity_count <= ECS_TABLE_MEMORY_HISTOGRAM_MAX_COUNT) { + /* Find the appropriate power-of-2 bucket */ + /* Buckets: 2-3, 4-7, 8-15, 16-31, ..., 32768-65535 */ + int32_t bucket_index = 2; + int32_t bucket_min = 2; + int32_t bucket_max = 3; + + while ((bucket_max < entity_count) && + (bucket_index < (ECS_TABLE_MEMORY_HISTOGRAM_BUCKET_COUNT - 2))) + { + bucket_index++; + bucket_min = bucket_max + 1; + bucket_max = bucket_min * 2 - 1; + } + + result.entity_counts[bucket_index]++; + } else { + result.entity_counts[ECS_TABLE_MEMORY_HISTOGRAM_BUCKET_COUNT - 1]++; } } return result; -error: - return 0; } -void FlecsSystemImport( - ecs_world_t *world) +static +ecs_size_t flecs_event_record_memory_get( + const ecs_event_record_t *er) { - ECS_MODULE(world, FlecsSystem); - - ecs_set_name_prefix(world, "Ecs"); - - flecs_bootstrap_component(world, EcsSystem); - flecs_bootstrap_component(world, EcsTickSource); - - /* Put following tags in flecs.core so they can be looked up - * without using the flecs.systems prefix. */ - ecs_entity_t old_scope = ecs_set_scope(world, EcsFlecsCore); - flecs_bootstrap_tag(world, EcsInactive); - flecs_bootstrap_tag(world, EcsMonitor); - ecs_set_scope(world, old_scope); + ecs_size_t result = flecs_map_memory_get(&er->event_ids, + ECS_SIZEOF(ecs_event_id_record_t)); - /* Bootstrap ctor and dtor for EcsSystem */ - ecs_set_component_actions_w_id(world, ecs_id(EcsSystem), - &(EcsComponentLifecycle) { - .ctor = ecs_default_ctor, - .on_remove = ecs_on_remove(EcsSystem) - }); + ecs_map_iter_t mit = ecs_map_iter(&er->event_ids); + while (ecs_map_next(&mit)) { + ecs_event_id_record_t *eir = ecs_map_ptr(&mit); + result += flecs_map_memory_get(&eir->self, 0); + result += flecs_map_memory_get(&eir->self_up, 0); + result += flecs_map_memory_get(&eir->up, 0); + } - ecs_observer_init(world, &(ecs_observer_desc_t) { - .entity.name = "EnableMonitor", - .filter.terms = { - { .id = ecs_id(EcsSystem) }, - { .id = EcsDisabled, .oper = EcsNot }, - }, - .events = {EcsMonitor}, - .callback = EnableMonitor - }); + return result; } -#endif +static +ecs_size_t flecs_observer_memory_get( + const ecs_observer_impl_t *o) +{ + ecs_size_t result = ECS_SIZEOF(ecs_observer_impl_t); + result += ecs_vec_size(&o->children) * ECS_SIZEOF(void*); + if (o->pub.query) { + result += flecs_query_total_memory_get(o->pub.query); + } -#ifdef FLECS_JSON + if (o->not_query) { + result += flecs_query_total_memory_get(o->not_query); + } -void flecs_json_next( - ecs_strbuf_t *buf); + ecs_observer_impl_t **children = ecs_vec_first(&o->children); + int32_t i, count = ecs_vec_count(&o->children); + for (i = 0; i < count; i ++) { + result += flecs_observer_memory_get(children[i]); + } -void flecs_json_literal( - ecs_strbuf_t *buf, - const char *value); + return result; +} -void flecs_json_number( - ecs_strbuf_t *buf, - double value); +static +ecs_size_t flecs_observers_memory_get( + const ecs_world_t *world) +{ + ecs_size_t result = 0; -void flecs_json_true( - ecs_strbuf_t *buf); + int32_t i, count = ecs_vec_count(&world->observable.global_observers); + for (i = 0; i < count; i ++) { + ecs_observer_t *o = ecs_vec_get_t( + &world->observable.global_observers, ecs_observer_t*, i)[0]; + result += flecs_observer_memory_get(flecs_observer_impl(o)); + } -void flecs_json_false( - ecs_strbuf_t *buf); + ecs_iter_t it = ecs_each_pair_t(world, EcsPoly, EcsObserver); + while (ecs_each_next(&it)) { + EcsPoly *p = ecs_field(&it, EcsPoly, 0); + count = it.count; + for (i = 0; i < count; i ++) { + ecs_observer_impl_t *o = p[i].poly; + if (!o) { + continue; + } -void flecs_json_bool( - ecs_strbuf_t *buf, - bool value); + result += flecs_observer_memory_get(o); + } + } -void flecs_json_array_push( - ecs_strbuf_t *buf); + return result; +} -void flecs_json_array_pop( - ecs_strbuf_t *buf); +static +ecs_size_t flecs_observer_index_memory_get( + const ecs_world_t *world) +{ + const ecs_observable_t *o = &world->observable; + ecs_size_t result = 0; + result += flecs_event_record_memory_get(&o->on_add); + result += flecs_event_record_memory_get(&o->on_remove); + result += flecs_event_record_memory_get(&o->on_set); + result += flecs_event_record_memory_get(&o->on_wildcard); -void flecs_json_object_push( - ecs_strbuf_t *buf); + const ecs_sparse_t *events = &o->events; + result += flecs_sparse_memory_get(events, + ECS_SIZEOF(ecs_event_record_t)); -void flecs_json_object_pop( - ecs_strbuf_t *buf); + int32_t i, count = flecs_sparse_count(events); + for (i = 0; i < count; i ++) { + ecs_event_record_t *er = flecs_sparse_get_dense_t( + events, ecs_event_record_t, i); + result += flecs_event_record_memory_get(er); + } -void flecs_json_string( - ecs_strbuf_t *buf, - const char *value); + return result; +} -void flecs_json_member( - ecs_strbuf_t *buf, - const char *name); +static +void flecs_system_memory_get( + const ecs_world_t *world, + ecs_misc_memory_t *result) +{ + ecs_iter_t it = ecs_each_pair_t(world, EcsPoly, EcsSystem); + while (ecs_each_next(&it)) { + EcsPoly *p = ecs_field(&it, EcsPoly, 0); + result->bytes_systems += it.count * ECS_SIZEOF(ecs_system_t); + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_system_t *s = p[i].poly; + if (!s) { + continue; + } + + if (s->name) { + result->bytes_systems += ecs_os_strlen(s->name); + } + } + } +} -void flecs_json_path( - ecs_strbuf_t *buf, +static +void flecs_pipeline_memory_get( const ecs_world_t *world, - ecs_entity_t e); + ecs_misc_memory_t *result) +{ + ecs_iter_t it = ecs_each(world, EcsPipeline); + while (ecs_each_next(&it)) { + EcsPipeline *p = ecs_field(&it, EcsPipeline, 0); + result->bytes_pipelines += it.count * ECS_SIZEOF(ecs_pipeline_state_t); + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + ecs_pipeline_state_t *ps = p[i].state; + if (!ps) { + continue; + } + + result->bytes_pipelines += ecs_vec_size(&ps->ops) * + ECS_SIZEOF(ecs_pipeline_op_t); + result->bytes_pipelines += ecs_vec_size(&ps->systems) * + ECS_SIZEOF(ecs_entity_t); + result->bytes_pipelines += ps->iter_count * + ECS_SIZEOF(ecs_iter_t); + } + } +} -void flecs_json_label( - ecs_strbuf_t *buf, +static +void flecs_rematch_monitor_memory_get( const ecs_world_t *world, - ecs_entity_t e); + ecs_misc_memory_t *result) +{ + ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); + while (ecs_map_next(&it)) { + ecs_monitor_t *m = ecs_map_ptr(&it); + result->bytes_rematch_monitor += ECS_SIZEOF(ecs_monitor_t); + result->bytes_rematch_monitor += ecs_vec_size(&m->queries) * + ECS_SIZEOF(void*); + } +} -void flecs_json_id( - ecs_strbuf_t *buf, +#ifdef FLECS_META +static +void flecs_reflection_memory_get( const ecs_world_t *world, - ecs_id_t id); + ecs_misc_memory_t *result) +{ + { + ecs_iter_t it = ecs_each(world, EcsStruct); + while (ecs_each_next(&it)) { + EcsStruct *s = ecs_field(&it, EcsStruct, 0); + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + result->bytes_reflection += ecs_vec_size(&s[i].members) * + ECS_SIZEOF(ecs_member_t); + + int32_t m, mcount = ecs_vec_count(&s[i].members); + ecs_member_t *members = ecs_vec_first(&s[i].members); + for (m = 0; m < mcount; m ++) { + ecs_member_t *member = &members[m]; + result->bytes_reflection += ecs_os_strlen(member->name); + } + } + } + } -ecs_primitive_kind_t flecs_json_op_to_primitive_kind( - ecs_meta_type_op_kind_t kind); + { + ecs_iter_t it = ecs_each(world, EcsConstants); + while (ecs_each_next(&it)) { + EcsConstants *c = ecs_field(&it, EcsConstants, 0); + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + result->bytes_reflection += flecs_map_memory_get( + c[i].constants, ECS_SIZEOF(ecs_enum_constant_t)); + result->bytes_reflection += + ecs_vec_size(&c[i].ordered_constants) * + ECS_SIZEOF(ecs_enum_constant_t); + + ecs_map_iter_t mit = ecs_map_iter(c[i].constants); + while (ecs_map_next(&mit)) { + ecs_enum_constant_t *constant = ecs_map_ptr(&mit); + result->bytes_reflection += ecs_os_strlen(constant->name); + } + } + } + } + { + ecs_iter_t it = ecs_each(world, EcsTypeSerializer); + while (ecs_each_next(&it)) { + EcsTypeSerializer *s = ecs_field(&it, EcsTypeSerializer, 0); + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + int32_t o, ocount = ecs_vec_count(&s[i].ops); + ecs_meta_op_t *ops = ecs_vec_first(&s[i].ops); + + result->bytes_reflection += ecs_vec_size(&s[i].ops) * + ECS_SIZEOF(ecs_meta_op_t); + + for (o = 0; o < ocount; o ++) { + ecs_meta_op_t *op = &ops[o]; + if (op->kind == EcsOpPushStruct) { + result->bytes_reflection += flecs_hashmap_memory_get( + op->is.members); + } else if (op->kind == EcsOpEnum || + op->kind == EcsOpBitmask) + { + result->bytes_reflection += flecs_map_memory_get( + op->is.constants, 0); + } + } + } + } + } +} #endif +static +void flecs_stats_memory_get( + const ecs_world_t *world, + ecs_misc_memory_t *result) +{ + ecs_entity_t periods[] = { + EcsPeriod1s, EcsPeriod1m, EcsPeriod1h, EcsPeriod1d, EcsPeriod1w}; + int32_t p, period_count = 5; -#ifdef FLECS_JSON + result->bytes_stats += ECS_SIZEOF(ecs_world_stats_t) * ecs_count_id(world, + ecs_pair_t(EcsWorldStats, EcsWildcard)); -void flecs_json_next( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_next(buf); + for (p = 0; p < period_count; p ++) { + ecs_iter_t it = ecs_each_id(world, + ecs_pair_t(EcsSystemStats, periods[p])); + while (ecs_each_next(&it)) { + EcsSystemStats *ss = ecs_field(&it, EcsSystemStats, 0); + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + result->bytes_stats += flecs_map_memory_get(&ss[i].stats, + ECS_SIZEOF(ecs_system_stats_t)); + } + } + } + for (p = 0; p < period_count; p ++) { + ecs_iter_t it = ecs_each_id(world, + ecs_pair_t(EcsPipelineStats, periods[p])); + while (ecs_each_next(&it)) { + EcsPipelineStats *ps = ecs_field(&it, EcsPipelineStats, 0); + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + result->bytes_stats += flecs_map_memory_get(&ps[i].stats, + ECS_SIZEOF(ecs_pipeline_stats_t)); + + ecs_map_iter_t pit = ecs_map_iter(&ps[i].stats); + while (ecs_map_next(&pit)) { + ecs_pipeline_stats_t *stats = ecs_map_ptr(&pit); + result->bytes_stats += ecs_vec_size(&stats->systems) * + ECS_SIZEOF(ecs_entity_t); + result->bytes_stats += ecs_vec_size(&stats->sync_points) * + ECS_SIZEOF(ecs_sync_stats_t); + } + } + } + } } -void flecs_json_literal( - ecs_strbuf_t *buf, - const char *value) +#ifdef FLECS_REST +static +void flecs_http_memory_get( + ecs_http_server_t *srv, + ecs_misc_memory_t *result) { - ecs_strbuf_appendstr(buf, value); -} + result->bytes_rest += ECS_SIZEOF(ecs_http_server_t); -void flecs_json_number( - ecs_strbuf_t *buf, - double value) -{ - ecs_strbuf_appendflt(buf, value, '"'); -} + result->bytes_rest += flecs_sparse_memory_get(&srv->connections, + ECS_SIZEOF(ecs_http_connection_t)); -void flecs_json_true( - ecs_strbuf_t *buf) -{ - flecs_json_literal(buf, "true"); -} + result->bytes_rest += flecs_sparse_memory_get(&srv->requests, + ECS_SIZEOF(ecs_http_request_t)); -void flecs_json_false( - ecs_strbuf_t *buf) -{ - flecs_json_literal(buf, "false"); -} + result->bytes_rest += flecs_hashmap_memory_get(&srv->request_cache); -void flecs_json_bool( - ecs_strbuf_t *buf, - bool value) -{ - if (value) { - flecs_json_true(buf); - } else { - flecs_json_false(buf); + ecs_map_iter_t it = ecs_map_iter(&srv->request_cache.impl); + while (ecs_map_next(&it)) { + ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); + int32_t i, count = ecs_vec_count(&bucket->values); + ecs_http_request_key_t *keys = ecs_vec_first(&bucket->keys); + ecs_http_request_entry_t *entries = ecs_vec_first(&bucket->values); + for (i = count - 1; i >= 0; i --) { + ecs_http_request_entry_t *entry = &entries[i]; + ecs_http_request_key_t *key = &keys[i]; + + result->bytes_rest += key->count; + if (entry->content) { + result->bytes_rest += ecs_os_strlen(entry->content); + } + } } } +#endif -void flecs_json_array_push( - ecs_strbuf_t *buf) +#ifdef FLECS_REST +static +void flecs_rest_memory_get( + const ecs_world_t *world, + ecs_misc_memory_t *result) { - ecs_strbuf_list_push(buf, "[", ", "); + ecs_iter_t it = ecs_each(world, EcsRest); + while (ecs_each_next(&it)) { + EcsRest *r = ecs_field(&it, EcsRest, 0); + + int32_t i, count = it.count; + for (i = 0; i < count; i ++) { + result->bytes_rest += ECS_SIZEOF(ecs_rest_ctx_t); + if (!r[i].impl) { + continue; + } + flecs_http_memory_get(r[i].impl->srv, result); + } + } } +#endif -void flecs_json_array_pop( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_pop(buf, "]"); -} +static +void flecs_tree_spawner_memory_get( + const ecs_world_t *world, + ecs_misc_memory_t *result) +{ + ecs_iter_t it = ecs_each(world, EcsTreeSpawner); + while (ecs_each_next(&it)) { + EcsTreeSpawner *t = ecs_field(&it, EcsTreeSpawner, 0); + for (int32_t i = 0; i < it.count; i ++) { + for (int32_t depth = 0; + depth < FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE; + depth ++) + { + result->bytes_tree_spawner += + ecs_vec_count(&t[i].data[depth].children) * + ECS_SIZEOF(ecs_tree_spawner_child_t); + } + } + } -void flecs_json_object_push( - ecs_strbuf_t *buf) -{ - ecs_strbuf_list_push(buf, "{", ", "); + result->bytes_tree_spawner += + ecs_vec_count(&world->allocators.tree_spawner) * + ECS_SIZEOF(ecs_entity_t); } -void flecs_json_object_pop( - ecs_strbuf_t *buf) +ecs_misc_memory_t ecs_misc_memory_get( + const ecs_world_t *world) { - ecs_strbuf_list_pop(buf, "}"); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_misc_memory_t result = {0}; + + int32_t stage_count = world->stage_count; + ecs_stage_t **stages = world->stages; -void flecs_json_string( - ecs_strbuf_t *buf, - const char *value) -{ - ecs_strbuf_appendch(buf, '"'); - ecs_strbuf_appendstr(buf, value); - ecs_strbuf_appendch(buf, '"'); -} + result.bytes_world = ECS_SIZEOF(ecs_world_t) + + ECS_SIZEOF(ecs_stage_t) * stage_count + + ECS_SIZEOF(ecs_stage_t*) * stage_count; + + result.bytes_observers = flecs_observers_memory_get(world); + result.bytes_observers = flecs_observer_index_memory_get(world); + flecs_system_memory_get(world, &result); + flecs_pipeline_memory_get(world, &result); + flecs_rematch_monitor_memory_get(world, &result); -void flecs_json_member( - ecs_strbuf_t *buf, - const char *name) -{ - ecs_strbuf_list_appendstr(buf, "\""); - ecs_strbuf_appendstr(buf, name); - ecs_strbuf_appendstr(buf, "\":"); -} + #ifdef FLECS_META + flecs_reflection_memory_get(world, &result); + #endif + flecs_stats_memory_get(world, &result); + #ifdef FLECS_REST + flecs_rest_memory_get(world, &result); + #endif + flecs_tree_spawner_memory_get(world, &result); -void flecs_json_path( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e) -{ - ecs_strbuf_appendch(buf, '"'); - ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf); - ecs_strbuf_appendch(buf, '"'); -} + result.bytes_prefab_child_indices = flecs_map_memory_get( + &world->prefab_child_indices, 0); + + result.bytes_component_ids += + ecs_vec_size(&world->component_ids) * ECS_SIZEOF(ecs_entity_t); + + result.bytes_table_lookup += + flecs_hashmap_memory_get(&world->store.table_map); + + result.bytes_component_record_lookup += FLECS_HI_ID_RECORD_ID * + ECS_SIZEOF(ecs_component_record_t*); + + result.bytes_component_record_lookup += flecs_map_memory_get( + &world->id_index_hi, 0); -void flecs_json_label( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e) -{ - const char *lbl = NULL; -#ifdef FLECS_DOC - lbl = ecs_doc_get_name(world, e); -#else - lbl = ecs_get_name(world, e); -#endif + result.bytes_type_info += flecs_map_memory_get( + &world->type_info, ECS_SIZEOF(ecs_type_info_t)); - if (lbl) { - ecs_strbuf_appendch(buf, '"'); - ecs_strbuf_appendstr(buf, lbl); - ecs_strbuf_appendch(buf, '"'); - } else { - ecs_strbuf_appendstr(buf, "0"); + /* Iterate through all stages to collect command memory usage */ + for (int32_t i = 0; i < stage_count; i++) { + ecs_stage_t *stage = stages[i]; + for (int32_t j = 0; j < 2; j++) { + ecs_commands_t *cmd = &stage->cmd_stack[j]; + + /* Calculate queue memory (ecs_vec_t) */ + result.bytes_commands += + ecs_vec_size(&cmd->queue) * ECS_SIZEOF(ecs_cmd_t); + + /* Calculate entries memory (ecs_sparse_t) */ + ecs_sparse_t *entries = &cmd->entries; + result.bytes_commands += flecs_sparse_memory_get(entries, + ECS_SIZEOF(ecs_cmd_entry_t)); + + /* Calculate stack memory (ecs_stack_t) */ + result.bytes_commands += flecs_stack_memory_get(&cmd->stack); + } } + +#ifdef FLECS_DEBUG + result.bytes_locked_components += flecs_map_memory_get( + &world->locked_components, 0); + result.bytes_locked_components += flecs_map_memory_get( + &world->locked_entities, 0); +#endif + +error: + return result; } -void flecs_json_id( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_id_t id) +ecs_allocator_memory_t ecs_allocator_memory_get( + const ecs_world_t *world) { - ecs_strbuf_appendch(buf, '"'); - ecs_id_str_buf(world, id, buf); - ecs_strbuf_appendch(buf, '"'); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_allocator_memory_t result = {0}; + + result.bytes_graph_edge = flecs_ballocator_memory_get( + &world->allocators.graph_edge_lo); + result.bytes_graph_edge += flecs_ballocator_memory_get( + &world->allocators.graph_edge); + result.bytes_component_record = flecs_ballocator_memory_get( + &world->allocators.component_record); + result.bytes_pair_record = flecs_ballocator_memory_get( + &world->allocators.pair_record); + result.bytes_table_diff = flecs_ballocator_memory_get( + &world->allocators.table_diff); + result.bytes_sparse_chunk = flecs_ballocator_memory_get( + &world->allocators.sparse_chunk); + + result.bytes_allocator = flecs_allocator_memory_get(&world->allocator); + result.bytes_misc += ecs_vec_size(&world->allocators.diff_builder.added) * + ECS_SIZEOF(ecs_id_t); + + result.bytes_misc += ecs_vec_size(&world->allocators.diff_builder.removed) * + ECS_SIZEOF(ecs_id_t); + result.bytes_misc += ecs_vec_size(&world->store.records) * + ECS_SIZEOF(ecs_table_record_t); + result.bytes_misc += ecs_vec_size(&world->store.marked_ids) * + ECS_SIZEOF(ecs_marked_id_t); + result.bytes_misc += ecs_vec_size(&world->store.deleted_components) * + ECS_SIZEOF(ecs_entity_t); + + int32_t i, stage_count = world->stage_count; + ecs_stage_t **stages = world->stages; + for (i = 0; i < stage_count; i++) { + ecs_stage_t *stage = stages[i]; + result.bytes_allocator += flecs_allocator_memory_get(&stage->allocator); + result.bytes_cmd_entry_chunk += flecs_ballocator_memory_get( + &stage->allocators.cmd_entry_chunk); + result.bytes_query_impl += flecs_ballocator_memory_get( + &stage->allocators.query_impl); + result.bytes_query_cache += flecs_ballocator_memory_get( + &stage->allocators.query_cache); + result.bytes_misc += ecs_vec_size(&stage->variables) * + ECS_SIZEOF(ecs_query_var_t); + result.bytes_misc += ecs_vec_size(&stage->operations) * + ECS_SIZEOF(ecs_query_op_t); + result.bytes_stack_allocator += + flecs_stack_memory_get(&stage->allocators.iter_stack); + } + +error: + return result; } -ecs_primitive_kind_t flecs_json_op_to_primitive_kind( - ecs_meta_type_op_kind_t kind) +#ifdef FLECS_META +static +int flecs_world_memory_serialize( + const ecs_serializer_t *s, + const void *ptr) { - return kind - EcsOpPrimitive; + (void)ptr; /* Unused, we compute values from world */ + const ecs_world_t *world = s->world; + + flecs_poly_assert(world, ecs_world_t); + + EcsWorldMemory value; + ecs_os_zeromem(&value); + + ecs_time_t t = {0}; + ecs_time_measure(&t); + + value.entities = ecs_entity_memory_get(world); + value.components = ecs_component_memory_get(world); + value.component_index = ecs_component_index_memory_get(world); + value.queries = ecs_queries_memory_get(world); + value.tables = ecs_tables_memory_get(world); + value.table_histogram = ecs_table_histogram_get(world); + value.misc = ecs_misc_memory_get(world); + value.allocators = ecs_allocator_memory_get(world); + + /* Use component IDs directly */ + s->member(s, "entities"); + s->value(s, ecs_id(ecs_entities_memory_t), &value.entities); + s->member(s, "components"); + s->value(s, ecs_id(ecs_component_memory_t), &value.components); + s->member(s, "component_index"); + s->value(s, ecs_id(ecs_component_index_memory_t), &value.component_index); + s->member(s, "queries"); + s->value(s, ecs_id(ecs_query_memory_t), &value.queries); + s->member(s, "tables"); + s->value(s, ecs_id(ecs_table_memory_t), &value.tables); + s->member(s, "table_histogram"); + s->value(s, ecs_id(ecs_table_histogram_t), &value.table_histogram); + s->member(s, "misc"); + s->value(s, ecs_id(ecs_misc_memory_t), &value.misc); + s->member(s, "allocators"); + s->value(s, ecs_id(ecs_allocator_memory_t), &value.allocators); + + value.collection_time = ecs_time_measure(&t); + + return 0; } #endif +void flecs_stats_memory_register_reflection( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsWorldMemory); -#include +#ifdef FLECS_META + ecs_entity_t unit = 0; + #ifdef FLECS_UNITS + unit = EcsBytes; + #endif -#ifdef FLECS_JSON + /* Register struct reflection for memory types */ + ecs_id(ecs_entities_memory_t) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "entity_index_memory_t" }), + .members = { + { .name = "alive_count", .type = ecs_id(ecs_i32_t) }, + { .name = "not_alive_count", .type = ecs_id(ecs_i32_t) }, + { .name = "bytes_entity_index", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_names", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_doc_strings", .type = ecs_id(ecs_i32_t), .unit = unit } + } + }); -static -int json_ser_type( - const ecs_world_t *world, - ecs_vector_t *ser, - const void *base, - ecs_strbuf_t *str); + ecs_id(ecs_component_memory_t) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "component_memory_t" }), + .members = { + { .name = "instances", .type = ecs_id(ecs_i32_t) }, + { .name = "bytes_table_components", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_table_components_unused", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_toggle_bitset", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_sparse_components", .type = ecs_id(ecs_i32_t), .unit = unit } + } + }); -static -int json_ser_type_ops( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - ecs_strbuf_t *str); + ecs_id(ecs_component_index_memory_t) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "component_index_memory_t" }), + .members = { + { .name = "count", .type = ecs_id(ecs_i32_t) }, + { .name = "bytes_component_record", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_table_cache", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_name_index", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_ordered_children", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_children_table_map", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_reachable_cache", .type = ecs_id(ecs_i32_t), .unit = unit } + } + }); -static -int json_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str); + ecs_id(ecs_query_memory_t) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "query_memory_t" }), + .members = { + { .name = "count", .type = ecs_id(ecs_i32_t) }, + { .name = "cached_count", .type = ecs_id(ecs_i32_t) }, + { .name = "bytes_query", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_cache", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_group_by", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_order_by", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_plan", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_terms", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_misc", .type = ecs_id(ecs_i32_t), .unit = unit } + } + }); -/* Serialize enumeration */ -static -int json_ser_enum( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str) -{ - const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); - ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_id(ecs_table_memory_t) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "table_memory_t" }), + .members = { + { .name = "count", .type = ecs_id(ecs_i32_t) }, + { .name = "empty_count", .type = ecs_id(ecs_i32_t) }, + { .name = "column_count", .type = ecs_id(ecs_i32_t) }, + { .name = "bytes_table", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_type", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_entities", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_overrides", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_column_map", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_component_map", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_dirty_state", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_edges", .type = ecs_id(ecs_i32_t), .unit = unit } + } + }); - int32_t value = *(int32_t*)base; - - /* Enumeration constants are stored in a map that is keyed on the - * enumeration value. */ - ecs_enum_constant_t *constant = ecs_map_get( - enum_type->constants, ecs_enum_constant_t, value); - if (!constant) { - goto error; - } + ecs_id(ecs_table_histogram_t) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "table_histogram_t" }), + .members = { + { .name = "entity_counts", .type = ecs_id(ecs_i32_t), .count = ECS_TABLE_MEMORY_HISTOGRAM_BUCKET_COUNT } + } + }); - ecs_strbuf_appendch(str, '"'); - ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); - ecs_strbuf_appendch(str, '"'); + ecs_id(ecs_misc_memory_t) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "misc_memory_t" }), + .members = { + { .name = "bytes_world", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_observers", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_systems", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_pipelines", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_table_lookup", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_component_record_lookup", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_locked_components", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_type_info", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_commands", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_rematch_monitor", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_component_ids", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_reflection", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_tree_spawner", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_prefab_child_indices", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_stats", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_rest", .type = ecs_id(ecs_i32_t), .unit = unit }, + } + }); - return 0; -error: - return -1; + ecs_id(ecs_allocator_memory_t) = ecs_struct(world, { + .entity = ecs_entity(world, { .name = "allocator_memory_t" }), + .members = { + { .name = "bytes_graph_edge", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_component_record", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_pair_record", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_table_diff", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_sparse_chunk", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_allocator", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_stack_allocator", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_cmd_entry_chunk", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_query_impl", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_query_cache", .type = ecs_id(ecs_i32_t), .unit = unit }, + { .name = "bytes_misc", .type = ecs_id(ecs_i32_t), .unit = unit } + } + }); + + /* Register EcsWorldMemory as opaque type that computes on demand */ + ecs_entity_t world_memory_struct = ecs_struct(world, { + .members = { + { .name = "entities", .type = ecs_id(ecs_entities_memory_t) }, + { .name = "components", .type = ecs_id(ecs_component_memory_t) }, + { .name = "component_index", .type = ecs_id(ecs_component_index_memory_t) }, + { .name = "queries", .type = ecs_id(ecs_query_memory_t) }, + { .name = "tables", .type = ecs_id(ecs_table_memory_t) }, + { .name = "table_histogram", .type = ecs_id(ecs_table_histogram_t) }, + { .name = "misc", .type = ecs_id(ecs_misc_memory_t) }, + { .name = "allocators", .type = ecs_id(ecs_allocator_memory_t) }, + { .name = "collection_time", .type = ecs_id(ecs_f32_t) } + } + }); + + ecs_opaque(world, { + .entity = ecs_id(EcsWorldMemory), + .type.as_type = world_memory_struct, + .type.serialize = flecs_world_memory_serialize + }); +#endif } -/* Serialize bitmask */ static -int json_ser_bitmask( +ecs_size_t flecs_get_memory_total( const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) + const void *ptr, + ecs_entity_t type) { - const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); - ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); - - uint32_t value = *(uint32_t*)ptr; - ecs_map_key_t key; - ecs_bitmask_constant_t *constant; + (void)world; + (void)ptr; + (void)type; - if (!value) { - ecs_strbuf_appendch(str, '0'); + if (!type) { + ecs_warn("import flecs.stats module before calling ecs_memory_get"); return 0; } - ecs_strbuf_list_push(str, "\"", "|"); + ecs_size_t result = 0; +#ifdef FLECS_META + const EcsTypeSerializer *s = ecs_get(world, type, EcsTypeSerializer); + ecs_meta_op_t *ops = ecs_vec_first(&s->ops); + int32_t i, count = ecs_vec_count(&s->ops); - /* Multiple flags can be set at a given time. Iterate through all the flags - * and append the ones that are set. */ - ecs_map_iter_t it = ecs_map_iter(bitmask_type->constants); - while ((constant = ecs_map_next(&it, ecs_bitmask_constant_t, &key))) { - if ((value & key) == key) { - ecs_strbuf_list_appendstr(str, - ecs_get_name(world, constant->constant)); - value -= (uint32_t)key; + for (i = 0; i < count; i ++) { + ecs_meta_op_t *op = &ops[i]; + if (!op->name) { + continue; } - } - if (value != 0) { - /* All bits must have been matched by a constant */ - goto error; + if (!ecs_os_strncmp(op->name, "bytes_", 6)) { + result += *(ecs_size_t*)ECS_OFFSET(ptr, op->offset); + } } +#else + ecs_warn("FLECS_META addon required for ecs_memory_get"); +#endif - ecs_strbuf_list_pop(str, "\""); - - return 0; -error: - return -1; + return result; } -/* Serialize elements of a contiguous array */ -static -int json_ser_elements( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - int32_t elem_count, - int32_t elem_size, - ecs_strbuf_t *str) +ecs_size_t ecs_memory_get( + const ecs_world_t *world) { - flecs_json_array_push(str); + flecs_poly_assert(world, ecs_world_t); + + ecs_entities_memory_t entities = {0}; + ecs_component_memory_t component = {0}; + ecs_component_index_memory_t component_index = {0}; + ecs_query_memory_t query = {0}; + ecs_table_memory_t table = {0}; + ecs_misc_memory_t misc = {0}; + ecs_allocator_memory_t allocator = {0}; + + entities = ecs_entity_memory_get(world); + component = ecs_component_memory_get(world); + component_index = ecs_component_index_memory_get(world); + query = ecs_queries_memory_get(world); + table = ecs_tables_memory_get(world); + misc = ecs_misc_memory_get(world); + allocator = ecs_allocator_memory_get(world); + + ecs_size_t result = 0; + result += flecs_get_memory_total(world, &entities, + ecs_id(ecs_entities_memory_t)); + result += flecs_get_memory_total(world, &component, + ecs_id(ecs_component_memory_t)); + result += flecs_get_memory_total(world, &component_index, + ecs_id(ecs_component_index_memory_t)); + result += flecs_get_memory_total(world, &query, + ecs_id(ecs_query_memory_t)); + result += flecs_get_memory_total(world, &table, + ecs_id(ecs_table_memory_t)); + result += flecs_get_memory_total(world, &misc, + ecs_id(ecs_misc_memory_t)); + result += flecs_get_memory_total(world, &allocator, + ecs_id(ecs_allocator_memory_t)); + return result; +} - const void *ptr = base; +#endif - int i; - for (i = 0; i < elem_count; i ++) { - ecs_strbuf_list_next(str); - if (json_ser_type_ops(world, ops, op_count, ptr, str)) { - return -1; - } - ptr = ECS_OFFSET(ptr, elem_size); - } +/** + * @file addons/monitor.c + * @brief Stats addon module. + */ - flecs_json_array_pop(str); - return 0; -} +#ifdef FLECS_STATS -static -int json_ser_type_elements( - const ecs_world_t *world, - ecs_entity_t type, - const void *base, - int32_t elem_count, - ecs_strbuf_t *str) -{ - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); +ECS_COMPONENT_DECLARE(FlecsStats); - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); +ecs_entity_t EcsPeriod1s = 0; +ecs_entity_t EcsPeriod1m = 0; +ecs_entity_t EcsPeriod1h = 0; +ecs_entity_t EcsPeriod1d = 0; +ecs_entity_t EcsPeriod1w = 0; - ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t); - int32_t op_count = ecs_vector_count(ser->ops); +#define FlecsDayIntervalCount (24) +#define FlecsWeekIntervalCount (168) - return json_ser_elements( - world, ops, op_count, base, elem_count, comp->size, str); -} +typedef struct { + ecs_stats_api_t api; + ecs_query_t *query; +} ecs_monitor_stats_ctx_t; -/* Serialize array */ -static -int json_ser_array( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) -{ - const EcsArray *a = ecs_get(world, op->type, EcsArray); - ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); +typedef struct { + ecs_stats_api_t api; +} ecs_reduce_stats_ctx_t; - return json_ser_type_elements( - world, a->type, ptr, a->count, str); -} +typedef struct { + ecs_stats_api_t api; + int32_t interval; +} ecs_aggregate_stats_ctx_t; -/* Serialize vector */ static -int json_ser_vector( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str) -{ - ecs_vector_t *value = *(ecs_vector_t**)base; - if (!value) { - ecs_strbuf_appendstr(str, "null"); - return 0; +void MonitorStats(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + ecs_monitor_stats_ctx_t *ctx = it->ctx; + + EcsStatsHeader *hdr = ecs_field_w_size(it, ecs_field_size(it, 0), 0); + + ecs_ftime_t elapsed = hdr->elapsed; + hdr->elapsed += it->delta_time; + + int32_t t_last = (int32_t)(elapsed * 60); + int32_t t_next = (int32_t)(hdr->elapsed * 60); + int32_t i, dif = t_next - t_last; + void *stats_storage = ecs_os_alloca(ctx->api.stats_size); + void *last = NULL; + + if (!dif) { + hdr->reduce_count ++; } - const EcsVector *v = ecs_get(world, op->type, EcsVector); - ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_iter_t qit; + int32_t cur = -1, count = 0; + void *stats = NULL; + ecs_map_t *stats_map = NULL; - const EcsComponent *comp = ecs_get(world, v->type, EcsComponent); - ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); + if (ctx->query) { + /* Query results are stored in a map */ + qit = ecs_query_iter(it->world, ctx->query); + stats_map = ECS_OFFSET_T(hdr, EcsStatsHeader); + } else { + /* No query, so tracking stats for single element */ + stats = ECS_OFFSET_T(hdr, EcsStatsHeader); + if (ctx->api.is_pointer) { + stats = *((void**)stats); + } + } - int32_t count = ecs_vector_count(value); - void *array = ecs_vector_first_t(value, comp->size, comp->alignment); + do { + ecs_entity_t res = 0; + if (ctx->query) { + /* Query, fetch resource entity & stats pointer */ + if (cur == (count - 1)) { + if (!ecs_query_next(&qit)) { + break; + } - /* Serialize contiguous buffer of vector */ - return json_ser_type_elements(world, v->type, array, count, str); -} + cur = 0; + count = qit.count; + if (!count) { + cur = -1; + continue; + } + } else { + cur ++; + } -/* Forward serialization to the different type kinds */ -static -int json_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) -{ - switch(op->kind) { - case EcsOpPush: - case EcsOpPop: - /* Should not be parsed as single op */ - ecs_throw(ECS_INVALID_PARAMETER, NULL); - break; - case EcsOpF32: - ecs_strbuf_appendflt(str, - (ecs_f64_t)*(ecs_f32_t*)ECS_OFFSET(ptr, op->offset), '"'); - break; - case EcsOpF64: - ecs_strbuf_appendflt(str, - *(ecs_f64_t*)ECS_OFFSET(ptr, op->offset), '"'); - break; - case EcsOpEnum: - if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; + res = qit.entities[cur]; + stats = ecs_map_ensure_alloc(stats_map, ctx->api.stats_size, res); + ctx->api.set_t(stats, t_last % ECS_STAT_WINDOW); } - break; - case EcsOpBitmask: - if (json_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; + + if (!dif) { + /* Copy last value so we can pass it to reduce_last */ + last = stats_storage; + ecs_os_memset(last, 0, ctx->api.stats_size); + ctx->api.copy_last(last, stats); } - break; - case EcsOpArray: - if (json_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; + + ctx->api.get(world, res, stats); + + if (!dif) { + /* Still in same interval, combine with last measurement */ + ctx->api.reduce_last(stats, last, hdr->reduce_count); + } else if (dif > 1) { + /* More than 16ms has passed, backfill */ + for (i = 1; i < dif; i ++) { + ctx->api.repeat_last(stats); + } } - break; - case EcsOpVector: - if (json_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; + + if (last && ctx->api.fini) { + ctx->api.fini(last); } - break; - case EcsOpEntity: { - ecs_entity_t e = *(ecs_entity_t*)ECS_OFFSET(ptr, op->offset); - if (!e) { - ecs_strbuf_appendch(str, '0'); - } else { - flecs_json_path(str, world, e); + + if (!ctx->query) { + break; } - break; + } while (true); + + if (dif > 1) { + hdr->reduce_count = 0; } +} - default: - if (ecs_primitive_to_expr_buf(world, - flecs_json_op_to_primitive_kind(op->kind), - ECS_OFFSET(ptr, op->offset), str)) - { - /* Unknown operation */ - ecs_throw(ECS_INTERNAL_ERROR, NULL); - return -1; +static +void ReduceStats(ecs_iter_t *it) { + ecs_reduce_stats_ctx_t *ctx = it->ctx; + + void *dst = ecs_field_w_size(it, ecs_field_size(it, 0), 0); + void *src = ecs_field_w_size(it, ecs_field_size(it, 1), 1); + + dst = ECS_OFFSET_T(dst, EcsStatsHeader); + src = ECS_OFFSET_T(src, EcsStatsHeader); + + if (!ctx->api.query_component_id) { + if (ctx->api.is_pointer) { + dst = *((void**)dst); + src = *((void**)src); + } + ctx->api.reduce(dst, src); + } else { + ecs_map_iter_t mit = ecs_map_iter(src); + while (ecs_map_next(&mit)) { + void *src_el = ecs_map_ptr(&mit); + void *dst_el = ecs_map_ensure_alloc( + dst, ctx->api.stats_size, ecs_map_key(&mit)); + ctx->api.reduce(dst_el, src_el); } - break; } - - return 0; -error: - return -1; } -/* Iterate over a slice of the type ops array */ static -int json_ser_type_ops( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - const void *base, - ecs_strbuf_t *str) -{ - for (int i = 0; i < op_count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; +void AggregateStats(ecs_iter_t *it) { + ecs_aggregate_stats_ctx_t *ctx = it->ctx; + int32_t interval = ctx->interval; - if (op != ops) { - if (op->name) { - flecs_json_member(str, op->name); - } + EcsStatsHeader *dst_hdr = ecs_field_w_size(it, ecs_field_size(it, 0), 0); + EcsStatsHeader *src_hdr = ecs_field_w_size(it, ecs_field_size(it, 1), 1); - int32_t elem_count = op->count; - if (elem_count > 1 && op != ops) { - /* Serialize inline array */ - if (json_ser_elements(world, op, op->op_count, base, - elem_count, op->size, str)) - { - return -1; - } + void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); + void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); + void *dst_map = NULL; + void *src_map = NULL; + if (ctx->api.query_component_id) { + dst_map = dst; + src_map = src; + dst = NULL; + src = NULL; + } else { + if (ctx->api.is_pointer) { + dst = *((void**)dst); + src = *((void**)src); + } + } - i += op->op_count - 1; - continue; + void *stats_storage = ecs_os_alloca(ctx->api.stats_size); + void *last = NULL; + + ecs_map_iter_t mit; + if (src_map) { + mit = ecs_map_iter(src_map); + } + + do { + if (src_map) { + if (!ecs_map_next(&mit)) { + break; } + + src = ecs_map_ptr(&mit); + dst = ecs_map_ensure_alloc( + dst_map, ctx->api.stats_size, ecs_map_key(&mit)); } - - switch(op->kind) { - case EcsOpPush: - flecs_json_object_push(str); - break; - case EcsOpPop: - flecs_json_object_pop(str); - break; - default: - if (json_ser_type_op(world, op, base, str)) { - goto error; - } + + if (dst_hdr->reduce_count != 0) { + /* Copy last value so we can pass it to reduce_last */ + last = stats_storage; + ecs_os_memset(last, 0, ctx->api.stats_size); + ctx->api.copy_last(last, dst); + } + + /* Reduce from minutes to the current day */ + ctx->api.reduce(dst, src); + + if (dst_hdr->reduce_count != 0) { + ctx->api.reduce_last(dst, last, dst_hdr->reduce_count); + } + + if (last && ctx->api.fini != NULL) { + ctx->api.fini(last); + } + + if (!src_map) { break; } + } while (true); + + /* A day has 60 24 minute intervals */ + dst_hdr->reduce_count ++; + if (dst_hdr->reduce_count >= interval) { + dst_hdr->reduce_count = 0; } +} - return 0; -error: - return -1; +static +void flecs_monitor_ctx_free( + void *ptr) +{ + ecs_monitor_stats_ctx_t *ctx = ptr; + if (ctx->query) { + ecs_query_fini(ctx->query); + } + ecs_os_free(ctx); } -/* Iterate over the type ops of a type */ static -int json_ser_type( - const ecs_world_t *world, - ecs_vector_t *v_ops, - const void *base, - ecs_strbuf_t *str) +void flecs_reduce_ctx_free( + void *ptr) { - ecs_meta_type_op_t *ops = ecs_vector_first(v_ops, ecs_meta_type_op_t); - int32_t count = ecs_vector_count(v_ops); - return json_ser_type_ops(world, ops, count, base, str); + ecs_os_free(ptr); } static -int array_to_json_buf_w_type_data( - const ecs_world_t *world, - const void *ptr, - int32_t count, - ecs_strbuf_t *buf, - const EcsComponent *comp, - const EcsMetaTypeSerialized *ser) +void flecs_aggregate_ctx_free( + void *ptr) { - if (count) { - ecs_size_t size = comp->size; + ecs_os_free(ptr); +} - flecs_json_array_push(buf); +void flecs_stats_api_import( + ecs_world_t *world, + ecs_stats_api_t *api) +{ + ecs_entity_t kind = api->monitor_component_id; + ecs_entity_t prev = ecs_set_scope(world, kind); - do { - ecs_strbuf_list_next(buf); - if (json_ser_type(world, ser->ops, ptr, buf)) { - return -1; - } + ecs_query_t *q = NULL; + if (api->query_component_id) { + q = ecs_query(world, { + .terms = {{ .id = api->query_component_id }}, + .cache_kind = EcsQueryCacheNone, + .flags = EcsQueryMatchDisabled + }); + } - ptr = ECS_OFFSET(ptr, size); - } while (-- count); + // Called each frame, collects 60 measurements per second + { + ecs_monitor_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_monitor_stats_ctx_t); + ctx->api = *api; + ctx->query = q; + + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1s", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1s), + .src.id = EcsWorld + }}, + .callback = MonitorStats, + .ctx = ctx, + .ctx_free = flecs_monitor_ctx_free + }); + } - flecs_json_array_pop(buf); - } else { - if (json_ser_type(world, ser->ops, ptr, buf)) { - return -1; - } + // Called each second, reduces into 60 measurements per minute + ecs_entity_t mw1m; + { + ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); + ctx->api = *api; + + mw1m = ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1m", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1s), + .src.id = EcsWorld + }}, + .callback = ReduceStats, + .interval = 1.0, + .ctx = ctx, + .ctx_free = flecs_reduce_ctx_free + }); } - return 0; + // Called each minute, reduces into 60 measurements per hour + { + ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); + ctx->api = *api; + + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1h", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1h), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }}, + .callback = ReduceStats, + .rate = 60, + .tick_source = mw1m, + .ctx = ctx, + .ctx_free = flecs_reduce_ctx_free + }); + } + + // Called each minute, reduces into 60 measurements per day + { + ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); + ctx->api = *api; + ctx->interval = FlecsDayIntervalCount; + + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1d", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1d), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1m), + .src.id = EcsWorld + }}, + .callback = AggregateStats, + .rate = 60, + .tick_source = mw1m, + .ctx = ctx, + .ctx_free = flecs_aggregate_ctx_free + }); + } + + // Called each hour, reduces into 60 measurements per week + { + ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); + ctx->api = *api; + ctx->interval = FlecsWeekIntervalCount; + + ecs_system(world, { + .entity = ecs_entity(world, { .name = "Monitor1w", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), + .query.terms = {{ + .id = ecs_pair(kind, EcsPeriod1w), + .src.id = EcsWorld + }, { + .id = ecs_pair(kind, EcsPeriod1h), + .src.id = EcsWorld + }}, + .callback = AggregateStats, + .rate = 60, + .tick_source = mw1m, + .ctx = ctx, + .ctx_free = flecs_aggregate_ctx_free + }); + } + + ecs_set_scope(world, prev); + + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1s); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1m); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1h); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1d); + ecs_add_pair(world, EcsWorld, kind, EcsPeriod1w); } -int ecs_array_to_json_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *ptr, - int32_t count, - ecs_strbuf_t *buf) +void FlecsStatsImport( + ecs_world_t *world) { - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - if (!comp) { - char *path = ecs_get_fullpath(world, type); - ecs_err("cannot serialize to JSON, '%s' is not a component", path); - ecs_os_free(path); - return -1; + ECS_MODULE_DEFINE(world, FlecsStats); + ECS_IMPORT(world, FlecsPipeline); + ECS_IMPORT(world, FlecsTimer); +#ifdef FLECS_META + ECS_IMPORT(world, FlecsMeta); +#endif + + ecs_set_name_prefix(world, "Ecs"); + + EcsPeriod1s = ecs_entity(world, { .name = "EcsPeriod1s" }); + EcsPeriod1m = ecs_entity(world, { .name = "EcsPeriod1m" }); + EcsPeriod1h = ecs_entity(world, { .name = "EcsPeriod1h" }); + EcsPeriod1d = ecs_entity(world, { .name = "EcsPeriod1d" }); + EcsPeriod1w = ecs_entity(world, { .name = "EcsPeriod1w" }); + + FlecsWorldSummaryImport(world); + FlecsWorldMonitorImport(world); + FlecsSystemMonitorImport(world); + FlecsPipelineMonitorImport(world); + + flecs_stats_memory_register_reflection(world); + + ecs_add(world, ecs_id(EcsWorldMemory), EcsWorldMemory); + + if (ecs_os_has_time()) { + ecs_measure_frame_time(world, true); + ecs_measure_system_time(world, true); } +} - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (!ser) { - char *path = ecs_get_fullpath(world, type); - ecs_err("cannot serialize to JSON, '%s' has no reflection data", path); - ecs_os_free(path); - return -1; +#endif + +/** + * @file addons/stats/pipeline_monitor.c + * @brief Stats addon pipeline monitor + */ + + +#ifdef FLECS_STATS + +ECS_COMPONENT_DECLARE(EcsPipelineStats); + +static +void flecs_pipeline_monitor_dtor(EcsPipelineStats *ptr) { + ecs_map_iter_t it = ecs_map_iter(&ptr->stats); + while (ecs_map_next(&it)) { + ecs_pipeline_stats_t *stats = ecs_map_ptr(&it); + ecs_pipeline_stats_fini(stats); + ecs_os_free(stats); } + ecs_map_fini(&ptr->stats); +} + +static ECS_CTOR(EcsPipelineStats, ptr, { + ecs_os_zeromem(ptr); + ecs_map_init(&ptr->stats, NULL); +}) + +static ECS_COPY(EcsPipelineStats, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); +}) + +static ECS_MOVE(EcsPipelineStats, dst, src, { + flecs_pipeline_monitor_dtor(dst); + ecs_os_memcpy_t(dst, src, EcsPipelineStats); + ecs_os_zeromem(src); +}) + +static ECS_DTOR(EcsPipelineStats, ptr, { + flecs_pipeline_monitor_dtor(ptr); +}) - return array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); +static +void flecs_pipeline_stats_set_t( + void *stats, int32_t t) +{ + ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); + ((ecs_pipeline_stats_t*)stats)->t = t; } -char* ecs_array_to_json( - const ecs_world_t *world, - ecs_entity_t type, - const void* ptr, - int32_t count) + +static +void flecs_pipeline_stats_copy_last( + void *stats, + void *src) { - ecs_strbuf_t str = ECS_STRBUF_INIT; + ecs_pipeline_stats_copy_last(stats, src); +} - if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { - ecs_strbuf_reset(&str); - return NULL; - } +static +void flecs_pipeline_stats_get( + ecs_world_t *world, + ecs_entity_t res, + void *stats) +{ + ecs_pipeline_stats_get(world, res, stats); +} - return ecs_strbuf_get(&str); +static +void flecs_pipeline_stats_reduce( + void *stats, + void *src) +{ + ecs_pipeline_stats_reduce(stats, src); } -int ecs_ptr_to_json_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *ptr, - ecs_strbuf_t *buf) +static +void flecs_pipeline_stats_reduce_last( + void *stats, + void *last, + int32_t reduce_count) { - return ecs_array_to_json_buf(world, type, ptr, 0, buf); + ecs_pipeline_stats_reduce_last(stats, last, reduce_count); } -char* ecs_ptr_to_json( - const ecs_world_t *world, - ecs_entity_t type, - const void* ptr) +static +void flecs_pipeline_stats_repeat_last( + void* stats) { - return ecs_array_to_json(world, type, ptr, 0); + ecs_pipeline_stats_repeat_last(stats); } static -bool skip_id( - const ecs_world_t *world, - ecs_id_t id, - const ecs_entity_to_json_desc_t *desc, - ecs_entity_t ent, - ecs_entity_t inst, - ecs_entity_t *pred_out, - ecs_entity_t *obj_out, - ecs_entity_t *role_out, - bool *hidden_out) +void flecs_pipeline_stats_fini( + void *stats) { - bool is_base = ent != inst; - ecs_entity_t pred = 0, obj = 0, role = 0; - bool hidden = false; + ecs_pipeline_stats_fini(stats); +} - if (ECS_HAS_ROLE(id, PAIR)) { - pred = ecs_pair_first(world, id); - obj = ecs_pair_second(world, id); - } else { - pred = id & ECS_COMPONENT_MASK; - if (id & ECS_ROLE_MASK) { - role = id & ECS_ROLE_MASK; - } - } +void FlecsPipelineMonitorImport( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsPipelineStats); + + ecs_set_hooks(world, EcsPipelineStats, { + .ctor = ecs_ctor(EcsPipelineStats), + .copy = ecs_copy(EcsPipelineStats), + .move = ecs_move(EcsPipelineStats), + .dtor = ecs_dtor(EcsPipelineStats) + }); + + ecs_stats_api_t api = { + .copy_last = flecs_pipeline_stats_copy_last, + .get = flecs_pipeline_stats_get, + .reduce = flecs_pipeline_stats_reduce, + .reduce_last = flecs_pipeline_stats_reduce_last, + .repeat_last = flecs_pipeline_stats_repeat_last, + .set_t = flecs_pipeline_stats_set_t, + .fini = flecs_pipeline_stats_fini, + .stats_size = ECS_SIZEOF(ecs_pipeline_stats_t), + .monitor_component_id = ecs_id(EcsPipelineStats), + .query_component_id = ecs_id(EcsPipeline) + }; + + flecs_stats_api_import(world, &api); +} - if (!desc || !desc->serialize_meta_ids) { - if (pred == EcsIsA || pred == EcsChildOf || - pred == ecs_id(EcsIdentifier)) - { - return true; - } -#ifdef FLECS_DOC - if (pred == ecs_id(EcsDocDescription)) { - return true; - } #endif - } - if (is_base) { - if (ecs_has_id(world, pred, EcsDontInherit)) { - return true; - } - } - if (!desc || !desc->serialize_private) { - if (ecs_has_id(world, pred, EcsPrivate)) { - return true; - } - } - if (is_base) { - if (ecs_get_object_for_id(world, inst, EcsIsA, id) != ent) { - hidden = true; - } - } - if (hidden && (!desc || !desc->serialize_hidden)) { - return true; +/** + * @file addons/stats.c + * @brief Stats addon. + */ + + + +#ifdef FLECS_STATS + +#define ECS_GAUGE_RECORD(m, t, value)\ + flecs_gauge_record(m, t, (ecs_float_t)(value)) + +#define ECS_COUNTER_RECORD(m, t, value)\ + flecs_counter_record(m, t, (double)(value)) + +#define ECS_METRIC_FIRST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) + +#define ECS_METRIC_LAST(stats)\ + ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) + +static +int32_t t_next( + int32_t t) +{ + return (t + 1) % ECS_STAT_WINDOW; +} + +static +int32_t t_prev( + int32_t t) +{ + return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; +} + +static +void flecs_gauge_record( + ecs_metric_t *m, + int32_t t, + ecs_float_t value) +{ + m->gauge.avg[t] = value; + m->gauge.min[t] = value; + m->gauge.max[t] = value; +} + +static +double flecs_counter_record( + ecs_metric_t *m, + int32_t t, + double value) +{ + int32_t tp = t_prev(t); + double prev = m->counter.value[tp]; + m->counter.value[t] = value; + double gauge_value = value - prev; + if (gauge_value < 0) { + gauge_value = 0; /* Counters are monotonically increasing */ } + flecs_gauge_record(m, t, (ecs_float_t)gauge_value); + return gauge_value; +} - *pred_out = pred; - *obj_out = obj; - *role_out = role; - if (hidden_out) *hidden_out = hidden; +static +void flecs_metric_print( + const char *name, + ecs_float_t value) +{ + ecs_size_t len = ecs_os_strlen(name); + ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); +} - return false; +static +void flecs_gauge_print( + const char *name, + int32_t t, + const ecs_metric_t *m) +{ + flecs_metric_print(name, m->gauge.avg[t]); } static -int append_type_labels( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_id_t *ids, - int32_t count, - ecs_entity_t ent, - ecs_entity_t inst, - const ecs_entity_to_json_desc_t *desc) +void flecs_counter_print( + const char *name, + int32_t t, + const ecs_metric_t *m) { - (void)world; (void)buf; (void)ids; (void)count; (void)ent; (void)inst; - (void)desc; - -#ifdef FLECS_DOC - if (!desc || !desc->serialize_id_labels) { - return 0; - } + flecs_metric_print(name, m->counter.rate.avg[t]); +} - flecs_json_member(buf, "id_labels"); - flecs_json_array_push(buf); +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, + int32_t t_dst, + int32_t t_src) +{ + ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t i; - for (i = 0; i < count; i ++) { - ecs_entity_t pred = 0, obj = 0, role = 0; - if (skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { - continue; - } + bool min_set = false; + dst->gauge.avg[t_dst] = 0; + dst->gauge.min[t_dst] = 0; + dst->gauge.max[t_dst] = 0; - if (desc && desc->serialize_id_labels) { - flecs_json_next(buf); + ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; - flecs_json_array_push(buf); - flecs_json_next(buf); - flecs_json_label(buf, world, pred); - if (obj) { - flecs_json_next(buf); - flecs_json_label(buf, world, obj); - } + int32_t i; + for (i = 0; i < ECS_STAT_WINDOW; i ++) { + int32_t t = (t_src + i) % ECS_STAT_WINDOW; + dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; - flecs_json_array_pop(buf); + if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { + dst->gauge.min[t_dst] = src->gauge.min[t]; + min_set = true; + } + if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { + dst->gauge.max[t_dst] = src->gauge.max[t]; } } - flecs_json_array_pop(buf); -#endif - return 0; + dst->counter.value[t_dst] = src->counter.value[t_src]; + +error: + return; } -static -int append_type_values( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_id_t *ids, - int32_t count, - ecs_entity_t ent, - ecs_entity_t inst, - const ecs_entity_to_json_desc_t *desc) +void ecs_metric_reduce_last( + ecs_metric_t *m, + int32_t prev, + int32_t count) { - if (!desc || !desc->serialize_values) { - return 0; + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t t = t_next(prev); + + if (m->gauge.min[t] < m->gauge.min[prev]) { + m->gauge.min[prev] = m->gauge.min[t]; } - flecs_json_member(buf, "values"); - flecs_json_array_push(buf); + if (m->gauge.max[t] > m->gauge.max[prev]) { + m->gauge.max[prev] = m->gauge.max[t]; + } - int32_t i; - for (i = 0; i < count; i ++) { - bool hidden; - ecs_entity_t pred = 0, obj = 0, role = 0; - ecs_id_t id = ids[i]; - if (skip_id(world, id, desc, ent, inst, &pred, &obj, &role, - &hidden)) - { - continue; - } + ecs_float_t fcount = (ecs_float_t)(count + 1); + ecs_float_t cur = m->gauge.avg[prev]; + ecs_float_t next = m->gauge.avg[t]; - if (!hidden) { - bool serialized = false; - ecs_entity_t typeid = ecs_get_typeid(world, id); - if (typeid) { - const EcsMetaTypeSerialized *ser = ecs_get( - world, typeid, EcsMetaTypeSerialized); - if (ser) { - const void *ptr = ecs_get_id(world, ent, id); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + cur *= ((fcount - 1) / fcount); + next *= 1 / fcount; - flecs_json_next(buf); - if (json_ser_type(world, ser->ops, ptr, buf) != 0) { - /* Entity contains invalid value */ - return -1; - } - serialized = true; - } - } - if (!serialized) { - flecs_json_next(buf); - flecs_json_number(buf, 0); - } - } else { - if (!desc || desc->serialize_hidden) { - flecs_json_next(buf); - flecs_json_number(buf, 0); - } - } - } + m->gauge.avg[prev] = cur + next; + m->counter.value[prev] = m->counter.value[t]; - flecs_json_array_pop(buf); - - return 0; +error: + return; } -static -int append_type_info( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_id_t *ids, - int32_t count, - ecs_entity_t ent, - ecs_entity_t inst, - const ecs_entity_to_json_desc_t *desc) +void ecs_metric_copy( + ecs_metric_t *m, + int32_t dst, + int32_t src) { - if (!desc || !desc->serialize_type_info) { - return 0; - } + ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); - flecs_json_member(buf, "type_info"); - flecs_json_array_push(buf); + m->gauge.avg[dst] = m->gauge.avg[src]; + m->gauge.min[dst] = m->gauge.min[src]; + m->gauge.max[dst] = m->gauge.max[src]; + m->counter.value[dst] = m->counter.value[src]; - int32_t i; - for (i = 0; i < count; i ++) { - bool hidden; - ecs_entity_t pred = 0, obj = 0, role = 0; - ecs_id_t id = ids[i]; - if (skip_id(world, id, desc, ent, inst, &pred, &obj, &role, - &hidden)) - { - continue; - } +error: + return; +} - if (!hidden) { - ecs_entity_t typeid = ecs_get_typeid(world, id); - if (typeid) { - flecs_json_next(buf); - if (ecs_type_info_to_json_buf(world, typeid, buf) != 0) { - return -1; - } - } else { - flecs_json_next(buf); - flecs_json_number(buf, 0); - } - } else { - if (!desc || desc->serialize_hidden) { - flecs_json_next(buf); - flecs_json_number(buf, 0); - } - } +static +void flecs_stats_reduce( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src) +{ + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); } - - flecs_json_array_pop(buf); - - return 0; } static -int append_type_hidden( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_id_t *ids, - int32_t count, - ecs_entity_t ent, - ecs_entity_t inst, - const ecs_entity_to_json_desc_t *desc) +void flecs_stats_reduce_last( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src, + int32_t count) { - if (!desc || !desc->serialize_hidden) { - return 0; - } + int32_t t_dst_next = t_next(t_dst); + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + /* Reduce into previous value */ + ecs_metric_reduce_last(dst_cur, t_dst, count); - if (ent == inst) { - return 0; /* if this is not a base, components are never hidden */ + /* Restore old value */ + dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; + dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; + dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; + dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; } +} - flecs_json_member(buf, "hidden"); - flecs_json_array_push(buf); - - int32_t i; - for (i = 0; i < count; i ++) { - bool hidden; - ecs_entity_t pred = 0, obj = 0, role = 0; - ecs_id_t id = ids[i]; - if (skip_id(world, id, desc, ent, inst, &pred, &obj, &role, - &hidden)) - { - continue; - } - - flecs_json_next(buf); - flecs_json_bool(buf, hidden); +static +void flecs_stats_repeat_last( + ecs_metric_t *cur, + ecs_metric_t *last, + int32_t t) +{ + int32_t prev = t_prev(t); + for (; cur <= last; cur ++) { + ecs_metric_copy(cur, t, prev); } - - flecs_json_array_pop(buf); - - return 0; } - static -int append_type( - const ecs_world_t *world, - ecs_strbuf_t *buf, - ecs_entity_t ent, - ecs_entity_t inst, - const ecs_entity_to_json_desc_t *desc) +void flecs_stats_copy_last( + ecs_metric_t *dst_cur, + ecs_metric_t *dst_last, + ecs_metric_t *src_cur, + int32_t t_dst, + int32_t t_src) { - ecs_type_t type = ecs_get_type(world, ent); - const ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); + for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { + dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; + dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; + dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; + dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; + } +} - flecs_json_member(buf, "ids"); - flecs_json_array_push(buf); +void ecs_world_stats_get( + const ecs_world_t *world, + ecs_world_stats_t *s) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - for (i = 0; i < count; i ++) { - ecs_entity_t pred = 0, obj = 0, role = 0; - if (skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { - continue; - } + world = ecs_get_world(world); + int32_t t = s->t = t_next(s->t); - flecs_json_next(buf); - flecs_json_array_push(buf); - flecs_json_next(buf); - flecs_json_path(buf, world, pred); - if (obj || role) { - flecs_json_next(buf); - if (obj) { - flecs_json_path(buf, world, obj); - } else { - flecs_json_number(buf, 0); - } - if (role) { - flecs_json_next(buf); - flecs_json_string(buf, ecs_role_str(role)); - } - } - flecs_json_array_pop(buf); - } + double delta_frame_count = + ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); + ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); + ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); + ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); + ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_total); + ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_total); + ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); + + double delta_world_time = + ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); + ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); + ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); + ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); + ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); + ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); + ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); + ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); + if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) { + ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); + } else { + ECS_GAUGE_RECORD(&s->performance.fps, t, 0); + } + + ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world)); + ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world)); + + ECS_GAUGE_RECORD(&s->components.tag_count, t, world->info.tag_id_count); + ECS_GAUGE_RECORD(&s->components.component_count, t, world->info.component_id_count); + ECS_GAUGE_RECORD(&s->components.pair_count, t, world->info.pair_id_count); + ECS_GAUGE_RECORD(&s->components.type_count, t, ecs_map_count(&world->type_info)); + ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); + ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); + + ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); + ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); + if (ecs_is_alive(world, EcsSystem)) { + ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); + } + ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); + ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); + ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); + + ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); + ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); + ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); + ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); + ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); + ECS_COUNTER_RECORD(&s->commands.ensure_count, t, world->info.cmd.ensure_count); + ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); + ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); + ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); + ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); + ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); + + int64_t outstanding_allocs = ecs_os_api_malloc_count + + ecs_os_api_calloc_count - ecs_os_api_free_count; + ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); + ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); + ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); + ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); + + outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; + ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); + ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); + ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); + + outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; + ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); + ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); + ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); - flecs_json_array_pop(buf); +#ifdef FLECS_HTTP + ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); + ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); + ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); + ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); + ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); + ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); + ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); + ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); + ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); +#endif - if (append_type_labels(world, buf, ids, count, ent, inst, desc)) { - return -1; - } - - if (append_type_values(world, buf, ids, count, ent, inst, desc)) { - return -1; - } +error: + return; +} - if (append_type_info(world, buf, ids, count, ent, inst, desc)) { - return -1; - } +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) +{ + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); +} - if (append_type_hidden(world, buf, ids, count, ent, inst, desc)) { - return -1; - } +void ecs_world_stats_reduce_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src, + int32_t count) +{ + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); +} - return 0; +void ecs_world_stats_repeat_last( + ecs_world_stats_t *stats) +{ + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->t = t_next(stats->t))); } -static -int append_base( - const ecs_world_t *world, - ecs_strbuf_t *buf, - ecs_entity_t ent, - ecs_entity_t inst, - const ecs_entity_to_json_desc_t *desc) +void ecs_world_stats_copy_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src) { - ecs_type_t type = ecs_get_type(world, ent); - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); +} - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - if (ECS_HAS_RELATION(id, EcsIsA)) { - if (append_base(world, buf, ecs_pair_second(world, id), inst, desc)) - { - return -1; - } - } - } +void ecs_query_stats_get( + const ecs_world_t *world, + const ecs_query_t *query, + ecs_query_stats_t *s) +{ + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - flecs_json_object_push(buf); - flecs_json_member(buf, "path"); - flecs_json_path(buf, world, ent); + int32_t t = s->t = t_next(s->t); + ecs_query_count_t counts = ecs_query_count(query); + ECS_GAUGE_RECORD(&s->result_count, t, counts.results); + ECS_GAUGE_RECORD(&s->matched_table_count, t, counts.tables); + ECS_GAUGE_RECORD(&s->matched_entity_count, t, counts.entities); - if (append_type(world, buf, ent, inst, desc)) { - return -1; - } +error: + return; +} - flecs_json_object_pop(buf); +void ecs_query_cache_stats_reduce( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src) +{ + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); +} - return 0; +void ecs_query_cache_stats_reduce_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src, + int32_t count) +{ + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } -int ecs_entity_to_json_buf( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_strbuf_t *buf, - const ecs_entity_to_json_desc_t *desc) +void ecs_query_cache_stats_repeat_last( + ecs_query_stats_t *stats) { - if (!entity || !ecs_is_valid(world, entity)) { - return -1; - } + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->t = t_next(stats->t))); +} - flecs_json_object_push(buf); +void ecs_query_cache_stats_copy_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src) +{ + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); +} - if (!desc || desc->serialize_path) { - char *path = ecs_get_fullpath(world, entity); - flecs_json_member(buf, "path"); - flecs_json_string(buf, path); - ecs_os_free(path); - } +#ifdef FLECS_SYSTEM -#ifdef FLECS_DOC - if (desc && desc->serialize_label) { - flecs_json_member(buf, "label"); - const char *doc_name = ecs_doc_get_name(world, entity); - if (doc_name) { - flecs_json_string(buf, doc_name); - } else { - char num_buf[20]; - ecs_os_sprintf(num_buf, "%u", (uint32_t)entity); - flecs_json_string(buf, num_buf); - } - } +bool ecs_system_stats_get( + const ecs_world_t *world, + ecs_entity_t system, + ecs_system_stats_t *s) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); - if (desc && desc->serialize_brief) { - const char *doc_brief = ecs_doc_get_brief(world, entity); - if (doc_brief) { - flecs_json_member(buf, "brief"); - flecs_json_string(buf, doc_brief); - } - } + world = ecs_get_world(world); - if (desc && desc->serialize_link) { - const char *doc_link = ecs_doc_get_link(world, entity); - if (doc_link) { - flecs_json_member(buf, "link"); - flecs_json_string(buf, doc_link); - } + const ecs_system_t *ptr = flecs_poly_get(world, system, ecs_system_t); + if (!ptr) { + return false; } -#endif - - ecs_type_t type = ecs_get_type(world, entity); - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t i, count = ecs_vector_count(type); - - if (!desc || desc->serialize_base) { - if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { - flecs_json_member(buf, "is_a"); - flecs_json_array_push(buf); - - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - if (ECS_HAS_RELATION(id, EcsIsA)) { - if (append_base( - world, buf, ecs_pair_second(world, id), entity, desc)) - { - return -1; - } - } - } - flecs_json_array_pop(buf); - } - } + ecs_query_stats_get(world, ptr->query, &s->query); + int32_t t = s->query.t; - if (append_type(world, buf, entity, entity, desc)) { - goto error; - } + ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); - flecs_json_object_pop(buf); + s->task = !(ptr->query->flags & EcsQueryMatchThis); - return 0; + return true; error: - return -1; + return false; } -char* ecs_entity_to_json( - const ecs_world_t *world, - ecs_entity_t entity, - const ecs_entity_to_json_desc_t *desc) +void ecs_system_stats_reduce( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - - if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { - ecs_strbuf_reset(&buf); - return NULL; - } + ecs_query_cache_stats_reduce(&dst->query, &src->query); + dst->task = src->task; + flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, src->query.t); +} - return ecs_strbuf_get(&buf); +void ecs_system_stats_reduce_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src, + int32_t count) +{ + ecs_query_cache_stats_reduce_last(&dst->query, &src->query, count); + dst->task = src->task; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); } -static -bool skip_variable( - const char *name) +void ecs_system_stats_repeat_last( + ecs_system_stats_t *stats) { - if (!name || name[0] == '_' || name[0] == '.') { - return true; - } else { - return false; - } + ecs_query_cache_stats_repeat_last(&stats->query); + flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), + (stats->query.t)); } -static -void serialize_id( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf) +void ecs_system_stats_copy_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src) { - flecs_json_id(buf, world, id); + ecs_query_cache_stats_copy_last(&dst->query, &src->query); + dst->task = src->task; + flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), + ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); } -static -void serialize_iter_ids( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +#endif + +#ifdef FLECS_PIPELINE + +bool ecs_pipeline_stats_get( + ecs_world_t *stage, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *s) { - int32_t term_count = it->term_count; - if (!term_count) { - return; + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); + + const ecs_world_t *world = ecs_get_world(stage); + const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); + if (!pqc) { + return false; } + ecs_pipeline_state_t *pq = pqc->state; + ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_json_member(buf, "ids"); - flecs_json_array_push(buf); + int32_t sys_count = 0, active_sys_count = 0; - for (int i = 0; i < term_count; i ++) { - flecs_json_next(buf); - serialize_id(world, it->terms[i].id, buf); + ecs_component_record_t *cr_empty = flecs_components_get( + world, EcsEmpty); + + /* Count number of active systems */ + ecs_iter_t it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + if (cr_empty && flecs_component_get_table(cr_empty, it.table) != NULL) { + continue; + } + active_sys_count += it.count; } - flecs_json_array_pop(buf); -} + /* Count total number of systems in pipeline */ + it = ecs_query_iter(stage, pq->query); + while (ecs_query_next(&it)) { + sys_count += it.count; + } -static -void serialize_type_info( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - int32_t term_count = it->term_count; - if (!term_count) { - return; + /* Also count synchronization points */ + ecs_vec_t *ops = &pq->ops; + ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t); + ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t); + int32_t pip_count = active_sys_count + ecs_vec_count(ops); + + if (!sys_count) { + return false; } - flecs_json_member(buf, "type_info"); - flecs_json_object_push(buf); + if (op) { + ecs_entity_t *systems = NULL; + if (pip_count) { + ecs_vec_init_if_t(&s->systems, ecs_entity_t); + ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); + systems = ecs_vec_first_t(&s->systems, ecs_entity_t); - for (int i = 0; i < term_count; i ++) { - flecs_json_next(buf); - ecs_entity_t typeid = ecs_get_typeid(world, it->terms[i].id); - if (typeid) { - serialize_id(world, typeid, buf); - ecs_strbuf_appendstr(buf, ":"); - ecs_type_info_to_json_buf(world, typeid, buf); + /* Populate systems vector, keep track of sync points */ + it = ecs_query_iter(stage, pq->query); + + int32_t i, i_system = 0, ran_since_merge = 0; + while (ecs_query_next(&it)) { + if (cr_empty && + flecs_component_get_table(cr_empty, it.table) != NULL) + { + continue; + } + + for (i = 0; i < it.count; i ++) { + systems[i_system ++] = it.entities[i]; + ran_since_merge ++; + if (op != op_last && ran_since_merge == op->count) { + ran_since_merge = 0; + op++; + systems[i_system ++] = 0; /* 0 indicates a merge point */ + } + } + } + + systems[i_system ++] = 0; /* Last merge */ + ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); } else { - serialize_id(world, it->terms[i].id, buf); - ecs_strbuf_appendstr(buf, ":"); - ecs_strbuf_appendstr(buf, "0"); + ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); } - } - flecs_json_object_pop(buf); -} + /* Get sync point statistics */ + int32_t i, count = ecs_vec_count(ops); + if (count) { + ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count); + op = ecs_vec_first_t(ops, ecs_pipeline_op_t); -static -void serialize_iter_variables(ecs_iter_t *it, ecs_strbuf_t *buf) { - char **variable_names = it->variable_names; - int32_t var_count = it->variable_count; - int32_t actual_count = 0; + for (i = 0; i < count; i ++) { + ecs_pipeline_op_t *cur = &op[i]; + ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points, + ecs_sync_stats_t, i); - for (int i = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (skip_variable(var_name)) continue; + ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); + ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, + cur->commands_enqueued); - if (!actual_count) { - flecs_json_member(buf, "vars"); - flecs_json_array_push(buf); - actual_count ++; + el->system_count = cur->count; + el->multi_threaded = cur->multi_threaded; + el->immediate = cur->immediate; + } } - - ecs_strbuf_list_next(buf); - flecs_json_string(buf, var_name); } - if (actual_count) { - flecs_json_array_pop(buf); - } + s->t = t_next(s->t); + + return true; +error: + return false; } -static -void serialize_iter_result_ids( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +void ecs_pipeline_stats_fini( + ecs_pipeline_stats_t *stats) { - flecs_json_member(buf, "ids"); - flecs_json_array_push(buf); - - for (int i = 0; i < it->term_count; i ++) { - flecs_json_next(buf); - serialize_id(world, ecs_term_id(it, i + 1), buf); - } - - flecs_json_array_pop(buf); + ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); + ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); } -static -void serialize_iter_result_subjects( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +void ecs_pipeline_stats_reduce( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src) { - flecs_json_member(buf, "subjects"); - flecs_json_array_push(buf); + int32_t system_count = ecs_vec_count(&src->systems); + ecs_vec_init_if_t(&dst->systems, ecs_entity_t); + ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); + ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); + ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); + ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); - for (int i = 0; i < it->term_count; i ++) { - flecs_json_next(buf); - ecs_entity_t subj = it->subjects[i]; - if (subj) { - flecs_json_path(buf, world, subj); - } else { - flecs_json_literal(buf, "0"); - } + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, src->t); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->immediate = src_el->immediate; } - flecs_json_array_pop(buf); + dst->t = t_next(dst->t); } -static -void serialize_iter_result_is_set( - const ecs_iter_t *it, - ecs_strbuf_t *buf) +void ecs_pipeline_stats_reduce_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src, + int32_t count) { - flecs_json_member(buf, "is_set"); - flecs_json_array_push(buf); + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); - for (int i = 0; i < it->term_count; i ++) { - ecs_strbuf_list_next(buf); - if (ecs_term_is_set(it, i + 1)) { - flecs_json_true(buf); - } else { - flecs_json_false(buf); - } + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, src->t, count); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->immediate = src_el->immediate; } - flecs_json_array_pop(buf); + dst->t = t_prev(dst->t); } -static -void serialize_iter_result_variables( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +void ecs_pipeline_stats_repeat_last( + ecs_pipeline_stats_t *stats) { - char **variable_names = it->variable_names; - ecs_var_t *variables = it->variables; - int32_t var_count = it->variable_count; - int32_t actual_count = 0; + int32_t i, sync_count = ecs_vec_count(&stats->sync_points); + ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t); - for (int i = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (skip_variable(var_name)) continue; + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *el = &syncs[i]; + flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el), + (stats->t)); + } - if (!actual_count) { - flecs_json_member(buf, "vars"); - flecs_json_array_push(buf); - actual_count ++; - } + stats->t = t_next(stats->t); +} - ecs_strbuf_list_next(buf); - flecs_json_path(buf, world, variables[i].entity); - } +void ecs_pipeline_stats_copy_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src) +{ + int32_t i, sync_count = ecs_vec_count(&src->sync_points); + ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); + ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); + ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); + ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); - if (actual_count) { - flecs_json_array_pop(buf); + for (i = 0; i < sync_count; i ++) { + ecs_sync_stats_t *dst_el = &dst_syncs[i]; + ecs_sync_stats_t *src_el = &src_syncs[i]; + flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), + ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t)); + dst_el->system_count = src_el->system_count; + dst_el->multi_threaded = src_el->multi_threaded; + dst_el->immediate = src_el->immediate; } } -static -void serialize_iter_result_variable_labels( +#endif + +void ecs_world_stats_log( const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) + const ecs_world_stats_t *s) { - char **variable_names = it->variable_names; - ecs_var_t *variables = it->variables; - int32_t var_count = it->variable_count; - int32_t actual_count = 0; + int32_t t = s->t; - for (int i = 0; i < var_count; i ++) { - const char *var_name = variable_names[i]; - if (skip_variable(var_name)) continue; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - if (!actual_count) { - flecs_json_member(buf, "var_labels"); - flecs_json_array_push(buf); - actual_count ++; - } + world = ecs_get_world(world); + + flecs_counter_print("Frame", t, &s->frame.frame_count); + ecs_trace("-------------------------------------"); + flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); + flecs_counter_print("systems ran", t, &s->frame.systems_ran); + ecs_trace(""); + flecs_metric_print("target FPS", (ecs_float_t)world->info.target_fps); + flecs_metric_print("time scale", (ecs_float_t)world->info.time_scale); + ecs_trace(""); + flecs_gauge_print("actual FPS", t, &s->performance.fps); + flecs_counter_print("frame time", t, &s->performance.frame_time); + flecs_counter_print("system time", t, &s->performance.system_time); + flecs_counter_print("merge time", t, &s->performance.merge_time); + flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); + ecs_trace(""); + flecs_gauge_print("tag id count", t, &s->components.tag_count); + flecs_gauge_print("component id count", t, &s->components.component_count); + flecs_gauge_print("pair id count", t, &s->components.pair_count); + flecs_gauge_print("type count", t, &s->components.type_count); + flecs_counter_print("id create count", t, &s->components.create_count); + flecs_counter_print("id delete count", t, &s->components.delete_count); + ecs_trace(""); + flecs_gauge_print("alive entity count", t, &s->entities.count); + flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); + ecs_trace(""); + flecs_gauge_print("query count", t, &s->queries.query_count); + flecs_gauge_print("observer count", t, &s->queries.observer_count); + flecs_gauge_print("system count", t, &s->queries.system_count); + ecs_trace(""); + flecs_gauge_print("table count", t, &s->tables.count); + flecs_gauge_print("empty table count", t, &s->tables.empty_count); + flecs_counter_print("table create count", t, &s->tables.create_count); + flecs_counter_print("table delete count", t, &s->tables.delete_count); + ecs_trace(""); + flecs_counter_print("add commands", t, &s->commands.add_count); + flecs_counter_print("remove commands", t, &s->commands.remove_count); + flecs_counter_print("delete commands", t, &s->commands.delete_count); + flecs_counter_print("clear commands", t, &s->commands.clear_count); + flecs_counter_print("set commands", t, &s->commands.set_count); + flecs_counter_print("ensure commands", t, &s->commands.ensure_count); + flecs_counter_print("modified commands", t, &s->commands.modified_count); + flecs_counter_print("other commands", t, &s->commands.other_count); + flecs_counter_print("discarded commands", t, &s->commands.discard_count); + flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); + flecs_counter_print("batched commands", t, &s->commands.batched_count); + ecs_trace(""); + +error: + return; +} - ecs_strbuf_list_next(buf); - flecs_json_label(buf, world, variables[i].entity); - } +#endif - if (actual_count) { - flecs_json_array_pop(buf); - } -} +/** + * @file addons/stats/system_monitor.c + * @brief Stats addon system monitor + */ + + +#ifdef FLECS_STATS + +ECS_COMPONENT_DECLARE(EcsSystemStats); static -void serialize_iter_result_entities( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) -{ - int32_t count = it->count; - if (!it->count) { - return; +void flecs_system_monitor_dtor(EcsSystemStats *ptr) { + ecs_map_iter_t it = ecs_map_iter(&ptr->stats); + while (ecs_map_next(&it)) { + ecs_system_stats_t *stats = ecs_map_ptr(&it); + ecs_os_free(stats); } + ecs_map_fini(&ptr->stats); +} - flecs_json_member(buf, "entities"); - flecs_json_array_push(buf); +static ECS_CTOR(EcsSystemStats, ptr, { + ecs_os_zeromem(ptr); + ecs_map_init(&ptr->stats, NULL); +}) - ecs_entity_t *entities = it->entities; +static ECS_COPY(EcsSystemStats, dst, src, { + (void)dst; + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "cannot copy system stats component"); +}) - for (int i = 0; i < count; i ++) { - flecs_json_next(buf); - flecs_json_path(buf, world, entities[i]); - } +static ECS_MOVE(EcsSystemStats, dst, src, { + flecs_system_monitor_dtor(dst); + ecs_os_memcpy_t(dst, src, EcsSystemStats); + ecs_os_zeromem(src); +}) - flecs_json_array_pop(buf); +static ECS_DTOR(EcsSystemStats, ptr, { + flecs_system_monitor_dtor(ptr); +}) + +static +void flecs_system_stats_set_t( + void *stats, int32_t t) +{ + ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); + ((ecs_system_stats_t*)stats)->query.t = t; } static -void serialize_iter_result_entity_labels( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +void flecs_system_stats_copy_last( + void *stats, + void *src) { - int32_t count = it->count; - if (!it->count) { - return; - } - - flecs_json_member(buf, "entity_labels"); - flecs_json_array_push(buf); - - ecs_entity_t *entities = it->entities; + ecs_system_stats_copy_last(stats, src); +} - for (int i = 0; i < count; i ++) { - flecs_json_next(buf); - flecs_json_label(buf, world, entities[i]); - } +static +void flecs_system_stats_get( + ecs_world_t *world, + ecs_entity_t res, + void *stats) +{ + ecs_system_stats_get(world, res, stats); +} - flecs_json_array_pop(buf); +static +void flecs_system_stats_reduce( + void *stats, + void *src) +{ + ecs_system_stats_reduce(stats, src); } static -void serialize_iter_result_values( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +void flecs_system_stats_reduce_last( + void *stats, + void *last, + int32_t reduce_count) { - flecs_json_member(buf, "values"); - flecs_json_array_push(buf); + ecs_system_stats_reduce_last(stats, last, reduce_count); +} - int32_t i, term_count = it->term_count; - for (i = 0; i < term_count; i ++) { - ecs_strbuf_list_next(buf); +static +void flecs_system_stats_repeat_last( + void* stats) +{ + ecs_system_stats_repeat_last(stats); +} - const void *ptr = NULL; - if (it->ptrs) { - ptr = it->ptrs[i]; - } +void FlecsSystemMonitorImport( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsSystemStats); - if (!ptr) { - /* No data in column. Append 0 if this is not an optional term */ - if (ecs_term_is_set(it, i + 1)) { - flecs_json_literal(buf, "0"); - continue; - } - } + ecs_set_hooks(world, EcsSystemStats, { + .ctor = ecs_ctor(EcsSystemStats), + .copy = ecs_copy(EcsSystemStats), + .move = ecs_move(EcsSystemStats), + .dtor = ecs_dtor(EcsSystemStats) + }); - if (ecs_term_is_writeonly(it, i + 1)) { - flecs_json_literal(buf, "0"); - continue; - } + ecs_stats_api_t api = { + .copy_last = flecs_system_stats_copy_last, + .get = flecs_system_stats_get, + .reduce = flecs_system_stats_reduce, + .reduce_last = flecs_system_stats_reduce_last, + .repeat_last = flecs_system_stats_repeat_last, + .set_t = flecs_system_stats_set_t, + .stats_size = ECS_SIZEOF(ecs_system_stats_t), + .monitor_component_id = ecs_id(EcsSystemStats), + .query_component_id = EcsSystem + }; - /* Get component id (can be different in case of pairs) */ - ecs_entity_t type = ecs_get_typeid(world, it->ids[i]); - if (!type) { - /* Odd, we have a ptr but no Component? Not the place of the - * serializer to complain about that. */ - flecs_json_literal(buf, "0"); - continue; - } + flecs_stats_api_import(world, &api); +} - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - if (!comp) { - /* Also odd, typeid but not a component? */ - flecs_json_literal(buf, "0"); - continue; - } +#endif - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (!ser) { - /* Not odd, component just has no reflection data */ - flecs_json_literal(buf, "0"); - continue; - } +/** + * @file addons/stats/world_monitor.c + * @brief Stats addon world monitor. + */ - /* If term is not set, append empty array. This indicates that the term - * could have had data but doesn't */ - if (!ecs_term_is_set(it, i + 1)) { - ecs_assert(ptr == NULL, ECS_INTERNAL_ERROR, NULL); - flecs_json_array_push(buf); - flecs_json_array_pop(buf); - continue; - } - if (ecs_term_is_owned(it, i + 1)) { - int32_t count = it->count; - array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); - } else { - array_to_json_buf_w_type_data(world, ptr, 0, buf, comp, ser); - } - } +#ifdef FLECS_STATS - flecs_json_array_pop(buf); -} +ECS_COMPONENT_DECLARE(EcsWorldStats); static -void serialize_iter_result( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf, - const ecs_iter_to_json_desc_t *desc) -{ - flecs_json_next(buf); - flecs_json_object_push(buf); - - /* Each result can be matched with different component ids. Add them to - * the result so clients know with which component an entity was matched */ - if (!desc || desc->serialize_ids) { - serialize_iter_result_ids(world, it, buf); - } +ECS_CTOR(EcsWorldStats, ptr, { + ecs_os_zeromem(ptr); + ptr->stats = ecs_os_calloc_t(ecs_world_stats_t); +}) - /* Include information on which entity the term is matched with */ - if (!desc || desc->serialize_ids) { - serialize_iter_result_subjects(world, it, buf); - } +static +ECS_COPY(EcsWorldStats, dst, src, { + ecs_os_free(dst->stats); + ecs_os_memcpy_t(dst, src, EcsWorldStats); + dst->stats = ecs_os_calloc_t(ecs_world_stats_t); + ecs_os_memcpy_t(dst->stats, src->stats, ecs_world_stats_t); +}) - /* Write variable values for current result */ - if (!desc || desc->serialize_variables) { - serialize_iter_result_variables(world, it, buf); - } +static +ECS_MOVE(EcsWorldStats, dst, src, { + ecs_os_free(dst->stats); + ecs_os_memcpy_t(dst, src, EcsWorldStats); + src->stats = NULL; +}) - /* Write labels for variables */ - if (desc && desc->serialize_variable_labels) { - serialize_iter_result_variable_labels(world, it, buf); - } +static +ECS_DTOR(EcsWorldStats, ptr, { + ecs_os_free(ptr->stats); + ptr->stats = NULL; +}) - /* Include information on which terms are set, to support optional terms */ - if (!desc || desc->serialize_is_set) { - serialize_iter_result_is_set(it, buf); - } +static +void flecs_world_stats_get( + ecs_world_t *world, ecs_entity_t res, void *stats) +{ + (void)res; + ecs_world_stats_get(world, stats); +} - /* Write entity ids for current result (for queries with This terms) */ - if (!desc || desc->serialize_entities) { - serialize_iter_result_entities(world, it, buf); - } +static +void flecs_world_stats_set_t( + void *stats, int32_t t) +{ + ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); + ((ecs_world_stats_t*)stats)->t = t; +} - /* Write labels for entities */ - if (desc && desc->serialize_entity_labels) { - serialize_iter_result_entity_labels(world, it, buf); - } +static +void flecs_world_stats_copy_last( + void *stats, + void *src) +{ + ecs_world_stats_copy_last(stats, src); +} - /* Serialize component values */ - if (!desc || desc->serialize_values) { - serialize_iter_result_values(world, it, buf); - } +static +void flecs_world_stats_reduce( + void *stats, + void *src) +{ + ecs_world_stats_reduce(stats, src); +} - flecs_json_object_pop(buf); +static +void flecs_world_stats_reduce_last( + void *stats, + void *last, + int32_t reduce_count) +{ + ecs_world_stats_reduce_last(stats, last, reduce_count); } -int ecs_iter_to_json_buf( - const ecs_world_t *world, - ecs_iter_t *it, - ecs_strbuf_t *buf, - const ecs_iter_to_json_desc_t *desc) +static +void flecs_world_stats_repeat_last( + void* stats) { - ecs_time_t duration = {0}; - if (desc && desc->measure_eval_duration) { - ecs_time_measure(&duration); - } + ecs_world_stats_repeat_last(stats); +} - flecs_json_object_push(buf); +void FlecsWorldMonitorImport( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsWorldStats); - /* Serialize component ids of the terms (usually provided by query) */ - if (!desc || desc->serialize_term_ids) { - serialize_iter_ids(world, it, buf); - } + ecs_set_hooks(world, EcsWorldStats, { + .ctor = ecs_ctor(EcsWorldStats), + .copy = ecs_copy(EcsWorldStats), + .move = ecs_move(EcsWorldStats), + .dtor = ecs_dtor(EcsWorldStats) + }); - /* Serialize type info if enabled */ - if (desc && desc->serialize_type_info) { - serialize_type_info(world, it, buf); - } + ecs_stats_api_t api = { + .copy_last = flecs_world_stats_copy_last, + .get = flecs_world_stats_get, + .reduce = flecs_world_stats_reduce, + .reduce_last = flecs_world_stats_reduce_last, + .repeat_last = flecs_world_stats_repeat_last, + .set_t = flecs_world_stats_set_t, + .fini = NULL, + .stats_size = ECS_SIZEOF(ecs_world_stats_t), + .monitor_component_id = ecs_id(EcsWorldStats), + .is_pointer = true + }; - /* Serialize variable names, if iterator has any */ - serialize_iter_variables(it, buf); + flecs_stats_api_import(world, &api); +} - /* Serialize results */ - flecs_json_member(buf, "results"); - flecs_json_array_push(buf); +#endif - /* Use instancing for improved performance */ - ECS_BIT_SET(it->flags, EcsIterIsInstanced); +/** + * @file addons/world_summary.c + * @brief Monitor addon. + */ - ecs_iter_next_action_t next = it->next; - while (next(it)) { - serialize_iter_result(world, it, buf, desc); - } - flecs_json_array_pop(buf); +#ifdef FLECS_STATS - if (desc && desc->measure_eval_duration) { - double dt = ecs_time_measure(&duration); - flecs_json_member(buf, "eval_duration"); - flecs_json_number(buf, dt); - } +ECS_COMPONENT_DECLARE(EcsWorldSummary); - flecs_json_object_pop(buf); +static +void flecs_copy_world_summary( + ecs_world_t *world, + EcsWorldSummary *dst) +{ + flecs_poly_assert(world, ecs_world_t); + const ecs_world_info_t *info = ecs_get_world_info(world); - return 0; -} + dst->target_fps = (double)info->target_fps; + dst->time_scale = (double)info->time_scale; + dst->fps = 1.0 / (double)info->delta_time_raw; -char* ecs_iter_to_json( - const ecs_world_t *world, - ecs_iter_t *it, - const ecs_iter_to_json_desc_t *desc) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; + dst->frame_time_frame = (double)info->frame_time_total - dst->frame_time_total; + dst->system_time_frame = (double)info->system_time_total - dst->system_time_total; + dst->merge_time_frame = (double)info->merge_time_total - dst->merge_time_total; - if (ecs_iter_to_json_buf(world, it, &buf, desc)) { - ecs_strbuf_reset(&buf); - return NULL; - } + dst->merge_count_frame = info->merge_count_total - dst->merge_count; + dst->systems_ran_frame = info->systems_ran_total - dst->systems_ran_total; + dst->observers_ran_frame = info->observers_ran_total - dst->observers_ran_total; + dst->queries_ran_frame = info->queries_ran_total - dst->queries_ran_total; - return ecs_strbuf_get(&buf); -} + int64_t command_count = + info->cmd.add_count + + info->cmd.remove_count + + info->cmd.delete_count + + info->cmd.clear_count + + info->cmd.set_count + + info->cmd.ensure_count + + info->cmd.modified_count + + info->cmd.discard_count + + info->cmd.event_count + + info->cmd.other_count; -#endif + dst->command_count_frame = command_count - dst->command_count; + dst->frame_time_total = (double)info->frame_time_total; + dst->system_time_total = (double)info->system_time_total; + dst->merge_time_total = (double)info->merge_time_total; -#ifdef FLECS_JSON + dst->entity_count = flecs_entities_count(world); + dst->table_count = flecs_sparse_count(&world->store.tables); + dst->frame_count = info->frame_count_total; + dst->command_count = command_count; + dst->merge_count = info->merge_count_total; + dst->systems_ran_total = info->systems_ran_total; + dst->observers_ran_total = info->observers_ran_total; + dst->queries_ran_total = info->queries_ran_total; + dst->tag_count = info->tag_id_count; + dst->component_count = info->component_id_count; + dst->pair_count = info->pair_id_count; -static -int json_typeinfo_ser_type( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *buf); + dst->simulation_time += (double)info->delta_time; -static -int json_typeinfo_ser_primitive( - ecs_primitive_kind_t kind, - ecs_strbuf_t *str) -{ - switch(kind) { - case EcsBool: - flecs_json_string(str, "bool"); - break; - case EcsChar: - case EcsString: - flecs_json_string(str, "text"); - break; - case EcsByte: - flecs_json_string(str, "byte"); - break; - case EcsU8: - case EcsU16: - case EcsU32: - case EcsU64: - case EcsI8: - case EcsI16: - case EcsI32: - case EcsI64: - case EcsIPtr: - case EcsUPtr: - flecs_json_string(str, "int"); - break; - case EcsF32: - case EcsF64: - flecs_json_string(str, "float"); - break; - case EcsEntity: - flecs_json_string(str, "entity"); - break; - default: - return -1; - } + ecs_time_t now; + ecs_os_get_time(&now); + dst->uptime = now.sec - info->creation_time; - return 0; + dst->build_info = *ecs_get_build_info(); } static -void json_typeinfo_ser_constants( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) -{ - ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { - .id = ecs_pair(EcsChildOf, type) - }); +void UpdateWorldSummary(ecs_iter_t *it) { + EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); - while (ecs_term_next(&it)) { - int32_t i, count = it.count; - for (i = 0; i < count; i ++) { - flecs_json_next(str); - flecs_json_string(str, ecs_get_name(world, it.entities[i])); - } + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + flecs_copy_world_summary(it->real_world, &summary[i]); } } static -void json_typeinfo_ser_enum( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) -{ - ecs_strbuf_list_appendstr(str, "\"enum\""); - json_typeinfo_ser_constants(world, type, str); -} +void OnSetWorldSummary(ecs_iter_t *it) { + EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); -static -void json_typeinfo_ser_bitmask( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) -{ - ecs_strbuf_list_appendstr(str, "\"bitmask\""); - json_typeinfo_ser_constants(world, type, str); + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_set_target_fps(it->world, (ecs_ftime_t)summary[i].target_fps); + ecs_set_time_scale(it->world, (ecs_ftime_t)summary[i].time_scale); + } } -static -int json_typeinfo_ser_array( - const ecs_world_t *world, - ecs_entity_t elem_type, - int32_t count, - ecs_strbuf_t *str) +void FlecsWorldSummaryImport( + ecs_world_t *world) { - ecs_strbuf_list_appendstr(str, "\"array\""); + ECS_COMPONENT_DEFINE(world, EcsWorldSummary); - flecs_json_next(str); - if (json_typeinfo_ser_type(world, elem_type, str)) { - goto error; - } +#if defined(FLECS_META) && defined(FLECS_UNITS) + ecs_entity_t build_info = ecs_lookup(world, "flecs.core.BuildInfo"); + ecs_struct(world, { + .entity = ecs_id(EcsWorldSummary), + .members = { + /* Time */ + { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, + { .name = "time_scale", .type = ecs_id(ecs_f64_t) }, + { .name = "fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, + + /* Totals */ + { .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + + { .name = "entity_count", .type = ecs_id(ecs_i64_t) }, + { .name = "table_count", .type = ecs_id(ecs_i64_t) }, + { .name = "frame_count", .type = ecs_id(ecs_i64_t) }, + { .name = "command_count", .type = ecs_id(ecs_i64_t) }, + { .name = "merge_count", .type = ecs_id(ecs_i64_t) }, + { .name = "systems_ran_total", .type = ecs_id(ecs_i64_t) }, + { .name = "observers_ran_total", .type = ecs_id(ecs_i64_t) }, + { .name = "queries_ran_total", .type = ecs_id(ecs_i64_t) }, + + { .name = "tag_count", .type = ecs_id(ecs_i32_t) }, + { .name = "component_count", .type = ecs_id(ecs_i32_t) }, + { .name = "pair_count", .type = ecs_id(ecs_i32_t) }, + + /* Per frame */ + { .name = "frame_time_frame", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "system_time_frame", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "merge_time_frame", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + + { .name = "merge_count_frame", .type = ecs_id(ecs_i64_t) }, + { .name = "systems_ran_frame", .type = ecs_id(ecs_i64_t) }, + { .name = "observers_ran_frame", .type = ecs_id(ecs_i64_t) }, + { .name = "queries_ran_frame", .type = ecs_id(ecs_i64_t) }, + { .name = "command_count_frame", .type = ecs_id(ecs_i64_t) }, + + { .name = "simulation_time", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, + { .name = "uptime", .type = ecs_id(ecs_u32_t), .unit = EcsSeconds }, + + /* Build info */ + { .name = "build_info", .type = build_info } + } + }); +#endif + const ecs_world_info_t *info = ecs_get_world_info(world); + + ecs_system(world, { + .entity = ecs_entity(world, { + .name = "UpdateWorldSummary", + .add = ecs_ids(ecs_pair(EcsDependsOn, EcsPreFrame)) + }), + .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, + .callback = UpdateWorldSummary + }); - ecs_strbuf_list_append(str, "%u", count); - return 0; -error: - return -1; -} + ecs_observer(world, { + .events = { EcsOnSet }, + .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, + .callback = OnSetWorldSummary, + .global_observer = true + }); -static -int json_typeinfo_ser_array_type( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) -{ - const EcsArray *arr = ecs_get(world, type, EcsArray); - ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); - if (json_typeinfo_ser_array(world, arr->type, arr->count, str)) { - goto error; - } + ecs_set(world, EcsWorld, EcsWorldSummary, { + .target_fps = (double)info->target_fps, + .time_scale = (double)info->time_scale + }); - return 0; -error: - return -1; + EcsWorldSummary *summary = ecs_ensure(world, EcsWorld, EcsWorldSummary); + flecs_copy_world_summary(world, summary); + ecs_modified(world, EcsWorld, EcsWorldSummary); } -static -int json_typeinfo_ser_vector( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *str) -{ - const EcsVector *arr = ecs_get(world, type, EcsVector); - ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); +#endif - ecs_strbuf_list_appendstr(str, "\"vector\""); +/** + * @file addons/system/system.c + * @brief System addon. + */ - flecs_json_next(str); - if (json_typeinfo_ser_type(world, arr->type, str)) { - goto error; + +#ifdef FLECS_SYSTEM + + +ecs_mixins_t ecs_system_t_mixins = { + .type_name = "ecs_system_t", + .elems = { + [EcsMixinDtor] = offsetof(ecs_system_t, dtor) } +}; - return 0; -error: - return -1; -} +/* -- Public API -- */ -/* Serialize unit information */ -static -int json_typeinfo_ser_unit( - const ecs_world_t *world, - ecs_strbuf_t *str, - ecs_entity_t unit) +ecs_entity_t flecs_run_system( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t system, + ecs_system_t *system_data, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param) { - flecs_json_member(str, "unit"); - flecs_json_path(str, world, unit); + flecs_poly_assert(world, ecs_world_t); + ecs_ftime_t time_elapsed = delta_time; + ecs_entity_t tick_source = system_data->tick_source; - const EcsUnit *uptr = ecs_get(world, unit, EcsUnit); - if (uptr) { - if (uptr->symbol) { - flecs_json_member(str, "symbol"); - flecs_json_string(str, uptr->symbol); - } - ecs_entity_t quantity = ecs_get_object(world, unit, EcsQuantity, 0); - if (quantity) { - flecs_json_member(str, "quantity"); - flecs_json_path(str, world, quantity); - } + /* Support legacy behavior */ + if (!param) { + param = system_data->ctx; } - return 0; -} + if (tick_source) { + const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); -/* Forward serialization to the different type kinds */ -static -int json_typeinfo_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - ecs_strbuf_t *str) -{ - flecs_json_array_push(str); + if (tick) { + time_elapsed = tick->time_elapsed; - switch(op->kind) { - case EcsOpPush: - case EcsOpPop: - /* Should not be parsed as single op */ - ecs_throw(ECS_INVALID_PARAMETER, NULL); - break; - case EcsOpEnum: - json_typeinfo_ser_enum(world, op->type, str); - break; - case EcsOpBitmask: - json_typeinfo_ser_bitmask(world, op->type, str); - break; - case EcsOpArray: - json_typeinfo_ser_array_type(world, op->type, str); - break; - case EcsOpVector: - json_typeinfo_ser_vector(world, op->type, str); - break; - default: - if (json_typeinfo_ser_primitive( - flecs_json_op_to_primitive_kind(op->kind), str)) - { - /* Unknown operation */ - ecs_throw(ECS_INTERNAL_ERROR, NULL); - return -1; + /* If timer hasn't fired we shouldn't run the system */ + if (!tick->tick) { + return 0; + } + } else { + /* If a timer has been set but the timer entity does not have the + * EcsTimer component, don't run the system. This can be the result + * of a single-shot timer that has fired already. Not resetting the + * timer field of the system will ensure that the system won't be + * ran after the timer has fired. */ + return 0; } - break; } - ecs_entity_t unit = op->unit; - if (unit) { - flecs_json_next(str); - flecs_json_next(str); + ecs_os_perf_trace_push(system_data->name); - flecs_json_object_push(str); - json_typeinfo_ser_unit(world, str, unit); - flecs_json_object_pop(str); + if (ecs_should_log_3()) { + char *path = ecs_get_path(world, system); + ecs_dbg_3("worker %d: %s", stage_index, path); + ecs_os_free(path); } - flecs_json_array_pop(str); + ecs_time_t time_start; + bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); + if (measure_time) { + ecs_os_get_time(&time_start); + } - return 0; -error: - return -1; -} + ecs_world_t *thread_ctx = world; + if (stage) { + thread_ctx = stage->thread_ctx; + } else { + stage = world->stages[0]; + } -/* Iterate over a slice of the type ops array */ -static -int json_typeinfo_ser_type_ops( - const ecs_world_t *world, - ecs_meta_type_op_t *ops, - int32_t op_count, - ecs_strbuf_t *str) -{ - for (int i = 0; i < op_count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; + flecs_poly_assert(stage, ecs_stage_t); - if (op != ops) { - if (op->name) { - flecs_json_member(str, op->name); - } + /* Prepare the query iterator */ + ecs_iter_t wit, qit = ecs_query_iter(thread_ctx, system_data->query); + ecs_iter_t *it = &qit; - int32_t elem_count = op->count; - if (elem_count > 1 && op != ops) { - flecs_json_array_push(str); - json_typeinfo_ser_array(world, op->type, op->count, str); - flecs_json_array_pop(str); - i += op->op_count - 1; - continue; + qit.system = system; + qit.delta_time = delta_time; + qit.delta_system_time = time_elapsed; + qit.param = param; + qit.ctx = system_data->ctx; + qit.callback_ctx = system_data->callback_ctx; + qit.run_ctx = system_data->run_ctx; + + if (stage_count > 1 && system_data->multi_threaded) { + wit = ecs_worker_iter(it, stage_index, stage_count); + it = &wit; + } + + ecs_entity_t old_system = flecs_stage_set_system(stage, system); + ecs_iter_action_t action = system_data->action; + it->callback = action; + + ecs_run_action_t run = system_data->run; + if (run) { + /* If system query matches nothing, the system run callback doesn't have + * anything to iterate, so the iterator resources don't get cleaned up + * automatically, so clean it up here. */ + if (system_data->query->flags & EcsQueryMatchNothing) { + it->next = flecs_default_next_callback; /* Return once */ + run(it); + ecs_iter_fini(&qit); + } else { + if (it == &qit && (qit.flags & EcsIterTrivialCached)) { + it->next = flecs_query_trivial_cached_next; } + run(it); } - - switch(op->kind) { - case EcsOpPush: - flecs_json_object_push(str); - break; - case EcsOpPop: - flecs_json_object_pop(str); - break; - default: - if (json_typeinfo_ser_type_op(world, op, str)) { - goto error; + } else { + if (system_data->query->term_count) { + if (it == &qit) { + if (qit.flags & EcsIterTrivialCached) { + while (flecs_query_trivial_cached_next(&qit)) { + action(&qit); + } + } else { + while (ecs_query_next(&qit)) { + action(&qit); + } + } + } else { + while (ecs_iter_next(it)) { + action(it); + } } - break; + } else { + action(&qit); + ecs_iter_fini(&qit); } } - return 0; -error: - return -1; + flecs_stage_set_system(stage, old_system); + + if (measure_time) { + system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); + } + + ecs_os_perf_trace_pop(system_data->name); + + return it->interrupted_by; } -static -int json_typeinfo_ser_type( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *buf) + +/* -- Public API -- */ + +ecs_entity_t ecs_run_worker( + ecs_world_t *world, + ecs_entity_t system, + int32_t stage_index, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param) { - const EcsComponent *comp = ecs_get(world, type, EcsComponent); - if (!comp) { - ecs_strbuf_appendstr(buf, "0"); - return 0; + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_defer_begin(world, stage); + ecs_entity_t result = flecs_run_system( + world, stage, system, system_data, stage_index, stage_count, + delta_time, param); + flecs_defer_end(world, stage); + return result; +} + +ecs_entity_t ecs_run( + ecs_world_t *world, + ecs_entity_t system, + ecs_ftime_t delta_time, + void *param) +{ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); + ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_defer_begin(world, stage); + ecs_entity_t result = flecs_run_system( + world, stage, system, system_data, 0, 0, delta_time, param); + flecs_defer_end(world, stage); + return result; +} + +/* System deinitialization */ +static +void flecs_system_fini(ecs_system_t *sys) { + if (sys->ctx_free) { + sys->ctx_free(sys->ctx); } - const EcsMetaTypeSerialized *ser = ecs_get( - world, type, EcsMetaTypeSerialized); - if (!ser) { - ecs_strbuf_appendstr(buf, "0"); - return 0; + if (sys->callback_ctx_free) { + sys->callback_ctx_free(sys->callback_ctx); + } + + if (sys->run_ctx_free) { + sys->run_ctx_free(sys->run_ctx); } - ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t); - int32_t count = ecs_vector_count(ser->ops); + /* Safe cast, type owns name */ + ecs_os_free(ECS_CONST_CAST(char*, sys->name)); - return json_typeinfo_ser_type_ops(world, ops, count, buf); + flecs_poly_free(sys, ecs_system_t); } -int ecs_type_info_to_json_buf( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *buf) +/* ecs_poly_dtor_t-compatible wrapper */ +static +void flecs_system_poly_fini(void *sys) { - return json_typeinfo_ser_type(world, type, buf); + flecs_system_fini(sys); } -char* ecs_type_info_to_json( - const ecs_world_t *world, - ecs_entity_t type) +static +int flecs_system_init_timer( + ecs_world_t *world, + ecs_entity_t entity, + const ecs_system_desc_t *desc) { - ecs_strbuf_t str = ECS_STRBUF_INIT; + if (ECS_NEQZERO(desc->interval) && ECS_NEQZERO(desc->rate)) { + char *name = ecs_get_path(world, entity); + ecs_err("system %s cannot have both interval and rate set", name); + ecs_os_free(name); + return -1; + } - if (ecs_type_info_to_json_buf(world, type, &str) != 0) { - ecs_strbuf_reset(&str); - return NULL; + if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || + ECS_NEQZERO(desc->tick_source)) + { +#ifdef FLECS_TIMER + if (ECS_NEQZERO(desc->interval)) { + ecs_set_interval(world, entity, desc->interval); + } + + if (desc->rate) { + ecs_entity_t tick_source = desc->tick_source; + ecs_set_rate(world, entity, desc->rate, tick_source); + } else if (desc->tick_source) { + ecs_set_tick_source(world, entity, desc->tick_source); + } +#else + (void)world; + (void)entity; + ecs_abort(ECS_UNSUPPORTED, "timer module not available"); +#endif } - return ecs_strbuf_get(&str); + return 0; } -#endif +ecs_entity_t ecs_system_init( + ecs_world_t *world, + const ecs_system_desc_t *desc) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_system_desc_t was not initialized to zero"); + ecs_assert(!(world->flags & EcsWorldReadonly), + ECS_INVALID_WHILE_READONLY, NULL); + ecs_entity_t entity = desc->entity; + if (!entity) { + entity = ecs_entity(world, {0}); + } + EcsPoly *poly = flecs_poly_bind(world, entity, ecs_system_t); + if (!poly->poly) { + ecs_check(desc->callback != NULL || desc->run != NULL, + ECS_INVALID_PARAMETER, + "missing implementation for system %s (set .callback or .run)", + desc->entity + ? flecs_errstr(ecs_get_path(world, desc->entity)) + : ""); -#ifdef FLECS_JSON + ecs_system_t *system = flecs_poly_new(ecs_system_t); + ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); + + poly->poly = system; + system->dtor = flecs_system_poly_fini; -const char* ecs_parse_json( - const ecs_world_t *world, - const char *ptr, - ecs_entity_t type, - void *data_out, - const ecs_parse_json_desc_t *desc) -{ - char token[ECS_MAX_TOKEN_SIZE]; - int depth = 0; + ecs_query_desc_t query_desc = desc->query; + query_desc.entity = entity; - const char *name = NULL; - const char *expr = NULL; + ecs_query_t *query = ecs_query_init(world, &query_desc); + if (!query) { + ecs_delete(world, entity); + return 0; + } - ptr = ecs_parse_fluff(ptr, NULL); + /* Prevent the system from moving while we're initializing */ + flecs_defer_begin(world, world->stages[0]); - ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, data_out); - if (cur.valid == false) { - return NULL; - } + if (desc->phase) { + ecs_add_id(world, entity, desc->phase); + ecs_add_pair(world, entity, EcsDependsOn, desc->phase); + } - if (desc) { - name = desc->name; - expr = desc->expr; - } + system->query = query; - while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { + system->run = desc->run; + system->action = desc->callback; - ptr = ecs_parse_fluff(ptr, NULL); + system->ctx = desc->ctx; + system->callback_ctx = desc->callback_ctx; + system->run_ctx = desc->run_ctx; - if (!ecs_os_strcmp(token, "{")) { - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } + system->ctx_free = desc->ctx_free; + system->callback_ctx_free = desc->callback_ctx_free; + system->run_ctx_free = desc->run_ctx_free; - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '['"); - return NULL; - } - } + system->tick_source = desc->tick_source; - else if (!ecs_os_strcmp(token, "}")) { - depth --; + system->multi_threaded = desc->multi_threaded; + system->immediate = desc->immediate; - if (ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected ']'"); - return NULL; - } + system->name = ecs_get_path(world, entity); - if (ecs_meta_pop(&cur) != 0) { - goto error; - } + if (flecs_system_init_timer(world, entity, desc)) { + ecs_delete(world, entity); + ecs_defer_end(world); + goto error; } - else if (!ecs_os_strcmp(token, "[")) { - depth ++; - if (ecs_meta_push(&cur) != 0) { - goto error; - } + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]system#[reset] %s created", + ecs_get_name(world, entity)); + } + + ecs_defer_end(world); + } else { + flecs_poly_assert(poly->poly, ecs_system_t); + ecs_system_t *system = (ecs_system_t*)poly->poly; - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '{'"); - return NULL; + if (system->ctx_free) { + if (system->ctx && system->ctx != desc->ctx) { + system->ctx_free(system->ctx); } } - else if (!ecs_os_strcmp(token, "]")) { - depth --; - - if (!ecs_meta_is_collection(&cur)) { - ecs_parser_error(name, expr, ptr - expr, "expected '}'"); - return NULL; + if (system->callback_ctx_free) { + if (system->callback_ctx && system->callback_ctx != desc->callback_ctx) { + system->callback_ctx_free(system->callback_ctx); + system->callback_ctx_free = NULL; + system->callback_ctx = NULL; } + } - if (ecs_meta_pop(&cur) != 0) { - goto error; + if (system->run_ctx_free) { + if (system->run_ctx && system->run_ctx != desc->run_ctx) { + system->run_ctx_free(system->run_ctx); + system->run_ctx_free = NULL; + system->run_ctx = NULL; } } - else if (!ecs_os_strcmp(token, ",")) { - if (ecs_meta_next(&cur) != 0) { - goto error; + if (desc->run) { + system->run = desc->run; + if (!desc->callback) { + system->action = NULL; } } - else if (!ecs_os_strcmp(token, "null")) { - if (ecs_meta_set_null(&cur) != 0) { - goto error; + if (desc->callback) { + system->action = desc->callback; + if (!desc->run) { + system->run = NULL; } } - else if (token[0] == '\"') { - if (ptr[0] == ':') { - /* Member assignment */ - ptr ++; + if (desc->ctx) { + system->ctx = desc->ctx; + } - /* Strip trailing " */ - ecs_size_t len = ecs_os_strlen(token); - if (token[len - 1] != '"') { - ecs_parser_error(name, expr, ptr - expr, "expected \""); - return NULL; - } else { - token[len - 1] = '\0'; - } + if (desc->callback_ctx) { + system->callback_ctx = desc->callback_ctx; + } - if (ecs_meta_member(&cur, token + 1) != 0) { - goto error; - } - } else { - if (ecs_meta_set_string_literal(&cur, token) != 0) { - goto error; - } - } + if (desc->run_ctx) { + system->run_ctx = desc->run_ctx; } - else { - if (ecs_meta_set_string(&cur, token) != 0) { - goto error; - } + if (desc->ctx_free) { + system->ctx_free = desc->ctx_free; } - if (!depth) { - break; + if (desc->callback_ctx_free) { + system->callback_ctx_free = desc->callback_ctx_free; + } + + if (desc->run_ctx_free) { + system->run_ctx_free = desc->run_ctx_free; + } + + if (desc->multi_threaded) { + system->multi_threaded = desc->multi_threaded; + } + + if (desc->immediate) { + system->immediate = desc->immediate; + } + + if (flecs_system_init_timer(world, entity, desc)) { + return 0; } } - return ptr; + flecs_poly_modified(world, entity, ecs_system_t); + + return entity; error: - return NULL; + return 0; } -#endif +const ecs_system_t* ecs_system_get( + const ecs_world_t *world, + ecs_entity_t entity) +{ + return flecs_poly_get(world, entity, ecs_system_t); +} +void FlecsSystemImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsSystem); -#ifdef FLECS_REST + ecs_set_name_prefix(world, "Ecs"); -typedef struct { - ecs_world_t *world; - ecs_entity_t entity; - ecs_http_server_t *srv; - int32_t rc; -} ecs_rest_ctx_t; + flecs_bootstrap_tag(world, EcsSystem); + flecs_bootstrap_component(world, EcsTickSource); -static ECS_COPY(EcsRest, dst, src, { - ecs_rest_ctx_t *impl = src->impl; - if (impl) { - impl->rc ++; - } + ecs_set_hooks(world, EcsTickSource, { + .ctor = flecs_default_ctor + }); - ecs_os_strset(&dst->ipaddr, src->ipaddr); - dst->port = src->port; - dst->impl = impl; -}) + /* Make sure to never inherit system component. This makes sure that any + * term created for the System component will default to 'self' traversal, + * which improves efficiency of the query. */ + ecs_add_pair(world, EcsSystem, EcsOnInstantiate, EcsDontInherit); +} -static ECS_MOVE(EcsRest, dst, src, { - *dst = *src; - src->ipaddr = NULL; - src->impl = NULL; -}) +#endif -static ECS_DTOR(EcsRest, ptr, { - ecs_rest_ctx_t *impl = ptr->impl; - if (impl) { - impl->rc --; - if (!impl->rc) { - ecs_http_server_fini(impl->srv); - ecs_os_free(impl); - } - } - ecs_os_free(ptr->ipaddr); -}) +/** + * @file query/cache/cache.c + * @brief Cached query implementation. + * + * Implements a cache that stores a list of tables that matches the query. + * Cached queries outperform uncached queries in many scenarios since they don't + * have to search for tables that match a query, but just iterate a list. + * + * A cache has a "cache query" which is derived from the actual query the + * application provides. This cache query differs in two significant ways from + * the application-provided query: + * - It does not contain terms that aren't cacheable + * - It always matches empty tables + * + * If the number of terms in the actual query differs from the cache query, the + * query will create a field_map array that maps from cached field indices to + * actual query indices. + * + * Cached queries use an observer to get notified of new/deleted tables. In most + * cases this is sufficient to keep the cache up to date, but for queries that + * match components outside of a table (for example, from a parent entity) the + * cache will have to be revalidated after a parent changes a matched component. + * This is called rematching, the implementation can be found in match.c. + * + * A cache can be trivial or non-trivial. A trivial cache is a cache for a query + * that meets the following criteria: + * - The query doesn't have any wildcards + * - The query doesn't use relationship traversal + * - The query doesn't use operators other than And, Not or Optional + * - The cached query and actual query have the same terms + * + * A trivial cache has to store much less data for each cached element, and uses + * an iterator implementation that is much faster. Because of this difference + * the code often needs to lookup the size of cache elements before doing work. + * + * The following types are important for the cache: + * - ecs_query_cache_match_t: Stores a single match for a table. + * - ecs_query_cache_table_t: Element in the cache->tables map, which allows for + * looking up a match by table id. + * - ecs_query_cache_group_t: Stores an array of matched tables. A query has a + * single group by default, but can have more (see + * group_by). + * + * There are three cache features that significantly alter the way how elements + * are stored in the cache, which are group_by, order_by and wildcards. + * + * Group_by + * ======== + * Group_by assigns a group id (unsigned 64bit integer) to each table. This + * number is computed by a group_by function that can be provided by the + * application. A group can only be computed from which components are stored in + * a table (the table type). + * + * By default a query only has a single group which contains all matched tables + * stored in an array. When a query uses group_by, the matched tables are split + * up across multiple groups depending on the computed group id of the table. + * When a group becomes empty (there are no more tables with the group id) it is + * deleted from the cache. + * + * The query cache contains a "groups" map and a "tables" map. The groups map + * allows for group id -> group lookups. The tables map allows for table id -> + * table element lookups. Because tables are stored in arrays that can be + * reallocated, rather than a pointer to the table element, the tables map + * stores a pointer to the group the table is part of, and an index into the + * group's table array. + * + * Queries can use group_by for two main reasons: + * - It allows for quickly iterating subsets of the matched query results + * - Groups are iterated in order based on group id + * + * An application can iterate a single group by calling ecs_iter_set_group on an + * iterator created from a query that uses group_by. This will cause the + * iterator to only iterate the table array for that specific group. To find the + * group's table array, the ecs_iter_set_group function uses the groups map. + * + * Groups are stored in a linked list that's ordered by the group id. This can + * be in ascending or descending order, depending on the query. Because of this + * ordering, group insertion and group removal is an O(N) operation where N is + * the number of groups in the query. The head of the list is stored in the + * first_group member of the query. + * + * The cascade query feature is built on top of group_by. It provides a callback + * that computes a hierarchy depth for the table with a specified relationship. + * Because groups are stored in ascending or descending order, this effectively + * means that tables will be iterated in a breadth-first order, which can be + * useful for hierarchy traversal code or transform systems. + * + * Order_by + * ======== + * Order_by will cause the query to return results in an order that is defined + * by either a component or entity id. To accomplish this, the query has to find + * the order across different tables. The code will first sort the elements in + * each matched table, and then build a list of (offset, count) slices across + * the matched tables that represents the correct iteration order. The algorithm + * used for sorting is qsort. + * + * Resorting is a very expensive operation. Queries use change detection, which + * at a table level can detect if any changes occurred to the entities or ordered + * by component. Only if a change has been detected will resorting occur. Even + * then, this remains an expensive feature and should only be used for data that + * doesn't change often. Flecs uses the query sorting feature to ensure that + * pipeline queries return systems in a well defined order. + * + * The sorted list of slices is stored in the table_slices member of the cache, + * and is only populated for sorted queries. + * + * When group_by and order_by are combined in a single query, the group order + * takes precedence over order_by. + * + * It is currently not possible to iterate a single group when order_by is used + * together with group_by. This is a TODO that has to be addressed by adding a + * table_slices array to each group instead of as a member of the cache object. + * + * Wildcards + * ========= + * Wildcards are a query feature that can cause a single table to be matched + * multiple times. A typical example would be a (Likes, *) query. If an entity + * has both (Likes, Bob) and (Likes, Alice), it (and all entities in the same + * table) will get matched twice by the query. + * + * When a table matches multiple times (through one or more wildcard terms), any + * match after the first is stored in a separate wildcard_matches member on the + * cache element. This separate array ensures that tables can be removed from + * groups without leaving arbitrarily sized holes in the group->tables array. + * + * When iterating a wildcard query, the iterator will alternate between + * iterating the group->tables array and the wildcard_matches array on each + * matched table, in a way that all matches for the same table are iterated + * together. + */ -static char *rest_last_err; -static -void rest_capture_log( - int32_t level, - const char *file, - int32_t line, - const char *msg) +/* Is cache trivial? */ +bool flecs_query_cache_is_trivial( + const ecs_query_cache_t *cache) { - (void)file; (void)line; - - if (!rest_last_err && level < 0) { - rest_last_err = ecs_os_strdup(msg); - } + return (cache->query->flags & EcsQueryTrivialCache) != 0; } -static -char* rest_get_captured_log(void) { - char *result = rest_last_err; - rest_last_err = NULL; - return result; +/* Trivial caches have a significantly smaller cache element size */ +ecs_size_t flecs_query_cache_elem_size( + const ecs_query_cache_t *cache) +{ + return flecs_query_cache_is_trivial(cache) + ? ECS_SIZEOF(ecs_query_triv_cache_match_t) + : ECS_SIZEOF(ecs_query_cache_match_t) + ; } +/* The default group_by function. When an application specifies a relationship + * for the ecs_query_desc_t::group_by field but does not provide a + * group_by_callback, this function will be automatically used. It will cause + * the query cache to be grouped by relationship target. */ static -void reply_verror( - ecs_http_reply_t *reply, - const char *fmt, - va_list args) +uint64_t flecs_query_cache_default_group_by( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + void *ctx) { - ecs_strbuf_appendstr(&reply->body, "{\"error\":\""); - ecs_strbuf_vappend(&reply->body, fmt, args); - ecs_strbuf_appendstr(&reply->body, "\"}"); + (void)ctx; + + ecs_id_t match; + if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { + if (ECS_IS_VALUE_PAIR(match)) { + return ECS_PAIR_SECOND(match); + } else { + return ecs_pair_second(world, match); + } + } + return 0; } +/* The group_by function that's used for the cascade feature. Groups tables by + * hierarchy depth, resulting in breadth-first iteration. */ static -void reply_error( - ecs_http_reply_t *reply, - const char *fmt, - ...) +uint64_t flecs_query_cache_group_by_cascade( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + void *ctx) { - va_list args; - va_start(args, fmt); - reply_verror(reply, fmt, args); - va_end(args); + (void)id; + ecs_term_t *term = ctx; + ecs_entity_t rel = term->trav; + int32_t depth = flecs_relation_depth(world, rel, table); + return flecs_ito(uint64_t, depth); } +/* Initialize cache with matching tables. */ static -void rest_bool_param( - const ecs_http_request_t *req, - const char *name, - bool *value_out) +void flecs_query_cache_match_tables( + ecs_world_t *world, + ecs_query_cache_t *cache) { - const char *value = ecs_http_get_param(req, name); - if (value) { - if (!ecs_os_strcmp(value, "true")) { - value_out[0] = true; - } else { - value_out[0] = false; - } + ecs_iter_t it = ecs_query_iter(world, cache->query); + ECS_BIT_SET(it.flags, EcsIterNoData); + ECS_BIT_SET(it.flags, EcsIterTableOnly); + + if (!ecs_query_next(&it)) { + return; } + + while (flecs_query_cache_match_next(cache, &it)) { } } +/* Match new table with cache. */ static -void rest_int_param( - const ecs_http_request_t *req, - const char *name, - int32_t *value_out) +bool flecs_query_cache_match_table( + ecs_world_t *world, + ecs_query_cache_t *cache, + ecs_table_t *table) { - const char *value = ecs_http_get_param(req, name); - if (value) { - *value_out = atoi(value); + if (!ecs_map_is_init(&cache->tables)) { + return false; } -} -static -void rest_parse_json_ser_entity_params( - ecs_entity_to_json_desc_t *desc, - const ecs_http_request_t *req) -{ - rest_bool_param(req, "path", &desc->serialize_path); - rest_bool_param(req, "label", &desc->serialize_label); - rest_bool_param(req, "brief", &desc->serialize_brief); - rest_bool_param(req, "link", &desc->serialize_link); - rest_bool_param(req, "id_labels", &desc->serialize_id_labels); - rest_bool_param(req, "base", &desc->serialize_base); - rest_bool_param(req, "values", &desc->serialize_values); - rest_bool_param(req, "private", &desc->serialize_private); - rest_bool_param(req, "type_info", &desc->serialize_type_info); + ecs_query_t *q = cache->query; + +#ifndef FLECS_SANITIZE + if (!flecs_table_bloom_filter_test(table, q->bloom_filter)) { + return false; + } +#endif + + if (table->flags & EcsTableNotQueryable) { + return false; + } + + /* Iterate uncached query for table to check if it matches. If this is a + * wildcard query, a table can match multiple times. */ + ecs_iter_t it = flecs_query_iter(world, q); + it.flags |= EcsIterNoData; + ecs_iter_set_var_as_table(&it, 0, table); + + if (!ecs_query_next(&it)) { + /* Table doesn't match */ + return false; + } + + if (flecs_query_cache_match_next(cache, &it)) { + /* Should not return for more than one table. */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + +#ifdef FLECS_SANITIZE + /* Sanity check to make sure bloom filter is correct */ + ecs_assert(flecs_table_bloom_filter_test(table, q->bloom_filter), + ECS_INTERNAL_ERROR, NULL); +#endif + + return true; } +/* Iterate component monitors for cache. Each field that could potentially cause + * up traversal will create a monitor. Component monitors are registered with + * the world and are used to determine whether a rematch is necessary. */ static -void rest_parse_json_ser_iter_params( - ecs_iter_to_json_desc_t *desc, - const ecs_http_request_t *req) +void flecs_query_cache_for_each_component_monitor( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_cache_t *cache, + void(*callback)( + ecs_world_t* world, + ecs_id_t id, + ecs_query_t *q)) { - rest_bool_param(req, "term_ids", &desc->serialize_term_ids); - rest_bool_param(req, "ids", &desc->serialize_ids); - rest_bool_param(req, "subjects", &desc->serialize_subjects); - rest_bool_param(req, "variables", &desc->serialize_variables); - rest_bool_param(req, "is_set", &desc->serialize_is_set); - rest_bool_param(req, "values", &desc->serialize_values); - rest_bool_param(req, "entities", &desc->serialize_entities); - rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); - rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); - rest_bool_param(req, "duration", &desc->measure_eval_duration); - rest_bool_param(req, "type_info", &desc->serialize_type_info); + ecs_query_t *q = &impl->pub; + ecs_term_t *terms = cache->query->terms; + int32_t i, count = cache->query->term_count; + + for (i = 0; i < count; i++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *src = &term->src; + + if (src->id & EcsUp) { + callback(world, ecs_pair(term->trav, EcsWildcard), q); + if (term->trav != EcsIsA) { + callback(world, ecs_pair(EcsIsA, EcsWildcard), q); + } + callback(world, term->id, q); + + } else if (src->id & EcsSelf && !ecs_term_match_this(term)) { + ecs_assert(!(src->id & EcsSelf) || ecs_term_match_this(term), + ECS_INTERNAL_ERROR, NULL); + } + } } +/* Iterate over terms in query to initialize necessary bookkeeping. */ static -bool rest_reply( - const ecs_http_request_t* req, - ecs_http_reply_t *reply, - void *ctx) +int flecs_query_cache_process_query( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_cache_t *cache) { - ecs_rest_ctx_t *impl = ctx; - ecs_world_t *world = impl->world; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_term_t *terms = cache->query->terms; + int32_t i, count = cache->query->term_count; - if (req->path == NULL) { - ecs_dbg("rest: bad request (missing path)"); - reply_error(reply, "bad request (missing path)"); - reply->code = 400; - return false; - } + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *src = &term->src; - ecs_strbuf_appendstr(&reply->headers, "Access-Control-Allow-Origin: *\r\n"); + /* Find cascade terms if query has any */ + if (src->id & EcsCascade) { + ecs_assert(cache->cascade_by == 0, ECS_INVALID_PARAMETER, + "query can only have one cascade term"); + cache->cascade_by = i + 1; + } - if (req->method == EcsHttpGet) { - /* Entity endpoint */ - if (!ecs_os_strncmp(req->path, "entity/", 7)) { - char *path = &req->path[7]; - ecs_dbg_2("rest: request entity '%s'", path); + /* Set the EcsQueryHasRefs flag. Ref fields are fields that can be + * matched on another entity, and can require rematching. */ + if (src->id & EcsUp) { + impl->pub.flags |= EcsQueryHasRefs; + } + } - ecs_entity_t e = ecs_lookup_path_w_sep( - world, 0, path, "/", NULL, false); - if (!e) { - ecs_dbg_2("rest: entity '%s' not found", path); - reply_error(reply, "entity '%s' not found", path); - reply->code = 404; - return true; - } + /* Register component monitor for each ref field. */ + flecs_query_cache_for_each_component_monitor( + world, impl, cache, flecs_monitor_register); - ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; - rest_parse_json_ser_entity_params(&desc, req); + return 0; +} - ecs_entity_to_json_buf(world, e, &reply->body, &desc); - return true; - - /* Query endpoint */ - } else if (!ecs_os_strcmp(req->path, "query")) { - const char *q = ecs_http_get_param(req, "q"); - if (!q) { - ecs_strbuf_appendstr(&reply->body, "Missing parameter 'q'"); - reply->code = 400; /* bad request */ - return true; +/* -- Private API -- */ + +/* Do bookkeeping after enabling order_by */ +static +int flecs_query_cache_order_by( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_entity_t order_by, + ecs_order_by_action_t order_by_callback, + ecs_sort_table_action_t action) +{ + ecs_check(impl != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_cache_t *cache = impl->cache; + ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!ecs_id_is_wildcard(order_by), + ECS_INVALID_PARAMETER, "cannot order by wildcard component"); + + /* Find order_by term & make sure it is queried for */ + const ecs_query_t *query = cache->query; + int32_t i, count = query->term_count; + int32_t order_by_term = -1; + + if (order_by) { + for (i = 0; i < count; i ++) { + const ecs_term_t *term = &query->terms[i]; + + /* Only And terms are supported */ + if (term->id == order_by && term->oper == EcsAnd) { + order_by_term = i; + break; } + } - ecs_dbg_2("rest: request query '%s'", q); - bool prev_color = ecs_log_enable_colors(false); - ecs_os_api_log_t prev_log_ = ecs_os_api.log_; - ecs_os_api.log_ = rest_capture_log; + if (order_by_term == -1) { + char *id_str = ecs_id_str(world, order_by); + ecs_err("order_by component '%s' is not queried for", id_str); + ecs_os_free(id_str); + goto error; + } + } - ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t) { - .expr = q - }); - if (!r) { - char *err = rest_get_captured_log(); - char *escaped_err = ecs_astresc('"', err); - reply_error(reply, escaped_err); - reply->code = 400; /* bad request */ - ecs_os_free(escaped_err); - ecs_os_free(err); - } else { - ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; - rest_parse_json_ser_iter_params(&desc, req); + cache->order_by = order_by; + cache->order_by_callback = order_by_callback; + cache->order_by_term = order_by_term; + cache->order_by_table_callback = action; - int32_t offset = 0; - int32_t limit = 100; + ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_match_t); + flecs_query_cache_sort_tables(world, impl); - rest_int_param(req, "offset", &offset); - rest_int_param(req, "limit", &limit); + if (!cache->table_slices.array) { + flecs_query_cache_build_sorted_tables(cache); + } - ecs_iter_t it = ecs_rule_iter(world, r); - ecs_iter_t pit = ecs_page_iter(&it, offset, limit); - ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); - ecs_rule_fini(r); - } + return 0; +error: + return -1; +} - ecs_os_api.log_ = prev_log_; - ecs_log_enable_colors(prev_color); +/* Do bookkeeping after enabling group_by */ +static +void flecs_query_cache_group_by( + ecs_query_cache_t *cache, + ecs_entity_t sort_component, + ecs_group_by_action_t group_by) +{ + ecs_check(cache->group_by == 0, ECS_INVALID_OPERATION, + "query is already grouped"); + ecs_check(cache->group_by_callback == 0, ECS_INVALID_OPERATION, + "query is already grouped"); - return true; - } - } - if (req->method == EcsHttpOptions) { - return true; + if (!group_by) { + /* Builtin function that groups by relationship */ + group_by = flecs_query_cache_default_group_by; } - return false; + cache->group_by = sort_component; + cache->group_by_callback = group_by; + + ecs_map_init(&cache->groups, &cache->query->world->allocator); +error: + return; } +/* Callback for the observer that is subscribed for table events. This function + * is the entry point for matching/unmatching new tables with the query. */ static -void on_set_rest(ecs_iter_t *it) +void flecs_query_cache_on_event( + ecs_iter_t *it) { - EcsRest *rest = it->ptrs[0]; - - int i; - for(i = 0; i < it->count; i ++) { - if (!rest[i].port) { - rest[i].port = ECS_REST_DEFAULT_PORT; + /* Because this is the observer::run callback, checking if this is event is + * already handled is not done for us. */ + ecs_world_t *world = it->world; + ecs_observer_t *o = it->ctx; + ecs_observer_impl_t *o_impl = flecs_observer_impl(o); + if (o_impl->last_event_id) { + if (o_impl->last_event_id[0] == world->event_id) { + return; } + o_impl->last_event_id[0] = world->event_id; + } - ecs_rest_ctx_t *srv_ctx = ecs_os_malloc_t(ecs_rest_ctx_t); - ecs_http_server_t *srv = ecs_http_server_init(&(ecs_http_server_desc_t){ - .ipaddr = rest[i].ipaddr, - .port = rest[i].port, - .callback = rest_reply, - .ctx = srv_ctx - }); + ecs_query_impl_t *impl = o->ctx; + flecs_poly_assert(impl, ecs_query_t); + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = it->table; + ecs_entity_t event = it->event; - if (!srv) { - const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; - ecs_err("failed to create REST server on %s:%u", - ipaddr, rest[i].port); - ecs_os_free(srv_ctx); - continue; + if (event == EcsOnTableCreate) { + /* Creation of new table */ + if (flecs_query_cache_match_table(world, cache, table)) { + if (ecs_should_log_3()) { + char *table_str = ecs_table_str(world, table); + ecs_dbg_3("query cache event: %s for [%s]", + ecs_get_name(world, event), + table_str); + ecs_os_free(table_str); + } } + return; + } + + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - srv_ctx->world = it->world; - srv_ctx->entity = it->entities[i]; - srv_ctx->srv = srv; - srv_ctx->rc = 1; + /* The observer isn't doing the matching because the query can do it more + * efficiently by checking the table with the query cache. */ + if (ecs_map_get(&cache->tables, table->id) == NULL) { + return; + } - rest[i].impl = srv_ctx; + if (ecs_should_log_3()) { + char *table_str = ecs_table_str(world, table); + ecs_dbg_3("query cache event: %s for [%s]", + ecs_get_name(world, event), + table_str); + ecs_os_free(table_str); + } - ecs_http_server_start(srv_ctx->srv); + if (event == EcsOnTableDelete) { + /* Deletion of table */ + flecs_query_cache_remove_table(cache, table); + return; } } +/* Create query-specific allocators. The reason these allocators are + * specific to the query is because they depend on the number of terms the + * query has. */ static -void DequeueRest(ecs_iter_t *it) { - EcsRest *rest = ecs_term(it, EcsRest, 1); - - if (it->delta_system_time > (FLECS_FLOAT)1.0) { - ecs_warn( - "detected large progress interval (%.2fs), REST request may timeout", - (double)it->delta_system_time); +void flecs_query_cache_allocators_init( + ecs_query_cache_t *cache) +{ + int32_t field_count = cache->query->field_count; + if (field_count) { + flecs_ballocator_init(&cache->allocators.pointers, + field_count * ECS_SIZEOF(ecs_table_record_t*)); + flecs_ballocator_init(&cache->allocators.ids, + field_count * ECS_SIZEOF(ecs_id_t)); + flecs_ballocator_init(&cache->allocators.monitors, + (1 + field_count) * ECS_SIZEOF(int32_t)); } - - int32_t i; - for(i = 0; i < it->count; i ++) { - ecs_rest_ctx_t *ctx = rest[i].impl; - if (ctx) { - ecs_http_server_dequeue(ctx->srv, it->delta_time); - } - } } -void FlecsRestImport( - ecs_world_t *world) +/* Free query-specific allocators. */ +static +void flecs_query_cache_allocators_fini( + ecs_query_cache_t *cache) { - ECS_MODULE(world, FlecsRest); + int32_t field_count = cache->query->field_count; + if (field_count) { + flecs_ballocator_fini(&cache->allocators.pointers); + flecs_ballocator_fini(&cache->allocators.ids); + flecs_ballocator_fini(&cache->allocators.monitors); + } +} - ecs_set_name_prefix(world, "Ecs"); +/* Free cache. */ +void flecs_query_cache_fini( + ecs_query_impl_t *impl) +{ + ecs_world_t *world = impl->pub.world; + ecs_stage_t *stage = impl->stage; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_bootstrap_component(world, EcsRest); + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_set_component_actions(world, EcsRest, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsRest), - .copy = ecs_copy(EcsRest), - .dtor = ecs_dtor(EcsRest), - .on_set = on_set_rest - }); + if (cache->observer) { + flecs_observer_fini(cache->observer); + } - ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); -} + ecs_group_delete_action_t on_delete = cache->on_group_delete; + if (on_delete) { + ecs_map_iter_t it = ecs_map_iter(&cache->groups); + while (ecs_map_next(&it)) { + ecs_query_cache_group_t *group = ecs_map_ptr(&it); + uint64_t group_id = ecs_map_key(&it); + on_delete(world, group_id, group->info.ctx, cache->group_by_ctx); + } + cache->on_group_delete = NULL; + } -#endif + if (cache->group_by_ctx_free) { + if (cache->group_by_ctx) { + cache->group_by_ctx_free(cache->group_by_ctx); + } + } + flecs_query_cache_for_each_component_monitor(world, impl, cache, + flecs_monitor_unregister); + flecs_query_cache_remove_all_tables(cache); -#ifdef FLECS_COREDOC + ecs_assert(ecs_map_count(&cache->tables) == 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_map_count(&cache->groups) == 0, ECS_INTERNAL_ERROR, NULL); + + ecs_map_fini(&cache->tables); + ecs_map_fini(&cache->groups); + ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_match_t); + + if (cache->query->term_count) { + flecs_bfree(&cache->allocators.ids, cache->sources); + } -#define URL_ROOT "https://flecs.docsforge.com/master/relations-manual/" + flecs_query_cache_allocators_fini(cache); + ecs_query_fini(cache->query); -void FlecsCoreDocImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsCoreDoc); + flecs_bfree(&stage->allocators.query_cache, cache); +} - ECS_IMPORT(world, FlecsMeta); - ECS_IMPORT(world, FlecsDoc); +/* Create new cache. */ +ecs_query_cache_t* flecs_query_cache_init( + ecs_query_impl_t *impl, + const ecs_query_desc_t *const_desc) +{ + ecs_world_t *world = impl->pub.real_world; + flecs_poly_assert(world, ecs_world_t); - ecs_set_name_prefix(world, "Ecs"); + ecs_stage_t *stage = impl->stage; + flecs_poly_assert(stage, ecs_stage_t); - /* Initialize reflection data for core components */ + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(const_desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(const_desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_query_desc_t was not initialized to zero"); + ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, + "cannot create query during world fini"); + + /* Create private version of desc to create the uncached query that will + * populate the query cache. */ + ecs_query_desc_t desc = *const_desc; + ecs_entity_t entity = desc.entity; + desc.cache_kind = EcsQueryCacheNone; /* Don't create caches recursively */ + desc.group_by_callback = NULL; + desc.group_by = 0; + desc.order_by_callback = NULL; + desc.order_by = 0; + desc.entity = 0; + + /* Don't pass ctx/binding_ctx to uncached query */ + desc.ctx = NULL; + desc.binding_ctx = NULL; + desc.ctx_free = NULL; + desc.binding_ctx_free = NULL; + + ecs_query_cache_t *result = flecs_bcalloc(&stage->allocators.query_cache); + result->entity = entity; + impl->cache = result; + + ecs_observer_desc_t observer_desc = { .query = desc }; + observer_desc.query.flags |= EcsQueryNested; + + ecs_flags32_t query_flags = const_desc->flags | world->default_query_flags; + desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly | EcsQueryNested; + + /* order_by is not compatible with matching empty tables, as it causes + * a query to return table slices, not entire tables. */ + if (const_desc->order_by_callback) { + query_flags &= ~EcsQueryMatchEmptyTables; + } + + ecs_query_t *q = result->query = ecs_query_init(world, &desc); + if (!q) { + goto error; + } - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsComponent), - .members = { - {.name = (char*)"size", .type = ecs_id(ecs_i32_t)}, - {.name = (char*)"alignment", .type = ecs_id(ecs_i32_t)} + /* Set flag for trivial caches which allows for faster iteration */ + if (impl->pub.flags & EcsQueryIsCacheable) { + /* Trivial caches may only contain And/Not operators. */ + int32_t t, count = q->term_count; + for (t = 0; t < count; t ++) { + if (q->terms[t].oper != EcsAnd && q->terms[t].oper != EcsNot && q->terms[t].oper != EcsOptional) { + break; + } } - }); - ecs_struct_init(world, &(ecs_struct_desc_t) { - .entity.entity = ecs_id(EcsDocDescription), - .members = { - {.name = "value", .type = ecs_id(ecs_string_t)} + if ((t == count) && (q->flags & EcsQueryMatchOnlySelf) && + !(q->flags & EcsQueryMatchWildcards) && + !(q->flags & EcsQueryCacheWithFilter)) + { + if (!const_desc->order_by && !const_desc->group_by && + !const_desc->order_by_callback && + !const_desc->group_by_callback && + !(const_desc->flags & EcsQueryDetectChanges)) + { + + q->flags |= EcsQueryTrivialCache; + } } - }); - - /* Initialize documentation data for core components */ - ecs_doc_set_brief(world, EcsFlecs, "Flecs root module"); - ecs_doc_set_link(world, EcsFlecs, "https://github.com/SanderMertens/flecs"); - - ecs_doc_set_brief(world, EcsFlecsCore, "Flecs module with builtin components"); - ecs_doc_set_brief(world, EcsFlecsHidden, "Flecs module with internal/anonymous entities"); - - ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); - - ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to all components"); - ecs_doc_set_brief(world, EcsModule, "Tag that is added to modules"); - ecs_doc_set_brief(world, EcsPrefab, "Tag that is added to prefabs"); - ecs_doc_set_brief(world, EcsDisabled, "Tag that is added to disabled entities"); - - ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); - ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to signal entity name"); - ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to signal entity symbol"); - - ecs_doc_set_brief(world, ecs_id(EcsComponentLifecycle), "Callbacks for component constructors, destructors, copy and move operations"); - - ecs_doc_set_brief(world, EcsTransitive, "Transitive relation property"); - ecs_doc_set_brief(world, EcsReflexive, "Reflexive relation property"); - ecs_doc_set_brief(world, EcsFinal, "Final relation property"); - ecs_doc_set_brief(world, EcsDontInherit, "DontInherit relation property"); - ecs_doc_set_brief(world, EcsTag, "Tag relation property"); - ecs_doc_set_brief(world, EcsAcyclic, "Acyclic relation property"); - ecs_doc_set_brief(world, EcsExclusive, "Exclusive relation property"); - ecs_doc_set_brief(world, EcsSymmetric, "Symmetric relation property"); - ecs_doc_set_brief(world, EcsWith, "With relation property"); - ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relation cleanup property"); - ecs_doc_set_brief(world, EcsOnDeleteObject, "OnDeleteObject relation cleanup property"); - ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); - ecs_doc_set_brief(world, EcsRemove, "Remove relation cleanup property"); - ecs_doc_set_brief(world, EcsDelete, "Delete relation cleanup property"); - ecs_doc_set_brief(world, EcsThrow, "Throw relation cleanup property"); - ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relation"); - ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relation"); - ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event"); - ecs_doc_set_brief(world, EcsOnRemove, "Builtin OnRemove event"); - ecs_doc_set_brief(world, EcsOnSet, "Builtin OnSet event"); - ecs_doc_set_brief(world, EcsUnSet, "Builtin UnSet event"); - - ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-property"); - ecs_doc_set_link(world, EcsReflexive, URL_ROOT "#reflexive-property"); - ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-property"); - ecs_doc_set_link(world, EcsDontInherit, URL_ROOT "#dontinherit-property"); - ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-property"); - ecs_doc_set_link(world, EcsAcyclic, URL_ROOT "#acyclic-property"); - ecs_doc_set_link(world, EcsExclusive, URL_ROOT "#exclusive-property"); - ecs_doc_set_link(world, EcsSymmetric, URL_ROOT "#symmetric-property"); - ecs_doc_set_link(world, EcsWith, URL_ROOT "#with-property"); - ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsOnDeleteObject, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsThrow, URL_ROOT "#cleanup-properties"); - ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relation"); - ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relation"); - - /* Initialize documentation for meta components */ - ecs_entity_t meta = ecs_lookup_fullpath(world, "flecs.meta"); - ecs_doc_set_brief(world, meta, "Flecs module with reflection components"); - - ecs_doc_set_brief(world, ecs_id(EcsMetaType), "Component added to types"); - ecs_doc_set_brief(world, ecs_id(EcsMetaTypeSerialized), "Component that stores reflection data in an optimized format"); - ecs_doc_set_brief(world, ecs_id(EcsPrimitive), "Component added to primitive types"); - ecs_doc_set_brief(world, ecs_id(EcsEnum), "Component added to enumeration types"); - ecs_doc_set_brief(world, ecs_id(EcsBitmask), "Component added to bitmask types"); - ecs_doc_set_brief(world, ecs_id(EcsMember), "Component added to struct members"); - ecs_doc_set_brief(world, ecs_id(EcsStruct), "Component added to struct types"); - ecs_doc_set_brief(world, ecs_id(EcsArray), "Component added to array types"); - ecs_doc_set_brief(world, ecs_id(EcsVector), "Component added to vector types"); - - ecs_doc_set_brief(world, ecs_id(ecs_bool_t), "bool component"); - ecs_doc_set_brief(world, ecs_id(ecs_char_t), "char component"); - ecs_doc_set_brief(world, ecs_id(ecs_byte_t), "byte component"); - ecs_doc_set_brief(world, ecs_id(ecs_u8_t), "8 bit unsigned int component"); - ecs_doc_set_brief(world, ecs_id(ecs_u16_t), "16 bit unsigned int component"); - ecs_doc_set_brief(world, ecs_id(ecs_u32_t), "32 bit unsigned int component"); - ecs_doc_set_brief(world, ecs_id(ecs_u64_t), "64 bit unsigned int component"); - ecs_doc_set_brief(world, ecs_id(ecs_uptr_t), "word sized unsigned int component"); - ecs_doc_set_brief(world, ecs_id(ecs_i8_t), "8 bit signed int component"); - ecs_doc_set_brief(world, ecs_id(ecs_i16_t), "16 bit signed int component"); - ecs_doc_set_brief(world, ecs_id(ecs_i32_t), "32 bit signed int component"); - ecs_doc_set_brief(world, ecs_id(ecs_i64_t), "64 bit signed int component"); - ecs_doc_set_brief(world, ecs_id(ecs_iptr_t), "word sized signed int component"); - ecs_doc_set_brief(world, ecs_id(ecs_f32_t), "32 bit floating point component"); - ecs_doc_set_brief(world, ecs_id(ecs_f64_t), "64 bit floating point component"); - ecs_doc_set_brief(world, ecs_id(ecs_string_t), "string component"); - ecs_doc_set_brief(world, ecs_id(ecs_entity_t), "entity component"); - - /* Initialize documentation for doc components */ - ecs_entity_t doc = ecs_lookup_fullpath(world, "flecs.doc"); - ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); - - ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); - ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); - ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); - ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); -} - -#endif - -/* This is a heavily modified version of the EmbeddableWebServer (see copyright - * below). This version has been stripped from everything not strictly necessary - * for receiving/replying to simple HTTP requests, and has been modified to use - * the Flecs OS API. */ - -/* EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and - * CONTRIBUTORS (see below) - All rights reserved. - * - * CONTRIBUTORS: - * Martin Pulec - bug fixes, warning fixes, IPv6 support - * Daniel Barry - bug fix (ifa_addr != NULL) - * - * Released under the BSD 2-clause license: - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. THIS SOFTWARE IS - * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - * NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ + } + if (const_desc->flags & EcsQueryDetectChanges) { + for (int i = 0; i < q->term_count; i ++) { + ecs_term_t *term = &q->terms[i]; -#ifdef FLECS_HTTP + /* If query has change detection, flag this on the component record. + * This allows code to skip calling modified() if there are no OnSet + * hooks/observers, and the component isn't used in any queries that use + * change detection. */ + + ecs_component_record_t *cr = + flecs_components_ensure(world, term->id); + cr->flags |= EcsIdHasOnSet; -#if defined(ECS_TARGET_WINDOWS) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#pragma comment(lib, "Ws2_32.lib") -#include -#include -#include -typedef SOCKET ecs_http_socket_t; -#else -#include -#include -#include -#include -#include -#include -typedef int ecs_http_socket_t; -#endif + if (term->id < FLECS_HI_COMPONENT_ID) { + world->non_trivial_set[term->id] = true; + } + } + } -/* Max length of request method */ -#define ECS_HTTP_METHOD_LEN_MAX (8) + if (const_desc->order_by) { + ecs_component_record_t *cr = + flecs_components_ensure(world, const_desc->order_by); + if (cr) { + cr->flags |= EcsIdHasOnSet; -/* Timeout (s) before connection purge */ -#define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) + if (const_desc->order_by < FLECS_HI_COMPONENT_ID) { + world->non_trivial_set[const_desc->order_by] = true; + } + } + } -/* Number of dequeues before purging */ -#define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) + ecs_size_t elem_size = flecs_query_cache_elem_size(result); + ecs_vec_init(&world->allocator, &result->default_group.tables, + elem_size, 0); + result->first_group = &result->default_group; -/* Minimum interval between dequeueing requests (ms) */ -#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (100) + /* The uncached query used to populate the cache always matches empty + * tables. This flag determines whether the empty tables are stored + * separately in the cache or are treated as regular tables. This is only + * enabled if the user requested that the query matches empty tables. */ + ECS_BIT_COND(q->flags, EcsQueryCacheYieldEmptyTables, + !!(query_flags & EcsQueryMatchEmptyTables)); -/* Minimum interval between printing statistics (ms) */ -#define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) + flecs_query_cache_allocators_init(result); -/* Max length of headers in reply */ -#define ECS_HTTP_REPLY_HEADER_SIZE (1024) + /* Zero'd out sources array that's used for results that only match $this. + * This reduces the amount of memory used by the cache, and improves CPU + * cache locality during iteration when doing source checks. */ + if (result->query->term_count) { + result->sources = flecs_bcalloc(&result->allocators.ids); + } -/* Receive buffer size */ -#define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) + if (q->term_count) { + observer_desc.run = flecs_query_cache_on_event; + observer_desc.ctx = impl; -/* Max length of request (path + query + headers + body) */ -#define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) + int32_t event_index = 0; + observer_desc.events[event_index ++] = EcsOnTableCreate; + observer_desc.events[event_index ++] = EcsOnTableDelete; + observer_desc.flags_ = EcsObserverBypassQuery; -/* HTTP server struct */ -struct ecs_http_server_t { - bool should_run; - bool running; + /* ecs_query_init could have moved away resources from the terms array + * in the descriptor, so use the terms array from the query. */ + ecs_os_memcpy_n(observer_desc.query.terms, q->terms, + ecs_term_t, q->term_count); + observer_desc.query.expr = NULL; /* Already parsed */ + observer_desc.query.flags |= EcsQueryTableOnly; - ecs_http_socket_t sock; - ecs_os_mutex_t lock; - ecs_os_thread_t thread; + result->observer = flecs_observer_init(world, entity, &observer_desc); + if (!result->observer) { + goto error; + } + } - ecs_http_reply_action_t callback; - void *ctx; + result->prev_match_count = -1; - ecs_sparse_t *connections; /* sparse */ - ecs_sparse_t *requests; /* sparse */ + if (ecs_should_log_1()) { + char *query_expr = ecs_query_str(result->query); + ecs_dbg_1("#[green]query#[normal] [%s] created", + query_expr ? query_expr : ""); + ecs_os_free(query_expr); + } - bool initialized; + ecs_log_push_1(); - uint16_t port; - const char *ipaddr; + if (flecs_query_cache_process_query(world, impl, result)) { + goto error; + } - FLECS_FLOAT dequeue_timeout; /* used to not lock request queue too often */ - FLECS_FLOAT stats_timeout; /* used for periodic reporting of statistics */ + /* Group before matching so we won't have to move tables around later */ + int32_t cascade_by = result->cascade_by; + if (cascade_by) { + flecs_query_cache_group_by(result, result->query->terms[cascade_by - 1].id, + flecs_query_cache_group_by_cascade); + result->group_by_ctx = &result->query->terms[cascade_by - 1]; + } - FLECS_FLOAT request_time; /* time spent on requests in last stats interval */ - FLECS_FLOAT request_time_total; /* total time spent on requests */ - int32_t requests_processed; /* requests processed in last stats interval */ - int32_t requests_processed_total; /* total requests processed */ - int32_t dequeue_count; /* number of dequeues in last stats interval */ -}; + if (const_desc->group_by_callback || const_desc->group_by) { + ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, + "cannot mix cascade and group_by"); + flecs_query_cache_group_by(result, + const_desc->group_by, const_desc->group_by_callback); + result->group_by_ctx = const_desc->group_by_ctx; + result->on_group_create = const_desc->on_group_create; + result->on_group_delete = const_desc->on_group_delete; + result->group_by_ctx_free = const_desc->group_by_ctx_free; + } -/** Fragment state, used by HTTP request parser */ -typedef enum { - HttpFragStateBegin, - HttpFragStateMethod, - HttpFragStatePath, - HttpFragStateVersion, - HttpFragStateHeaderStart, - HttpFragStateHeaderName, - HttpFragStateHeaderValueStart, - HttpFragStateHeaderValue, - HttpFragStateCR, - HttpFragStateCRLF, - HttpFragStateCRLFCR, - HttpFragStateBody, - HttpFragStateDone -} HttpFragState; + ecs_map_init(&result->tables, &world->allocator); + flecs_query_cache_match_tables(world, result); -/** A fragment is a partially received HTTP request */ -typedef struct { - HttpFragState state; - ecs_strbuf_t buf; - ecs_http_method_t method; - int32_t body_offset; - int32_t query_offset; - int32_t header_offsets[ECS_HTTP_HEADER_COUNT_MAX]; - int32_t header_value_offsets[ECS_HTTP_HEADER_COUNT_MAX]; - int32_t header_count; - int32_t param_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; - int32_t param_value_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; - int32_t param_count; - char header_buf[32]; - char *header_buf_ptr; - int32_t content_length; - bool parse_content_length; - bool invalid; -} ecs_http_fragment_t; + if (const_desc->order_by_callback) { + if (flecs_query_cache_order_by(world, impl, + const_desc->order_by, const_desc->order_by_callback, + const_desc->order_by_table_callback)) + { + goto error; + } + } -/** Extend public connection type with fragment data */ -typedef struct { - ecs_http_connection_t pub; - ecs_http_fragment_t frag; - ecs_http_socket_t sock; + if (entity) { + if (!ecs_map_count(&result->tables) && result->query->term_count){ + ecs_add_id(world, entity, EcsEmpty); + } + } - /* Connection is purged after both timeout expires and connection has - * exceeded retry count. This ensures that a connection does not immediately - * timeout when a frame takes longer than usual */ - FLECS_FLOAT dequeue_timeout; - int32_t dequeue_retries; -} ecs_http_connection_impl_t; + ecs_log_pop_1(); + + return result; +error: + return NULL; +} + +/** + * @file query/cache/cache_iter.c + * @brief Cache iterator functions. + */ -typedef struct { - ecs_http_request_t pub; - uint64_t conn_id; /* for sanity check */ - void *res; -} ecs_http_request_impl_t; +/* Update trs for cached query that has up fields. If a component got matched on + * another entity (typically a parent or prefab), that component could have + * moved which would cause the table record in the trs array to become invalid. + * This function updates the table records array to make sure they're pointing + * to the right table/column for fields that used up traversal. */ static -ecs_size_t http_send( - ecs_http_socket_t sock, - const void *buf, - ecs_size_t size, - int flags) +void flecs_query_update_node_up_trs( + const ecs_query_run_ctx_t *ctx, + ecs_query_cache_match_t *node) { -#ifndef ECS_TARGET_MSVC - ssize_t send_bytes = send(sock, buf, flecs_itosize(size), flags); - return flecs_itoi32(send_bytes); -#else - int send_bytes = send(sock, buf, size, flags); - return flecs_itoi32(send_bytes); -#endif + const ecs_query_impl_t *impl = ctx->query; + ecs_query_cache_t *cache = impl->cache; + ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INTERNAL_ERROR, NULL); + + ecs_termset_t fields = node->_up_fields & node->base.set_fields; + if (fields) { + const ecs_query_t *q = cache->query; + int32_t f, field_count = q->field_count; + int8_t *field_map = cache->field_map; + for (f = 0; f < field_count; f ++) { + if (!(fields & (1llu << f))) { + continue; + } + + ecs_entity_t src = node->_sources[f]; + if (src && src != EcsWildcard) { + ecs_record_t *r = flecs_entities_get(ctx->world, src); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + if (r->table != node->_tables[f]) { + node->_tables[f] = r->table; + + ecs_component_record_t *cr = flecs_components_get( + ctx->world, q->ids[f]); + const ecs_table_record_t *tr = node->base.trs[f] = + flecs_component_get_table(cr, r->table); + + ctx->it->trs[field_map ? field_map[f] : f] = tr; + } + } + } + } } -static -ecs_size_t http_recv( - ecs_http_socket_t sock, - void *buf, - ecs_size_t size, - int flags) +/* Initialize cached query iterator. */ +void flecs_query_cache_iter_init( + ecs_iter_t *it, + ecs_query_iter_t *qit, + ecs_query_impl_t *impl) { - ecs_size_t ret; -#ifndef ECS_TARGET_MSVC - ssize_t recv_bytes = recv(sock, buf, flecs_itosize(size), flags); - ret = flecs_itoi32(recv_bytes); -#else - int recv_bytes = recv(sock, buf, size, flags); - ret = flecs_itoi32(recv_bytes); -#endif - if (ret == -1) { - ecs_dbg("recv failed: %s (sock = %d)", ecs_os_strerror(errno), sock); - } else if (ret == 0) { - ecs_dbg("recv: received 0 bytes (sock = %d)", sock); + ecs_query_cache_t *cache = impl->cache; + if (!cache) { + return; } - return ret; -} + qit->group = cache->first_group; + qit->tables = &qit->group->tables; + qit->all_tables = qit->tables; + qit->cur = 0; -static -int http_getnameinfo( - const struct sockaddr* addr, - ecs_size_t addr_len, - char *host, - ecs_size_t host_len, - char *port, - ecs_size_t port_len, - int flags) -{ - ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); - return getnameinfo(addr, (uint32_t)addr_len, host, (uint32_t)host_len, - port, (uint32_t)port_len, flags); + /* If query uses order_by, iterate the array with ordered table slices. */ + if (cache->order_by_callback) { + /* Check if query needs sorting. */ + flecs_query_cache_sort_tables(it->real_world, impl); + qit->tables = &cache->table_slices; + qit->all_tables = qit->tables; + qit->group = NULL; + } + + cache->prev_match_count = cache->match_count; } +/* Find next match in cache. This function is called for non-trivial caches and + * handles features like wildcards, up traversal and grouping. */ static -int http_bind( - ecs_http_socket_t sock, - const struct sockaddr* addr, - ecs_size_t addr_len) +ecs_query_cache_match_t* flecs_query_cache_next( + const ecs_query_run_ctx_t *ctx, + bool always_match_empty) { - ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); - return bind(sock, addr, (uint32_t)addr_len); + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; + + repeat: { + if (qit->cur >= ecs_vec_count(qit->tables)) { + /* We're iterating the table vector of the group */ + if (qit->tables == qit->all_tables) { + /* If a group is set, we might have to iterate multiple */ + ecs_query_cache_group_t *group = qit->group; + if (!group || qit->iter_single_group) { + return NULL; + } + + /* Check if this was the last group to iterate */ + qit->group = group->next; + if (!qit->group) { + return NULL; + } + + /* Prepare iterator for the next group */ + qit->all_tables = qit->tables = &qit->group->tables; + qit->cur = 0; + + /* Not common, but can happen if a query uses group_by and there + * are no tables in the default group (group id 0). */ + if (!ecs_vec_count(qit->tables)) { + goto repeat; + } + + /* We're iterating a wildcard table vector */ + } else { + qit->tables = qit->all_tables; + qit->cur = qit->all_cur; + goto repeat; + } + } + + /* Get currently iterated cache element */ + ecs_query_cache_match_t *qm = + ecs_vec_get_t(qit->tables, ecs_query_cache_match_t, qit->cur); + + /* Check if table is empty and whether we need to skip it */ + ecs_table_t *table = qm->base.table; + if (!ecs_table_count(table)) { + if (!(always_match_empty || (it->flags & EcsIterMatchEmptyTables))) { + if (ctx->query->pub.flags & EcsQueryHasChangeDetection) { + flecs_query_sync_match_monitor( + flecs_query_impl(it->query), qm); + } + qit->cur ++; + goto repeat; + } + } + + qit->elem = qm; + + /* If there are multiple matches for table iterate those first. */ + if (qm->wildcard_matches) { + qit->tables = qm->wildcard_matches; + qit->all_cur = qit->cur + 1; + qit->cur = 0; + } else { + qit->cur ++; + } + + ctx->vars[0].range.table = table; + + return qm; + } } +/* Update cached pointers. Cached queries store the column pointers of a matched + * table which improves cache locality of fetching component pointers while + * iterating a cache as it avoids having to go through a table record. + * Component pointers only need to be updated when a table column got + * reallocated, in which case the table_column_version will have increased. */ static -void http_close( - ecs_http_socket_t sock) +void flecs_query_cache_update_ptrs( + ecs_iter_t *it, + ecs_query_triv_cache_match_t *qm, + ecs_table_t *table) { -#if defined(ECS_TARGET_WINDOWS) - closesocket(sock); -#else - shutdown(sock, SHUT_RDWR); - close(sock); -#endif + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + it->ptrs = qm->ptrs; + + uint32_t version = flecs_get_table_column_version( + it->real_world, table->id); + if (qm->table_version == version) { + /* This is the most common case, as table columns pointers only change + * when the table grows in size. */ + return; + } + + qm->table_version = version; + + /* Update the pointers. This can be done safely from multiple threads since + * all the read data is immutable, and thus each thread will arrive at the + * same result. */ + int32_t i, field_count = it->field_count; + for (i = 0; i < field_count; i ++) { + qm->ptrs[i] = NULL; + + const ecs_table_record_t *tr = qm->trs[i]; + if (!tr || tr->column == -1) { + /* Field is not set or is not a component. */ + continue; + } + + if (it->sources[i]) { + /* Field is not matched on table */ + continue; + } + + qm->ptrs[i] = table->data.columns[tr->column].data; + } } +/* Find next match in trivial cache. A trivial cache doesn't have to handle + * wildcards, multiple groups or fields matched through up traversal. */ static -ecs_http_socket_t http_accept( - ecs_http_socket_t sock, - struct sockaddr* addr, - ecs_size_t *addr_len) +ecs_query_cache_match_t* flecs_query_trivial_cache_next( + const ecs_query_run_ctx_t *ctx) { - socklen_t len = (socklen_t)addr_len[0]; - ecs_http_socket_t result = accept(sock, addr, &len); - addr_len[0] = (ecs_size_t)len; - return result; -} + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; -static -void reply_free(ecs_http_reply_t* response) { - ecs_assert(response != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(response->body.content); -} + repeat: { + if (qit->cur == ecs_vec_count(qit->tables)) { + return NULL; + } -static -void request_free(ecs_http_request_impl_t *req) { - ecs_assert(req != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(req->pub.conn != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(req->pub.conn->server != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(req->pub.conn->server->requests != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(req->pub.conn->id == req->conn_id, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(req->res); - flecs_sparse_remove(req->pub.conn->server->requests, req->pub.id); + ecs_query_triv_cache_match_t *qm = ecs_vec_get_t( + qit->tables, ecs_query_triv_cache_match_t, qit->cur); + ecs_table_t *table = it->table = qm->table; + int32_t count = it->count = ecs_table_count(table); + + qit->cur ++; + + if (!count) { + if (!(it->flags & EcsIterMatchEmptyTables)) { + goto repeat; + } + } + + flecs_query_cache_update_ptrs(it, qm, table); + + it->entities = ecs_table_entities(table); + it->trs = qm->trs; + it->set_fields = qm->set_fields; + + return qit->elem = (ecs_query_cache_match_t*)qm; + } } static -void connection_free(ecs_http_connection_impl_t *conn) { - ecs_assert(conn != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(conn->pub.id != 0, ECS_INTERNAL_ERROR, NULL); - uint64_t conn_id = conn->pub.id; +ecs_query_cache_match_t* flecs_query_test( + const ecs_query_run_ctx_t *ctx, + bool redo) +{ + ecs_iter_t *it = ctx->it; + if (!redo) { + ecs_var_t *var = &ctx->vars[0]; + ecs_table_t *table = var->range.table; + ecs_assert(table != NULL, ECS_INVALID_OPERATION, + "the variable set on the iterator is missing a table"); + + ecs_query_cache_table_t *qt = flecs_query_cache_get_table( + ctx->query->cache, table); + if (!qt) { + return NULL; + } - if (conn->sock) { - http_close(conn->sock); + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_assert(qt->group != NULL, ECS_INTERNAL_ERROR, NULL); + qit->group = qt->group; + qit->tables = &qt->group->tables; + qit->cur = qt->index; } - flecs_sparse_remove(conn->pub.server->connections, conn_id); -} + ecs_query_cache_match_t *qm = flecs_query_cache_next(ctx, true /* always match empty */); + if (redo && qm) { + if (qm->base.table != it->table) { + return NULL; + } + } -// https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int -static -char hex_2_int(char a, char b){ - a = (a <= '9') ? (char)(a - '0') : (char)((a & 0x7) + 9); - b = (b <= '9') ? (char)(b - '0') : (char)((b & 0x7) + 9); - return (char)((a << 4) + b); + return qm; } static -void decode_url_str( - char *str) +void flecs_query_cache_init_mapped_fields( + const ecs_query_run_ctx_t *ctx, + ecs_query_cache_match_t *node) { - char ch, *ptr, *dst = str; - for (ptr = str; (ch = *ptr); ptr++) { - if (ch == '%') { - dst[0] = hex_2_int(ptr[1], ptr[2]); - dst ++; - ptr += 2; - } else { - dst[0] = ptr[0]; - dst ++; - } + ecs_iter_t *it = ctx->it; + const ecs_query_impl_t *impl = ctx->query; + ecs_query_cache_t *cache = impl->cache; + ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INTERNAL_ERROR, NULL); + + int32_t i, field_count = cache->query->field_count; + int8_t *field_map = cache->field_map; + + for (i = 0; i < field_count; i ++) { + int8_t field_index = field_map[i]; + it->trs[field_index] = node->base.trs[i]; + + it->ids[field_index] = node->_ids[i]; + it->sources[field_index] = node->_sources[i]; + + ecs_termset_t bit = (ecs_termset_t)(1u << i); + ecs_termset_t field_bit = (ecs_termset_t)(1u << field_index); + + ECS_TERMSET_COND(it->set_fields, field_bit, node->base.set_fields & bit); + ECS_TERMSET_COND(it->up_fields, field_bit, node->_up_fields & bit); } - dst[0] = '\0'; } -static -void parse_method( - ecs_http_fragment_t *frag) +/* Iterate cache for query that's partially cached */ +bool flecs_query_cache_search( + const ecs_query_run_ctx_t *ctx) { - char *method = ecs_strbuf_get_small(&frag->buf); - if (!ecs_os_strcmp(method, "GET")) frag->method = EcsHttpGet; - else if (!ecs_os_strcmp(method, "POST")) frag->method = EcsHttpPost; - else if (!ecs_os_strcmp(method, "PUT")) frag->method = EcsHttpPut; - else if (!ecs_os_strcmp(method, "DELETE")) frag->method = EcsHttpDelete; - else if (!ecs_os_strcmp(method, "OPTIONS")) frag->method = EcsHttpOptions; - else { - frag->method = EcsHttpMethodUnsupported; - frag->invalid = true; + ecs_assert(!flecs_query_cache_is_trivial(ctx->query->cache), + ECS_INTERNAL_ERROR, NULL); + + ecs_query_cache_match_t *node = flecs_query_cache_next(ctx, false); + if (!node) { + return false; } - ecs_strbuf_reset(&frag->buf); + + flecs_query_cache_init_mapped_fields(ctx, node); + ctx->vars[0].range.count = node->_count; + ctx->vars[0].range.offset = node->_offset; + + flecs_query_update_node_up_trs(ctx, node); + + return true; } -static -bool header_writable( - ecs_http_fragment_t *frag) +/* Iterate cache for query that's entirely cached */ +bool flecs_query_is_cache_search( + const ecs_query_run_ctx_t *ctx) { - return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; + ecs_assert(!flecs_query_cache_is_trivial(ctx->query->cache), + ECS_INTERNAL_ERROR, NULL); + + ecs_query_cache_match_t *node = flecs_query_cache_next(ctx, false); + if (!node) { + return false; + } + + ctx->vars[0].range.count = node->_count; + ctx->vars[0].range.offset = node->_offset; + + ecs_iter_t *it = ctx->it; + it->trs = node->base.trs; + it->ids = node->_ids; + it->sources = node->_sources; + it->set_fields = node->base.set_fields; + it->up_fields = node->_up_fields; + +#ifdef FLECS_DEBUG + it->flags |= EcsIterImmutableCacheData; +#endif + + flecs_query_update_node_up_trs(ctx, node); + + flecs_query_cache_update_ptrs(it, &node->base, node->base.table); + + return true; } -static -void header_buf_reset( - ecs_http_fragment_t *frag) +/* Iterate cache for query that's entirely cached */ +bool flecs_query_is_trivial_cache_search( + const ecs_query_run_ctx_t *ctx) { - frag->header_buf[0] = '\0'; - frag->header_buf_ptr = frag->header_buf; + return flecs_query_trivial_cache_next(ctx) != NULL; } -static -void header_buf_append( - ecs_http_fragment_t *frag, - char ch) +/* Test if query that is entirely cached matches constrained $this */ +bool flecs_query_cache_test( + const ecs_query_run_ctx_t *ctx, + bool redo) { - if ((frag->header_buf_ptr - frag->header_buf) < - ECS_SIZEOF(frag->header_buf)) - { - frag->header_buf_ptr[0] = ch; - frag->header_buf_ptr ++; - } else { - frag->header_buf_ptr[0] = '\0'; + ecs_query_cache_match_t *node = flecs_query_test(ctx, redo); + if (!node) { + return false; } + + flecs_query_cache_init_mapped_fields(ctx, node); + flecs_query_update_node_up_trs(ctx, node); + + return true; } -static -void enqueue_request( - ecs_http_connection_impl_t *conn) +/* Test if query that is entirely cached matches constrained $this */ +bool flecs_query_is_cache_test( + const ecs_query_run_ctx_t *ctx, + bool redo) { - ecs_http_server_t *srv = conn->pub.server; - ecs_http_fragment_t *frag = &conn->frag; + ecs_assert(!flecs_query_cache_is_trivial(ctx->query->cache), + ECS_INTERNAL_ERROR, NULL); - if (frag->invalid) { /* invalid request received, don't enqueue */ - ecs_strbuf_reset(&frag->buf); - } else { - char *res = ecs_strbuf_get(&frag->buf); - if (res) { - ecs_os_mutex_lock(srv->lock); - ecs_http_request_impl_t *req = flecs_sparse_add( - srv->requests, ecs_http_request_impl_t); - req->pub.id = flecs_sparse_last_id(srv->requests); - req->conn_id = conn->pub.id; - ecs_os_mutex_unlock(srv->lock); + ecs_query_cache_match_t *node = flecs_query_test(ctx, redo); + if (!node) { + return false; + } - req->pub.conn = (ecs_http_connection_t*)conn; - req->pub.method = frag->method; - req->pub.path = res + 1; - if (frag->body_offset) { - req->pub.body = &res[frag->body_offset]; - } - int32_t i, count = frag->header_count; - for (i = 0; i < count; i ++) { - req->pub.headers[i].key = &res[frag->header_offsets[i]]; - req->pub.headers[i].value = &res[frag->header_value_offsets[i]]; - } - count = frag->param_count; - for (i = 0; i < count; i ++) { - req->pub.params[i].key = &res[frag->param_offsets[i]]; - req->pub.params[i].value = &res[frag->param_value_offsets[i]]; - decode_url_str((char*)req->pub.params[i].value); - } + ecs_iter_t *it = ctx->it; + it->trs = node->base.trs; + it->ids = node->_ids; + it->sources = node->_sources; + it->set_fields = node->base.set_fields; - req->pub.header_count = frag->header_count; - req->pub.param_count = frag->param_count; - req->res = res; - } - } +#ifdef FLECS_DEBUG + it->flags |= EcsIterImmutableCacheData; +#endif + + flecs_query_update_node_up_trs(ctx, node); + + return true; } -static -bool parse_request( - ecs_http_connection_impl_t *conn, - uint64_t conn_id, - const char* req_frag, - ecs_size_t req_frag_len) +bool flecs_query_is_trivial_cache_test( + const ecs_query_run_ctx_t *ctx, + bool redo) { - ecs_http_fragment_t *frag = &conn->frag; + ecs_assert(flecs_query_cache_is_trivial(ctx->query->cache), + ECS_INTERNAL_ERROR, NULL); - int32_t i; - for (i = 0; i < req_frag_len; i++) { - char c = req_frag[i]; - switch (frag->state) { - case HttpFragStateBegin: - ecs_os_memset_t(frag, 0, ecs_http_fragment_t); - frag->buf.max = ECS_HTTP_METHOD_LEN_MAX; - frag->state = HttpFragStateMethod; - frag->header_buf_ptr = frag->header_buf; - /* fallthrough */ - case HttpFragStateMethod: - if (c == ' ') { - parse_method(frag); - frag->state = HttpFragStatePath; - frag->buf.max = ECS_HTTP_REQUEST_LEN_MAX; - } else { - ecs_strbuf_appendch(&frag->buf, c); - } - break; - case HttpFragStatePath: - if (c == ' ') { - frag->state = HttpFragStateVersion; - ecs_strbuf_appendch(&frag->buf, '\0'); - } else { - if (c == '?' || c == '=' || c == '&') { - ecs_strbuf_appendch(&frag->buf, '\0'); - int32_t offset = ecs_strbuf_written(&frag->buf); - if (c == '?' || c == '&') { - frag->param_offsets[frag->param_count] = offset; - } else { - frag->param_value_offsets[frag->param_count] = offset; - frag->param_count ++; - } - } else { - ecs_strbuf_appendch(&frag->buf, c); - } - } - break; - case HttpFragStateVersion: - if (c == '\r') { - frag->state = HttpFragStateCR; - } /* version is not stored */ - break; - case HttpFragStateHeaderStart: - if (header_writable(frag)) { - frag->header_offsets[frag->header_count] = - ecs_strbuf_written(&frag->buf); - } - header_buf_reset(frag); - frag->state = HttpFragStateHeaderName; - /* fallthrough */ - case HttpFragStateHeaderName: - if (c == ':') { - frag->state = HttpFragStateHeaderValueStart; - header_buf_append(frag, '\0'); - frag->parse_content_length = !ecs_os_strcmp( - frag->header_buf, "Content-Length"); + ecs_iter_t *it = ctx->it; + if (!redo) { + ecs_var_t *var = &ctx->vars[0]; + ecs_table_t *table = var->range.table; + ecs_assert(table != NULL, ECS_INVALID_OPERATION, + "the variable set on the iterator is missing a table"); - if (header_writable(frag)) { - ecs_strbuf_appendch(&frag->buf, '\0'); - frag->header_value_offsets[frag->header_count] = - ecs_strbuf_written(&frag->buf); - } - } else if (c == '\r') { - frag->state = HttpFragStateCR; - } else { - header_buf_append(frag, c); - if (header_writable(frag)) { - ecs_strbuf_appendch(&frag->buf, c); - } - } - break; - case HttpFragStateHeaderValueStart: - header_buf_reset(frag); - frag->state = HttpFragStateHeaderValue; - if (c == ' ') { /* skip first space */ - break; - } - /* fallthrough */ - case HttpFragStateHeaderValue: - if (c == '\r') { - if (frag->parse_content_length) { - header_buf_append(frag, '\0'); - int32_t len = atoi(frag->header_buf); - if (len < 0) { - frag->invalid = true; - } else { - frag->content_length = len; - } - frag->parse_content_length = false; - } - if (header_writable(frag)) { - int32_t cur = ecs_strbuf_written(&frag->buf); - if (frag->header_offsets[frag->header_count] < cur && - frag->header_value_offsets[frag->header_count] < cur) - { - ecs_strbuf_appendch(&frag->buf, '\0'); - frag->header_count ++; - } - } - frag->state = HttpFragStateCR; - } else { - if (frag->parse_content_length) { - header_buf_append(frag, c); - } - if (header_writable(frag)) { - ecs_strbuf_appendch(&frag->buf, c); - } - } - break; - case HttpFragStateCR: - if (c == '\n') { - frag->state = HttpFragStateCRLF; - } else { - frag->state = HttpFragStateHeaderStart; - } - break; - case HttpFragStateCRLF: - if (c == '\r') { - frag->state = HttpFragStateCRLFCR; - } else { - frag->state = HttpFragStateHeaderStart; - i--; - } - break; - case HttpFragStateCRLFCR: - if (c == '\n') { - if (frag->content_length != 0) { - frag->body_offset = ecs_strbuf_written(&frag->buf); - frag->state = HttpFragStateBody; - } else { - frag->state = HttpFragStateDone; - } - } else { - frag->state = HttpFragStateHeaderStart; - } - break; - case HttpFragStateBody: { - ecs_strbuf_appendch(&frag->buf, c); - if ((ecs_strbuf_written(&frag->buf) - frag->body_offset) == - frag->content_length) - { - frag->state = HttpFragStateDone; - } - } - break; - case HttpFragStateDone: - break; + ecs_query_cache_t *cache = ctx->query->cache; + ecs_query_cache_table_t *qt = flecs_query_cache_get_table(cache, table); + if (!qt) { + return false; } - } - if (frag->state == HttpFragStateDone) { - frag->state = HttpFragStateBegin; - if (conn->pub.id == conn_id) { - enqueue_request(conn); - } + ecs_query_cache_match_t *qm = + flecs_query_cache_match_from_table(cache, qt); + it->trs = qm->base.trs; + it->set_fields = qm->base.set_fields; return true; - } else { - return false; } + + return false; } -static -void append_send_headers( - ecs_strbuf_t *hdrs, - int code, - const char* status, - const char* content_type, - ecs_strbuf_t *extra_headers, - ecs_size_t content_len) -{ - ecs_strbuf_appendstr(hdrs, "HTTP/1.1 "); - ecs_strbuf_append(hdrs, "%d ", code); - ecs_strbuf_appendstr(hdrs, status); - ecs_strbuf_appendstr(hdrs, "\r\n"); +/** + * @file query/cache/change_detection.c + * @brief Query change detection implementation. + */ - ecs_strbuf_appendstr(hdrs, "Content-Type: "); - ecs_strbuf_appendstr(hdrs, content_type); - ecs_strbuf_appendstr(hdrs, "\r\n"); - ecs_strbuf_appendstr(hdrs, "Content-Length: "); - ecs_strbuf_append(hdrs, "%d", content_len); - ecs_strbuf_appendstr(hdrs, "\r\n"); +typedef struct { + ecs_table_t *table; + int32_t column; +} flecs_table_column_t; - ecs_strbuf_appendstr(hdrs, "Server: flecs\r\n"); +/* Get table column index for query field. */ +static +void flecs_query_get_column_for_field( + const ecs_query_t *q, + ecs_query_cache_match_t *match, + int32_t field, + flecs_table_column_t *out) +{ + ecs_assert(field >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(field < q->field_count, ECS_INTERNAL_ERROR, NULL); + (void)q; - ecs_strbuf_mergebuff(hdrs, extra_headers); + const ecs_table_record_t *tr = match->base.trs[field]; + ecs_table_t *table = tr->hdr.table; + int32_t column = tr->column; - ecs_strbuf_appendstr(hdrs, "\r\n"); + out->table = table; + out->column = column; } +/* Get match monitor. Monitors are used to keep track of whether components + * matched by the query in a table have changed. */ static -void send_reply( - ecs_http_connection_impl_t* conn, - ecs_http_reply_t* reply) +bool flecs_query_get_match_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_match_t *match) { - char hdrs[ECS_HTTP_REPLY_HEADER_SIZE]; - ecs_strbuf_t hdr_buf = ECS_STRBUF_INIT; - hdr_buf.buf = hdrs; - hdr_buf.max = ECS_HTTP_REPLY_HEADER_SIZE; - hdr_buf.buf = hdrs; + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - char *content = ecs_strbuf_get(&reply->body); - int32_t content_length = reply->body.length - 1; + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + bool is_trivial = flecs_query_cache_is_trivial(cache); + ecs_assert(!is_trivial, ECS_INVALID_OPERATION, + "query was not created with change detection enabled"); - /* First, send the response HTTP headers */ - append_send_headers(&hdr_buf, reply->code, reply->status, - reply->content_type, &reply->headers, content_length); + if (match->_monitor) { + return false; + } - ecs_size_t hdrs_len = ecs_strbuf_written(&hdr_buf); - hdrs[hdrs_len] = '\0'; - ecs_size_t written = http_send(conn->sock, hdrs, hdrs_len, 0); + int32_t *monitor = flecs_balloc(&cache->allocators.monitors); + monitor[0] = 0; - if (written != hdrs_len) { - ecs_err("failed to write HTTP response headers to '%s:%s': %s", - conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); - return; - } + /* Mark terms that don't need to be monitored. This saves time when reading + * and/or updating the monitor. */ + const ecs_query_t *q = cache->query; + int32_t i, field = -1, term_count = q->term_count; + flecs_table_column_t tc; - /* Second, send response body */ - if (content_length > 0) { - written = http_send(conn->sock, content, content_length, 0); - if (written != content_length) { - ecs_err("failed to write HTTP response body to '%s:%s': %s", - conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); + for (i = 0; i < term_count; i ++) { + if (field == q->terms[i].field_index) { + if (monitor[field + 1] != -1) { + continue; + } } - } -} -static -void recv_request( - ecs_http_server_t *srv, - ecs_http_connection_impl_t *conn, - uint64_t conn_id, - ecs_http_socket_t sock) -{ - ecs_size_t bytes_read; - char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; + field = q->terms[i].field_index; + monitor[field + 1] = -1; - while ((bytes_read = http_recv( - sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) - { - ecs_os_mutex_lock(srv->lock); - bool is_alive = conn->pub.id == conn_id; - if (is_alive) { - conn->dequeue_timeout = 0; - conn->dequeue_retries = 0; + /* If term isn't read, don't monitor */ + if (q->terms[i].inout != EcsIn && + q->terms[i].inout != EcsInOut && + q->terms[i].inout != EcsInOutDefault) { + continue; } - ecs_os_mutex_unlock(srv->lock); - if (is_alive) { - if (parse_request(conn, conn_id, recv_buf, bytes_read)) { - return; + /* Don't track fields that aren't set */ + if (!is_trivial) { + if (!(match->base.set_fields & (1llu << field))) { + continue; } - } else { - return; } - } -} - -static -void init_connection( - ecs_http_server_t *srv, - ecs_http_socket_t sock_conn, - struct sockaddr_storage *remote_addr, - ecs_size_t remote_addr_len) -{ - /* Create new connection */ - ecs_os_mutex_lock(srv->lock); - ecs_http_connection_impl_t *conn = flecs_sparse_add( - srv->connections, ecs_http_connection_impl_t); - uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(srv->connections); - conn->pub.server = srv; - conn->sock = sock_conn; - ecs_os_mutex_unlock(srv->lock); - char *remote_host = conn->pub.host; - char *remote_port = conn->pub.port; + flecs_query_get_column_for_field(q, match, field, &tc); + if (tc.column == -1) { + continue; /* Don't track terms that aren't stored */ + } - /* Fetch name & port info */ - if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, - remote_host, ECS_SIZEOF(conn->pub.host), - remote_port, ECS_SIZEOF(conn->pub.port), - NI_NUMERICHOST | NI_NUMERICSERV)) - { - ecs_os_strcpy(remote_host, "unknown"); - ecs_os_strcpy(remote_port, "unknown"); + monitor[field + 1] = 0; } - ecs_dbg_2("http: connection established from '%s:%s'", - remote_host, remote_port); + match->_monitor = monitor; - recv_request(srv, conn, conn_id, sock_conn); + impl->pub.flags |= EcsQueryHasChangeDetection; - ecs_dbg_2("http: request received from '%s:%s'", - remote_host, remote_port); + return true; } +/* Get monitor for fixed query terms. Fixed terms are handled separately as they + * don't require a query cache, and fixed terms aren't stored in the cache. */ static -void accept_connections( - ecs_http_server_t* srv, - const struct sockaddr* addr, - ecs_size_t addr_len) +bool flecs_query_get_fixed_monitor( + ecs_query_impl_t *impl, + bool check) { -#ifdef ECS_TARGET_WINDOWS - /* If on Windows, test if winsock needs to be initialized */ - SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (SOCKET_ERROR == testsocket && WSANOTINITIALISED == WSAGetLastError()) { - WSADATA data = { 0 }; - int result = WSAStartup(MAKEWORD(2, 2), &data); - if (result) { - ecs_warn("WSAStartup failed with GetLastError = %d\n", - GetLastError()); - return; - } - } else { - http_close(testsocket); - } -#endif - - /* Resolve name + port (used for logging) */ - char addr_host[256]; - char addr_port[20]; + ecs_query_t *q = &impl->pub; + ecs_world_t *world = q->real_world; + ecs_term_t *terms = q->terms; + int32_t i, term_count = q->term_count; - if (http_getnameinfo( - addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, - ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) - { - ecs_os_strcpy(addr_host, "unknown"); - ecs_os_strcpy(addr_port, "unknown"); + if (!impl->monitor) { + impl->monitor = flecs_alloc_n(&impl->stage->allocator, + int32_t, q->field_count); + check = false; /* If the monitor is new, initialize it with dirty state */ } - ecs_os_mutex_lock(srv->lock); - if (srv->should_run) { - ecs_dbg_2("http: initializing connection socket"); - - srv->sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); - if (srv->sock < 0) { - ecs_err("unable to create new connection socket: %s", - ecs_os_strerror(errno)); - ecs_os_mutex_unlock(srv->lock); - goto done; - } + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + int16_t field_index = term->field_index; - int reuse = 1; - int result = setsockopt(srv->sock, SOL_SOCKET, SO_REUSEADDR, - (char*)&reuse, ECS_SIZEOF(reuse)); - if (result) { - ecs_warn("failed to setsockopt: %s", ecs_os_strerror(errno)); + if (!(q->read_fields & flecs_ito(uint32_t, 1 << field_index))) { + continue; /* If term doesn't read data there's nothing to track */ } - if (addr->sa_family == AF_INET6) { - int ipv6only = 0; - if (setsockopt(srv->sock, IPPROTO_IPV6, IPV6_V6ONLY, - (char*)&ipv6only, ECS_SIZEOF(ipv6only))) - { - ecs_warn("failed to setsockopt: %s", ecs_os_strerror(errno)); - } - } - - result = http_bind(srv->sock, addr, addr_len); - if (result) { - ecs_err("http: failed to bind to '%s:%s': %s", - addr_host, addr_port, ecs_os_strerror(errno)); - ecs_os_mutex_unlock(srv->lock); - goto done; + if (!(term->src.id & EcsIsEntity)) { + continue; /* Not a term with a fixed source */ } - result = listen(srv->sock, SOMAXCONN); - if (result) { - ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", - SOMAXCONN, ecs_os_strerror(errno)); + ecs_entity_t src = ECS_TERM_REF_ID(&term->src); + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_record_t *r = flecs_entities_get(world, src); + if (!r || !r->table) { + continue; /* Entity is empty, nothing to track */ } - ecs_trace("http: listening for incoming connections on '%s:%s'", - addr_host, addr_port); - } - ecs_os_mutex_unlock(srv->lock); + ecs_component_record_t *cr = flecs_components_get(world, term->id); + if (!cr) { + continue; /* If id doesn't exist, entity can't have it */ + } - ecs_http_socket_t sock_conn; - struct sockaddr_storage remote_addr; - ecs_size_t remote_addr_len; + const ecs_table_record_t *tr = flecs_component_get_table(cr, r->table); + if (!tr) { + continue; /* Entity doesn't have the component */ + } - while (srv->should_run) { - remote_addr_len = ECS_SIZEOF(remote_addr); - sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, - &remote_addr_len); + /* Copy/check column dirty state from table */ + int32_t *dirty_state = flecs_table_get_dirty_state(world, r->table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - if (sock_conn == -1) { - if (srv->should_run) { - ecs_dbg("http: connection attempt failed: %s", - ecs_os_strerror(errno)); + if (!check) { + impl->monitor[field_index] = dirty_state[tr->column + 1]; + } else { + if (impl->monitor[field_index] != dirty_state[tr->column + 1]) { + return true; } - continue; } - - init_connection(srv, sock_conn, &remote_addr, remote_addr_len); } -done: - if (srv->sock && errno != EBADF) { - http_close(srv->sock); - srv->sock = 0; - } + return !check; +} - ecs_trace("http: no longer accepting connections on '%s:%s'", - addr_host, addr_port); +/* Synchronize fixed source monitor */ +bool flecs_query_update_fixed_monitor( + ecs_query_impl_t *impl) +{ + return flecs_query_get_fixed_monitor(impl, false); +} + +/* Compare fixed source monitor */ +bool flecs_query_check_fixed_monitor( + ecs_query_impl_t *impl) +{ + return flecs_query_get_fixed_monitor(impl, true); } +/* Check if single match term has changed */ static -void* http_server_thread(void* arg) { - ecs_http_server_t *srv = arg; - struct sockaddr_in addr; - ecs_os_zeromem(&addr); - addr.sin_family = AF_INET; - addr.sin_port = htons(srv->port); +bool flecs_query_check_match_monitor_term( + ecs_query_impl_t *impl, + ecs_query_cache_match_t *match, + int32_t field) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - if (!srv->ipaddr) { - addr.sin_addr.s_addr = htonl(INADDR_ANY); - } else { - inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INVALID_OPERATION, + "query was not created with change detection enabled"); + + if (flecs_query_get_match_monitor(impl, match)) { + return true; } - accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); - return NULL; + int32_t *monitor = match->_monitor; + int32_t state = monitor[field]; + if (state == -1) { + return false; + } + + ecs_table_t *table = match->base.table; + if (table) { + int32_t *dirty_state = flecs_table_get_dirty_state( + cache->query->world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (!field) { + return monitor[0] != dirty_state[0]; + } + } else if (!field) { + return false; + } + + flecs_table_column_t cur; + flecs_query_get_column_for_field( + &impl->pub, match, field - 1, &cur); + ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); + + return monitor[field] != flecs_table_get_dirty_state( + cache->query->world, cur.table)[cur.column + 1]; } +/* Check if any tables in the cache changed. */ static -void handle_request( - ecs_http_server_t *srv, - ecs_http_request_impl_t *req) +bool flecs_query_check_cache_monitor( + ecs_query_impl_t *impl) { - ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; - ecs_http_connection_impl_t *conn = - (ecs_http_connection_impl_t*)req->pub.conn; + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == 0) { - reply.code = 404; - reply.status = "Resource not found"; + /* If the match count changed, tables got matched/unmatched for the + * cache, so return that the query has changed. */ + if (cache->match_count != cache->prev_match_count) { + return true; } - send_reply(conn, &reply); - ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); + const ecs_query_cache_group_t *cur = cache->first_group; + do { + int32_t i, count = ecs_vec_count(&cur->tables); + for (i = 0; i < count; i ++) { + ecs_query_cache_match_t *qm = + ecs_vec_get_t(&cur->tables, ecs_query_cache_match_t, i); + if (flecs_query_check_table_monitor(impl, qm, -1)) { + return true; + } + + if (qm->wildcard_matches) { + ecs_query_cache_match_t *wc_qms = + ecs_vec_first(qm->wildcard_matches); + int32_t j, wc_count = ecs_vec_count(qm->wildcard_matches); + for (j = 0; j < wc_count; j ++) { + if (flecs_query_check_table_monitor(impl, &wc_qms[j], -1)) { + return true; + } + } + } + } + } while ((cur = cur->next)); - reply_free(&reply); - request_free(req); - connection_free(conn); + return false; } +/* Initialize monitors for the elements in the query cache. */ static -int32_t dequeue_requests( - ecs_http_server_t *srv, - float delta_time) +void flecs_query_init_query_monitors( + ecs_query_impl_t *impl) { - ecs_os_mutex_lock(srv->lock); - - int32_t i, request_count = flecs_sparse_count(srv->requests); - for (i = request_count - 1; i >= 1; i --) { - ecs_http_request_impl_t *req = flecs_sparse_get_dense( - srv->requests, ecs_http_request_impl_t, i); - handle_request(srv, req); + /* Change monitor for cache */ + ecs_query_cache_t *cache = impl->cache; + if (cache) { + const ecs_query_cache_group_t *cur = cache->first_group; + do { + int32_t i, count = ecs_vec_count(&cur->tables); + for (i = 0; i < count; i ++) { + ecs_query_cache_match_t *qm = + ecs_vec_get_t(&cur->tables, ecs_query_cache_match_t, i); + flecs_query_get_match_monitor(impl, qm); + + if (qm->wildcard_matches) { + ecs_query_cache_match_t *wc_qms = + ecs_vec_first(qm->wildcard_matches); + int32_t j, wc_count = ecs_vec_count(qm->wildcard_matches); + for (j = 0; j < wc_count; j ++) { + flecs_query_get_match_monitor(impl, &wc_qms[j]); + } + } + } + } while ((cur = cur->next)); } +} - int32_t connections_count = flecs_sparse_count(srv->connections); - for (i = connections_count - 1; i >= 1; i --) { - ecs_http_connection_impl_t *conn = flecs_sparse_get_dense( - srv->connections, ecs_http_connection_impl_t, i); +/* Check if a specific match (table) has changed. */ +static +bool flecs_query_check_match_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_match_t *match, + const ecs_iter_t *it) +{ + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - conn->dequeue_timeout += delta_time; - conn->dequeue_retries ++; - - if ((conn->dequeue_timeout > - (FLECS_FLOAT)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && - (conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) - { - ecs_dbg("http: purging connection '%s:%s' (sock = %d)", - conn->pub.host, conn->pub.port, conn->sock); - connection_free(conn); + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INVALID_OPERATION, + "query was not created with change detection enabled"); + + if (flecs_query_get_match_monitor(impl, match)) { + return true; + } + + int32_t *monitor = match->_monitor; + ecs_table_t *table = match->base.table; + int32_t *dirty_state = NULL; + if (table) { + dirty_state = flecs_table_get_dirty_state( + cache->query->world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (monitor[0] != dirty_state[0]) { + return true; } } - ecs_os_mutex_unlock(srv->lock); + const ecs_query_t *query = cache->query; + ecs_world_t *world = query->world; + int32_t i, field_count = query->field_count; + const ecs_table_record_t **trs = it ? it->trs : match->base.trs; + bool trivial_cache = flecs_query_cache_is_trivial(cache); + + ecs_entity_t *sources = NULL; + ecs_flags64_t set_fields = 0; + + if (it) { + set_fields = it->set_fields; + } else if (!trivial_cache) { + set_fields = match->base.set_fields; + } else { + set_fields = (1llu << field_count) - 1; + } + + if (!trivial_cache) { + sources = match->_sources; + } + + ecs_assert(trs != NULL, ECS_INTERNAL_ERROR, NULL); + + for (i = 0; i < field_count; i ++) { + int32_t mon = monitor[i + 1]; + if (mon == -1) { + continue; + } + + if (!(set_fields & (1llu << i))) { + continue; + } + + int32_t column = trs[i]->column; + ecs_entity_t src = sources ? sources[i] : 0; + if (!src) { + if (column >= 0) { + /* owned component */ + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + if (mon != dirty_state[column + 1]) { + return true; + } + continue; + } else if (column == -1) { + continue; /* owned but not a component */ + } + } + + if (trivial_cache) { + continue; + } + + /* Component from non-this source */ + ecs_assert(match->_sources != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t fixed_src = match->_sources[i]; + ecs_table_t *src_table = ecs_get_table(world, fixed_src); + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *src_dirty_state = flecs_table_get_dirty_state( + world, src_table); + if (mon != src_dirty_state[column + 1]) { + return true; + } + } - return request_count; + return false; } -const char* ecs_http_get_header( - const ecs_http_request_t* req, - const char* name) +/* Check if one or more fields of a specific match have changed. */ +static +bool flecs_query_check_table_monitor_match( + ecs_query_impl_t *impl, + ecs_query_cache_match_t *qm, + int32_t field) { - for (ecs_size_t i = 0; i < req->header_count; i++) { - if (!ecs_os_strcmp(req->headers[i].key, name)) { - return req->headers[i].value; + if (field == -1) { + if (flecs_query_check_match_monitor(impl, qm, NULL)) { + return true; } + } else { + if (flecs_query_check_match_monitor_term(impl, qm, field)) { + return true; + } } - return NULL; + + return false; } -const char* ecs_http_get_param( - const ecs_http_request_t* req, - const char* name) +/* Compare cache monitor with table dirty state to detect changes */ +bool flecs_query_check_table_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_match_t *qm, + int32_t field) { - for (ecs_size_t i = 0; i < req->param_count; i++) { - if (!ecs_os_strcmp(req->params[i].key, name)) { - return req->params[i].value; + if (flecs_query_check_table_monitor_match(impl, qm, field)) { + return true; + } + + if (qm->wildcard_matches) { + ecs_query_cache_match_t *wc_qms = ecs_vec_first(qm->wildcard_matches); + int32_t i, count = ecs_vec_count(qm->wildcard_matches); + for (i = 0; i < count; i ++) { + if (flecs_query_check_table_monitor_match(impl, &wc_qms[i], field)) { + return true; + } } } - return NULL; + + return false; } -ecs_http_server_t* ecs_http_server_init( - const ecs_http_server_desc_t *desc) +/* Mark iterated out fields dirty */ +void flecs_query_mark_fields_dirty( + ecs_query_impl_t *impl, + ecs_iter_t *it) { - ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, - "missing OS API implementation"); + ecs_query_t *q = &impl->pub; - ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); - srv->lock = ecs_os_mutex_new(); + /* Evaluate all writeable non-fixed fields, set fields */ + ecs_termset_t write_fields = + (ecs_termset_t)(q->write_fields & ~q->fixed_fields & it->set_fields); + if (!write_fields || (it->flags & EcsIterNoData)) { + return; + } - srv->should_run = false; - srv->initialized = true; + ecs_world_t *world = q->real_world; + int16_t i, field_count = q->field_count; + for (i = 0; i < field_count; i ++) { + ecs_termset_t field_bit = (ecs_termset_t)(1u << i); + if (!(write_fields & field_bit)) { + continue; /* If term doesn't write data there's nothing to track */ + } - srv->callback = desc->callback; - srv->ctx = desc->ctx; - srv->port = desc->port; - srv->ipaddr = desc->ipaddr; + ecs_entity_t src = it->sources[i]; + ecs_table_t *table; + if (!src) { + table = it->table; + } else { + ecs_record_t *r = flecs_entities_get(world, src); + if (!r || !(table = r->table)) { + continue; + } - srv->connections = flecs_sparse_new(ecs_http_connection_impl_t); - srv->requests = flecs_sparse_new(ecs_http_request_impl_t); + if (q->shared_readonly_fields & flecs_ito(uint32_t, 1 << i)) { + /* Shared fields that aren't marked explicitly as out/inout + * default to readonly */ + continue; + } + } - /* Start at id 1 */ - flecs_sparse_new_id(srv->connections); - flecs_sparse_new_id(srv->requests); + const ecs_table_record_t *tr = it->trs[i]; + if (!tr) { + continue; /* Non-fragmenting component */ + } -#ifndef ECS_TARGET_WINDOWS - /* Ignore pipe signal. SIGPIPE can occur when a message is sent to a client - * but te client already disconnected. */ - signal(SIGPIPE, SIG_IGN); -#endif + int32_t type_index = it->trs[i]->index; + ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t *dirty_state = table->dirty_state; + if (!dirty_state) { + continue; + } - return srv; -error: - return NULL; + ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); + int32_t column = table->column_map[type_index]; + dirty_state[column + 1] ++; + } } -void ecs_http_server_fini( - ecs_http_server_t* srv) +/* Mark out fields with fixed source dirty */ +void flecs_query_mark_fixed_fields_dirty( + ecs_query_impl_t *impl, + ecs_iter_t *it) { - if (srv->should_run) { - ecs_http_server_stop(srv); + /* This function marks fields dirty for terms with fixed sources. */ + ecs_query_t *q = &impl->pub; + ecs_termset_t fixed_write_fields = q->write_fields & q->fixed_fields; + if (!fixed_write_fields) { + return; } - ecs_os_mutex_free(srv->lock); - flecs_sparse_free(srv->connections); - flecs_sparse_free(srv->requests); - ecs_os_free(srv); -} -int ecs_http_server_start( - ecs_http_server_t *srv) -{ - ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); - ecs_check(!srv->should_run, ECS_INVALID_PARAMETER, NULL); - ecs_check(!srv->thread, ECS_INVALID_PARAMETER, NULL); + ecs_world_t *world = q->real_world; + int32_t i, field_count = q->field_count; + for (i = 0; i < field_count; i ++) { + if (!(fixed_write_fields & flecs_ito(uint32_t, 1 << i))) { + continue; /* If term doesn't write data there's nothing to track */ + } - srv->should_run = true; + ecs_entity_t src = it->sources[i]; + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, src); + ecs_table_t *table; + if (!r || !(table = r->table)) { + /* If the field is optional, it's possible that it didn't match */ + continue; + } - ecs_dbg("http: starting server thread"); + int32_t *dirty_state = table->dirty_state; + if (!dirty_state) { + continue; + } - srv->thread = ecs_os_thread_new(http_server_thread, srv); - if (!srv->thread) { - goto error; + ecs_assert(it->trs[i]->column >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t column = table->column_map[it->trs[i]->column]; + dirty_state[column + 1] ++; } - - return 0; -error: - return -1; } -void ecs_http_server_stop( - ecs_http_server_t* srv) +/* Synchronize cache monitor with table dirty state */ +void flecs_query_sync_match_monitor( + ecs_query_impl_t *impl, + ecs_query_cache_match_t *match) { - ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(srv->initialized, ECS_INVALID_OPERATION, NULL); - ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - /* Stop server thread */ - ecs_dbg("http: shutting down server thread"); + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INVALID_OPERATION, + "query was not created with change detection enabled"); - ecs_os_mutex_lock(srv->lock); - srv->should_run = false; - if (srv->sock >= 0) { - http_close(srv->sock); + if (!match->_monitor) { + if (impl->pub.flags & EcsQueryHasChangeDetection) { + flecs_query_get_match_monitor(impl, match); + } else { + return; + } } - ecs_os_mutex_unlock(srv->lock); - ecs_os_thread_join(srv->thread); + int32_t *monitor = match->_monitor; + ecs_table_t *table = match->base.table; + if (table) { + int32_t *dirty_state = flecs_table_get_dirty_state( + cache->query->world, table); + ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); + monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ + } - ecs_trace("http: server thread shut down"); + ecs_query_t *q = cache->query; + { + flecs_table_column_t tc; + int32_t t, term_count = q->term_count; + for (t = 0; t < term_count; t ++) { + int32_t field = q->terms[t].field_index; + if (monitor[field + 1] == -1) { + continue; + } - /* Cleanup all outstanding requests */ - int i, count = flecs_sparse_count(srv->requests); - for (i = count - 1; i >= 1; i --) { - request_free(flecs_sparse_get_dense( - srv->requests, ecs_http_request_impl_t, i)); - } + flecs_query_get_column_for_field(q, match, field, &tc); - /* Close all connections */ - count = flecs_sparse_count(srv->connections); - for (i = count - 1; i >= 1; i --) { - connection_free(flecs_sparse_get_dense( - srv->connections, ecs_http_connection_impl_t, i)); - } + /* Query for cache should never point to stage */ + ecs_assert(q->world == q->real_world, ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_sparse_count(srv->connections) == 1, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_sparse_count(srv->requests) == 1, - ECS_INTERNAL_ERROR, NULL); + monitor[field + 1] = flecs_table_get_dirty_state( + q->world, tc.table)[tc.column + 1]; + } + } - srv->thread = 0; -error: - return; + cache->prev_match_count = cache->match_count; } -void ecs_http_server_dequeue( - ecs_http_server_t* srv, - float delta_time) +/* Public API call to check if any matches in the query have changed. */ +bool ecs_query_changed( + ecs_query_t *q) { - ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); - ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); - - srv->dequeue_timeout += delta_time; - srv->stats_timeout += delta_time; + flecs_poly_assert(q, ecs_query_t); + ecs_query_impl_t *impl = flecs_query_impl(q); - if ((1000 * srv->dequeue_timeout) > - (FLECS_FLOAT)ECS_HTTP_MIN_DEQUEUE_INTERVAL) - { - srv->dequeue_timeout = 0; + ecs_assert(q->cache_kind != EcsQueryCacheNone, ECS_INVALID_OPERATION, + "change detection is only supported on cached queries"); - ecs_time_t t = {0}; - ecs_time_measure(&t); - int32_t request_count = dequeue_requests(srv, srv->dequeue_timeout); - srv->requests_processed += request_count; - srv->requests_processed_total += request_count; - FLECS_FLOAT time_spent = (FLECS_FLOAT)ecs_time_measure(&t); - srv->request_time += time_spent; - srv->request_time_total += time_spent; - srv->dequeue_count ++; + if (q->read_fields & q->fixed_fields) { + if (!impl->monitor) { + /* Create change monitor for fixed fields */ + flecs_query_get_fixed_monitor(impl, false); + } } - if ((1000 * srv->stats_timeout) > - (FLECS_FLOAT)ECS_HTTP_MIN_STATS_INTERVAL) - { - srv->stats_timeout = 0; - ecs_dbg("http: processed %d requests in %.3fs (avg %.3fs / dequeue)", - srv->requests_processed, (double)srv->request_time, - (double)(srv->request_time / (FLECS_FLOAT)srv->dequeue_count)); - srv->requests_processed = 0; - srv->request_time = 0; - srv->dequeue_count = 0; + /* If query reads terms with fixed sources, check those first as that's + * cheaper than checking entries in the cache. */ + if (impl->monitor) { + if (flecs_query_check_fixed_monitor(impl)) { + return true; + } } -error: - return; -} - -#endif + /* Check cache for changes. We can't detect changes for terms that are not + * cached/cacheable and don't have a fixed source, since that requires + * storing state per result, which doesn't happen for uncached queries. */ + if (impl->cache) { + if (!(impl->pub.flags & EcsQueryHasChangeDetection)) { + flecs_query_init_query_monitors(impl); + } + /* Check cache entries for changes */ + return flecs_query_check_cache_monitor(impl); + } + return false; +} -#ifdef FLECS_DOC +/* Public API call to check if the currently iterated result has changed. */ +bool ecs_iter_changed( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_UNSUPPORTED, NULL); + ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); -static ECS_COPY(EcsDocDescription, dst, src, { - ecs_os_strset((char**)&dst->value, src->value); + ecs_query_impl_t *impl = flecs_query_impl(it->query); + ecs_query_t *q = &impl->pub; -}) + /* First check for changes for terms with fixed sources, if query has any */ + if (q->read_fields & q->fixed_fields) { + /* Detecting changes for uncached terms is costly, so only do it once + * per iteration. */ + if (!(it->flags & EcsIterFixedInChangeComputed)) { + it->flags |= EcsIterFixedInChangeComputed; + ECS_BIT_COND(it->flags, EcsIterFixedInChanged, + flecs_query_check_fixed_monitor(impl)); + } -static ECS_MOVE(EcsDocDescription, dst, src, { - ecs_os_free((char*)dst->value); - dst->value = src->value; - src->value = NULL; -}) + if (it->flags & EcsIterFixedInChanged) { + return true; + } + } -static ECS_DTOR(EcsDocDescription, ptr, { - ecs_os_free((char*)ptr->value); -}) + /* If query has a cache, check for changes in current matched result */ + if (impl->cache) { + ecs_query_cache_match_t *qm = + (ecs_query_cache_match_t*)it->priv_.iter.query.elem; + ecs_check(qm != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_query_check_match_monitor(impl, qm, it); + } -void ecs_doc_set_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - ecs_set_pair(world, entity, EcsDocDescription, EcsName, { - .value = name - }); +error: + return false; } -void ecs_doc_set_brief( - ecs_world_t *world, - ecs_entity_t entity, - const char *description) +/* Public API call for skipping change detection (don't mark fields dirty) */ +void ecs_iter_skip( + ecs_iter_t *it) { - ecs_set_pair(world, entity, EcsDocDescription, EcsDocBrief, { - .value = description - }); + ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), + ECS_INVALID_PARAMETER, NULL); + it->flags |= EcsIterSkip; } -void ecs_doc_set_detail( - ecs_world_t *world, - ecs_entity_t entity, - const char *description) -{ - ecs_set_pair(world, entity, EcsDocDescription, EcsDocDetail, { - .value = description - }); -} +/** + * @file query/cache/group.c + * @brief Adding/removing tables to query groups. + */ -void ecs_doc_set_link( - ecs_world_t *world, - ecs_entity_t entity, - const char *link) -{ - ecs_set_pair(world, entity, EcsDocDescription, EcsDocLink, { - .value = link - }); -} -const char* ecs_doc_get_name( - const ecs_world_t *world, - ecs_entity_t entity) +/* Get group id for table. */ +static +uint64_t flecs_query_cache_get_group_id( + const ecs_query_cache_t *cache, + ecs_table_t *table) { - EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsName); - if (ptr) { - return ptr->value; + if (cache->group_by_callback) { + return cache->group_by_callback(cache->query->world, table, + cache->group_by, cache->group_by_ctx); } else { - return ecs_get_name(world, entity); + return 0; } } -const char* ecs_doc_get_brief( - const ecs_world_t *world, - ecs_entity_t entity) +/* Get group for group id. */ +ecs_query_cache_group_t* flecs_query_cache_get_group( + const ecs_query_cache_t *cache, + uint64_t group_id) { - EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocBrief); - if (ptr) { - return ptr->value; - } else { - return NULL; + if (!group_id) { + return ECS_CONST_CAST(ecs_query_cache_group_t*, &cache->default_group); } -} -const char* ecs_doc_get_detail( - const ecs_world_t *world, - ecs_entity_t entity) -{ - EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocDetail); - if (ptr) { - return ptr->value; - } else { - return NULL; - } + return ecs_map_get_deref( + &cache->groups, ecs_query_cache_group_t, group_id); } -const char* ecs_doc_get_link( - const ecs_world_t *world, - ecs_entity_t entity) +/* Insert group in list that's ordered by group id */ +static +void flecs_query_cache_group_insert( + ecs_query_cache_t *cache, + ecs_query_cache_group_t *group) { - EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocLink); - if (ptr) { - return ptr->value; - } else { - return NULL; + bool desc = false; /* Descending order */ + if (cache->cascade_by) { + desc = (cache->query->terms[ + cache->cascade_by - 1].src.id & EcsDesc) != 0; } -} -void FlecsDocImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsDoc); + if (!group->info.id) { + /* Default group is always inserted */ + return; + } - ecs_set_name_prefix(world, "EcsDoc"); + ecs_query_cache_group_t *cur = cache->first_group, *prev = NULL; + do { + ecs_assert(cur->info.id != group->info.id, ECS_INTERNAL_ERROR, NULL); + bool insert = cur->info.id > group->info.id; + if (desc) { + insert = !insert; /* Works since ids can't be the same. */ + } - flecs_bootstrap_component(world, EcsDocDescription); - flecs_bootstrap_tag(world, EcsDocBrief); - flecs_bootstrap_tag(world, EcsDocDetail); - flecs_bootstrap_tag(world, EcsDocLink); + if (insert) { + if (prev) { + prev->next = group; + } else { + cache->first_group = group; + } + group->next = cur; + return; + } - ecs_set_component_actions(world, EcsDocDescription, { - .ctor = ecs_default_ctor, - .move = ecs_move(EcsDocDescription), - .copy = ecs_copy(EcsDocDescription), - .dtor = ecs_dtor(EcsDocDescription) - }); + prev = cur; + } while ((cur = cur->next)); - ecs_add_id(world, ecs_id(EcsDocDescription), EcsDontInherit); + prev->next = group; + ecs_assert(group->next == NULL, ECS_INTERNAL_ERROR, NULL); } -#endif - - -#ifdef FLECS_PARSER +/* Make sure a group exists for the provided group id. */ +static +ecs_query_cache_group_t* flecs_query_cache_ensure_group( + ecs_query_cache_t *cache, + uint64_t group_id) +{ + if (!group_id) { + ecs_query_cache_group_t *group = &cache->default_group; + if (!group->info.table_count) { + if (ecs_map_is_init(&cache->groups)) { + ecs_query_cache_group_t **group_ptr = ecs_map_ensure_ref( + &cache->groups, ecs_query_cache_group_t, 0); + *group_ptr = group; + } + } -#include + if (cache->on_group_create) { + group->info.ctx = cache->on_group_create( + cache->query->world, 0, cache->group_by_ctx); + } -#define ECS_ANNOTATION_LENGTH_MAX (16) - -#define TOK_NEWLINE '\n' -#define TOK_COLON ':' -#define TOK_AND ',' -#define TOK_OR "||" -#define TOK_NOT '!' -#define TOK_OPTIONAL '?' -#define TOK_BITWISE_OR '|' -#define TOK_NAME_SEP '.' -#define TOK_BRACKET_OPEN '[' -#define TOK_BRACKET_CLOSE ']' -#define TOK_WILDCARD '*' -#define TOK_VARIABLE '$' -#define TOK_PAREN_OPEN '(' -#define TOK_PAREN_CLOSE ')' - -#define TOK_SELF "self" -#define TOK_SUPERSET "super" -#define TOK_SUBSET "sub" -#define TOK_CASCADE "cascade" -#define TOK_PARENT "parent" -#define TOK_ALL "all" - -#define TOK_OVERRIDE "OVERRIDE" - -#define TOK_ROLE_PAIR "PAIR" -#define TOK_ROLE_AND "AND" -#define TOK_ROLE_OR "OR" -#define TOK_ROLE_XOR "XOR" -#define TOK_ROLE_NOT "NOT" -#define TOK_ROLE_SWITCH "SWITCH" -#define TOK_ROLE_CASE "CASE" -#define TOK_ROLE_DISABLED "DISABLED" - -#define TOK_IN "in" -#define TOK_OUT "out" -#define TOK_INOUT "inout" -#define TOK_INOUT_FILTER "filter" - -#define ECS_MAX_TOKEN_SIZE (256) - -typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; - -const char* ecs_parse_eol_and_whitespace( - const char *ptr) -{ - while (isspace(*ptr)) { - ptr ++; + return group; } - return ptr; -} + ecs_query_cache_group_t *group = ecs_map_get_deref(&cache->groups, + ecs_query_cache_group_t, group_id); -/** Skip spaces when parsing signature */ -const char* ecs_parse_whitespace( - const char *ptr) -{ - while ((*ptr != '\n') && isspace(*ptr)) { - ptr ++; - } + if (!group) { + group = ecs_map_insert_alloc_t(&cache->groups, + ecs_query_cache_group_t, group_id); + ecs_os_zeromem(group); - return ptr; -} + ecs_allocator_t *a = &cache->query->real_world->allocator; + if (flecs_query_cache_is_trivial(cache)) { + ecs_vec_init_t(a, &group->tables, ecs_query_triv_cache_match_t, 0); + } else { + ecs_vec_init_t(a, &group->tables, ecs_query_cache_match_t, 0); + } -const char* ecs_parse_digit( - const char *ptr, - char *token) -{ - char *tptr = token; - char ch = ptr[0]; + group->info.id = group_id; - if (!isdigit(ch) && ch != '-') { - ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); - return NULL; - } + flecs_query_cache_group_insert(cache, group); - tptr[0] = ch; - tptr ++; - ptr ++; - for (; (ch = *ptr); ptr ++) { - if (!isdigit(ch)) { - break; + if (cache->on_group_create) { + group->info.ctx = cache->on_group_create( + cache->query->world, group_id, cache->group_by_ctx); } - - tptr[0] = ch; - tptr ++; } - tptr[0] = '\0'; - - return ptr; + return group; } +/* Free group resources. */ static -bool is_newline_comment( - const char *ptr) +void flecs_query_cache_group_fini( + ecs_query_cache_t *cache, + ecs_query_cache_group_t *group) { - if (ptr[0] == '/' && ptr[1] == '/') { - return true; + if (cache->on_group_delete) { + cache->on_group_delete(cache->query->world, group->info.id, + group->info.ctx, cache->group_by_ctx); + } + + ecs_size_t elem_size = flecs_query_cache_elem_size(cache); + + ecs_allocator_t *a = &cache->query->real_world->allocator; + ecs_vec_fini(a, &group->tables, elem_size); + + if (group != &cache->default_group) { + ecs_map_remove_free(&cache->groups, group->info.id); + } else { + if (ecs_map_is_init(&cache->groups)) { + ecs_map_remove(&cache->groups, 0); + } } - return false; } -const char* ecs_parse_fluff( - const char *ptr, - char **last_comment) +/* Remove group from cache. */ +static +void flecs_query_cache_remove_group( + ecs_query_cache_t *cache, + ecs_query_cache_group_t *group) { - const char *last_comment_start = NULL; + ecs_query_cache_group_t *cur = cache->first_group, *prev = NULL; do { - /* Skip whitespaces before checking for a comment */ - ptr = ecs_parse_whitespace(ptr); - - /* Newline comment, skip until newline character */ - if (is_newline_comment(ptr)) { - ptr += 2; - last_comment_start = ptr; - - while (ptr[0] && ptr[0] != TOK_NEWLINE) { - ptr ++; + if (cur == group) { + if (prev) { + prev->next = group->next; + } else { + cache->first_group = group->next; } + break; } + + prev = cur; + } while ((cur = cur->next)); + + /* If this is the default_group, make sure next is set to NULL since we + * never delete the default group. */ + group->next = NULL; - /* If a newline character is found, skip it */ - if (ptr[0] == TOK_NEWLINE) { - ptr ++; - } - - } while (isspace(ptr[0]) || is_newline_comment(ptr)); + /* ensure group was found */ + ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); - if (last_comment) { - *last_comment = (char*)last_comment_start; + if (!cache->first_group) { + cache->first_group = &cache->default_group; } - return ptr; -} - -/* -- Private functions -- */ + flecs_query_cache_group_fini(cache, group); +} -static -bool valid_identifier_start_char( - char ch) +/* Get cache entry for table. */ +ecs_query_cache_table_t* flecs_query_cache_get_table( + const ecs_query_cache_t *cache, + ecs_table_t *table) { - if (ch && (isalpha(ch) || (ch == '.') || (ch == '_') || (ch == '*') || - (ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) - { - return true; - } + return ecs_map_get_ptr(&cache->tables, table->id); +} - return false; +/* Get match for table cache entry. */ +ecs_query_cache_match_t* flecs_query_cache_match_from_table( + const ecs_query_cache_t *cache, + const ecs_query_cache_table_t *qt) +{ + ecs_size_t elem_size = flecs_query_cache_elem_size(cache); + return ecs_vec_get(&qt->group->tables, elem_size, qt->index); } +/* Add table to query group. */ static -bool valid_token_start_char( - char ch) +ecs_query_cache_match_t* flecs_query_cache_add_table_to_group( + ecs_query_cache_t *cache, + ecs_query_cache_group_t *group, + ecs_query_cache_table_t *qt, + ecs_table_t *table) { - if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') - || (ch == '[') || (ch == ']') || valid_identifier_start_char(ch)) - { - return true; - } + ecs_size_t elem_size = flecs_query_cache_elem_size(cache); + ecs_allocator_t *a = &cache->query->real_world->allocator; - return false; + ecs_query_cache_match_t *result = ecs_vec_append( + a, &group->tables, elem_size); + ecs_os_memset(result, 0, elem_size); + result->base.table = table; + + qt->group = group; + qt->index = ecs_vec_count(&group->tables) - 1; + + group->info.table_count ++; + group->info.match_count ++; + cache->match_count ++; + + return result; } +/* Remove table from query group. */ static -bool valid_token_char( - char ch) +void flecs_query_cache_remove_table_from_group( + ecs_query_cache_t *cache, + ecs_query_cache_group_t *group, + int32_t index) { - if (ch && - (isalpha(ch) || isdigit(ch) || ch == '_' || ch == '.' || ch == '"')) - { - return true; + cache->match_count ++; + + ecs_size_t elem_size = flecs_query_cache_elem_size(cache); + ecs_vec_remove(&group->tables, elem_size, index); + int32_t count = ecs_vec_count(&group->tables); + + if (!count) { + flecs_query_cache_remove_group(cache, group); + return; } - return false; + if (index != count) { + /* The element now points to the previously last table in the group. + * Update the entry of that table to the new index. */ + ecs_query_cache_match_t *match = ecs_vec_get( + &group->tables, elem_size, index); + ecs_query_cache_table_t *qt_other = flecs_query_cache_get_table( + cache, match->base.table); + ecs_assert(qt_other != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(qt_other->index == ecs_vec_count(&group->tables), + ECS_INTERNAL_ERROR, NULL); + qt_other->index = index; + } + + group->info.table_count --; + group->info.match_count ++; } -static -bool valid_operator_char( - char ch) +/* Add matched table to cache. */ +ecs_query_cache_match_t* flecs_query_cache_add_table( + ecs_query_cache_t *cache, + ecs_table_t *table) { - if (ch == TOK_OPTIONAL || ch == TOK_NOT) { - return true; + ecs_assert(ecs_map_get(&cache->tables, table->id) == NULL, + ECS_INTERNAL_ERROR, NULL); + + if (!ecs_map_count(&cache->tables) && cache->entity) { + ecs_remove_id(cache->query->world, cache->entity, EcsEmpty); } - return false; + uint64_t group_id = flecs_query_cache_get_group_id(cache, table); + + ecs_query_cache_group_t *group = flecs_query_cache_ensure_group( + cache, group_id); + ecs_assert(group != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_allocator_t *a = &cache->query->real_world->allocator; + + ecs_query_cache_table_t *qt = flecs_alloc_t(a, ecs_query_cache_table_t); + qt->group = group; + qt->index = ecs_vec_count(&group->tables); + ecs_map_insert_ptr(&cache->tables, table->id, qt); + + return flecs_query_cache_add_table_to_group(cache, group, qt, table); } +/* Move table to a different group. This can happen if the value returned by + * group_by_callback changed for a table. Typically called during rematch. */ static -const char* parse_digit( - const char *ptr, - char *token_out) +void flecs_query_cache_move_table_to_group( + ecs_query_cache_t *cache, + ecs_query_cache_table_t *qt, + ecs_query_cache_group_t *group) { - ptr = ecs_parse_whitespace(ptr); - ptr = ecs_parse_digit(ptr, token_out); - return ecs_parse_whitespace(ptr); -} + ecs_query_cache_match_t *src_match = + flecs_query_cache_match_from_table(cache, qt); -const char* ecs_parse_token( - const char *name, - const char *expr, - const char *ptr, - char *token_out) -{ - int64_t column = ptr - expr; + /* Cache values of previous group since add_table_to_group will modify qt */ + ecs_query_cache_group_t *src_group = qt->group; + int32_t src_index = qt->index; - ptr = ecs_parse_whitespace(ptr); - char *tptr = token_out, ch = ptr[0]; + ecs_table_t *table = src_match->base.table; - if (!valid_token_start_char(ch)) { - if (ch == '\0' || ch == '\n') { - ecs_parser_error(name, expr, column, - "unexpected end of expression"); - } else { - ecs_parser_error(name, expr, column, - "invalid start of token '%s'", ptr); + /* Add table to new group */ + ecs_query_cache_match_t *dst_match = + flecs_query_cache_add_table_to_group(cache, group, qt, table); + ecs_size_t elem_size = flecs_query_cache_elem_size(cache); + ecs_os_memcpy(dst_match, src_match, elem_size); + ecs_assert(qt->group == group, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_query_cache_match_from_table(cache, qt) == dst_match, + ECS_INTERNAL_ERROR, NULL); + + /* Remove table from old group */ + flecs_query_cache_remove_table_from_group(cache, src_group, src_index); +} + +/* Make sure a cache entry exists for table. */ +ecs_query_cache_match_t* flecs_query_cache_ensure_table( + ecs_query_cache_t *cache, + ecs_table_t *table) +{ + ecs_query_cache_table_t *qt = flecs_query_cache_get_table(cache, table); + if (qt) { + /* Ensure existing table is in the right group */ + if (cache->group_by_callback) { + uint64_t group_id = flecs_query_cache_get_group_id(cache, table); + ecs_query_cache_group_t *group = flecs_query_cache_ensure_group( + cache, group_id); + ecs_assert(group != NULL, ECS_INTERNAL_ERROR, NULL); + + if (group != qt->group) { + flecs_query_cache_move_table_to_group(cache, qt, group); + } } - return NULL; + + return flecs_query_cache_match_from_table(cache, qt); } - tptr[0] = ch; - tptr ++; - ptr ++; + return flecs_query_cache_add_table(cache, table); +} - if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',') { - tptr[0] = 0; - return ptr; +/* Remove matched table from cache. */ +void flecs_query_cache_remove_table( + ecs_query_cache_t *cache, + ecs_table_t *table) +{ + ecs_query_cache_table_t *qt = flecs_query_cache_get_table(cache, table); + if (!qt) { + return; } - int tmpl_nesting = 0; - bool in_str = ch == '"'; + ecs_query_cache_group_t *group = qt->group; + ecs_size_t elem_size = flecs_query_cache_elem_size(cache); - for (; (ch = *ptr); ptr ++) { - if (ch == '<') { - tmpl_nesting ++; - } else if (ch == '>') { - if (!tmpl_nesting) { - break; - } - tmpl_nesting --; - } else if (ch == '"') { - in_str = !in_str; - } else - if (!valid_token_char(ch) && !in_str) { - break; - } + ecs_query_cache_match_t *match = ecs_vec_get( + &group->tables, elem_size, qt->index); - tptr[0] = ch; - tptr ++; - } + ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(match->base.table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(match->base.table->id == table->id, ECS_INTERNAL_ERROR, NULL); - tptr[0] = '\0'; + flecs_query_cache_match_fini(cache, match); - if (tmpl_nesting != 0) { - ecs_parser_error(name, expr, column, - "identifier '%s' has mismatching < > pairs", ptr); - return NULL; - } + flecs_query_cache_remove_table_from_group(cache, group, qt->index); - const char *next_ptr = ecs_parse_whitespace(ptr); - if (next_ptr[0] == ':' && next_ptr != ptr) { - /* Whitespace between token and : is significant */ - ptr = next_ptr - 1; - } else { - ptr = next_ptr; - } + ecs_allocator_t *a = &cache->query->real_world->allocator; + flecs_free_t(a, ecs_query_cache_table_t, qt); - return ptr; + ecs_map_remove(&cache->tables, table->id); } +/* Remove all groups from the cache. Typically called during query cleanup. */ static -const char* ecs_parse_identifier( - const char *name, - const char *expr, - const char *ptr, - char *token_out) +void flecs_query_cache_remove_all_groups( + ecs_query_cache_t *cache) { - if (!valid_identifier_start_char(ptr[0])) { - ecs_parser_error(name, expr, (ptr - expr), - "expected start of identifier"); - return NULL; - } + ecs_query_cache_group_t *cur = cache->first_group, *next = cur; - ptr = ecs_parse_token(name, expr, ptr, token_out); + while ((cur = next)) { + next = cur->next; + flecs_query_cache_group_fini(cache, cur); + } - return ptr; + cache->first_group = &cache->default_group; + cache->default_group.next = NULL; } -static -int parse_identifier( - const char *token, - ecs_term_id_t *out) +/* Remove all tables from the cache. Typically called during query cleanup. */ +void flecs_query_cache_remove_all_tables( + ecs_query_cache_t *cache) { - const char *tptr = token; - if (tptr[0] == TOK_VARIABLE && tptr[1]) { - out->var = EcsVarIsVariable; - tptr ++; + ecs_allocator_t *a = &cache->query->real_world->allocator; + ecs_size_t elem_size = flecs_query_cache_elem_size(cache); + + ecs_query_cache_group_t *cur = cache->first_group; + do { + int32_t i, count = ecs_vec_count(&cur->tables); + for (i = 0; i < count; i ++) { + flecs_query_cache_match_fini( + cache, ecs_vec_get(&cur->tables, elem_size, i)); + } + + ecs_vec_fini(a, &cur->tables, elem_size); + } while ((cur = cur->next)); + + ecs_map_iter_t it = ecs_map_iter(&cache->tables); + while (ecs_map_next(&it)) { + ecs_query_cache_table_t *qt = ecs_map_ptr(&it); + flecs_free_t(a, ecs_query_cache_table_t, qt); } - out->name = ecs_os_strdup(tptr); + ecs_map_clear(&cache->tables); - return 0; + flecs_query_cache_remove_all_groups(cache); + + ecs_assert(ecs_map_count(&cache->groups) == 0, ECS_INTERNAL_ERROR, NULL); } +/** + * @file query/cache/match.c + * @brief Match table one or more times with query. + */ + + +/* Free cache entry element. */ static -ecs_entity_t parse_role( - const char *name, - const char *sig, - int64_t column, - const char *token) +void flecs_query_cache_match_elem_fini( + ecs_query_cache_t *cache, + ecs_query_cache_match_t *qm) { - if (!ecs_os_strcmp(token, TOK_ROLE_PAIR)) - { - return ECS_PAIR; - } else if (!ecs_os_strcmp(token, TOK_ROLE_AND)) { - return ECS_AND; - } else if (!ecs_os_strcmp(token, TOK_ROLE_OR)) { - return ECS_OR; - } else if (!ecs_os_strcmp(token, TOK_ROLE_XOR)) { - return ECS_XOR; - } else if (!ecs_os_strcmp(token, TOK_ROLE_NOT)) { - return ECS_NOT; - } else if (!ecs_os_strcmp(token, TOK_ROLE_SWITCH)) { - return ECS_SWITCH; - } else if (!ecs_os_strcmp(token, TOK_ROLE_CASE)) { - return ECS_CASE; - } else if (!ecs_os_strcmp(token, TOK_OVERRIDE)) { - return ECS_OVERRIDE; - } else if (!ecs_os_strcmp(token, TOK_ROLE_DISABLED)) { - return ECS_DISABLED; - } else { - ecs_parser_error(name, sig, column, "invalid role '%s'", token); - return 0; + flecs_bfree(&cache->allocators.pointers, + ECS_CONST_CAST(void*, qm->base.trs)); + + flecs_bfree(&cache->allocators.pointers, + ECS_CONST_CAST(void*, qm->base.ptrs)); + + if (!flecs_query_cache_is_trivial(cache)) { + if (qm->_ids != cache->query->ids) { + flecs_bfree(&cache->allocators.ids, qm->_ids); + } + + if (qm->_sources != cache->sources) { + flecs_bfree(&cache->allocators.ids, qm->_sources); + } + + if (qm->_tables) { + flecs_bfree(&cache->allocators.pointers, qm->_tables); + } + + if (qm->_monitor) { + flecs_bfree(&cache->allocators.monitors, qm->_monitor); + } } } -static -ecs_oper_kind_t parse_operator( - char ch) +/* Free cache entry element and optional wildcard matches. */ +void flecs_query_cache_match_fini( + ecs_query_cache_t *cache, + ecs_query_cache_match_t *qm) { - if (ch == TOK_OPTIONAL) { - return EcsOptional; - } else if (ch == TOK_NOT) { - return EcsNot; - } else { - ecs_abort(ECS_INTERNAL_ERROR, NULL); + flecs_query_cache_match_elem_fini(cache, qm); + + if (!flecs_query_cache_is_trivial(cache)) { + if (qm->wildcard_matches) { + ecs_query_cache_match_t *elems = ecs_vec_first(qm->wildcard_matches); + int32_t i, count = ecs_vec_count(qm->wildcard_matches); + for (i = 0; i < count; i ++) { + flecs_query_cache_match_elem_fini(cache, &elems[i]); + } + + ecs_allocator_t *a = &cache->query->real_world->allocator; + ecs_vec_fini_t(a, qm->wildcard_matches, ecs_query_cache_match_t); + flecs_free_t(a, ecs_vec_t, qm->wildcard_matches); + } } } +/* Initialize cache entry element. */ static -const char* parse_annotation( - const char *name, - const char *sig, - int64_t column, - const char *ptr, - ecs_inout_kind_t *inout_kind_out) +void flecs_query_cache_match_set( + ecs_query_cache_t *cache, + ecs_query_cache_match_t *qm, + ecs_iter_t *it) { - char token[ECS_MAX_TOKEN_SIZE]; + bool trivial_cache = flecs_query_cache_is_trivial(cache); + ecs_query_t *query = cache->query; + int8_t i, field_count = query->field_count; + ecs_assert(field_count > 0, ECS_INTERNAL_ERROR, NULL); - ptr = ecs_parse_identifier(name, sig, ptr, token); - if (!ptr) { - return NULL; + qm->base.table = it->table; + qm->base.set_fields = it->set_fields; + + if (!qm->base.ptrs) { + qm->base.ptrs = flecs_balloc(&cache->allocators.pointers); } - if (!ecs_os_strcmp(token, TOK_IN)) { - *inout_kind_out = EcsIn; - } else - if (!ecs_os_strcmp(token, TOK_OUT)) { - *inout_kind_out = EcsOut; - } else - if (!ecs_os_strcmp(token, TOK_INOUT)) { - *inout_kind_out = EcsInOut; - } else if (!ecs_os_strcmp(token, TOK_INOUT_FILTER)) { - *inout_kind_out = EcsInOutFilter; + if (!qm->base.trs) { + qm->base.trs = flecs_balloc(&cache->allocators.pointers); } - ptr = ecs_parse_whitespace(ptr); + /* Reset resources in case this is an existing record */ + ecs_os_memcpy_n(ECS_CONST_CAST(ecs_table_record_t**, qm->base.trs), + it->trs, ecs_table_record_t*, field_count); + + /* Initialize pointers*/ + ecs_os_memset_n(qm->base.ptrs, 0, void*, field_count); + + /* Set table version to sentinel that'll force reevaluation */ + qm->base.table_version = UINT32_MAX; - if (ptr[0] != TOK_BRACKET_CLOSE) { - ecs_parser_error(name, sig, column, "expected ]"); - return NULL; + /* Find out whether to store result-specific ids array or fixed array */ + ecs_id_t *ids = cache->query->ids; + for (i = 0; i < field_count; i ++) { + if (it->ids[i] != ids[i]) { + break; + } } - return ptr + 1; -} + if (!trivial_cache) { + if (i != field_count) { + if (qm->_ids == ids || !qm->_ids) { + qm->_ids = flecs_balloc(&cache->allocators.ids); + } + ecs_os_memcpy_n(qm->_ids, it->ids, ecs_id_t, field_count); + } else { + if (qm->_ids != ids) { + flecs_bfree(&cache->allocators.ids, qm->_ids); + qm->_ids = ids; + } + } + } -static -uint8_t parse_set_token( - const char *token) -{ - if (!ecs_os_strcmp(token, TOK_SELF)) { - return EcsSelf; - } else if (!ecs_os_strcmp(token, TOK_SUPERSET)) { - return EcsSuperSet; - } else if (!ecs_os_strcmp(token, TOK_SUBSET)) { - return EcsSubSet; - } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { - return EcsCascade; - } else if (!ecs_os_strcmp(token, TOK_ALL)) { - return EcsAll; - } else if (!ecs_os_strcmp(token, TOK_PARENT)) { - return EcsParent; + /* Find out whether to store result-specific sources array or fixed array */ + for (i = 0; i < field_count; i ++) { + if (it->sources[i]) { + break; + } + } + + if (!trivial_cache) { + if (i != field_count) { + if (qm->_sources == cache->sources || !qm->_sources) { + qm->_sources = flecs_balloc(&cache->allocators.ids); + } + ecs_os_memcpy_n(qm->_sources, it->sources, ecs_entity_t, field_count); + if (!qm->_tables) { + qm->_tables = flecs_balloc(&cache->allocators.pointers); + } + for (i = 0; i < field_count; i ++) { + if (it->trs[i]) { + qm->_tables[i] = it->trs[i]->hdr.table; + } + } + } else { + if (qm->_sources != cache->sources) { + flecs_bfree(&cache->allocators.ids, qm->_sources); + qm->_sources = cache->sources; + } + if (qm->_tables) { + flecs_bfree(&cache->allocators.pointers, qm->_tables); + qm->_tables = NULL; + } + } + + qm->_up_fields = it->up_fields; } else { - return 0; + /* If this is a trivial cache, we shouldn't have any fields with + * non-$this sources */ + ecs_assert(i == field_count, ECS_INTERNAL_ERROR, NULL); } } -static -const char* parse_set_expr( - const ecs_world_t *world, - const char *name, - const char *expr, - int64_t column, - const char *ptr, - char *token, - ecs_term_id_t *id, - char tok_end) +/* Iterate the next match for table. This function accepts an iterator for the + * cache query and will keep on iterating until a result for a different table + * is returned. Typically each table only returns one result, but wildcard + * queries can return multiple results for the same table. + * + * For each iterated result the function will initialize the cache entry for the + * matched table. */ +bool flecs_query_cache_match_next( + ecs_query_cache_t *cache, + ecs_iter_t *it) { - char token_buf[ECS_MAX_TOKEN_SIZE] = {0}; - if (!token) { - token = token_buf; - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } - } + ecs_table_t *table = it->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_query_cache_match_t *first = flecs_query_cache_add_table(cache, table); + ecs_query_cache_match_t *qm = first; + + ecs_size_t elem_size = flecs_query_cache_elem_size(cache); + ecs_allocator_t *a = &cache->query->real_world->allocator; do { - uint8_t tok = parse_set_token(token); - if (!tok) { - ecs_parser_error(name, expr, column, - "invalid set token '%s'", token); - return NULL; + flecs_query_cache_match_set(cache, qm, it); + + if (!ecs_query_next(it)) { + return false; } - if (id->set.mask & tok) { - ecs_parser_error(name, expr, column, - "duplicate set token '%s'", token); - return NULL; + if (it->table != table) { + return true; } - if ((tok == EcsSubSet && id->set.mask & EcsSuperSet) || - (tok == EcsSuperSet && id->set.mask & EcsSubSet)) - { - ecs_parser_error(name, expr, column, - "cannot mix super and sub", token); - return NULL; + /* Another match for the same table (for wildcard queries) */ + + if (!first->wildcard_matches) { + first->wildcard_matches = flecs_alloc_t(a, ecs_vec_t); + ecs_vec_init(a, first->wildcard_matches, elem_size, 1); } - - id->set.mask |= tok; - if (ptr[0] == TOK_PAREN_OPEN) { - ptr ++; + qm = ecs_vec_append(a, first->wildcard_matches, elem_size); + ecs_os_zeromem(qm); + } while (true); +} - /* Relationship (overrides IsA default) */ - if (!isdigit(ptr[0]) && valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } +/* Same as flecs_query_cache_match_next, but for rematching. This function will + * overwrite existing cache entries with new match data. */ +static +bool flecs_query_cache_rematch_next( + ecs_query_cache_t *cache, + ecs_iter_t *it) +{ + ecs_table_t *table = it->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - id->set.relation = ecs_lookup_fullpath(world, token); - if (!id->set.relation) { - ecs_parser_error(name, expr, column, - "unresolved identifier '%s'", token); - return NULL; - } + ecs_query_cache_match_t *first = + flecs_query_cache_ensure_table(cache, table); + ecs_query_cache_match_t *qm = first; + + ecs_size_t elem_size = flecs_query_cache_elem_size(cache); + ecs_allocator_t *a = &cache->query->real_world->allocator; + ecs_vec_t *wildcard_matches = first->wildcard_matches; + int32_t wildcard_elem = 0; - if (ptr[0] == TOK_AND) { - ptr = ecs_parse_whitespace(ptr + 1); - } else if (ptr[0] != TOK_PAREN_CLOSE) { - ecs_parser_error(name, expr, column, - "expected ',' or ')'"); - return NULL; - } - } + bool result = true, has_more = true; - /* Max depth of search */ - if (isdigit(ptr[0])) { - ptr = parse_digit(ptr, token); - if (!ptr) { - return NULL; - } + /* So we can tell which tables in the cache got rematched. All tables for + * which this counter hasn't changed are no longer matched by the query. */ + first->rematch_count = cache->rematch_count; - id->set.max_depth = atoi(token); - if (id->set.max_depth < 0) { - ecs_parser_error(name, expr, column, - "invalid negative depth"); - return NULL; - } + do { + flecs_query_cache_match_set(cache, qm, it); - if (ptr[0] == ',') { - ptr = ecs_parse_whitespace(ptr + 1); - } - } + if (!ecs_query_next(it)) { + has_more = false; + result = false; + } - /* If another digit is found, previous depth was min depth */ - if (isdigit(ptr[0])) { - ptr = parse_digit(ptr, token); - if (!ptr) { - return NULL; - } + if (it->table != table) { + has_more = false; + } + + /* Are there more results for this table? */ + if (!has_more) { + /* If existing match had more wildcard matches than new match free + * the superfluous ones. */ - id->set.min_depth = id->set.max_depth; - id->set.max_depth = atoi(token); - if (id->set.max_depth < 0) { - ecs_parser_error(name, expr, column, - "invalid negative depth"); - return NULL; + if (wildcard_matches) { + int32_t i, count = ecs_vec_count(wildcard_matches); + for (i = wildcard_elem; i < count; i ++) { + ecs_query_cache_match_t *qm_elem = ecs_vec_get( + wildcard_matches, elem_size, i); + flecs_query_cache_match_fini(cache, qm_elem); } - } - if (ptr[0] != TOK_PAREN_CLOSE) { - ecs_parser_error(name, expr, column, "expected ')', got '%c'", - ptr[0]); - return NULL; - } else { - ptr = ecs_parse_whitespace(ptr + 1); - if (ptr[0] != tok_end && ptr[0] != TOK_AND && ptr[0] != 0) { - ecs_parser_error(name, expr, column, - "expected end of set expr"); - return NULL; + if (!wildcard_elem) { + ecs_vec_fini(a, wildcard_matches, elem_size); + flecs_free_t(a, ecs_vec_t, wildcard_matches); + qm->wildcard_matches = NULL; } } + + /* Are there more results for other tables? */ + return result; } - /* Next token in set expression */ - if (ptr[0] == TOK_BITWISE_OR) { - ptr ++; - if (valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } - } + /* Another match for the same table (for wildcard queries) */ - /* End of set expression */ - } else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { - break; + if (!wildcard_matches) { + first->wildcard_matches = wildcard_matches = + flecs_alloc_t(a, ecs_vec_t); + ecs_vec_init(a, wildcard_matches, elem_size, 1); } - } while (true); - if (id->set.mask & EcsSelf && id->set.min_depth != 0) { - ecs_parser_error(name, expr, column, - "min_depth must be zero for set expression with 'self'"); - return NULL; - } + if (ecs_vec_count(wildcard_matches) <= wildcard_elem) { + qm = ecs_vec_append(a, wildcard_matches, elem_size); + ecs_os_zeromem(qm); + } else { + qm = ecs_vec_get(wildcard_matches, elem_size, wildcard_elem); + } - return ptr; + wildcard_elem ++; + } while (true); } -static -const char* parse_arguments( - const ecs_world_t *world, - const char *name, - const char *expr, - int64_t column, - const char *ptr, - char *token, - ecs_term_t *term) +/* Rematch query cache. This function is called whenever something happened that + * could have caused a previously matching table to no longer match with the + * query. This function is not called for regular table creation or deletion. + * + * An example of when this function is called is when a query matched a + * component on a parent, and that component was removed from the parent. This + * means that tables with (ChildOf, parent) previously matched the query, but + * after the component got removed, no longer match. + * + * This operation is expensive, since it needs to: + * - make sure that optional fields matched on parents are updated + * - groups are up to date for all the matched tables + * - tables that no longer match are removed from the cache. + */ +void flecs_query_rematch( + ecs_world_t *world, + ecs_query_t *q) { - (void)column; + flecs_poly_assert(world, ecs_world_t); + ecs_allocator_t *a = &world->allocator; - int32_t arg = 0; + ecs_iter_t it; + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_query_cache_t *cache = impl->cache; + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - do { - if (valid_token_start_char(ptr[0])) { - if (arg == 2) { - ecs_parser_error(name, expr, (ptr - expr), - "too many arguments in term"); - return NULL; - } + /* Queries with trivial caches can't trigger rematching */ + ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INTERNAL_ERROR, NULL); - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - return NULL; - } + if (cache->monitor_generation == world->monitor_generation) { + return; + } - ecs_term_id_t *term_id = NULL; + ecs_os_perf_trace_push("flecs.query.rematch"); - if (arg == 0) { - term_id = &term->subj; - } else if (arg == 1) { - term_id = &term->obj; - } + cache->monitor_generation = world->monitor_generation; + cache->match_count ++; - /* If token is a colon, the token is an identifier followed by a - * set expression. */ - if (ptr[0] == TOK_COLON) { - if (parse_identifier(token, term_id)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - return NULL; - } + world->info.rematch_count_total ++; + int32_t rematch_count = ++ cache->rematch_count; - ptr = ecs_parse_whitespace(ptr + 1); - ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, - NULL, term_id, TOK_PAREN_CLOSE); - if (!ptr) { - return NULL; - } + ecs_time_t t = {0}; + if (world->flags & EcsWorldMeasureFrameTime) { + ecs_time_measure(&t); + } - /* If token is a self, super or sub token, this is a set - * expression */ - } else if (!ecs_os_strcmp(token, TOK_ALL) || - !ecs_os_strcmp(token, TOK_CASCADE) || - !ecs_os_strcmp(token, TOK_SELF) || - !ecs_os_strcmp(token, TOK_SUPERSET) || - !ecs_os_strcmp(token, TOK_SUBSET) || - !(ecs_os_strcmp(token, TOK_PARENT))) - { - ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, - token, term_id, TOK_PAREN_CLOSE); - if (!ptr) { - return NULL; - } + it = ecs_query_iter(world, cache->query); + ECS_BIT_SET(it.flags, EcsIterNoData); - /* Regular identifier */ - } else if (parse_identifier(token, term_id)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - return NULL; + if (!ecs_query_next(&it)) { + flecs_query_cache_remove_all_tables(cache); + goto done; + } + + while (flecs_query_cache_rematch_next(cache, &it)) { } + + /* Iterate all tables in cache, remove ones that weren't just matched */ + ecs_vec_t unmatched; ecs_vec_init_t(a, &unmatched, ecs_table_t*, 0); + ecs_size_t elem_size = flecs_query_cache_elem_size(cache); + ecs_query_cache_group_t *cur = &cache->default_group; + do { + int32_t i, count = ecs_vec_count(&cur->tables); + for (i = 0; i < count; i ++) { + ecs_query_cache_match_t *qm = + ecs_vec_get(&cur->tables, elem_size, i); + if (qm->rematch_count != rematch_count) { + /* Collect tables, don't modify map while updating it */ + ecs_vec_append_t(a, &unmatched, ecs_table_t*)[0] = + qm->base.table; } + } + } while ((cur = cur->next)); - if (ptr[0] == TOK_AND) { - ptr = ecs_parse_whitespace(ptr + 1); + /* Actually unmatch tables */ + int32_t i, count = ecs_vec_count(&unmatched); + ecs_table_t **unmatched_tables = ecs_vec_first(&unmatched); + for (i = 0; i < count; i ++) { + flecs_query_cache_remove_table(cache, unmatched_tables[i]); + ecs_assert(flecs_query_cache_get_table( + cache, unmatched_tables[i]) == NULL, ECS_INTERNAL_ERROR, NULL); + } + ecs_vec_fini_t(a, &unmatched, ecs_table_t*); - term->role = ECS_PAIR; + if (world->flags & EcsWorldMeasureFrameTime) { + world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); + } - } else if (ptr[0] == TOK_PAREN_CLOSE) { - ptr = ecs_parse_whitespace(ptr + 1); - break; +done: + ecs_os_perf_trace_pop("flecs.query.rematch"); +} - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected ',' or ')'"); - return NULL; - } +/** + * @file query/cache/cache_order_by.c + * @brief Query sorting (order_by) implenentation. + */ - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected identifier or set expression"); - return NULL; - } - arg ++; +ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_cache_sort_table_generic, order_by, static) - } while (true); +static +void flecs_query_cache_sort_table( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_index, + ecs_order_by_action_t compare, + ecs_sort_table_action_t sort) +{ + int32_t count = ecs_table_count(table); + if (!count) { + /* Nothing to sort */ + return; + } + + if (count < 2) { + return; + } - return ptr; + ecs_entity_t *entities = table->data.entities; + void *ptr = NULL; + int32_t size = 0; + if (column_index != -1) { + ecs_column_t *column = &table->data.columns[column_index]; + ecs_type_info_t *ti = column->ti; + size = ti->size; + ptr = column->data; + } + + if (sort) { + sort(world, table, entities, ptr, size, 0, count - 1, compare); + } else { + flecs_query_cache_sort_table_generic( + world, table, entities, ptr, size, 0, count - 1, compare); + } } +/* Helper struct for building sorted table ranges */ +typedef struct sort_helper_t { + ecs_query_cache_match_t *match; + ecs_entity_t *entities; + const void *ptr; + int32_t row; + int32_t elem_size; + int32_t count; + bool shared; +} sort_helper_t; + static -void parser_unexpected_char( - const char *name, - const char *expr, - const char *ptr, - char ch) +const void* ptr_from_helper( + sort_helper_t *helper) { - if (ch && (ch != '\n')) { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected character '%c'", ch); + ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); + if (helper->shared) { + return helper->ptr; } else { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected end of term"); + return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); } } static -const char* parse_term( - const ecs_world_t *world, - const char *name, - const char *expr, - ecs_term_t *term_out) +ecs_entity_t e_from_helper( + sort_helper_t *helper) { - const char *ptr = expr; - char token[ECS_MAX_TOKEN_SIZE] = {0}; - ecs_term_t term = { .move = true /* parser never owns resources */ }; + if (helper->row < helper->count) { + return helper->entities[helper->row]; + } else { + return 0; + } +} - ptr = ecs_parse_whitespace(ptr); +static +void flecs_query_cache_build_sorted_table_range( + ecs_query_cache_t *cache, + ecs_query_cache_group_t *group) +{ + ecs_world_t *world = cache->query->world; + flecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, + "cannot sort query in multithreaded mode"); - /* Inout specifiers always come first */ - if (ptr[0] == TOK_BRACKET_OPEN) { - ptr = parse_annotation(name, expr, (ptr - expr), ptr + 1, &term.inout); - if (!ptr) { - goto error; + ecs_entity_t id = cache->order_by; + ecs_order_by_action_t compare = cache->order_by_callback; + int32_t i, table_count = ecs_vec_count(&group->tables); + if (!table_count) { + return; + } + + ecs_vec_init_if_t(&cache->table_slices, ecs_query_cache_match_t); + int32_t to_sort = 0; + int32_t order_by_term = cache->order_by_term; + + sort_helper_t *helper = flecs_alloc_n( + &world->allocator, sort_helper_t, table_count); + for (i = 0; i < table_count; i ++) { + ecs_query_cache_match_t *qm = + ecs_vec_get_t(&group->tables, ecs_query_cache_match_t, i); + ecs_table_t *table = qm->base.table; + + if (ecs_table_count(table) == 0) { + continue; + } + + if (id) { + const ecs_term_t *term = &cache->query->terms[order_by_term]; + int32_t field = term->field_index; + ecs_size_t size = cache->query->sizes[field]; + ecs_entity_t src = qm->_sources[field]; + if (src == 0) { + int32_t column_index = qm->base.trs[field]->column; + ecs_column_t *column = &table->data.columns[column_index]; + helper[to_sort].ptr = column->data; + helper[to_sort].elem_size = size; + helper[to_sort].shared = false; + } else { + ecs_record_t *r = flecs_entities_get(world, src); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (term->src.id & EcsUp) { + ecs_entity_t base = 0; + ecs_search_relation(world, r->table, 0, id, + EcsIsA, term->src.id & EcsTraverseFlags, &base, 0, 0); + if (base && base != src) { /* Component could be inherited */ + r = flecs_entities_get(world, base); + } + } + + helper[to_sort].ptr = ecs_table_get_id( + world, r->table, id, ECS_RECORD_TO_ROW(r->row)); + helper[to_sort].elem_size = size; + helper[to_sort].shared = true; + } + ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); + } else { + helper[to_sort].ptr = NULL; + helper[to_sort].elem_size = 0; + helper[to_sort].shared = false; } - ptr = ecs_parse_whitespace(ptr); + + helper[to_sort].match = qm; + helper[to_sort].entities = table->data.entities; + helper[to_sort].row = 0; + helper[to_sort].count = ecs_table_count(table); + to_sort ++; } - if (valid_operator_char(ptr[0])) { - term.oper = parse_operator(ptr[0]); - ptr = ecs_parse_whitespace(ptr + 1); + if (!to_sort) { + goto done; } - /* If next token is the start of an identifier, it could be either a type - * role, source or component identifier */ - if (valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - goto error; + ecs_query_cache_match_t *cur = NULL; + + bool proceed; + do { + int32_t j, min = 0; + proceed = true; + + ecs_entity_t e1; + while (!(e1 = e_from_helper(&helper[min]))) { + min ++; + if (min == to_sort) { + proceed = false; + break; + } } - /* Is token a type role? */ - if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { - ptr ++; - goto parse_role; + if (!proceed) { + break; + } + + for (j = min + 1; j < to_sort; j++) { + ecs_entity_t e2 = e_from_helper(&helper[j]); + if (!e2) { + continue; + } + + const void *ptr1 = ptr_from_helper(&helper[min]); + const void *ptr2 = ptr_from_helper(&helper[j]); + + if (compare(e1, ptr1, e2, ptr2) > 0) { + min = j; + e1 = e_from_helper(&helper[min]); + } } - /* Is token a predicate? */ - if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_predicate; + sort_helper_t *cur_helper = &helper[min]; + if (!cur || cur->base.trs != cur_helper->match->base.trs) { + cur = ecs_vec_append_t(NULL, &cache->table_slices, + ecs_query_cache_match_t); + *cur = *(cur_helper->match); + cur->_offset = cur_helper->row; + cur->_count = 1; + } else { + cur->_count ++; } - /* Next token must be a predicate */ - goto parse_predicate; + cur_helper->row ++; + } while (proceed); - /* Pair with implicit subject */ - } else if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_pair; +done: + flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); +} - /* Nothing else expected here */ - } else { - parser_unexpected_char(name, expr, ptr, ptr[0]); - goto error; - } +void flecs_query_cache_build_sorted_tables( + ecs_query_cache_t *cache) +{ + ecs_vec_clear(&cache->table_slices); -parse_role: - term.role = parse_role(name, expr, (ptr - expr), token); - if (!term.role) { - goto error; + /* Sort tables in group order */ + ecs_query_cache_group_t *cur = &cache->default_group; + do { + flecs_query_cache_build_sorted_table_range(cache, cur); + } while ((cur = cur->next)); +} + +void flecs_query_cache_sort_tables( + ecs_world_t *world, + ecs_query_impl_t *impl) +{ + ecs_query_cache_t *cache = impl->cache; + ecs_order_by_action_t compare = cache->order_by_callback; + if (!compare) { + return; } - ptr = ecs_parse_whitespace(ptr); + ecs_sort_table_action_t sort = cache->order_by_table_callback; + ecs_entity_t order_by = cache->order_by; + int32_t order_by_term = cache->order_by_term; + ecs_component_record_t *cr = flecs_components_get(world, order_by); - /* If next token is the source token, this is an empty source */ - if (valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - goto error; - } + /* Iterate over non-empty tables. Don't bother with empty tables as they + * have nothing to sort */ - /* If not, it's a predicate */ - goto parse_predicate; + bool tables_sorted = false; - } else if (ptr[0] == TOK_PAREN_OPEN) { - goto parse_pair; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected identifier after role"); - goto error; - } + ecs_query_cache_group_t *cur = &cache->default_group; + do { + int32_t i, count = ecs_vec_count(&cur->tables); + for (i = 0; i < count; i ++) { + ecs_query_cache_match_t *qm = + ecs_vec_get_t(&cur->tables, ecs_query_cache_match_t, i); + ecs_table_t *table = qm->base.table; + bool dirty = false; -parse_predicate: - if (parse_identifier(token, &term.pred)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; - } + if (flecs_query_check_table_monitor(impl, qm, 0)) { + tables_sorted = true; + dirty = true; - /* Set expression */ - if (ptr[0] == TOK_COLON) { - ptr = ecs_parse_whitespace(ptr + 1); - ptr = parse_set_expr(world, name, expr, (ptr - expr), ptr, NULL, - &term.pred, TOK_COLON); - if (!ptr) { - goto error; - } + if (!ecs_table_count(table)) { + /* If table is empty, there's a chance the query won't + * iterate it so update the match monitor here. */ + flecs_query_sync_match_monitor(impl, qm); + + ecs_vec_t *matches = qm->wildcard_matches; + if (matches) { + int32_t j, qms_count = ecs_vec_count(matches); + ecs_query_cache_match_t *qms = ecs_vec_first(matches); + for (j = 0; j < qms_count; j ++) { + flecs_query_sync_match_monitor(impl, &qms[j]); + } + } + } + } - ptr = ecs_parse_whitespace(ptr); + int32_t column = -1; + if (order_by) { + if (flecs_query_check_table_monitor( + impl, qm, order_by_term + 1)) + { + dirty = true; + } - if (ptr[0] == TOK_AND || !ptr[0]) { - goto parse_done; - } + if (dirty) { + column = -1; - if (ptr[0] != TOK_COLON) { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected token '%c' after predicate set expression", ptr[0]); - goto error; - } + const ecs_table_record_t *tr = flecs_component_get_table( + cr, table); + if (tr) { + column = tr->column; + } - ptr = ecs_parse_whitespace(ptr + 1); - } else { - ptr = ecs_parse_whitespace(ptr); - } - - if (ptr[0] == TOK_PAREN_OPEN) { - ptr ++; - if (ptr[0] == TOK_PAREN_CLOSE) { - term.subj.set.mask = EcsNothing; - ptr ++; - ptr = ecs_parse_whitespace(ptr); - } else { - ptr = parse_arguments( - world, name, expr, (ptr - expr), ptr, token, &term); + if (column == -1) { + /* Component is shared, no sorting is needed */ + dirty = false; + } + } + } + + if (!dirty) { + continue; + } + + /* Something has changed, sort the table. Prefers using + * flecs_query_cache_sort_table when available */ + flecs_query_cache_sort_table(world, table, column, compare, sort); + tables_sorted = true; } + } while ((cur = cur->next)); /* Next group */ - goto parse_done; + if (tables_sorted || cache->match_count != cache->prev_match_count) { + flecs_query_cache_build_sorted_tables(cache); + cache->match_count ++; /* Increase version if tables changed */ } +} - goto parse_done; +/** + * @file query/compiler/compile.c + * @brief Compile query program from query. + */ -parse_pair: - ptr = ecs_parse_identifier(name, expr, ptr + 1, token); - if (!ptr) { - goto error; - } - if (ptr[0] == TOK_AND) { - ptr ++; - term.subj.entity = EcsThis; - goto parse_pair_predicate; - } else if (ptr[0] == TOK_PAREN_CLOSE) { - term.subj.entity = EcsThis; - goto parse_pair_predicate; - } else { - parser_unexpected_char(name, expr, ptr, ptr[0]); - goto error; - } +static +bool flecs_query_var_is_anonymous( + const ecs_query_impl_t *query, + ecs_var_id_t var_id) +{ + ecs_query_var_t *var = &query->vars[var_id]; + return var->anonymous; +} -parse_pair_predicate: - if (parse_identifier(token, &term.pred)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; +ecs_var_id_t flecs_query_add_var( + ecs_query_impl_t *query, + const char *name, + ecs_vec_t *vars, + ecs_var_kind_t kind) +{ + const char *dot = NULL; + if (name) { + dot = strchr(name, '.'); + if (dot) { + kind = EcsVarEntity; /* lookup variables are always entities */ + } } - ptr = ecs_parse_whitespace(ptr); - if (valid_token_start_char(ptr[0])) { - ptr = ecs_parse_identifier(name, expr, ptr, token); - if (!ptr) { - goto error; - } + ecs_hashmap_t *var_index = NULL; + ecs_var_id_t var_id = EcsVarNone; + if (name) { + if (kind == EcsVarAny) { + var_id = flecs_query_find_var_id(query, name, EcsVarEntity); + if (var_id != EcsVarNone) { + return var_id; + } - if (ptr[0] == TOK_PAREN_CLOSE) { - ptr ++; - goto parse_pair_object; + var_id = flecs_query_find_var_id(query, name, EcsVarTable); + if (var_id != EcsVarNone) { + return var_id; + } + + kind = EcsVarTable; } else { - parser_unexpected_char(name, expr, ptr, ptr[0]); - goto error; + var_id = flecs_query_find_var_id(query, name, kind); + if (var_id != EcsVarNone) { + return var_id; + } } - } else if (ptr[0] == TOK_PAREN_CLOSE) { - /* No object */ - ptr ++; - goto parse_done; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "expected pair object or ')'"); - goto error; - } -parse_pair_object: - if (parse_identifier(token, &term.obj)) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid identifier '%s'", token); - goto error; - } + if (kind == EcsVarTable) { + var_index = &query->tvar_index; + } else { + var_index = &query->evar_index; + } - if (term.role != 0) { - if (term.role != ECS_PAIR && term.role != ECS_CASE) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid combination of role '%s' with pair", - ecs_role_str(term.role)); - goto error; + /* If we're creating an entity var, check if it has a table variant */ + if (kind == EcsVarEntity && var_id == EcsVarNone) { + var_id = flecs_query_find_var_id(query, name, EcsVarTable); } + } + + ecs_query_var_t *var; + ecs_var_id_t result; + if (vars) { + var = ecs_vec_append_t(NULL, vars, ecs_query_var_t); + result = var->id = flecs_itovar(ecs_vec_count(vars)); } else { - term.role = ECS_PAIR; + ecs_dbg_assert(query->var_count < query->var_size, + ECS_INTERNAL_ERROR, NULL); + var = &query->vars[query->var_count]; + result = var->id = flecs_itovar(query->var_count); + query->var_count ++; } - ptr = ecs_parse_whitespace(ptr); - goto parse_done; + var->kind = flecs_ito(int8_t, kind); + var->name = name; + var->table_id = var_id; + var->base_id = 0; + var->lookup = NULL; + flecs_set_var_label(var, NULL); -parse_done: - *term_out = term; - return ptr; + if (name) { + flecs_name_index_init_if(var_index, NULL); + flecs_name_index_ensure(var_index, var->id, name, 0, 0); + var->anonymous = name[0] == '_'; -error: - ecs_term_fini(&term); - *term_out = (ecs_term_t){0}; - return NULL; + /* Handle variables that require a by-name lookup, e.g. $this.wheel */ + if (dot != NULL) { + ecs_assert(var->table_id == EcsVarNone, ECS_INTERNAL_ERROR, NULL); + var->lookup = dot + 1; + } + } + + return result; } static -bool is_valid_end_of_term( - const char *ptr) +ecs_var_id_t flecs_query_add_var_for_term_id( + ecs_query_impl_t *query, + ecs_term_ref_t *term_id, + ecs_vec_t *vars, + ecs_var_kind_t kind) { - if ((ptr[0] == TOK_AND) || /* another term with And operator */ - (ptr[0] == TOK_OR[0]) || /* another term with Or operator */ - (ptr[0] == '\n') || /* newlines are valid */ - (ptr[0] == '\0') || /* end of string */ - (ptr[0] == '/') || /* comment (in plecs) */ - (ptr[0] == '{') || /* scope (in plecs) */ - (ptr[0] == '}') || - (ptr[0] == ':') || /* inheritance (in plecs) */ - (ptr[0] == '=')) /* assignment (in plecs) */ - { - return true; + const char *name = flecs_term_ref_var_name(term_id); + if (!name) { + return EcsVarNone; } - return false; + + return flecs_query_add_var(query, name, vars, kind); } -char* ecs_parse_term( - const ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - ecs_term_t *term) +/* This function walks over terms to discover which variables are used in the + * query. It needs to provide the following functionality: + * - create table vars for all variables used as source + * - create entity vars for all variables not used as source + * - create entity vars for all non-$this vars + * - create anonymous vars to store the content of wildcards + * - create anonymous vars to store result of lookups (for $var.child_name) + * - create anonymous vars for resolving component inheritance + * - create array that stores the source variable for each field + * - ensure table vars for non-$this variables are anonymous + * - ensure variables created inside scopes are anonymous + * - place anonymous variables after public variables in vars array + */ +static +int flecs_query_discover_vars( + ecs_stage_t *stage, + ecs_query_impl_t *query) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ + ecs_vec_reset_t(NULL, vars, ecs_query_var_t); - ecs_term_id_t *subj = &term->subj; + ecs_term_t *terms = query->pub.terms; + int32_t a, i, anonymous_count = 0, count = query->pub.term_count; + int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; + bool table_this = false, entity_before_table_this = false; - bool prev_or = false; - if (ptr != expr) { - if (ptr[0]) { - if (ptr[0] == ',') { - ptr ++; - } else if (ptr[0] == '|') { - ptr += 2; - prev_or = true; - } else { - ecs_parser_error(name, expr, (ptr - expr), - "invalid preceding token"); + /* For This table lookups during discovery. This will be overwritten after + * discovery with whether the query actually has a This table variable. */ + query->pub.flags |= EcsQueryHasTableThisVar; + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *first = &term->first; + ecs_term_ref_t *second = &term->second; + ecs_term_ref_t *src = &term->src; + + if (ECS_TERM_REF_ID(first) == EcsScopeOpen) { + /* Keep track of which variables are first used in scope, so that we + * can mark them as anonymous. Terms inside a scope are collapsed + * into a single result, which means that outside of the scope the + * value of those variables is undefined. */ + if (!scope) { + scoped_var_index = ecs_vec_count(vars); + } + scope ++; + continue; + } else if (ECS_TERM_REF_ID(first) == EcsScopeClose) { + if (!--scope) { + /* Any new variables declared after entering a scope should be + * marked as anonymous. */ + int32_t v; + for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { + ecs_vec_get_t(vars, ecs_query_var_t, v)->anonymous = true; + } } + continue; } - } - - ptr = ecs_parse_eol_and_whitespace(ptr); - if (!ptr[0]) { - *term = (ecs_term_t){0}; - return (char*)ptr; - } - - if (ptr == expr && !strcmp(expr, "0")) { - return (char*)&ptr[1]; - } - int32_t prev_set = subj->set.mask; - - /* Parse next element */ - ptr = parse_term(world, name, ptr, term); - if (!ptr) { - goto error; - } - - /* Check for $() notation */ - if (!ecs_os_strcmp(term->pred.name, "$")) { - if (term->subj.name) { - ecs_os_free(term->pred.name); - - term->pred = term->subj; - - if (term->obj.name) { - term->subj = term->obj; - } else { - term->subj.entity = EcsThis; - term->subj.name = NULL; - term->subj.var = EcsVarIsVariable; + ecs_var_id_t first_var_id = flecs_query_add_var_for_term_id( + query, first, vars, EcsVarEntity); + if (first_var_id == EcsVarNone) { + /* If first is not a variable, check if we need to insert anonymous + * variable for resolving component inheritance */ + if (term->flags_ & EcsTermIdInherited) { + anonymous_count += 2; /* table & entity variable */ } - term->obj.name = ecs_os_strdup(term->pred.name); - term->obj.var = EcsVarIsVariable; + /* If first is a wildcard, insert anonymous variable */ + if (flecs_term_ref_is_wildcard(first)) { + anonymous_count ++; + } } - } - /* Post-parse consistency checks */ + if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) != EcsThis)) { + const char *var_name = flecs_term_ref_var_name(src); + if (var_name) { + ecs_var_id_t var_id = flecs_query_find_var_id( + query, var_name, EcsVarEntity); + if (var_id == EcsVarNone || var_id == first_var_id) { + var_id = flecs_query_add_var( + query, var_name, vars, EcsVarEntity); + } - /* If next token is OR, term is part of an OR expression */ - if (!ecs_os_strncmp(ptr, TOK_OR, 2) || prev_or) { - /* An OR operator must always follow an AND or another OR */ - if (term->oper != EcsAnd) { - ecs_parser_error(name, expr, (ptr - expr), - "cannot combine || with other operators"); - goto error; - } + if (var_id != EcsVarNone) { + /* Mark variable as one for which we need to create a table + * variable. Don't create table variable now, so that we can + * store it in the non-public part of the variable array. */ + ecs_query_var_t *var = ecs_vec_get_t( + vars, ecs_query_var_t, (int32_t)var_id - 1); + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + if (!var->lookup) { + var->kind = EcsVarAny; + anonymous_table_count ++; + } - term->oper = EcsOr; - } + if (((1llu << term->field_index) & query->pub.data_fields)) { + /* Can't have an anonymous variable as source of a term + * that returns a component. We need to return each + * instance of the component, whereas anonymous + * variables are not guaranteed to be resolved to + * individual entities. */ + if (var->anonymous) { + ecs_err( + "can't use anonymous variable '%s' as source of " + "data term", var->name); + goto error; + } + } - /* Term must either end in end of expression, AND or OR token */ - if (!is_valid_end_of_term(ptr)) { - ecs_parser_error(name, expr, (ptr - expr), - "expected end of expression or next term"); - goto error; - } + /* Track which variable ids are used as field source */ + if (!query->src_vars) { + query->src_vars = flecs_calloc_n(&stage->allocator, + ecs_var_id_t, query->pub.field_count); + } - /* If the term just contained a 0, the expression has nothing. Ensure - * that after the 0 nothing else follows */ - if (!ecs_os_strcmp(term->pred.name, "0")) { - if (ptr[0]) { - ecs_parser_error(name, expr, (ptr - expr), - "unexpected term after 0"); - goto error; + query->src_vars[term->field_index] = var_id; + } + } else { + if (flecs_term_ref_is_wildcard(src)) { + anonymous_count ++; + } + } + } else if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) == EcsThis)) { + if (flecs_term_is_builtin_pred(term) && term->oper == EcsOr) { + flecs_query_add_var(query, EcsThisName, vars, EcsVarEntity); + } } - if (subj->set.mask != EcsDefaultSet || - (subj->entity && subj->entity != EcsThis) || - (subj->name && ecs_os_strcmp(subj->name, "This"))) + if (flecs_query_add_var_for_term_id( + query, second, vars, EcsVarEntity) == EcsVarNone) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid combination of 0 with non-default subject"); - goto error; + /* If second is a wildcard, insert anonymous variable */ + if (flecs_term_ref_is_wildcard(second)) { + anonymous_count ++; + } } - subj->set.mask = EcsNothing; - ecs_os_free(term->pred.name); - term->pred.name = NULL; - } + if (src->id & EcsIsVariable && second->id & EcsIsVariable) { + if (term->flags_ & EcsTermTransitive) { + /* Anonymous variable to store temporary id for finding + * targets for transitive relationship, see compile_term. */ + anonymous_count ++; + } + } - /* Cannot combine EcsNothing with operators other than AND */ - if (term->oper != EcsAnd && subj->set.mask == EcsNothing) { - ecs_parser_error(name, expr, (ptr - expr), - "invalid operator for empty source"); - goto error; - } + /* If member term, make sure source is available as entity */ + if (term->flags_ & EcsTermIsMember) { + flecs_query_add_var_for_term_id(query, src, vars, EcsVarEntity); + } - /* Verify consistency of OR expression */ - if (prev_or && term->oper == EcsOr) { - /* Set expressions must be the same for all OR terms */ - if (subj->set.mask != prev_set) { - ecs_parser_error(name, expr, (ptr - expr), - "cannot combine different sources in OR expression"); - goto error; + /* Track if a This entity variable is used before a potential This table + * variable. If this happens, the query has no This table variable */ + if (ECS_TERM_REF_ID(src) == EcsThis) { + table_this = true; } - term->oper = EcsOr; - } + bool first_is_this = + (ECS_TERM_REF_ID(first) == EcsThis) && (first->id & EcsIsVariable); + bool second_is_this = + (ECS_TERM_REF_ID(first) == EcsThis) && (first->id & EcsIsVariable); - /* Automatically assign This if entity is not assigned and the set is - * nothing */ - if (subj->set.mask != EcsNothing) { - if (!subj->name) { - if (!subj->entity) { - subj->entity = EcsThis; + if (first_is_this || second_is_this) { + if (!table_this) { + entity_before_table_this = true; } } } - if (subj->name && !ecs_os_strcmp(subj->name, "0")) { - subj->entity = 0; - subj->set.mask = EcsNothing; - } + int32_t var_count = ecs_vec_count(vars); + ecs_var_id_t placeholder = EcsVarNone - 1; + bool replace_placeholders = false; - /* Process role */ - if (term->role == ECS_AND) { - term->oper = EcsAndFrom; - term->role = 0; - } else if (term->role == ECS_OR) { - term->oper = EcsOrFrom; - term->role = 0; - } else if (term->role == ECS_NOT) { - term->oper = EcsNotFrom; - term->role = 0; - } + /* Ensure lookup variables have table and/or entity variables */ + for (i = 0; i < var_count; i ++) { + ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); + if (var->lookup) { + char *var_name = ecs_os_strdup(var->name); + var_name[var->lookup - var->name - 1] = '\0'; + + ecs_var_id_t base_table_id = flecs_query_find_var_id( + query, var_name, EcsVarTable); + if (base_table_id != EcsVarNone) { + var->table_id = base_table_id; + } else if (anonymous_table_count) { + /* Scan for implicit anonymous table variables that haven't been + * inserted yet (happens after this step). Doing this here vs. + * ensures that anonymous variables are appended at the end of + * the variable array, while also ensuring that variable ids are + * stable (no swapping of table var ids that are in use). */ + for (a = 0; a < var_count; a ++) { + ecs_query_var_t *avar = ecs_vec_get_t( + vars, ecs_query_var_t, a); + if (avar->kind == EcsVarAny) { + if (!ecs_os_strcmp(avar->name, var_name)) { + base_table_id = (ecs_var_id_t)(a + 1); + break; + } + } + } + if (base_table_id != EcsVarNone) { + /* Set marker so we can set the new table id afterwards */ + var->table_id = placeholder; + replace_placeholders = true; + } + } - ptr = ecs_parse_whitespace(ptr); + ecs_var_id_t base_entity_id = flecs_query_find_var_id( + query, var_name, EcsVarEntity); + if (base_entity_id == EcsVarNone) { + /* Get name from table var (must exist). We can't use allocated + * name since variables don't own names. */ + const char *base_name = NULL; + if (base_table_id != EcsVarNone && base_table_id) { + ecs_query_var_t *base_table_var = ecs_vec_get_t( + vars, ecs_query_var_t, (int32_t)base_table_id - 1); + base_name = base_table_var->name; + } else { + base_name = EcsThisName; + } - return (char*)ptr; -error: - if (term) { - ecs_term_fini(term); + base_entity_id = flecs_query_add_var( + query, base_name, vars, EcsVarEntity); + var = ecs_vec_get_t(vars, ecs_query_var_t, i); + } + + var->base_id = base_entity_id; + + ecs_os_free(var_name); + } } - return NULL; -} -#endif + var_count = ecs_vec_count(vars); + /* Add non-This table variables */ + if (anonymous_table_count) { + anonymous_table_count = 0; + for (i = 0; i < var_count; i ++) { + ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); + if (var->kind == EcsVarAny) { + var->kind = EcsVarEntity; -#ifdef FLECS_META_C + ecs_var_id_t var_id = flecs_query_add_var( + query, var->name, vars, EcsVarTable); + ecs_vec_get_t(vars, ecs_query_var_t, i)->table_id = var_id; + anonymous_table_count ++; + } + } -#include + var_count = ecs_vec_count(vars); + } -#define ECS_META_IDENTIFIER_LENGTH (256) + /* If any forward references to newly added anonymous tables exist, replace + * them with the actual table variable ids. */ + if (replace_placeholders) { + for (i = 0; i < var_count; i ++) { + ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); + if (var->table_id == placeholder) { + char *var_name = ecs_os_strdup(var->name); + var_name[var->lookup - var->name - 1] = '\0'; -#define ecs_meta_error(ctx, ptr, ...)\ - ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); + var->table_id = flecs_query_find_var_id( + query, var_name, EcsVarTable); + ecs_assert(var->table_id != EcsVarNone, + ECS_INTERNAL_ERROR, NULL); -typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH]; + ecs_os_free(var_name); + } + } + } -typedef struct meta_parse_ctx_t { - const char *name; - const char *desc; -} meta_parse_ctx_t; + /* Always include spot for This variable, even if query doesn't use it */ + var_count ++; -typedef struct meta_type_t { - ecs_meta_token_t type; - ecs_meta_token_t params; - bool is_const; - bool is_ptr; -} meta_type_t; + ecs_query_var_t *query_vars = &flecs_this_array; + if ((var_count + anonymous_count) > 1) { + query_vars = flecs_alloc(&stage->allocator, + (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * + (var_count + anonymous_count)); + } -typedef struct meta_member_t { - meta_type_t type; - ecs_meta_token_t name; - int64_t count; - bool is_partial; -} meta_member_t; + query->vars = query_vars; + query->var_count = var_count; + query->pub.var_count = flecs_ito(int8_t, var_count); + ECS_BIT_COND(query->pub.flags, EcsQueryHasTableThisVar, + !entity_before_table_this); + query->var_size = var_count + anonymous_count; -typedef struct meta_constant_t { - ecs_meta_token_t name; - int64_t value; - bool is_value_set; -} meta_constant_t; + char **var_names; + if (query_vars != &flecs_this_array) { + query_vars[0].kind = EcsVarTable; + query_vars[0].name = NULL; + flecs_set_var_label(&query_vars[0], NULL); + query_vars[0].id = 0; + query_vars[0].table_id = EcsVarNone; + query_vars[0].lookup = NULL; -typedef struct meta_params_t { - meta_type_t key_type; - meta_type_t type; - int64_t count; - bool is_key_value; - bool is_fixed_size; -} meta_params_t; + var_names = ECS_ELEM(query_vars, ECS_SIZEOF(ecs_query_var_t), + var_count + anonymous_count); + var_names[0] = ECS_CONST_CAST(char*, query_vars[0].name); + } else { + var_names = &flecs_this_name_array; + } -static -const char* skip_scope(const char *ptr, meta_parse_ctx_t *ctx) { - /* Keep track of which characters were used to open the scope */ - char stack[256]; - int32_t sp = 0; - char ch; + query->pub.vars = (char**)var_names; - while ((ch = *ptr)) { - if (ch == '(' || ch == '<') { - stack[sp] = ch; + query_vars ++; + var_names ++; + var_count --; - sp ++; - if (sp >= 256) { - ecs_meta_error(ctx, ptr, "maximum level of nesting reached"); - goto error; - } - } else if (ch == ')' || ch == '>') { - sp --; - if ((sp < 0) || (ch == '>' && stack[sp] != '<') || - (ch == ')' && stack[sp] != '(')) - { - ecs_meta_error(ctx, ptr, "mismatching %c in identifier", ch); - goto error; - } + if (var_count) { + ecs_query_var_t *user_vars = ecs_vec_first_t(vars, ecs_query_var_t); + ecs_os_memcpy_n(query_vars, user_vars, ecs_query_var_t, var_count); + for (i = 0; i < var_count; i ++) { + ecs_assert(&var_names[i] != &(&flecs_this_name_array)[i], + ECS_INTERNAL_ERROR, NULL); + var_names[i] = ECS_CONST_CAST(char*, query_vars[i].name); } + } - ptr ++; + /* Hide anonymous table variables from application */ + query->pub.var_count = + flecs_ito(int8_t, query->pub.var_count - anonymous_table_count); - if (!sp) { - break; - } + /* Sanity check to make sure that the public part of the variable array only + * contains entity variables. */ +#ifdef FLECS_DEBUG + for (i = 1 /* first element = $this */; i < query->pub.var_count; i ++) { + ecs_assert(query->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); } +#endif - return ptr; + return 0; error: - return NULL; + return -1; } static -const char* parse_c_digit( - const char *ptr, - int64_t *value_out) +bool flecs_query_var_is_unknown( + ecs_query_impl_t *query, + ecs_var_id_t var_id, + ecs_query_compile_ctx_t *ctx) { - char token[24]; - ptr = ecs_parse_eol_and_whitespace(ptr); - ptr = ecs_parse_digit(ptr, token); - if (!ptr) { - goto error; + ecs_query_var_t *vars = query->vars; + if (ctx->written & (1ull << var_id)) { + return false; + } else { + ecs_var_id_t table_var = vars[var_id].table_id; + if (table_var != EcsVarNone) { + return flecs_query_var_is_unknown(query, table_var, ctx); + } } - - *value_out = strtol(token, NULL, 0); - - return ecs_parse_eol_and_whitespace(ptr); -error: - return NULL; + return true; } +/* Returns whether term is unknown. A term is unknown when it has variable + * elements (first, second, src) that are all unknown. */ static -const char* parse_c_identifier( - const char *ptr, - char *buff, - char *params, - meta_parse_ctx_t *ctx) -{ - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); - - char *bptr = buff, ch; - - if (params) { - params[0] = '\0'; +bool flecs_query_term_is_unknown( + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_op_t dummy = {0}; + + flecs_query_compile_term_ref(NULL, query, &dummy, &term->first, + &dummy.first, EcsQueryFirst, EcsVarEntity, ctx, false); + flecs_query_compile_term_ref(NULL, query, &dummy, &term->second, + &dummy.second, EcsQuerySecond, EcsVarEntity, ctx, false); + flecs_query_compile_term_ref(NULL, query, &dummy, &term->src, + &dummy.src, EcsQuerySrc, EcsVarAny, ctx, false); + + bool has_vars = dummy.flags & + ((EcsQueryIsVar << EcsQueryFirst) | + (EcsQueryIsVar << EcsQuerySecond) | + (EcsQueryIsVar << EcsQuerySrc)); + if (!has_vars) { + /* If term has no variables (typically terms with a static src) there + * can't be anything that's unknown. */ + return false; } - /* Ignore whitespaces */ - ptr = ecs_parse_eol_and_whitespace(ptr); - - if (!isalpha(*ptr)) { - ecs_meta_error(ctx, ptr, - "invalid identifier (starts with '%c')", *ptr); - goto error; + if (dummy.flags & (EcsQueryIsVar << EcsQueryFirst)) { + if (!flecs_query_var_is_unknown(query, dummy.first.var, ctx)) { + return false; + } } - - while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' && ch != '>' && ch != '}') { - /* Type definitions can contain macro's or templates */ - if (ch == '(' || ch == '<') { - if (!params) { - ecs_meta_error(ctx, ptr, "unexpected %c", *ptr); - goto error; - } - - const char *end = skip_scope(ptr, ctx); - ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr)); - params[end - ptr] = '\0'; - - ptr = end; - } else { - *bptr = ch; - bptr ++; - ptr ++; + if (dummy.flags & (EcsQueryIsVar << EcsQuerySecond)) { + if (!flecs_query_var_is_unknown(query, dummy.second.var, ctx)) { + return false; } } - - *bptr = '\0'; - - if (!ch) { - ecs_meta_error(ctx, ptr, "unexpected end of token"); - goto error; + if (dummy.flags & (EcsQueryIsVar << EcsQuerySrc)) { + if (!flecs_query_var_is_unknown(query, dummy.src.var, ctx)) { + return false; + } } - return ptr; -error: - return NULL; + return true; } +/* Find the next known term from specified offset. This function is used to find + * a term that can be evaluated before a term that is unknown. Evaluating known + * before unknown terms can significantly decrease the search space. */ static -const char * meta_open_scope( - const char *ptr, - meta_parse_ctx_t *ctx) +int32_t flecs_query_term_next_known( + ecs_query_impl_t *query, + ecs_query_compile_ctx_t *ctx, + int32_t offset, + ecs_flags64_t compiled) { - /* Skip initial whitespaces */ - ptr = ecs_parse_eol_and_whitespace(ptr); + ecs_query_t *q = &query->pub; + ecs_term_t *terms = q->terms; + int32_t i, count = q->term_count; - /* Is this the start of the type definition? */ - if (ctx->desc == ptr) { - if (*ptr != '{') { - ecs_meta_error(ctx, ptr, "missing '{' in struct definition"); - goto error; + for (i = offset; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (compiled & (1ull << i)) { + continue; } - ptr ++; - ptr = ecs_parse_eol_and_whitespace(ptr); - } + /* Only evaluate And terms */ + if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ + continue; + } - /* Is this the end of the type definition? */ - if (!*ptr) { - ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); - goto error; - } + /* Don't reorder terms in scopes */ + if (term->flags_ & EcsTermIsScope) { + continue; + } - /* Is this the end of the type definition? */ - if (*ptr == '}') { - ptr = ecs_parse_eol_and_whitespace(ptr + 1); - if (*ptr) { - ecs_meta_error(ctx, ptr, - "stray characters after struct definition"); - goto error; + if (flecs_query_term_is_unknown(query, term, ctx)) { + continue; } - return NULL; + + return i; } - return ptr; -error: - return NULL; + return -1; } +/* If the first part of a query contains more than one trivial term, insert a + * special instruction which batch-evaluates multiple terms. */ static -const char* meta_parse_constant( - const char *ptr, - meta_constant_t *token, - meta_parse_ctx_t *ctx) -{ - ptr = meta_open_scope(ptr, ctx); - if (!ptr) { - return NULL; - } - - token->is_value_set = false; +void flecs_query_insert_trivial_search( + ecs_query_impl_t *query, + ecs_flags64_t *compiled, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_t *q = &query->pub; + ecs_term_t *terms = q->terms; + int32_t i, term_count = q->term_count; + ecs_flags64_t trivial_set = 0; - /* Parse token, constant identifier */ - ptr = parse_c_identifier(ptr, token->name, NULL, ctx); - if (!ptr) { - return NULL; + /* Trivial search always ignores prefabs and disabled entities */ + if (query->pub.flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)) { + return; } - ptr = ecs_parse_eol_and_whitespace(ptr); - if (!ptr) { - return NULL; - } + /* Find trivial terms, which can be handled in single instruction */ + int32_t trivial_wildcard_terms = 0; + int32_t trivial_terms = 0; - /* Explicit value assignment */ - if (*ptr == '=') { - int64_t value = 0; - ptr = parse_c_digit(ptr + 1, &value); - token->value = value; - token->is_value_set = true; - } + for (i = 0; i < term_count; i ++) { + /* Term is already compiled */ + if (*compiled & (1ull << i)) { + continue; + } - /* Expect a ',' or '}' */ - if (*ptr != ',' && *ptr != '}') { - ecs_meta_error(ctx, ptr, "missing , after enum constant"); - goto error; - } + ecs_term_t *term = &terms[i]; + if (!(term->flags_ & EcsTermIsTrivial)) { + continue; + } - if (*ptr == ',') { - return ptr + 1; - } else { - return ptr; - } -error: - return NULL; -} + /* We can only add trivial terms to plan if they no up traversal */ + if ((term->src.id & EcsTraverseFlags) != EcsSelf) { + continue; + } -static -const char* meta_parse_type( - const char *ptr, - meta_type_t *token, - meta_parse_ctx_t *ctx) -{ - token->is_ptr = false; - token->is_const = false; + /* Wildcards are not supported for trivial queries */ + if (ecs_id_is_wildcard(term->id)) { + continue; + } - ptr = ecs_parse_eol_and_whitespace(ptr); + trivial_set |= (1llu << i); - /* Parse token, expect type identifier or ECS_PROPERTY */ - ptr = parse_c_identifier(ptr, token->type, token->params, ctx); - if (!ptr) { - goto error; + trivial_terms ++; } - if (!strcmp(token->type, "ECS_PRIVATE")) { - /* Members from this point are not stored in metadata */ - ptr += ecs_os_strlen(ptr); - goto done; - } + if (trivial_terms >= 2) { + /* Mark terms as compiled & populated */ + for (i = 0; i < q->term_count; i ++) { + if (trivial_set & (1llu << i)) { + *compiled |= (1ull << i); + } + } - /* If token is const, set const flag and continue parsing type */ - if (!strcmp(token->type, "const")) { - token->is_const = true; + /* If there's more than 1 trivial term, batch them in trivial search */ + ecs_query_op_t trivial = {0}; + if (!trivial_wildcard_terms) { + trivial.kind = EcsQueryTriv; + } - /* Parse type after const */ - ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); - } + /* Store the bitset with trivial terms on the instruction */ + trivial.src.entity = trivial_set; + flecs_query_op_insert(&trivial, ctx); - /* Check if type is a pointer */ - ptr = ecs_parse_eol_and_whitespace(ptr); - if (*ptr == '*') { - token->is_ptr = true; - ptr ++; + /* Mark $this as written */ + ctx->written |= (1llu << 0); } - -done: - return ptr; -error: - return NULL; } static -const char* meta_parse_member( - const char *ptr, - meta_member_t *token, - meta_parse_ctx_t *ctx) +void flecs_query_insert_cache_search( + ecs_query_impl_t *query, + ecs_flags64_t *compiled, + ecs_query_compile_ctx_t *ctx) { - ptr = meta_open_scope(ptr, ctx); - if (!ptr) { - return NULL; - } - - token->count = 1; - token->is_partial = false; - - /* Parse member type */ - ptr = meta_parse_type(ptr, &token->type, ctx); - if (!ptr) { - token->is_partial = true; - goto error; + if (!query->cache) { + return; } - /* Next token is the identifier */ - ptr = parse_c_identifier(ptr, token->name, NULL, ctx); - if (!ptr) { - goto error; - } + ecs_query_t *q = &query->pub; + int32_t childof_term = -1; + bool has_childof_trav = false; - /* Skip whitespace between member and [ or ; */ - ptr = ecs_parse_eol_and_whitespace(ptr); + if (q->cache_kind == EcsQueryCacheAll) { + /* If all terms are cacheable, make sure no other terms are compiled */ + *compiled = 0xFFFFFFFFFFFFFFFF; + } else if (q->cache_kind == EcsQueryCacheAuto) { + /* The query is partially cacheable */ + ecs_term_t *terms = q->terms; + int32_t i, count = q->term_count; - /* Check if this is an array */ - char *array_start = strchr(token->name, '['); - if (!array_start) { - /* If the [ was separated by a space, it will not be parsed as part of - * the name */ - if (*ptr == '[') { - array_start = (char*)ptr; /* safe, will not be modified */ - } - } + for (i = 0; i < count; i ++) { + if ((*compiled) & (1ull << i)) { + continue; + } - if (array_start) { - /* Check if the [ matches with a ] */ - char *array_end = strchr(array_start, ']'); - if (!array_end) { - ecs_meta_error(ctx, ptr, "missing ']'"); - goto error; + ecs_term_t *term = &terms[i]; + if (!(term->flags_ & EcsTermIsCacheable)) { + continue; + } - } else if (array_end - array_start == 0) { - ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); - goto error; - } + if (term->flags_ & EcsTermNonFragmentingChildOf) { + if (!term->trav) { + childof_term = i; + } + } - token->count = atoi(array_start + 1); + if (term->trav == EcsChildOf) { + has_childof_trav = true; + } - if (array_start == ptr) { - /* If [ was found after name, continue parsing after ] */ - ptr = array_end + 1; - } else { - /* If [ was fonud in name, replace it with 0 terminator */ - array_start[0] = '\0'; + *compiled |= (1ull << i); } } - /* Expect a ; */ - if (*ptr != ';') { - ecs_meta_error(ctx, ptr, "missing ; after member declaration"); - goto error; - } - - return ptr + 1; -error: - return NULL; -} - -static -int meta_parse_desc( - const char *ptr, - meta_params_t *token, - meta_parse_ctx_t *ctx) -{ - token->is_key_value = false; - token->is_fixed_size = false; + /* Insert the operation for cache traversal */ + ecs_query_op_t op = {0}; - ptr = ecs_parse_eol_and_whitespace(ptr); - if (*ptr != '(' && *ptr != '<') { - ecs_meta_error(ctx, ptr, - "expected '(' at start of collection definition"); - goto error; + if (q->flags & EcsQueryIsCacheable) { + op.kind = EcsQueryIsCache; + } else { + op.kind = EcsQueryCache; } - ptr ++; + flecs_query_write(0, &op.written); + flecs_query_write_ctx(0, ctx, false); + flecs_query_op_insert(&op, ctx); - /* Parse type identifier */ - ptr = meta_parse_type(ptr, &token->type, ctx); - if (!ptr) { - goto error; + if (childof_term != -1) { + flecs_query_compile_term( + q->world, query, &q->terms[childof_term], ctx); } - ptr = ecs_parse_eol_and_whitespace(ptr); + if (has_childof_trav) { + ecs_term_t *terms = q->terms; + int32_t i, count = q->term_count; - /* If next token is a ',' the first type was a key type */ - if (*ptr == ',') { - ptr = ecs_parse_eol_and_whitespace(ptr + 1); - - if (isdigit(*ptr)) { - int64_t value; - ptr = parse_c_digit(ptr, &value); - if (!ptr) { - goto error; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (!((*compiled) & (1ull << i))) { + continue; } - token->count = value; - token->is_fixed_size = true; - } else { - token->key_type = token->type; - - /* Parse element type */ - ptr = meta_parse_type(ptr, &token->type, ctx); - ptr = ecs_parse_eol_and_whitespace(ptr); + if (!(term->flags_ & EcsTermIsCacheable)) { + continue; + } - token->is_key_value = true; + if (term->trav == EcsChildOf && (term->oper == EcsAnd || term->oper == EcsOptional)) { + ecs_oper_kind_t oper = q->terms[i].oper; + q->terms[i].oper = EcsAnd; + flecs_query_compile_term( + q->world, query, &q->terms[i], ctx); + q->terms[i].oper = (int16_t)oper; + } } } - - if (*ptr != ')' && *ptr != '>') { - ecs_meta_error(ctx, ptr, - "expected ')' at end of collection definition"); - goto error; - } - - return 0; -error: - return -1; } static -ecs_entity_t meta_lookup( - ecs_world_t *world, - meta_type_t *token, - const char *ptr, - int64_t count, - meta_parse_ctx_t *ctx); +bool flecs_term_ref_match_multiple( + ecs_term_ref_t *ref) +{ + return (ref->id & EcsIsVariable) && (ECS_TERM_REF_ID(ref) != EcsAny); +} static -ecs_entity_t meta_lookup_array( - ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) +bool flecs_term_match_multiple( + ecs_term_t *term) { - meta_parse_ctx_t param_ctx = { - .name = ctx->name, - .desc = params_decl - }; + return flecs_term_ref_match_multiple(&term->first) || + flecs_term_ref_match_multiple(&term->second); +} - meta_params_t params; - if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { - goto error; - } - if (!params.is_fixed_size) { - ecs_meta_error(ctx, params_decl, "missing size for array"); - goto error; - } +static +int flecs_query_insert_toggle( + ecs_query_impl_t *impl, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_t *q = &impl->pub; + int32_t i, j, term_count = q->term_count; + ecs_term_t *terms = q->terms; + ecs_flags64_t fields_done = 0; - if (!params.count) { - ecs_meta_error(ctx, params_decl, "invalid array size"); - goto error; - } + for (i = 0; i < term_count; i ++) { + if (fields_done & (1llu << i)) { + continue; + } - ecs_entity_t element_type = ecs_lookup_symbol(world, params.type.type, true); - if (!element_type) { - ecs_meta_error(ctx, params_decl, "unknown element type '%s'", - params.type.type); - } + ecs_term_t *term = &terms[i]; + if (term->flags_ & EcsTermIsToggle) { + ecs_query_op_t cur = {0}; + flecs_query_compile_term_ref(NULL, impl, &cur, &term->src, + &cur.src, EcsQuerySrc, EcsVarAny, ctx, false); - if (!e) { - e = ecs_new_id(world); - } + ecs_flags64_t and_toggles = 0; + ecs_flags64_t not_toggles = 0; + ecs_flags64_t optional_toggles = 0; - ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); + for (j = i; j < term_count; j ++) { + if (fields_done & (1llu << j)) { + continue; + } - return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); -error: - return 0; -} + /* Also includes term[i], so flags get set correctly */ + term = &terms[j]; -static -ecs_entity_t meta_lookup_vector( - ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) -{ - meta_parse_ctx_t param_ctx = { - .name = ctx->name, - .desc = params_decl - }; + /* If term is not for the same src, skip */ + ecs_query_op_t next = {0}; + flecs_query_compile_term_ref(NULL, impl, &next, &term->src, + &next.src, EcsQuerySrc, EcsVarAny, ctx, false); + if (next.src.entity != cur.src.entity || + next.flags != cur.flags) + { + continue; + } - meta_params_t params; - if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { - goto error; - } + /* Source matches, set flag */ + if (term->oper == EcsNot) { + not_toggles |= (1llu << j); + } else if (term->oper == EcsOptional) { + optional_toggles |= (1llu << j); + } else { + and_toggles |= (1llu << j); + } - if (params.is_key_value) { - ecs_meta_error(ctx, params_decl, - "unexpected key value parameters for vector"); - goto error; - } + fields_done |= (1llu << j); + } - ecs_entity_t element_type = meta_lookup( - world, ¶ms.type, params_decl, 1, ¶m_ctx); + if (and_toggles || not_toggles) { + ecs_query_op_t op = {0}; + op.kind = EcsQueryToggle; + op.src = cur.src; + op.flags = cur.flags; - if (!e) { - e = ecs_new_id(world); + if (op.flags & (EcsQueryIsVar << EcsQuerySrc)) { + flecs_query_write(op.src.var, &op.written); + } + + /* Encode fields: + * - first.entity is the fields that match enabled bits + * - second.entity is the fields that match disabled bits + */ + op.first.entity = and_toggles; + op.second.entity = not_toggles; + flecs_query_op_insert(&op, ctx); + } + + /* Insert separate instructions for optional terms. To make sure + * entities are returned in batches where fields are never partially + * set or unset, the result must be split up into batches that have + * the exact same toggle masks. Instead of complicating the toggle + * instruction with code to scan for blocks that have the same bits + * set, separate instructions let the query engine backtrack to get + * the right results. */ + if (optional_toggles) { + for (j = i; j < term_count; j ++) { + uint64_t field_bit = 1ull << j; + if (!(optional_toggles & field_bit)) { + continue; + } + + ecs_query_op_t op = {0}; + op.kind = EcsQueryToggleOption; + op.src = cur.src; + op.first.entity = field_bit; + op.flags = cur.flags; + flecs_query_op_insert(&op, ctx); + } + } + } } - return ecs_set(world, e, EcsVector, { element_type }); -error: return 0; } static -ecs_entity_t meta_lookup_bitmask( +int flecs_query_insert_fixed_src_terms( ecs_world_t *world, - ecs_entity_t e, - const char *params_decl, - meta_parse_ctx_t *ctx) + ecs_query_impl_t *impl, + ecs_flags64_t *compiled, + ecs_query_compile_ctx_t *ctx) { - (void)e; + ecs_query_t *q = &impl->pub; + int32_t i, term_count = q->term_count; + ecs_term_t *terms = q->terms; - meta_parse_ctx_t param_ctx = { - .name = ctx->name, - .desc = params_decl - }; - - meta_params_t params; - if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { - goto error; - } + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; - if (params.is_key_value) { - ecs_meta_error(ctx, params_decl, - "unexpected key value parameters for bitmask"); - goto error; - } + if (term->oper == EcsNot) { + /* If term has not operator and variables for first/second, we can't + * put the term first as this could prevent us from getting back + * valid results. For example: + * !$var(e), Tag($var) + * + * Here, the first term would evaluate to false (and cause the + * entire query not to match) if 'e' has any components. + * + * However, when reordering we get results: + * Tag($var), !$var(e) + * + * Now the query returns all entities with Tag, that 'e' does not + * have as component. For this reason, queries should never use + * unwritten variables in not terms- and we should also not reorder + * terms in a way that results in doing this. */ + if (flecs_term_match_multiple(term)) { + continue; + } + } - if (params.is_fixed_size) { - ecs_meta_error(ctx, params_decl, - "unexpected size for bitmask"); - goto error; - } + /* Don't reorder terms in scopes */ + if (term->flags_ & EcsTermIsScope) { + continue; + } - ecs_entity_t bitmask_type = meta_lookup( - world, ¶ms.type, params_decl, 1, ¶m_ctx); - ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); + if (term->src.id & EcsIsEntity && ECS_TERM_REF_ID(&term->src)) { + if (flecs_query_compile_term(world, impl, term, ctx)) { + return -1; + } -#ifndef FLECS_NDEBUG - /* Make sure this is a bitmask type */ - const EcsMetaType *type_ptr = ecs_get(world, bitmask_type, EcsMetaType); - ecs_check(type_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(type_ptr->kind == EcsBitmaskType, ECS_INVALID_PARAMETER, NULL); -#endif + *compiled |= (1llu << i); + } + } - return bitmask_type; -error: return 0; } -static -ecs_entity_t meta_lookup( +int flecs_query_compile( ecs_world_t *world, - meta_type_t *token, - const char *ptr, - int64_t count, - meta_parse_ctx_t *ctx) + ecs_stage_t *stage, + ecs_query_impl_t *query) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(token != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); - - const char *typename = token->type; - ecs_entity_t type = 0; + /* Compile query to operations. Only necessary for non-trivial queries, as + * trivial queries use trivial iterators that don't use query ops. */ + bool needs_plan = true; + ecs_flags32_t flags = query->pub.flags; + + if (query->cache) { + if (flags & EcsQueryIsCacheable) { + if (!(flags & EcsQueryCacheWithFilter)) { + needs_plan = false; + } + } + } else { + ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; + if ((flags & trivial_flags) == trivial_flags) { + if (!(flags & EcsQueryMatchWildcards)) { + needs_plan = false; + } + } + } - /* Parse vector type */ - if (!token->is_ptr) { - if (!ecs_os_strcmp(typename, "ecs_array")) { - type = meta_lookup_array(world, 0, token->params, ctx); + if (!needs_plan) { + /* Initialize space for $this variable */ + query->pub.var_count = 1; + query->var_count = 1; + query->var_size = 1; + query->vars = &flecs_this_array; + query->pub.vars = &flecs_this_name_array; + query->pub.flags |= EcsQueryHasTableThisVar; + return 0; + } - } else if (!ecs_os_strcmp(typename, "ecs_vector") || - !ecs_os_strcmp(typename, "flecs::vector")) - { - type = meta_lookup_vector(world, 0, token->params, ctx); + ecs_query_t *q = &query->pub; + ecs_term_t *terms = q->terms; + ecs_query_compile_ctx_t ctx = {0}; + ecs_vec_reset_t(NULL, &stage->operations, ecs_query_op_t); + ctx.ops = &stage->operations; + ctx.cur = ctx.ctrlflow; + ctx.cur->lbl_begin = -1; + ctx.cur->lbl_begin = -1; + ecs_vec_clear(ctx.ops); - } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { - type = meta_lookup_bitmask(world, 0, token->params, ctx); + /* Find all variables defined in query */ + if (flecs_query_discover_vars(stage, query)) { + return -1; + } - } else if (!ecs_os_strcmp(typename, "flecs::byte")) { - type = ecs_id(ecs_byte_t); + /* If query contains fixed source terms, insert operation to set sources */ + int32_t i, term_count = q->term_count; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; - } else if (!ecs_os_strcmp(typename, "char")) { - type = ecs_id(ecs_char_t); + if (term->src.id & EcsIsEntity) { + ecs_query_op_t set_fixed = {0}; + set_fixed.kind = EcsQuerySetFixed; + flecs_query_op_insert(&set_fixed, &ctx); + break; + } + } - } else if (!ecs_os_strcmp(typename, "bool") || - !ecs_os_strcmp(typename, "_Bool")) - { - type = ecs_id(ecs_bool_t); + /* If the query contains terms with fixed ids (no wildcards, variables), + * insert instruction that initializes ecs_iter_t::ids. This allows for the + * insertion of simpler instructions later on. + * If the query is entirely cacheable, ids are populated by the cache. */ + if (q->cache_kind != EcsQueryCacheAll) { + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (flecs_term_is_fixed_id(q, term) || + (term->src.id & EcsIsEntity && + !(term->src.id & ~EcsTermRefFlags))) + { + ecs_query_op_t set_ids = {0}; + set_ids.kind = EcsQuerySetIds; + flecs_query_op_insert(&set_ids, &ctx); + break; + } + } + } - } else if (!ecs_os_strcmp(typename, "int8_t")) { - type = ecs_id(ecs_i8_t); - } else if (!ecs_os_strcmp(typename, "int16_t")) { - type = ecs_id(ecs_i16_t); - } else if (!ecs_os_strcmp(typename, "int32_t")) { - type = ecs_id(ecs_i32_t); - } else if (!ecs_os_strcmp(typename, "int64_t")) { - type = ecs_id(ecs_i64_t); + ecs_flags64_t compiled = 0; - } else if (!ecs_os_strcmp(typename, "uint8_t")) { - type = ecs_id(ecs_u8_t); - } else if (!ecs_os_strcmp(typename, "uint16_t")) { - type = ecs_id(ecs_u16_t); - } else if (!ecs_os_strcmp(typename, "uint32_t")) { - type = ecs_id(ecs_u32_t); - } else if (!ecs_os_strcmp(typename, "uint64_t")) { - type = ecs_id(ecs_u64_t); + /* Always evaluate terms with fixed source before other terms */ + flecs_query_insert_fixed_src_terms( + world, query, &compiled, &ctx); - } else if (!ecs_os_strcmp(typename, "float")) { - type = ecs_id(ecs_f32_t); - } else if (!ecs_os_strcmp(typename, "double")) { - type = ecs_id(ecs_f64_t); + /* Compile cacheable terms */ + flecs_query_insert_cache_search(query, &compiled, &ctx); - } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { - type = ecs_id(ecs_entity_t); + /* Insert trivial term search if query allows for it */ + flecs_query_insert_trivial_search(query, &compiled, &ctx); - } else if (!ecs_os_strcmp(typename, "char*")) { - type = ecs_id(ecs_string_t); - } else { - type = ecs_lookup_symbol(world, typename, true); - } - } else { - if (!ecs_os_strcmp(typename, "char")) { - typename = "flecs.meta.string"; - } else - if (token->is_ptr) { - typename = "flecs.meta.uptr"; - } else - if (!ecs_os_strcmp(typename, "char*") || - !ecs_os_strcmp(typename, "flecs::string")) - { - typename = "flecs.meta.string"; + /* If a query starts with one or more optional terms, first compile the non + * optional terms. This prevents having to insert an instruction that + * matches the query against every entity in the storage. + * Only skip optional terms at the start of the query so that any + * short-circuiting behavior isn't affected (a non-optional term can become + * optional if it uses a variable set in an optional term). */ + int32_t start_term = 0; + for (; start_term < term_count; start_term ++) { + if (terms[start_term].oper != EcsOptional) { + break; } - - type = ecs_lookup_symbol(world, typename, true); } - if (count != 1) { - ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); - - type = ecs_set(world, 0, EcsArray, {type, (int32_t)count}); - } + do { + /* Compile remaining query terms to instructions */ + for (i = start_term; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + int32_t compile = i; - if (!type) { - ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); - goto error; - } + if (compiled & (1ull << i)) { + continue; /* Already compiled */ + } - return type; -error: - return 0; -} + if (term->oper == EcsOptional && start_term) { + /* Don't reorder past the first optional term that's not in the + * initial list of optional terms. This protects short + * circuiting branching in the query. + * A future algorithm could look at which variables are + * accessed by optional terms, and continue reordering terms + * that don't access those variables. */ + break; + } -static -int meta_parse_struct( - ecs_world_t *world, - ecs_entity_t t, - const char *desc) -{ - const char *ptr = desc; - const char *name = ecs_get_name(world, t); + bool can_reorder = true; + if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ + can_reorder = false; + } - meta_member_t token; - meta_parse_ctx_t ctx = { - .name = name, - .desc = ptr - }; + /* If variables have been written, but this term has no known variables, + * first try to resolve terms that have known variables. This can + * significantly reduce the search space. + * Only perform this optimization after at least one variable has been + * written to, as all terms are unknown otherwise. */ + if (can_reorder && ctx.written && + flecs_query_term_is_unknown(query, term, &ctx)) + { + int32_t term_index = flecs_query_term_next_known( + query, &ctx, i + 1, compiled); + if (term_index != -1) { + term = &q->terms[term_index]; + compile = term_index; + i --; /* Repeat current term */ + } + } - ecs_entity_t old_scope = ecs_set_scope(world, t); + if (flecs_query_compile_term(world, query, term, &ctx)) { + return -1; + } - while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) { - ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = token.name - }); + compiled |= (1ull << compile); + } - ecs_entity_t type = meta_lookup( - world, &token.type, ptr, 1, &ctx); - if (!type) { - goto error; + if (start_term) { + start_term = 0; /* Repeat, now also insert optional terms */ + } else { + break; } + } while (true); - ecs_set(world, m, EcsMember, { - .type = type, - .count = (ecs_size_t)token.count - }); + /* If this is the last term and it's a Tree instruction, replace it + * with Children. If the queried for parent has the OrderedChildren + * trait, the Children instruction will return the array with child + * entities vs. returning children one by one. */ + if (term_count == 1 && ecs_vec_count(ctx.ops)) { + ecs_query_op_t *op = ecs_vec_last_t(ctx.ops, ecs_query_op_t); + ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); + if (op->kind == EcsQueryTree) { + op->kind = EcsQueryChildren; + } else if (op->kind == EcsQueryTreeWildcard) { + op->kind = EcsQueryChildrenWc; + } + } + + ecs_var_id_t this_id = flecs_query_find_var_id(query, "this", EcsVarEntity); + if (this_id != EcsVarNone) { + /* If This variable has been written as entity, insert an operation to + * assign it to it.entities for consistency. */ + if (ctx.written & (1ull << this_id)) { + ecs_query_op_t set_this = {0}; + set_this.kind = EcsQuerySetThis; + set_this.flags |= (EcsQueryIsVar << EcsQueryFirst); + set_this.first.var = this_id; + flecs_query_op_insert(&set_this, &ctx); + } + } + + /* Make sure non-This variables are written as entities */ + if (query->vars) { + for (i = 0; i < query->var_count; i ++) { + ecs_query_var_t *var = &query->vars[i]; + if (var->id && var->kind == EcsVarTable && var->name) { + ecs_var_id_t var_id = flecs_query_find_var_id(query, var->name, + EcsVarEntity); + if (!flecs_query_is_written(var_id, ctx.written)) { + /* Skip anonymous variables */ + if (!flecs_query_var_is_anonymous(query, var_id)) { + flecs_query_insert_each(var->id, var_id, &ctx, false); + } + } + } + } } - ecs_set_scope(world, old_scope); + /* If query contains non-This variables as term source, build lookup array */ + if (query->src_vars) { + ecs_assert(query->vars != NULL, ECS_INTERNAL_ERROR, NULL); + bool only_anonymous = true; - return 0; -error: - return -1; -} + for (i = 0; i < q->field_count; i ++) { + ecs_var_id_t var_id = query->src_vars[i]; + if (!var_id) { + continue; + } -static -int meta_parse_constants( - ecs_world_t *world, - ecs_entity_t t, - const char *desc, - bool is_bitmask) -{ - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + if (!flecs_query_var_is_anonymous(query, var_id)) { + only_anonymous = false; + break; + } else { + /* Don't fetch component data for anonymous variables. Because + * not all metadata (such as it.sources) is initialized for + * anonymous variables, and because they may only be available + * as table variables (each is not guaranteed to be inserted for + * anonymous variables) the iterator may not have sufficient + * information to resolve component data. */ + for (int32_t t = 0; t < q->term_count; t ++) { + ecs_term_t *term = &q->terms[t]; + if (term->field_index == i) { + term->inout = EcsInOutNone; + } + } + } + } - const char *ptr = desc; - const char *name = ecs_get_name(world, t); + /* Don't insert setvar instruction if all vars are anonymous */ + if (!only_anonymous) { + ecs_query_op_t set_vars = {0}; + set_vars.kind = EcsQuerySetVars; + flecs_query_op_insert(&set_vars, &ctx); + } - meta_parse_ctx_t ctx = { - .name = name, - .desc = ptr - }; + for (i = 0; i < q->field_count; i ++) { + ecs_var_id_t var_id = query->src_vars[i]; + if (!var_id) { + continue; + } - meta_constant_t token; - int64_t last_value = 0; + if (query->vars[var_id].kind == EcsVarTable) { + var_id = flecs_query_find_var_id(query, query->vars[var_id].name, + EcsVarEntity); - ecs_entity_t old_scope = ecs_set_scope(world, t); + /* Variables used as source that aren't This must be entities */ + ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } - while ((ptr = meta_parse_constant(ptr, &token, &ctx))) { - if (token.is_value_set) { - last_value = token.value; - } else if (is_bitmask) { - ecs_meta_error(&ctx, ptr, - "bitmask requires explicit value assignment"); - goto error; + query->src_vars[i] = var_id; } + } - ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t) { - .name = token.name - }); + ecs_assert((term_count - ctx.skipped) >= 0, ECS_INTERNAL_ERROR, NULL); - if (!is_bitmask) { - ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, - {(ecs_i32_t)last_value}); - } else { - ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, - {(ecs_u32_t)last_value}); + /* If query is empty, insert Nothing instruction */ + if (!(term_count - ctx.skipped)) { + ecs_vec_clear(ctx.ops); + ecs_query_op_t nothing = {0}; + nothing.kind = EcsQueryNothing; + flecs_query_op_insert(¬hing, &ctx); + } else { + /* If query contains terms for toggleable components, insert toggle */ + if (!(q->flags & EcsQueryTableOnly)) { + flecs_query_insert_toggle(query, &ctx); } - last_value ++; + /* Insert yield. If program reaches this operation, a result was found */ + ecs_query_op_t yield = {0}; + yield.kind = EcsQueryYield; + flecs_query_op_insert(&yield, &ctx); } - ecs_set_scope(world, old_scope); + int32_t op_count = ecs_vec_count(ctx.ops); + if (op_count) { + query->op_count = op_count; + query->ops = flecs_alloc_n(&stage->allocator, ecs_query_op_t, op_count); + ecs_query_op_t *query_ops = ecs_vec_first_t(ctx.ops, ecs_query_op_t); + ecs_os_memcpy_n(query->ops, query_ops, ecs_query_op_t, op_count); + } return 0; -error: - return -1; } -static -int meta_parse_enum( - ecs_world_t *world, - ecs_entity_t t, - const char *desc) -{ - ecs_add(world, t, EcsEnum); - return meta_parse_constants(world, t, desc, false); -} +/** + * @file query/compiler/compiler_term.c + * @brief Compile query term. + */ -static -int meta_parse_bitmask( - ecs_world_t *world, - ecs_entity_t t, - const char *desc) -{ - ecs_add(world, t, EcsBitmask); - return meta_parse_constants(world, t, desc, true); -} -int ecs_meta_from_desc( - ecs_world_t *world, - ecs_entity_t component, - ecs_type_kind_t kind, - const char *desc) +#define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ + +ecs_var_id_t flecs_query_find_var_id( + const ecs_query_impl_t *query, + const char *name, + ecs_var_kind_t kind) { - switch(kind) { - case EcsStructType: - if (meta_parse_struct(world, component, desc)) { - goto error; + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + + if (kind == EcsVarTable) { + if (!ecs_os_strcmp(name, EcsThisName)) { + if (query->pub.flags & EcsQueryHasTableThisVar) { + return 0; + } else { + return EcsVarNone; + } } - break; - case EcsEnumType: - if (meta_parse_enum(world, component, desc)) { - goto error; + + if (!flecs_name_index_is_init(&query->tvar_index)) { + return EcsVarNone; } - break; - case EcsBitmaskType: - if (meta_parse_bitmask(world, component, desc)) { - goto error; + + uint64_t index = flecs_name_index_find( + &query->tvar_index, name, 0, 0); + if (index == 0) { + return EcsVarNone; } - break; - default: - break; + return flecs_utovar(index); } - return 0; -error: - return -1; -} - -#endif - - -#ifdef FLECS_APP + if (kind == EcsVarEntity) { + if (!flecs_name_index_is_init(&query->evar_index)) { + return EcsVarNone; + } -static -int default_run_action( - ecs_world_t *world, - ecs_app_desc_t *desc) -{ - if (desc->init) { - desc->init(world); + uint64_t index = flecs_name_index_find( + &query->evar_index, name, 0, 0); + if (index == 0) { + return EcsVarNone; + } + return flecs_utovar(index); } - int result; - while ((result = ecs_app_run_frame(world, desc)) == 0) { } + ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); - if (result == 1) { - return 0; /* Normal exit */ - } else { - return result; /* Error code */ + /* If searching for any kind of variable, start with most specific */ + ecs_var_id_t index = flecs_query_find_var_id(query, name, EcsVarEntity); + if (index != EcsVarNone) { + return index; } -} -static -int default_frame_action( - ecs_world_t *world, - const ecs_app_desc_t *desc) -{ - return !ecs_progress(world, desc->delta_time); + return flecs_query_find_var_id(query, name, EcsVarTable); } -static ecs_app_run_action_t run_action = default_run_action; -static ecs_app_frame_action_t frame_action = default_frame_action; -static ecs_app_desc_t ecs_app_desc; - -int ecs_app_run( - ecs_world_t *world, - ecs_app_desc_t *desc) +static +ecs_var_id_t flecs_query_most_specific_var( + ecs_query_impl_t *query, + const char *name, + ecs_var_kind_t kind, + ecs_query_compile_ctx_t *ctx) { - ecs_app_desc = *desc; - - /* Don't set FPS & threads if custom run action is set, as the platform on - * which the app is running may not support it. */ - if (run_action == default_run_action) { - ecs_set_target_fps(world, ecs_app_desc.target_fps); - ecs_set_threads(world, ecs_app_desc.threads); + if (kind == EcsVarTable || kind == EcsVarEntity) { + return flecs_query_find_var_id(query, name, kind); } - /* REST server enables connecting to app with explorer */ - if (desc->enable_rest) { -#ifdef FLECS_REST - ecs_set(world, EcsWorld, EcsRest, {.port = 0}); -#else - ecs_warn("cannot enable remote API, REST addon not available"); -#endif + ecs_var_id_t evar = flecs_query_find_var_id(query, name, EcsVarEntity); + if ((evar != EcsVarNone) && flecs_query_is_written(evar, ctx->written)) { + /* If entity variable is available and written to, it contains the most + * specific result and should be used. */ + return evar; } - return run_action(world, &ecs_app_desc); -} + ecs_var_id_t tvar = flecs_query_find_var_id(query, name, EcsVarTable); + if ((tvar != EcsVarNone) && !flecs_query_is_written(tvar, ctx->written)) { + /* If variable of any kind is requested and variable hasn't been written + * yet, write to table variable */ + return tvar; + } -int ecs_app_run_frame( - ecs_world_t *world, - const ecs_app_desc_t *desc) -{ - return frame_action(world, desc); + /* If table var is written, and entity var doesn't exist or is not written, + * return table var */ + if (tvar != EcsVarNone) { + return tvar; + } else { + return evar; + } } -int ecs_app_set_run_action( - ecs_app_run_action_t callback) +ecs_query_lbl_t flecs_query_op_insert( + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx) { - if (run_action != default_run_action) { - ecs_err("run action already set"); - return -1; + ecs_query_op_t *elem = ecs_vec_append_t(NULL, ctx->ops, ecs_query_op_t); + int32_t count = ecs_vec_count(ctx->ops); + *elem = *op; + if (count > 1) { + if (ctx->cur->lbl_begin == -1) { + /* Variables written by previous instruction can't be written by + * this instruction, except when this is part of an OR chain. */ + elem->written &= ~elem[-1].written; + } } - run_action = callback; + elem->next = flecs_itolbl(count); + elem->prev = flecs_itolbl(count - 2); + return flecs_itolbl(count - 1); +} - return 0; +ecs_query_op_t* flecs_query_begin_block( + ecs_query_op_kind_t kind, + ecs_query_compile_ctx_t *ctx) +{ + ecs_query_op_t op = {0}; + op.kind = flecs_ito(uint8_t, kind); + ctx->cur->lbl_begin = flecs_query_op_insert(&op, ctx); + return ecs_vec_get_t(ctx->ops, ecs_query_op_t, ctx->cur->lbl_begin); } -int ecs_app_set_frame_action( - ecs_app_frame_action_t callback) +void flecs_query_end_block( + ecs_query_compile_ctx_t *ctx, + bool reset) { - if (frame_action != default_frame_action) { - ecs_err("frame action already set"); - return -1; + ecs_query_op_t new_op = {0}; + new_op.kind = EcsQueryEnd; + ecs_query_lbl_t end = flecs_query_op_insert(&new_op, ctx); + + ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); + ops[ctx->cur->lbl_begin].next = end; + + ecs_query_op_t *end_op = &ops[end]; + if (reset && ctx->cur->lbl_query != -1) { + ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; + end_op->prev = ctx->cur->lbl_begin; + end_op->src = query_op->src; + end_op->first = query_op->first; + end_op->second = query_op->second; + end_op->flags = query_op->flags; + end_op->field_index = query_op->field_index; + } else { + end_op->prev = ctx->cur->lbl_begin; + end_op->field_index = -1; } - frame_action = callback; - - return 0; + ctx->cur->lbl_begin = -1; } -#endif - - -/* Roles */ -const ecs_id_t ECS_CASE = (ECS_ROLE | (0x7Cull << 56)); -const ecs_id_t ECS_SWITCH = (ECS_ROLE | (0x7Bull << 56)); -const ecs_id_t ECS_PAIR = (ECS_ROLE | (0x7Aull << 56)); -const ecs_id_t ECS_OVERRIDE = (ECS_ROLE | (0x75ull << 56)); -const ecs_id_t ECS_DISABLED = (ECS_ROLE | (0x74ull << 56)); - -/** Builtin component ids */ -const ecs_entity_t ecs_id(EcsComponent) = 1; -const ecs_entity_t ecs_id(EcsComponentLifecycle) = 2; -const ecs_entity_t ecs_id(EcsType) = 3; -const ecs_entity_t ecs_id(EcsIdentifier) = 4; -const ecs_entity_t ecs_id(EcsTrigger) = 5; -const ecs_entity_t ecs_id(EcsQuery) = 6; -const ecs_entity_t ecs_id(EcsObserver) = 7; -const ecs_entity_t ecs_id(EcsIterable) = 8; - -/* System module component ids */ -const ecs_entity_t ecs_id(EcsSystem) = 10; -const ecs_entity_t ecs_id(EcsTickSource) = 11; - -/** Pipeline module component ids */ -const ecs_entity_t ecs_id(EcsPipelineQuery) = 12; - -/** Timer module component ids */ -const ecs_entity_t ecs_id(EcsTimer) = 13; -const ecs_entity_t ecs_id(EcsRateFilter) = 14; - -/** Meta module component ids */ -const ecs_entity_t ecs_id(EcsMetaType) = 15; -const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = 16; -const ecs_entity_t ecs_id(EcsPrimitive) = 17; -const ecs_entity_t ecs_id(EcsEnum) = 18; -const ecs_entity_t ecs_id(EcsBitmask) = 19; -const ecs_entity_t ecs_id(EcsMember) = 20; -const ecs_entity_t ecs_id(EcsStruct) = 21; -const ecs_entity_t ecs_id(EcsArray) = 22; -const ecs_entity_t ecs_id(EcsVector) = 23; -const ecs_entity_t ecs_id(EcsUnit) = 24; -const ecs_entity_t ecs_id(EcsUnitPrefix) = 25; - -/* Core scopes & entities */ -const ecs_entity_t EcsWorld = ECS_HI_COMPONENT_ID + 0; -const ecs_entity_t EcsFlecs = ECS_HI_COMPONENT_ID + 1; -const ecs_entity_t EcsFlecsCore = ECS_HI_COMPONENT_ID + 2; -const ecs_entity_t EcsFlecsHidden = ECS_HI_COMPONENT_ID + 3; -const ecs_entity_t EcsModule = ECS_HI_COMPONENT_ID + 4; -const ecs_entity_t EcsPrivate = ECS_HI_COMPONENT_ID + 5; -const ecs_entity_t EcsPrefab = ECS_HI_COMPONENT_ID + 6; -const ecs_entity_t EcsDisabled = ECS_HI_COMPONENT_ID + 7; - -/* Relation properties */ -const ecs_entity_t EcsWildcard = ECS_HI_COMPONENT_ID + 10; -const ecs_entity_t EcsAny = ECS_HI_COMPONENT_ID + 11; -const ecs_entity_t EcsThis = ECS_HI_COMPONENT_ID + 12; -const ecs_entity_t EcsVariable = ECS_HI_COMPONENT_ID + 13; - -const ecs_entity_t EcsTransitive = ECS_HI_COMPONENT_ID + 14; -const ecs_entity_t EcsReflexive = ECS_HI_COMPONENT_ID + 15; -const ecs_entity_t EcsSymmetric = ECS_HI_COMPONENT_ID + 16; -const ecs_entity_t EcsFinal = ECS_HI_COMPONENT_ID + 17; -const ecs_entity_t EcsDontInherit = ECS_HI_COMPONENT_ID + 18; -const ecs_entity_t EcsTag = ECS_HI_COMPONENT_ID + 19; -const ecs_entity_t EcsExclusive = ECS_HI_COMPONENT_ID + 20; -const ecs_entity_t EcsAcyclic = ECS_HI_COMPONENT_ID + 21; -const ecs_entity_t EcsWith = ECS_HI_COMPONENT_ID + 22; -const ecs_entity_t EcsOneOf = ECS_HI_COMPONENT_ID + 23; - -/* Builtin relations */ -const ecs_entity_t EcsChildOf = ECS_HI_COMPONENT_ID + 25; -const ecs_entity_t EcsIsA = ECS_HI_COMPONENT_ID + 26; - -/* Identifier tags */ -const ecs_entity_t EcsName = ECS_HI_COMPONENT_ID + 27; -const ecs_entity_t EcsSymbol = ECS_HI_COMPONENT_ID + 28; -const ecs_entity_t EcsAlias = ECS_HI_COMPONENT_ID + 29; - -/* Events */ -const ecs_entity_t EcsOnAdd = ECS_HI_COMPONENT_ID + 30; -const ecs_entity_t EcsOnRemove = ECS_HI_COMPONENT_ID + 31; -const ecs_entity_t EcsOnSet = ECS_HI_COMPONENT_ID + 32; -const ecs_entity_t EcsUnSet = ECS_HI_COMPONENT_ID + 33; -const ecs_entity_t EcsOnDelete = ECS_HI_COMPONENT_ID + 34; -const ecs_entity_t EcsOnCreateTable = ECS_HI_COMPONENT_ID + 35; -const ecs_entity_t EcsOnDeleteTable = ECS_HI_COMPONENT_ID + 36; -const ecs_entity_t EcsOnTableEmpty = ECS_HI_COMPONENT_ID + 37; -const ecs_entity_t EcsOnTableFill = ECS_HI_COMPONENT_ID + 38; -const ecs_entity_t EcsOnCreateTrigger = ECS_HI_COMPONENT_ID + 39; -const ecs_entity_t EcsOnDeleteTrigger = ECS_HI_COMPONENT_ID + 40; -const ecs_entity_t EcsOnDeleteObservable = ECS_HI_COMPONENT_ID + 41; -const ecs_entity_t EcsOnComponentLifecycle = ECS_HI_COMPONENT_ID + 42; -const ecs_entity_t EcsOnDeleteObject = ECS_HI_COMPONENT_ID + 43; - -/* Actions */ -const ecs_entity_t EcsRemove = ECS_HI_COMPONENT_ID + 50; -const ecs_entity_t EcsDelete = ECS_HI_COMPONENT_ID + 51; -const ecs_entity_t EcsThrow = ECS_HI_COMPONENT_ID + 52; - -/* Misc */ -const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55; +static +void flecs_query_begin_block_cond_eval( + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx, + ecs_write_flags_t cond_write_state) +{ + ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; + ecs_write_flags_t cond_mask = 0; -/* Systems */ -const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; -const ecs_entity_t EcsInactive = ECS_HI_COMPONENT_ID + 63; -const ecs_entity_t EcsPipeline = ECS_HI_COMPONENT_ID + 64; -const ecs_entity_t EcsPreFrame = ECS_HI_COMPONENT_ID + 65; -const ecs_entity_t EcsOnLoad = ECS_HI_COMPONENT_ID + 66; -const ecs_entity_t EcsPostLoad = ECS_HI_COMPONENT_ID + 67; -const ecs_entity_t EcsPreUpdate = ECS_HI_COMPONENT_ID + 68; -const ecs_entity_t EcsOnUpdate = ECS_HI_COMPONENT_ID + 69; -const ecs_entity_t EcsOnValidate = ECS_HI_COMPONENT_ID + 70; -const ecs_entity_t EcsPostUpdate = ECS_HI_COMPONENT_ID + 71; -const ecs_entity_t EcsPreStore = ECS_HI_COMPONENT_ID + 72; -const ecs_entity_t EcsOnStore = ECS_HI_COMPONENT_ID + 73; -const ecs_entity_t EcsPostFrame = ECS_HI_COMPONENT_ID + 74; + if (flecs_query_ref_flags(op->flags, EcsQueryFirst) == EcsQueryIsVar) { + first_var = op->first.var; + ecs_assert(first_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << first_var); + } + if (flecs_query_ref_flags(op->flags, EcsQuerySecond) == EcsQueryIsVar) { + second_var = op->second.var; + ecs_assert(second_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << second_var); + } + if (flecs_query_ref_flags(op->flags, EcsQuerySrc) == EcsQueryIsVar) { + src_var = op->src.var; + ecs_assert(src_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + cond_mask |= (1ull << src_var); + } -/* Meta primitive components (don't use low ids to save id space) */ -const ecs_entity_t ecs_id(ecs_bool_t) = ECS_HI_COMPONENT_ID + 80; -const ecs_entity_t ecs_id(ecs_char_t) = ECS_HI_COMPONENT_ID + 81; -const ecs_entity_t ecs_id(ecs_byte_t) = ECS_HI_COMPONENT_ID + 82; -const ecs_entity_t ecs_id(ecs_u8_t) = ECS_HI_COMPONENT_ID + 83; -const ecs_entity_t ecs_id(ecs_u16_t) = ECS_HI_COMPONENT_ID + 84; -const ecs_entity_t ecs_id(ecs_u32_t) = ECS_HI_COMPONENT_ID + 85; -const ecs_entity_t ecs_id(ecs_u64_t) = ECS_HI_COMPONENT_ID + 86; -const ecs_entity_t ecs_id(ecs_uptr_t) = ECS_HI_COMPONENT_ID + 87; -const ecs_entity_t ecs_id(ecs_i8_t) = ECS_HI_COMPONENT_ID + 88; -const ecs_entity_t ecs_id(ecs_i16_t) = ECS_HI_COMPONENT_ID + 89; -const ecs_entity_t ecs_id(ecs_i32_t) = ECS_HI_COMPONENT_ID + 90; -const ecs_entity_t ecs_id(ecs_i64_t) = ECS_HI_COMPONENT_ID + 91; -const ecs_entity_t ecs_id(ecs_iptr_t) = ECS_HI_COMPONENT_ID + 92; -const ecs_entity_t ecs_id(ecs_f32_t) = ECS_HI_COMPONENT_ID + 93; -const ecs_entity_t ecs_id(ecs_f64_t) = ECS_HI_COMPONENT_ID + 94; -const ecs_entity_t ecs_id(ecs_string_t) = ECS_HI_COMPONENT_ID + 95; -const ecs_entity_t ecs_id(ecs_entity_t) = ECS_HI_COMPONENT_ID + 96; -const ecs_entity_t EcsConstant = ECS_HI_COMPONENT_ID + 97; -const ecs_entity_t EcsQuantity = ECS_HI_COMPONENT_ID + 98; + /* Variables set in an OR chain are marked as conditional writes. However, + * writes from previous terms in the current OR chain shouldn't be treated + * as variables that are conditionally set, so instead use the write mask + * from before the chain started. */ + if (ctx->ctrlflow->in_or) { + cond_write_state = ctx->ctrlflow->cond_written_or; + } -/* Doc module components */ -const ecs_entity_t ecs_id(EcsDocDescription) =ECS_HI_COMPONENT_ID + 100; -const ecs_entity_t EcsDocBrief = ECS_HI_COMPONENT_ID + 101; -const ecs_entity_t EcsDocDetail = ECS_HI_COMPONENT_ID + 102; -const ecs_entity_t EcsDocLink = ECS_HI_COMPONENT_ID + 103; + /* If this term uses conditionally set variables, insert instruction that + * jumps over the term if the variables weren't set yet. */ + if (cond_mask & cond_write_state) { + ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); -/* REST module components */ -const ecs_entity_t ecs_id(EcsRest) = ECS_HI_COMPONENT_ID + 105; + ecs_query_op_t jmp_op = {0}; + jmp_op.kind = EcsQueryIfVar; -/* Default lookup path */ -static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; + if ((first_var != EcsVarNone) && cond_write_state & (1ull << first_var)) { + jmp_op.flags |= (EcsQueryIsVar << EcsQueryFirst); + jmp_op.first.var = first_var; + } + if ((second_var != EcsVarNone) && cond_write_state & (1ull << second_var)) { + jmp_op.flags |= (EcsQueryIsVar << EcsQuerySecond); + jmp_op.second.var = second_var; + } + if ((src_var != EcsVarNone) && cond_write_state & (1ull << src_var)) { + jmp_op.flags |= (EcsQueryIsVar << EcsQuerySrc); + jmp_op.src.var = src_var; + } -/* -- Private functions -- */ + flecs_query_op_insert(&jmp_op, ctx); + } else { + ctx->cur->lbl_cond_eval = -1; + } +} -const ecs_stage_t* flecs_stage_from_readonly_world( - const ecs_world_t *world) +static +void flecs_query_end_block_cond_eval( + ecs_query_compile_ctx_t *ctx) { - ecs_assert(ecs_poly_is(world, ecs_world_t) || - ecs_poly_is(world, ecs_stage_t), - ECS_INTERNAL_ERROR, - NULL); + if (ctx->cur->lbl_cond_eval == -1) { + return; + } - if (ecs_poly_is(world, ecs_world_t)) { - return &world->stage; + ecs_assert(ctx->cur->lbl_query >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_query_op_t end_op = {0}; + end_op.kind = EcsQueryEnd; + ecs_query_lbl_t end = flecs_query_op_insert(&end_op, ctx); - } else if (ecs_poly_is(world, ecs_stage_t)) { - return (ecs_stage_t*)world; - } - - return NULL; + ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); + ops[ctx->cur->lbl_cond_eval].next = end; + + ecs_query_op_t *end_op_ptr = &ops[end]; + ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; + end_op_ptr->prev = ctx->cur->lbl_cond_eval; + end_op_ptr->src = query_op->src; + end_op_ptr->first = query_op->first; + end_op_ptr->second = query_op->second; + end_op_ptr->flags = query_op->flags; + end_op_ptr->field_index = query_op->field_index; } -ecs_stage_t *flecs_stage_from_world( - ecs_world_t **world_ptr) +static +void flecs_query_begin_block_or( + ecs_query_op_t *op, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) { - ecs_world_t *world = *world_ptr; + ecs_query_op_t *or_op = flecs_query_begin_block(EcsQueryNot, ctx); + or_op->kind = EcsQueryOr; + or_op->field_index = term->field_index; - ecs_assert(ecs_poly_is(world, ecs_world_t) || - ecs_poly_is(world, ecs_stage_t), - ECS_INTERNAL_ERROR, - NULL); + /* Set the source of the evaluate terms as source of the Or instruction. + * This lets the engine determine whether the variable has already been + * written. When the source is not yet written, an OR operation needs to + * take the union of all the terms in the OR chain. When the variable is + * known, it will return after the first matching term. + * + * In case a term in the OR expression is an equality predicate which + * compares the left hand side with a variable, the variable acts as an + * alias, so we can always assume that it's written. */ + bool add_src = true; + if (ECS_TERM_REF_ID(&term->first) == EcsPredEq && term->second.id & EcsIsVariable) { + if (!(flecs_query_is_written(op->src.var, ctx->written))) { + add_src = false; + } + } - if (ecs_poly_is(world, ecs_world_t)) { - ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); - return &world->stage; + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + if (add_src) { + or_op->flags = (EcsQueryIsVar << EcsQuerySrc); + or_op->src = op->src; + ctx->cur->src_or = op->src; + } - } else if (ecs_poly_is(world, ecs_stage_t)) { - ecs_stage_t *stage = (ecs_stage_t*)world; - *world_ptr = stage->world; - return stage; + ctx->cur->src_written_or = flecs_query_is_written( + op->src.var, ctx->written); } - - return NULL; } -ecs_world_t* flecs_suspend_readonly( - const ecs_world_t *stage_world, - ecs_suspend_readonly_state_t *state) +static +void flecs_query_end_block_or( + ecs_query_impl_t *impl, + ecs_query_compile_ctx_t *ctx) { - ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_op_t op = {0}; + op.kind = EcsQueryEnd; + ecs_query_lbl_t end = flecs_query_op_insert(&op, ctx); - ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage_world); - ecs_poly_assert(world, ecs_world_t); + ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); + int32_t i, prev_or = ctx->cur->lbl_begin + 1; + for (i = ctx->cur->lbl_begin + 1; i < end; i ++) { + if (ops[i].next == FlecsRuleOrMarker) { + if (i == (end - 1)) { + ops[prev_or].prev = ctx->cur->lbl_begin; + } else { + ops[prev_or].prev = flecs_itolbl(i + 1); + } - bool is_readonly = world->is_readonly; - bool is_deferred = ecs_is_deferred(world); + ops[i].next = flecs_itolbl(end); - if (!world->is_readonly && !is_deferred) { - state->is_readonly = false; - state->is_deferred = false; - return world; + prev_or = i + 1; + } } - ecs_dbg_3("suspending readonly mode"); + ecs_query_op_t *first = &ops[ctx->cur->lbl_begin]; + bool src_is_var = first->flags & (EcsQueryIsVar << EcsQuerySrc); + first->next = flecs_itolbl(end); + ops[end].prev = ctx->cur->lbl_begin; + ops[end - 1].prev = ctx->cur->lbl_begin; - /* Cannot suspend when running with multiple threads */ - ecs_assert(ecs_get_stage_count(world) <= 1, - ECS_INVALID_WHILE_ITERATING, NULL); + ctx->ctrlflow->in_or = false; + ctx->cur->lbl_begin = -1; + if (src_is_var) { + ecs_var_id_t src_var = first->src.var; + ctx->written |= (1llu << src_var); - state->is_readonly = is_readonly; - state->is_deferred = is_deferred; + /* If src is a table variable, it is possible that this was resolved to + * an entity variable in all of the OR terms. If this is the case, mark + * entity variable as written as well. */ + ecs_query_var_t *var = &impl->vars[src_var]; + if (var->kind == EcsVarTable) { + const char *name = var->name; + if (!name) { + name = "this"; + } - /* Silence readonly checks */ - world->is_readonly = false; + ecs_var_id_t evar = flecs_query_find_var_id( + impl, name, EcsVarEntity); + if (evar != EcsVarNone && (ctx->cond_written & (1llu << evar))) { + ctx->written |= (1llu << evar); + ctx->cond_written &= ~(1llu << evar); + } + } + } + ctx->written |= ctx->cond_written; - /* Hack around safety checks (this ought to look ugly) */ - ecs_world_t *temp_world = world; - ecs_stage_t *stage = flecs_stage_from_world(&temp_world); - state->defer_count = stage->defer; - state->defer_queue = stage->defer_queue; - state->scope = world->stage.scope; - state->with = world->stage.with; - stage->defer = 0; - stage->defer_queue = NULL; + /* Scan which variables were conditionally written in the OR chain and + * reset instructions after the OR chain. If a variable is set in part one + * of a chain but not part two, there would be nothing writing to the + * variable in part two, leaving it to the previous value. To address this + * a reset is inserted that resets the variable value on redo. */ + for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { + ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); + ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); - if (&world->stage != (ecs_stage_t*)stage_world) { - world->stage.scope = stage->scope; - world->stage.with = stage->with; - } - - return world; -} + /* Skip variable if it's the source for the OR chain */ + if (src_is_var && (i == first->src.var)) { + continue; + } -void flecs_resume_readonly( + if (!prev && cur) { + ecs_query_op_t reset_op = {0}; + reset_op.kind = EcsQueryReset; + reset_op.flags |= (EcsQueryIsVar << EcsQuerySrc); + reset_op.src.var = flecs_itovar(i); + flecs_query_op_insert(&reset_op, ctx); + } + } +} + +void flecs_query_insert_each( + ecs_var_id_t tvar, + ecs_var_id_t evar, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_query_op_t each = {0}; + each.kind = EcsQueryEach; + each.src.var = evar; + each.first.var = tvar; + each.flags = (EcsQueryIsVar << EcsQuerySrc) | + (EcsQueryIsVar << EcsQueryFirst); + flecs_query_write_ctx(evar, ctx, cond_write); + flecs_query_write(evar, &each.written); + flecs_query_op_insert(&each, ctx); +} + +static +void flecs_query_insert_lookup( + ecs_var_id_t base_var, + ecs_var_id_t evar, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + ecs_query_op_t lookup = {0}; + lookup.kind = EcsQueryLookup; + lookup.src.var = evar; + lookup.first.var = base_var; + lookup.flags = (EcsQueryIsVar << EcsQuerySrc) | + (EcsQueryIsVar << EcsQueryFirst); + flecs_query_write_ctx(evar, ctx, cond_write); + flecs_query_write(evar, &lookup.written); + flecs_query_op_insert(&lookup, ctx); +} + +static +void flecs_query_insert_unconstrained_transitive( + ecs_query_impl_t *query, + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + /* Create anonymous variable to store the target ids. This will return the + * list of targets without constraining the variable of the term, which + * needs to stay variable to find all transitive relationships for a src. */ + ecs_var_id_t tgt = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); + flecs_set_var_label(&query->vars[tgt], query->vars[op->second.var].name); + + /* First, find ids to start traversal from. This fixes op.second. */ + ecs_query_op_t find_ids = {0}; + find_ids.kind = EcsQueryIdsRight; + find_ids.field_index = -1; + find_ids.first = op->first; + find_ids.second = op->second; + find_ids.flags = op->flags; + find_ids.flags &= (ecs_flags8_t)~((EcsQueryIsVar|EcsQueryIsEntity) << EcsQuerySrc); + find_ids.second.var = tgt; + flecs_query_write_ctx(tgt, ctx, cond_write); + flecs_query_write(tgt, &find_ids.written); + flecs_query_op_insert(&find_ids, ctx); + + /* Next, iterate all tables for the ids. This fixes op.src */ + ecs_query_op_t and_op = {0}; + and_op.kind = EcsQueryAnd; + and_op.field_index = op->field_index; + and_op.first = op->first; + and_op.second = op->second; + and_op.src = op->src; + and_op.flags = op->flags | EcsQueryIsSelf; + and_op.second.var = tgt; + flecs_query_write_ctx(and_op.src.var, ctx, cond_write); + flecs_query_write(and_op.src.var, &and_op.written); + flecs_query_op_insert(&and_op, ctx); +} + +static +void flecs_query_insert_inheritance( + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_query_op_t *op, + ecs_query_compile_ctx_t *ctx, + bool cond_write) +{ + /* Anonymous variable to store the resolved component ids */ + ecs_var_id_t tvar = flecs_query_add_var(query, NULL, NULL, EcsVarTable); + ecs_var_id_t evar = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); + + flecs_set_var_label(&query->vars[tvar], ecs_get_name(query->pub.world, + ECS_TERM_REF_ID(&term->first))); + flecs_set_var_label(&query->vars[evar], ecs_get_name(query->pub.world, + ECS_TERM_REF_ID(&term->first))); + + ecs_query_op_t trav_op = {0}; + trav_op.kind = EcsQueryTrav; + trav_op.field_index = -1; + trav_op.first.entity = EcsIsA; + trav_op.second.entity = ECS_TERM_REF_ID(&term->first); + trav_op.src.var = tvar; + trav_op.flags = EcsQueryIsSelf; + trav_op.flags |= (EcsQueryIsEntity << EcsQueryFirst); + trav_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); + trav_op.flags |= (EcsQueryIsVar << EcsQuerySrc); + trav_op.written |= (1ull << tvar); + if (term->first.id & EcsSelf) { + trav_op.match_flags |= EcsTermReflexive; + } + flecs_query_op_insert(&trav_op, ctx); + flecs_query_insert_each(tvar, evar, ctx, cond_write); + + ecs_query_ref_t r = { .var = evar }; + op->first = r; + op->flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); + op->flags |= (EcsQueryIsVar << EcsQueryFirst); +} + +void flecs_query_compile_term_ref( ecs_world_t *world, - ecs_suspend_readonly_state_t *state) + ecs_query_impl_t *query, + ecs_query_op_t *op, + ecs_term_ref_t *term_ref, + ecs_query_ref_t *ref, + ecs_flags8_t ref_kind, + ecs_var_kind_t kind, + ecs_query_compile_ctx_t *ctx, + bool create_wildcard_vars) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_world_t *temp_world = world; - ecs_stage_t *stage = flecs_stage_from_world(&temp_world); + (void)world; - if (state->is_readonly || state->is_deferred) { - ecs_dbg_3("resuming readonly mode"); - - ecs_force_aperiodic(world); + if (!ecs_term_ref_is_set(term_ref)) { + return; + } - /* Restore readonly state / defer count */ - world->is_readonly = state->is_readonly; - stage->defer = state->defer_count; - stage->defer_queue = state->defer_queue; - world->stage.scope = state->scope; - world->stage.with = state->with; + if (term_ref->id & EcsIsVariable) { + op->flags |= (ecs_flags8_t)(EcsQueryIsVar << ref_kind); + const char *name = flecs_term_ref_var_name(term_ref); + if (name) { + ref->var = flecs_query_most_specific_var(query, name, kind, ctx); + ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } else if (create_wildcard_vars) { + bool is_wildcard = flecs_term_ref_is_wildcard(term_ref); + if (is_wildcard && (kind == EcsVarAny)) { + ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarTable); + } else { + ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); + } + if (is_wildcard) { + flecs_set_var_label(&query->vars[ref->var], + ecs_get_name(world, ECS_TERM_REF_ID(term_ref))); + } + ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); + } + } + + if (term_ref->id & EcsIsEntity) { + op->flags |= (ecs_flags8_t)(EcsQueryIsEntity << ref_kind); + ref->entity = ECS_TERM_REF_ID(term_ref); } } -/* Evaluate component monitor. If a monitored entity changed it will have set a - * flag in one of the world's component monitors. Queries can register - * themselves with component monitors to determine whether they need to rematch - * with tables. */ static -void eval_component_monitor( - ecs_world_t *world) +int flecs_query_compile_ensure_vars( + ecs_query_impl_t *query, + ecs_query_op_t *op, + ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + ecs_query_compile_ctx_t *ctx, + bool cond_write, + bool *written_out) { - ecs_poly_assert(world, ecs_world_t); + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); + bool written = false; - if (!world->monitors.is_dirty) { - return; - } + if (flags & EcsQueryIsVar) { + ecs_var_id_t var_id = ref->var; + ecs_query_var_t *var = &query->vars[var_id]; - world->monitors.is_dirty = false; + if (var->kind == EcsVarEntity && + !flecs_query_is_written(var_id, ctx->written)) + { + /* If entity variable is not yet written but a table variant exists + * that has been written, insert each operation to translate from + * entity variable to table */ + ecs_var_id_t tvar = var->table_id; + if ((tvar != EcsVarNone) && + flecs_query_is_written(tvar, ctx->written)) + { + if (var->lookup) { + if (!flecs_query_is_written(tvar, ctx->written)) { + ecs_err("dependent variable of '$%s' is not written", + var->name); + return -1; + } - ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); - ecs_monitor_t *m; - while ((m = ecs_map_next(&it, ecs_monitor_t, NULL))) { - if (!m->is_dirty) { - continue; + if (!flecs_query_is_written(var->base_id, ctx->written)) { + flecs_query_insert_each( + tvar, var->base_id, ctx, cond_write); + } + } else { + flecs_query_insert_each(tvar, var_id, ctx, cond_write); + } + + /* Variable was written, just not as entity */ + written = true; + } else if (var->lookup) { + if (!flecs_query_is_written(var->base_id, ctx->written)) { + ecs_err("dependent variable of '$%s' is not written", + var->name); + return -1; + } + } } - m->is_dirty = false; + written |= flecs_query_is_written(var_id, ctx->written); + } else { + /* If it's not a variable, it's always written */ + written = true; + } - ecs_vector_each(m->queries, ecs_query_t*, q_ptr, { - flecs_query_notify(world, *q_ptr, &(ecs_query_event_t) { - .kind = EcsQueryTableRematch - }); - }); + if (written_out) { + *written_out = written; } + + return 0; } -void flecs_monitor_mark_dirty( - ecs_world_t *world, - ecs_entity_t id) +static +bool flecs_query_compile_lookup( + ecs_query_impl_t *query, + ecs_var_id_t var_id, + ecs_query_compile_ctx_t *ctx, + bool cond_write) { - ecs_map_t *monitors = &world->monitors.monitors; - - /* Only flag if there are actually monitors registered, so that we - * don't waste cycles evaluating monitors if there's no interest */ - if (ecs_map_is_initialized(monitors)) { - ecs_monitor_t *m = ecs_map_get(monitors, ecs_monitor_t, id); - if (m) { - m->is_dirty = true; - world->monitors.is_dirty = true; - } + ecs_query_var_t *var = &query->vars[var_id]; + if (var->lookup) { + flecs_query_insert_lookup(var->base_id, var_id, ctx, cond_write); + return true; + } else { + return false; } } -void flecs_monitor_register( - ecs_world_t *world, - ecs_entity_t id, - ecs_query_t *query) +static +void flecs_query_insert_contains( + ecs_query_impl_t *query, + ecs_var_id_t src_var, + ecs_var_id_t other_var, + ecs_query_compile_ctx_t *ctx) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_map_t *monitors = &world->monitors.monitors; - - if (!ecs_map_is_initialized(monitors)) { - ecs_map_init(monitors, ecs_monitor_t, 1); + ecs_query_op_t contains = {0}; + if ((src_var != other_var) && (src_var == query->vars[other_var].table_id)) { + contains.kind = EcsQueryContain; + contains.src.var = src_var; + contains.first.var = other_var; + contains.flags |= (EcsQueryIsVar << EcsQuerySrc) | + (EcsQueryIsVar << EcsQueryFirst); + flecs_query_op_insert(&contains, ctx); } - - ecs_monitor_t *m = ecs_map_ensure(monitors, ecs_monitor_t, id); - ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_query_t **q = ecs_vector_add(&m->queries, ecs_query_t*); - *q = query; } -void flecs_monitor_unregister( - ecs_world_t *world, - ecs_entity_t id, - ecs_query_t *query) +static +void flecs_query_insert_pair_eq( + int32_t field_index, + ecs_query_compile_ctx_t *ctx) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_op_t contains = {0}; + contains.kind = EcsQueryPairEq; + contains.field_index = flecs_ito(int8_t, field_index); + flecs_query_op_insert(&contains, ctx); +} - ecs_map_t *monitors = &world->monitors.monitors; +static +int flecs_query_compile_builtin_pred( + ecs_query_t *q, + ecs_term_t *term, + ecs_query_op_t *op, + ecs_write_flags_t write_state) +{ + ecs_entity_t id = ECS_TERM_REF_ID(&term->first); - if (!ecs_map_is_initialized(monitors)) { - return; - } + ecs_query_op_kind_t eq[] = {EcsQueryPredEq, EcsQueryPredNeq}; + ecs_query_op_kind_t eq_name[] = {EcsQueryPredEqName, EcsQueryPredNeqName}; + ecs_query_op_kind_t eq_match[] = {EcsQueryPredEqMatch, EcsQueryPredNeqMatch}; + + ecs_flags16_t flags_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); - ecs_monitor_t *m = ecs_map_get(monitors, ecs_monitor_t, id); - if (!m) { - return; + if (id == EcsPredEq) { + if (term->second.id & EcsIsName) { + op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); + } else { + op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); + } + } else if (id == EcsPredMatch) { + op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); } - int32_t i, count = ecs_vector_count(m->queries); - ecs_query_t **queries = ecs_vector_first(m->queries, ecs_query_t*); - for (i = 0; i < count; i ++) { - if (queries[i] == query) { - ecs_vector_remove(m->queries, ecs_query_t*, i); - count --; - break; + if (flags_2nd & EcsQueryIsVar) { + if (!(write_state & (1ull << op->second.var))) { + ecs_err("uninitialized variable '%s' on right-hand side of " + "equality operator", ecs_query_var_name(q, op->second.var)); + return -1; } } - if (!count) { - ecs_vector_free(m->queries); - ecs_map_remove(monitors, id); - } + ecs_assert(flags_src & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); + (void)flags_src; - if (!ecs_map_count(monitors)) { - ecs_map_fini(monitors); + if (!(write_state & (1ull << op->src.var))) { + /* If this is an == operator with a right-hand side that resolves to a + * single entity, the left-hand side is allowed to be undefined, as the + * instruction will be evaluated as an assignment. */ + if (op->kind != EcsQueryPredEq && op->kind != EcsQueryPredEqName) { + ecs_err("uninitialized variable '%s' on left-hand side of " + "equality operator", ecs_query_var_name(q, op->src.var)); + return -1; + } } + + return 0; } static -void monitors_fini( - ecs_world_t *world) +int flecs_query_ensure_scope_var( + ecs_query_impl_t *query, + ecs_query_op_t *op, + ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + ecs_query_compile_ctx_t *ctx) { - ecs_map_t *monitors = &world->monitors.monitors; + ecs_var_id_t var = ref->var; - ecs_map_iter_t it = ecs_map_iter(monitors); - ecs_monitor_t *m; - while ((m = ecs_map_next(&it, ecs_monitor_t, NULL))) { - ecs_vector_free(m->queries); + if (query->vars[var].kind == EcsVarEntity && + !flecs_query_is_written(var, ctx->written)) + { + ecs_var_id_t table_var = query->vars[var].table_id; + if (table_var != EcsVarNone && + flecs_query_is_written(table_var, ctx->written)) + { + if (flecs_query_compile_ensure_vars( + query, op, ref, ref_kind, ctx, false, NULL)) + { + goto error; + } + } } - ecs_map_fini(monitors); + return 0; +error: + return -1; } static -void init_store( - ecs_world_t *world) +int flecs_query_ensure_scope_vars( + ecs_world_t *world, + ecs_query_impl_t *query, + ecs_query_compile_ctx_t *ctx, + ecs_term_t *term) { - ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); - - /* Initialize entity index */ - flecs_sparse_init(&world->store.entity_index, ecs_record_t); - flecs_sparse_set_id_source(&world->store.entity_index, - &world->info.last_id); + /* If the scope uses variables as entity that have only been written as + * table, resolve them as entities before entering the scope. */ + ecs_term_t *cur = term; + while(ECS_TERM_REF_ID(&cur->first) != EcsScopeClose) { + /* Dummy operation to obtain variable information for term */ + ecs_query_op_t op = {0}; + flecs_query_compile_term_ref(world, query, &op, &cur->first, + &op.first, EcsQueryFirst, EcsVarEntity, ctx, false); + flecs_query_compile_term_ref(world, query, &op, &cur->second, + &op.second, EcsQuerySecond, EcsVarEntity, ctx, false); + + if (op.flags & (EcsQueryIsVar << EcsQueryFirst)) { + if (flecs_query_ensure_scope_var( + query, &op, &op.first, EcsQueryFirst, ctx)) + { + goto error; + } + } - /* Initialize root table */ - flecs_sparse_init(&world->store.tables, ecs_table_t); + if (op.flags & (EcsQueryIsVar << EcsQuerySecond)) { + if (flecs_query_ensure_scope_var( + query, &op, &op.second, EcsQuerySecond, ctx)) + { + goto error; + } + } - /* Initialize table map */ - flecs_table_hashmap_init(&world->store.table_map); + cur ++; + } - /* Initialize one root table per stage */ - flecs_init_root_table(world); + return 0; +error: + return -1; } static -void clean_tables( - ecs_world_t *world) +void flecs_query_compile_push( + ecs_query_compile_ctx_t *ctx) { - int32_t i, count = flecs_sparse_count(&world->store.tables); - - /* Ensure that first table in sparse set has id 0. This is a dummy table - * that only exists so that there is no table with id 0 */ - ecs_table_t *first = flecs_sparse_get_dense(&world->store.tables, - ecs_table_t, 0); - ecs_assert(first->id == 0, ECS_INTERNAL_ERROR, NULL); - (void)first; - - for (i = 1; i < count; i ++) { - ecs_table_t *t = flecs_sparse_get_dense(&world->store.tables, - ecs_table_t, i); - flecs_table_release(world, t); - } - - /* Free table types separately so that if application destructors rely on - * a type it's still valid. */ - for (i = 1; i < count; i ++) { - ecs_table_t *t = flecs_sparse_get_dense(&world->store.tables, - ecs_table_t, i); - flecs_table_free_type(t); - } - - /* Clear the root table */ - if (count) { - flecs_table_reset(world, &world->store.root); - } + ctx->cur = &ctx->ctrlflow[++ ctx->scope]; + ctx->cur->lbl_begin = -1; + ctx->cur->lbl_begin = -1; } static -void fini_store(ecs_world_t *world) { - clean_tables(world); - flecs_sparse_fini(&world->store.tables); - flecs_table_release(world, &world->store.root); - flecs_sparse_clear(&world->store.entity_index); - flecs_hashmap_fini(&world->store.table_map); - - ecs_graph_edge_hdr_t *cur, *next = world->store.first_free; - while ((cur = next)) { - next = cur->next; - ecs_os_free(cur); - } +void flecs_query_compile_pop( + ecs_query_compile_ctx_t *ctx) +{ + /* Should've been caught by query validator */ + ecs_assert(ctx->scope > 0, ECS_INTERNAL_ERROR, NULL); + ctx->cur = &ctx->ctrlflow[-- ctx->scope]; } -/* Implementation for iterable mixin */ static -bool world_iter_next( - ecs_iter_t *it) +int flecs_query_compile_0_src( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) { - if (ECS_BIT_IS_SET(it->flags, EcsIterIsValid)) { - ECS_BIT_CLEAR(it->flags, EcsIterIsValid); - return false; + /* If the term has a 0 source, check if it's a scope open/close */ + if (ECS_TERM_REF_ID(&term->first) == EcsScopeOpen) { + if (flecs_query_ensure_scope_vars(world, impl, ctx, term)) { + goto error; + } + if (term->oper == EcsNot) { + ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); + flecs_query_begin_block(EcsQueryNot, ctx); + } else { + ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); + } + flecs_query_compile_push(ctx); + } else if (ECS_TERM_REF_ID(&term->first) == EcsScopeClose) { + flecs_query_compile_pop(ctx); + if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { + flecs_query_end_block(ctx, false); + } + } else { + /* Noop */ } - ecs_world_t *world = it->real_world; - ecs_sparse_t *entity_index = &world->store.entity_index; - it->entities = (ecs_entity_t*)flecs_sparse_ids(entity_index); - it->count = flecs_sparse_count(entity_index); - flecs_iter_validate(it); - - return true; + return 0; +error: + return -1; } static -void world_iter_init( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) +ecs_flags32_t flecs_query_to_table_flags( + const ecs_query_t *q) { - ecs_poly_assert(poly, ecs_world_t); - (void)poly; + ecs_flags32_t query_flags = q->flags; + if (!(query_flags & EcsQueryMatchDisabled) || + !(query_flags & EcsQueryMatchPrefab)) + { + ecs_flags32_t table_flags = EcsTableNotQueryable; + if (!(query_flags & EcsQueryMatchDisabled)) { + table_flags |= EcsTableIsDisabled; + } + if (!(query_flags & EcsQueryMatchPrefab)) { + table_flags |= EcsTableIsPrefab; + } - if (filter) { - iter[0] = ecs_term_iter(world, filter); - } else { - iter[0] = (ecs_iter_t){ - .world = (ecs_world_t*)world, - .real_world = (ecs_world_t*)ecs_get_world(world), - .next = world_iter_next - }; + return table_flags; } + + return EcsTableNotQueryable; } static -void log_addons(void) { - ecs_trace("addons included in build:"); - ecs_log_push(); - #ifdef FLECS_CPP - ecs_trace("FLECS_CPP"); - #endif - #ifdef FLECS_MODULE - ecs_trace("FLECS_MODULE"); - #endif - #ifdef FLECS_PARSER - ecs_trace("FLECS_PARSER"); - #endif - #ifdef FLECS_PLECS - ecs_trace("FLECS_PLECS"); - #endif - #ifdef FLECS_RULES - ecs_trace("FLECS_RULES"); - #endif - #ifdef FLECS_SNAPSHOT - ecs_trace("FLECS_SNAPSHOT"); - #endif - #ifdef FLECS_STATS - ecs_trace("FLECS_STATS"); - #endif - #ifdef FLECS_SYSTEM - ecs_trace("FLECS_SYSTEM"); - #endif - #ifdef FLECS_PIPELINE - ecs_trace("FLECS_PIPELINE"); - #endif - #ifdef FLECS_TIMER - ecs_trace("FLECS_TIMER"); - #endif - #ifdef FLECS_META - ecs_trace("FLECS_META"); - #endif - #ifdef FLECS_META_C - ecs_trace("FLECS_META_C"); - #endif - #ifdef FLECS_UNITS - ecs_trace("FLECS_UNITS"); - #endif - #ifdef FLECS_EXPR - ecs_trace("FLECS_EXPR"); - #endif - #ifdef FLECS_JSON - ecs_trace("FLECS_JSON"); - #endif - #ifdef FLECS_DOC - ecs_trace("FLECS_DOC"); - #endif - #ifdef FLECS_COREDOC - ecs_trace("FLECS_COREDOC"); - #endif - #ifdef FLECS_LOG - ecs_trace("FLECS_LOG"); - #endif - #ifdef FLECS_APP - ecs_trace("FLECS_APP"); - #endif - #ifdef FLECS_OS_API_IMPL - ecs_trace("FLECS_OS_API_IMPL"); - #endif - #ifdef FLECS_HTTP - ecs_trace("FLECS_HTTP"); - #endif - #ifdef FLECS_REST - ecs_trace("FLECS_REST"); - #endif - ecs_log_pop(); -} +bool flecs_query_select_all( + const ecs_query_t *q, + ecs_term_t *term, + ecs_query_op_t *op, + ecs_var_id_t src_var, + ecs_query_compile_ctx_t *ctx) +{ + bool builtin_pred = flecs_term_is_builtin_pred(term); + bool pred_match = builtin_pred && ECS_TERM_REF_ID(&term->first) == EcsPredMatch; -/* -- Public functions -- */ + if (term->oper == EcsNot || term->oper == EcsOptional || + term->oper == EcsNotFrom || pred_match) + { + ecs_query_op_t match_any = {0}; + match_any.kind = EcsQueryAll; + match_any.flags = EcsQueryIsSelf | (EcsQueryIsEntity << EcsQueryFirst); + match_any.flags |= (EcsQueryIsVar << EcsQuerySrc); + match_any.src = op->src; + match_any.field_index = -1; + if (!pred_match) { + match_any.first.entity = EcsAny; + } else { + /* If matching by name, instead of finding all tables, just find + * the ones with a name. */ + match_any.first.entity = ecs_id(EcsIdentifier); + match_any.second.entity = EcsName; + match_any.flags |= (EcsQueryIsEntity << EcsQuerySecond); + } + match_any.written = (1ull << src_var); + match_any.other = flecs_itolbl(flecs_query_to_table_flags(q)); + flecs_query_op_insert(&match_any, ctx); + flecs_query_write_ctx(op->src.var, ctx, false); + + /* Update write administration */ + return true; + } + return false; +} -ecs_world_t *ecs_mini(void) { -#ifdef FLECS_OS_API_IMPL - ecs_set_os_api_impl(); -#endif - ecs_os_init(); +#ifdef FLECS_META +static +int flecs_query_compile_begin_member_term( + ecs_world_t *world, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_entity_t first_id) +{ + ecs_assert(first_id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(first_id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); - ecs_trace("#[bold]bootstrapping world"); - ecs_log_push(); + first_id = ECS_TERM_REF_ID(&term->first); - ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); + /* First compile as if it's a regular term, to match the component */ + term->flags_ &= (uint16_t)~EcsTermIsMember; - if (!ecs_os_has_heap()) { - ecs_abort(ECS_MISSING_OS_API, NULL); + /* Replace term id with member parent (the component) */ + ecs_entity_t component = ecs_get_parent(world, first_id); + if (!component) { + ecs_err("member without parent in query"); + return -1; } - if (!ecs_os_has_threading()) { - ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); + if (!ecs_has(world, component, EcsComponent)) { + ecs_err("parent of member is not a component"); + return -1; } - if (!ecs_os_has_time()) { - ecs_trace("time management not available"); + bool second_wildcard = + (ECS_TERM_REF_ID(&term->second) == EcsWildcard || + ECS_TERM_REF_ID(&term->second) == EcsAny) && + (term->second.id & EcsIsVariable) && !term->second.name; + + term->first.id = component | ECS_TERM_REF_FLAGS(&term->first); + term->second.id = 0; + term->id = component; + + ctx->oper = (ecs_oper_kind_t)term->oper; + if (term->oper == EcsNot && !second_wildcard) { + /* When matching a member term with not operator, we need to cover both + * the case where an entity doesn't have the component, and where it + * does have the component, but doesn't match the member. */ + term->oper = EcsOptional; } - log_addons(); + return 0; +} -#ifdef FLECS_SANITIZE - ecs_trace("sanitize build, rebuild witohut FLECS_SANITIZE for (much) " - "improved performance"); -#elif defined(FLECS_DEBUG) - ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for improved " - "performance"); -#else - ecs_trace("#[green]release#[reset] build"); -#endif +static +int flecs_query_compile_end_member_term( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_op_t *op, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_id_t term_id, + ecs_entity_t first_id, + ecs_entity_t second_id, + bool cond_write) +{ + ecs_entity_t component = ECS_TERM_REF_ID(&term->first); + const EcsComponent *comp = ecs_get(world, component, EcsComponent); + ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); -#ifdef __clang__ - ecs_trace("compiled with clang %s", __clang_version__); -#elif defined(__GNUC__) - ecs_trace("compiled with gcc %d.%d", __GNUC__, __GNUC_MINOR__); -#elif defined (_MSC_VER) - ecs_trace("compiled with msvc %d", _MSC_VER); -#endif + /* Restore term values */ + term->id = term_id; + term->first.id = first_id; + term->second.id = second_id; + term->flags_ |= EcsTermIsMember; + term->oper = flecs_ito(int16_t, ctx->oper); + + first_id = ECS_TERM_REF_ID(&term->first); + const EcsMember *member = ecs_get(world, first_id, EcsMember); + ecs_assert(member != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_var_t *var = &impl->vars[op->src.var]; + const char *var_name = flecs_term_ref_var_name(&term->src); + ecs_var_id_t evar = flecs_query_find_var_id( + impl, var_name, EcsVarEntity); + + bool second_wildcard = + (ECS_TERM_REF_ID(&term->second) == EcsWildcard || + ECS_TERM_REF_ID(&term->second) == EcsAny) && + (term->second.id & EcsIsVariable) && !term->second.name; - ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); - ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_poly_init(world, ecs_world_t); + if (term->oper == EcsOptional) { + second_wildcard = true; + } - world->self = world; - world->type_info = flecs_sparse_new(ecs_type_info_t); - ecs_map_init(&world->id_index, ecs_id_record_t*, ECS_HI_COMPONENT_ID); - flecs_observable_init(&world->observable); - world->iterable.init = world_iter_init; + ecs_query_op_t mbr_op = *op; + mbr_op.kind = EcsQueryMemberEq; + mbr_op.first.entity = /* Encode type size and member offset */ + flecs_ito(uint32_t, member->offset) | + (flecs_ito(uint64_t, comp->size) << 32); - world->queries = flecs_sparse_new(ecs_query_t); - world->triggers = flecs_sparse_new(ecs_trigger_t); - world->observers = flecs_sparse_new(ecs_observer_t); - - world->pending_tables = flecs_sparse_new(ecs_table_t*); - world->pending_buffer = flecs_sparse_new(ecs_table_t*); + /* If this is a term with a Not operator, conditionally evaluate member on + * whether term was set by previous operation (see begin_member_term). */ + if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { + if (second_wildcard && ctx->oper == EcsNot) { + /* A !(T.value, *) term doesn't need special operations */ + return 0; + } - world->fini_tasks = ecs_vector_new(ecs_entity_t, 0); - flecs_name_index_init(&world->aliases); - flecs_name_index_init(&world->symbols); - ecs_map_init(&world->type_handles, ecs_entity_t, 0); + /* Resolve to entity variable before entering if block, so that we + * don't have different branches of the query working with different + * versions of the same variable. */ + if (var->kind == EcsVarTable) { + flecs_query_insert_each(op->src.var, evar, ctx, cond_write); + var = &impl->vars[evar]; + } - world->info.time_scale = 1.0; + ecs_query_op_t *if_op = flecs_query_begin_block(EcsQueryIfSet, ctx); + if_op->other = term->field_index; - if (ecs_os_has_time()) { - ecs_os_get_time(&world->world_start_time); + if (ctx->oper == EcsNot) { + mbr_op.kind = EcsQueryMemberNeq; + } } - flecs_stage_init(world, &world->stage); - ecs_set_stages(world, 1); + if (var->kind == EcsVarTable) { + /* If MemberEq is called on table variable, store it on .other member. + * This causes MemberEq to do double duty as 'each' instruction, + * which is faster than having to go back & forth between instructions + * while finding matching values. */ + mbr_op.other = flecs_itolbl(op->src.var + 1); - ecs_default_lookup_path[0] = EcsFlecsCore; - ecs_set_lookup_path(world, ecs_default_lookup_path); + /* Mark entity variable as written */ + flecs_query_write_ctx(evar, ctx, cond_write); + flecs_query_write(evar, &mbr_op.written); + } - init_store(world); - ecs_trace("table store initialized"); + flecs_query_compile_term_ref(world, impl, &mbr_op, &term->src, + &mbr_op.src, EcsQuerySrc, EcsVarEntity, ctx, true); - flecs_bootstrap(world); + if (second_wildcard) { + mbr_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); + mbr_op.second.entity = EcsWildcard; + } else { + flecs_query_compile_term_ref(world, impl, &mbr_op, &term->second, + &mbr_op.second, EcsQuerySecond, EcsVarEntity, ctx, true); + + if (term->second.id & EcsIsVariable) { + if (flecs_query_compile_ensure_vars(impl, &mbr_op, &mbr_op.second, + EcsQuerySecond, ctx, cond_write, NULL)) + { + goto error; + } - ecs_trace("world ready!"); - ecs_log_pop(); + flecs_query_write_ctx(mbr_op.second.var, ctx, cond_write); + flecs_query_write(mbr_op.second.var, &mbr_op.written); + } + } - return world; -} + flecs_query_op_insert(&mbr_op, ctx); -ecs_world_t *ecs_init(void) { - ecs_world_t *world = ecs_mini(); + if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { + flecs_query_end_block(ctx, false); + } -#ifdef FLECS_MODULE_H - ecs_trace("#[bold]import addons"); - ecs_log_push(); - ecs_trace("use ecs_mini to create world without importing addons"); -#ifdef FLECS_SYSTEM - ECS_IMPORT(world, FlecsSystem); -#endif -#ifdef FLECS_PIPELINE - ECS_IMPORT(world, FlecsPipeline); -#endif -#ifdef FLECS_TIMER - ECS_IMPORT(world, FlecsTimer); -#endif -#ifdef FLECS_META - ECS_IMPORT(world, FlecsMeta); -#endif -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); -#endif -#ifdef FLECS_COREDOC - ECS_IMPORT(world, FlecsCoreDoc); -#endif -#ifdef FLECS_REST - ECS_IMPORT(world, FlecsRest); -#endif -#ifdef FLECS_UNITS - ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); -#endif - ecs_trace("addons imported!"); - ecs_log_pop(); -#endif - return world; + return 0; +error: + return -1; +} +#else +static +int flecs_query_compile_begin_member_term( + ecs_world_t *world, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_entity_t first_id) +{ + (void)world; (void)term; (void)ctx; (void)first_id; + return 0; } -#define ARG(short, long, action)\ - if (i < argc) {\ - if (argv[i][0] == '-') {\ - if (argv[i][1] == '-') {\ - if (long && !strcmp(&argv[i][2], long ? long : "")) {\ - action;\ - parsed = true;\ - }\ - } else {\ - if (short && argv[i][1] == short) {\ - action;\ - parsed = true;\ - }\ - }\ - }\ - } +static +int flecs_query_compile_end_member_term( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_op_t *op, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx, + ecs_id_t term_id, + ecs_entity_t first_id, + ecs_entity_t second_id, + bool cond_write) +{ + (void)world; (void)impl; (void)op; (void)term; (void)ctx; (void)term_id; + (void)first_id; (void)second_id; (void)cond_write; + return 0; +} +#endif -ecs_world_t* ecs_init_w_args( - int argc, - char *argv[]) +static +void flecs_query_mark_last_or_op( + ecs_query_compile_ctx_t *ctx) { - ecs_world_t *world = ecs_init(); + ecs_query_op_t *op_ptr = ecs_vec_last_t(ctx->ops, ecs_query_op_t); + op_ptr->next = FlecsRuleOrMarker; +} - (void)argc; - (void) argv; +static +void flecs_query_set_op_kind( + ecs_query_impl_t *query, + ecs_query_op_t *op, + ecs_term_t *term, + bool src_is_var) +{ + (void)query; -#ifdef FLECS_DOC - if (argc) { - char *app = argv[0]; - char *last_elem = strrchr(app, '/'); - if (!last_elem) { - last_elem = strrchr(app, '\\'); - } - if (last_elem) { - app = last_elem + 1; + /* Default instruction for And operators. If the source is fixed (like for + * singletons or terms with an entity source), use With, which like And but + * just matches against a source (vs. finding a source). */ + op->kind = src_is_var ? EcsQueryAnd : EcsQueryWith; + + /* Ignore cascade flag */ + ecs_entity_t trav_flags = EcsTraverseFlags & ~(EcsCascade|EcsDesc); + + /* Handle *From operators */ + if (term->oper == EcsAndFrom) { + op->kind = EcsQueryAndFrom; + } else if (term->oper == EcsOrFrom) { + op->kind = EcsQueryOrFrom; + } else if (term->oper == EcsNotFrom) { + op->kind = EcsQueryNotFrom; + + /* If query is transitive, use Trav(ersal) instruction */ + } else if (term->flags_ & EcsTermTransitive) { + ecs_assert(ecs_term_ref_is_set(&term->second), + ECS_INTERNAL_ERROR, NULL); + op->kind = EcsQueryTrav; + + /* Handle non-fragmenting components */ + } else if (term->flags_ & EcsTermDontFragment) { + if (op->kind == EcsQueryAnd) { + op->kind = EcsQuerySparse; + if (term->oper == EcsNot) { + op->kind = EcsQuerySparseNot; + } + } else { + op->kind = EcsQuerySparseWith; } - ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); - } -#endif - return world; -} + if ((term->src.id & trav_flags) == EcsUp) { + op->kind = EcsQuerySparseUp; + } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { + op->kind = EcsQuerySparseSelfUp; + } + } else { + if ((term->src.id & trav_flags) == EcsUp) { + op->kind = EcsQueryUp; + if (term->trav == EcsChildOf) { + if (term->flags_ & EcsTermIsCacheable && query->cache) { + op->kind = EcsQueryTreeUpPost; + } else if (query->pub.flags & EcsQueryNested) { + op->kind = EcsQueryTreeUpPre; + } + } + } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { + op->kind = EcsQuerySelfUp; + if (term->trav == EcsChildOf) { + if (term->flags_ & EcsTermIsCacheable && query->cache) { + op->kind = EcsQueryTreeSelfUpPost; + } else if (query->pub.flags & EcsQueryNested) { + op->kind = EcsQueryTreeSelfUpPre; + } + } + } else if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { + op->kind = EcsQueryAndAny; + } else if (ECS_IS_PAIR(term->id) && + ECS_PAIR_FIRST(term->id) == EcsWildcard) + { + if (op->kind == EcsQueryAnd) { + op->kind = EcsQueryAndWcTgt; + } else { + op->kind = EcsQueryWithWcTgt; + } + } -void ecs_quit( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_stage_from_world(&world); - world->should_quit = true; -error: - return; -} + /* ChildOf terms need to take into account both ChildOf pairs and the + * Parent component for non-fragmenting hierarchies. */ + if (term->flags_ & EcsTermNonFragmentingChildOf && !term->trav) { + if (query->pub.flags & EcsQueryNested) { + /* If this is a nested query (used to populate a cache), insert + * instruction that matches tables with ChildOf pairs and Parent + * component, without filtering the non-fragmenting parents as + * this cannot be cached. */ + op->kind = EcsQueryTreePre; + } else { + if (!src_is_var) { + op->kind = EcsQueryTreeWith; + } else { + if (op->kind == EcsQueryAnd) { + if (ECS_PAIR_SECOND(term->id) == EcsWildcard) { + op->kind = EcsQueryTreeWildcard; + } else { + op->kind = EcsQueryTree; + } -bool ecs_should_quit( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return world->should_quit; -error: - return true; + if (term->flags_ & EcsTermIsCacheable) { + if (query->cache) { + op->kind = EcsQueryTreePost; + } + } + } else if (op->kind == EcsQueryAndAny) { + if (ECS_PAIR_SECOND(term->id)) { + op->kind = EcsQueryTreeWildcard; + } else { + /* If it's a (ChildOf, 0) query then we don't need to + * evaluate it as a wildcard. */ + } + } + } + } + } + } } -void flecs_notify_tables( +int flecs_query_compile_term( ecs_world_t *world, - ecs_id_t id, - ecs_table_event_t *event) -{ - ecs_poly_assert(world, ecs_world_t); + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_query_compile_ctx_t *ctx) +{ + ecs_id_t term_id = term->id; + ecs_entity_t first_id = term->first.id; + ecs_entity_t second_id = term->second.id; + bool toggle_term = (term->flags_ & EcsTermIsToggle) != 0; + bool member_term = (term->flags_ & EcsTermIsMember) != 0; + if (member_term) { + flecs_query_compile_begin_member_term(world, term, ctx, first_id); + } + + ecs_query_t *q = &query->pub; + bool first_term = term == q->terms; + bool first_is_var = term->first.id & EcsIsVariable; + bool second_is_var = term->second.id & EcsIsVariable; + bool src_is_var = term->src.id & EcsIsVariable; + bool src_is_wildcard = src_is_var && + (ECS_TERM_REF_ID(&term->src) == EcsWildcard || + ECS_TERM_REF_ID(&term->src) == EcsAny); + bool src_is_lookup = false; + bool builtin_pred = flecs_term_is_builtin_pred(term); + bool is_optional = (term->oper == EcsOptional); + bool is_or = flecs_term_is_or(q, term); + bool first_or = false, last_or = false; + bool cond_write = term->oper == EcsOptional || is_or; + ecs_query_op_t op = {0}; + + if (is_or) { + first_or = first_term || (term[-1].oper != EcsOr); + last_or = term->oper != EcsOr; + } + + if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || + term->oper == EcsNotFrom) + { + const ecs_type_t *type = ecs_get_type(world, term->id); + if (!type) { + /* Empty type for id in *From operation is a noop */ + ctx->skipped ++; + return 0; + } + + int32_t i, count = type->count; + ecs_id_t *ti_ids = type->array; - /* If no id is specified, broadcast to all tables */ - if (!id) { - ecs_sparse_t *tables = &world->store.tables; - int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); - flecs_table_notify(world, table, event); + ecs_id_t ti_id = ti_ids[i]; + ecs_component_record_t *cr = flecs_components_get(world, ti_id); + if (!(cr->flags & EcsIdOnInstantiateDontInherit)) { + break; + } } - /* If id is specified, only broadcast to tables with id */ - } else { - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return; + if (i == count) { + /* Type did not contain any ids to perform operation on */ + ctx->skipped ++; + return 0; } + } - ecs_table_cache_iter_t it; - const ecs_table_record_t *tr; + /* !_ (don't match anything) terms always return nothing. */ + if (term->oper == EcsNot && term->id == EcsAny) { + op.kind = EcsQueryNothing; + flecs_query_op_insert(&op, ctx); + return 0; + } - flecs_table_cache_iter(&idr->cache, &it); - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - flecs_table_notify(world, tr->hdr.table, event); + if (first_or) { + ctx->ctrlflow->cond_written_or = ctx->cond_written; + ctx->ctrlflow->in_or = true; + } else if (is_or) { + ctx->written = ctx->ctrlflow->written_or; + } + + if (!ECS_TERM_REF_ID(&term->src) && term->src.id & EcsIsEntity) { + if (flecs_query_compile_0_src(world, query, term, ctx)) { + goto error; } + return 0; + } - flecs_table_cache_empty_iter(&idr->cache, &it); - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - flecs_table_notify(world, tr->hdr.table, event); + if (builtin_pred) { + ecs_entity_t id_noflags = ECS_TERM_REF_ID(&term->second); + if (id_noflags == EcsWildcard || id_noflags == EcsAny) { + /* Noop */ + return 0; } } -} -void ecs_default_ctor( - void *ptr, - int32_t count, - const ecs_type_info_t *ti) -{ - ecs_os_memset(ptr, 0, ti->size * count); -} + op.field_index = flecs_ito(int8_t, term->field_index); + op.term_index = flecs_ito(int8_t, term - q->terms); -static -void default_copy_ctor(void *dst_ptr, const void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - const EcsComponentLifecycle *cl = &ti->lifecycle; - cl->ctor(dst_ptr, count, ti); - cl->copy(dst_ptr, src_ptr, count, ti); -} + flecs_query_set_op_kind(query, &op, term, src_is_var); -static -void default_move_ctor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - const EcsComponentLifecycle *cl = &ti->lifecycle; - cl->ctor(dst_ptr, count, ti); - cl->move(dst_ptr, src_ptr, count, ti); -} + bool is_not = (term->oper == EcsNot) && !builtin_pred; + if (op.kind == EcsQuerySparseNot) { + is_not = false; + } -static -void default_ctor_w_move_w_dtor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - const EcsComponentLifecycle *cl = &ti->lifecycle; - cl->ctor(dst_ptr, count, ti); - cl->move(dst_ptr, src_ptr, count, ti); - cl->dtor(src_ptr, count, ti); -} + /* Save write state at start of term so we can use it to reliably track + * variables got written by this term. */ + ecs_write_flags_t cond_write_state = ctx->cond_written; -static -void default_move_ctor_w_dtor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - const EcsComponentLifecycle *cl = &ti->lifecycle; - cl->move_ctor(dst_ptr, src_ptr, count, ti); - cl->dtor(src_ptr, count, ti); -} + /* Resolve variables and entities for operation arguments */ + flecs_query_compile_term_ref(world, query, &op, &term->first, + &op.first, EcsQueryFirst, EcsVarEntity, ctx, true); + flecs_query_compile_term_ref(world, query, &op, &term->second, + &op.second, EcsQuerySecond, EcsVarEntity, ctx, true); + flecs_query_compile_term_ref(world, query, &op, &term->src, + &op.src, EcsQuerySrc, EcsVarAny, ctx, true); -static -void default_move(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - const EcsComponentLifecycle *cl = &ti->lifecycle; - cl->move(dst_ptr, src_ptr, count, ti); -} + bool src_written = true; + if (src_is_var) { + src_is_lookup = query->vars[op.src.var].lookup != NULL; + src_written = flecs_query_is_written(op.src.var, ctx->written); + } -static -void default_dtor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - /* When there is no move, destruct the destination component & memcpy the - * component to dst. The src component does not have to be destructed when - * a component has a trivial move. */ - const EcsComponentLifecycle *cl = &ti->lifecycle; - cl->dtor(dst_ptr, count, ti); - ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); -} + /* Insert each instructions for table -> entity variable if needed */ + bool first_written, second_written; + if (flecs_query_compile_ensure_vars( + query, &op, &op.first, EcsQueryFirst, ctx, cond_write, &first_written)) + { + goto error; + } -static -void default_move_w_dtor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - /* If a component has a move, the move will take care of memcpying the data - * and destroying any data in dst. Because this is not a trivial move, the - * src component must also be destructed. */ - const EcsComponentLifecycle *cl = &ti->lifecycle; - cl->move(dst_ptr, src_ptr, count, ti); - cl->dtor(src_ptr, count, ti); -} + if (flecs_query_compile_ensure_vars( + query, &op, &op.second, EcsQuerySecond, ctx, cond_write, &second_written)) + { + goto error; + } -void ecs_set_component_actions_w_id( - ecs_world_t *world, - ecs_entity_t component, - EcsComponentLifecycle *lifecycle) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_stage_from_world(&world); + /* Store write state of variables for first OR term in chain which will get + * restored for the other terms in the chain, so that all OR terms make the + * same assumptions about which variables were already written. */ + if (first_or) { + ctx->ctrlflow->written_or = ctx->written; + } - ecs_type_info_t *ti = flecs_ensure_type_info(world, component); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + /* If an optional or not term is inserted for a source that's not been + * written to yet, insert instruction that selects all entities so we have + * something to match the optional/not against. */ + if (src_is_var && !src_written && !src_is_wildcard && !src_is_lookup) { + src_written = flecs_query_select_all(q, term, &op, op.src.var, ctx); + } - ecs_size_t size = ti->size; - ecs_size_t alignment = ti->alignment; + /* A bit of special logic for OR expressions and equality predicates. If the + * left-hand of an equality operator is a table, and there are multiple + * operators in an Or expression, the Or chain should match all entities in + * the table that match the right hand sides of the operator expressions. + * For this to work, the src variable needs to be resolved as entity, as an + * Or chain would otherwise only yield the first match from a table. */ + if (src_is_var && src_written && (builtin_pred || member_term) && term->oper == EcsOr) { + if (query->vars[op.src.var].kind == EcsVarTable) { + flecs_query_compile_term_ref(world, query, &op, &term->src, + &op.src, EcsQuerySrc, EcsVarEntity, ctx, true); + ctx->ctrlflow->written_or |= (1llu << op.src.var); + } + } - if (!size) { - const EcsComponent *component_ptr = ecs_get( - world, component, EcsComponent); + if (flecs_query_compile_ensure_vars( + query, &op, &op.src, EcsQuerySrc, ctx, cond_write, NULL)) + { + goto error; + } - /* Cannot register lifecycle actions for things that aren't a component */ - ecs_check(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - /* Cannot register lifecycle actions for components with size 0 */ - ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL); + /* If source is Any (_) and first and/or second are unconstrained, insert an + * ids instruction instead of an And */ + if (term->flags_ & EcsTermMatchAnySrc) { + op.kind = EcsQueryIds; + /* Use up-to-date written values after potentially inserting each */ + if (!first_written || !second_written) { + if (!first_written) { + /* If first is unknown, traverse left: <- (*, t) */ + if (ECS_TERM_REF_ID(&term->first) != EcsAny) { + op.kind = EcsQueryIdsLeft; + } + } else { + /* If second is wildcard, traverse right: (r, *) -> */ + if (ECS_TERM_REF_ID(&term->second) != EcsAny) { + op.kind = EcsQueryIdsRight; + } + } + op.src.entity = 0; + src_is_var = false; + op.flags &= (ecs_flags8_t)~(EcsQueryIsVar << EcsQuerySrc); /* ids has no src */ + op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQuerySrc); + } + + /* If source variable is not written and we're querying just for Any, insert + * a dedicated instruction that uses the Any record in the id index. Any + * queries that are evaluated against written sources can use Wildcard + * records, which is what the AndAny instruction does. */ + } else if (!src_written && term->id == EcsAny && op.kind == EcsQueryAndAny) { + /* Lookup variables ($var.child_name) are always written */ + if (!src_is_lookup) { + op.kind = EcsQueryAll; /* Uses Any (_) component record */ + } + } + + /* If this is a transitive term and both the target and source are unknown, + * find the targets for the relationship first. This clusters together + * tables for the same target, which allows for more efficient usage of the + * traversal caches. */ + if (term->flags_ & EcsTermTransitive && src_is_var && second_is_var) { + if (!src_written && !second_written) { + flecs_query_insert_unconstrained_transitive( + query, &op, ctx, cond_write); + } + } + + /* Check if this term has variables that have been conditionally written, + * like variables written by an optional term. */ + if (ctx->cond_written) { + if (!is_or || first_or) { + flecs_query_begin_block_cond_eval(&op, ctx, cond_write_state); + } + } + + /* If term can toggle and is Not, change operator to Optional as we + * have to match entities that have the component but disabled. */ + if (toggle_term && is_not) { + is_not = false; + is_optional = true; + } + + /* Handle Not, Optional, Or operators */ + if (is_not) { + flecs_query_begin_block(EcsQueryNot, ctx); + } else if (is_optional) { + flecs_query_begin_block(EcsQueryOptional, ctx); + } else if (first_or) { + flecs_query_begin_block_or(&op, term, ctx); + } + + /* If term has component inheritance enabled, insert instruction to walk + * down the relationship tree of the id. */ + if (term->flags_ & EcsTermIdInherited) { + flecs_query_insert_inheritance(query, term, &op, ctx, cond_write); + } - size = component_ptr->size; - alignment = component_ptr->alignment; + op.match_flags = term->flags_; + + ecs_write_flags_t write_state = ctx->written; + if (first_is_var) { + op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); + op.flags |= (EcsQueryIsVar << EcsQueryFirst); } - if (ti->lifecycle_set) { - ecs_assert(ti->component == component, ECS_INTERNAL_ERROR, NULL); - ecs_check(!lifecycle->ctor || ti->lifecycle.ctor == lifecycle->ctor, - ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - ecs_check(!lifecycle->dtor || ti->lifecycle.dtor == lifecycle->dtor, - ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - ecs_check(!lifecycle->copy || ti->lifecycle.copy == lifecycle->copy, - ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - ecs_check(!lifecycle->move || ti->lifecycle.move == lifecycle->move, - ECS_INCONSISTENT_COMPONENT_ACTION, NULL); + if (term->src.id & EcsSelf) { + op.flags |= EcsQueryIsSelf; + } - if (!ti->lifecycle.on_set) { - ti->lifecycle.on_set = lifecycle->on_set; + /* Insert instructions for lookup variables */ + if (first_is_var) { + if (flecs_query_compile_lookup(query, op.first.var, ctx, cond_write)) { + write_state |= (1ull << op.first.var); // lookups are resolved inline } - if (!ti->lifecycle.on_remove) { - ti->lifecycle.on_remove = lifecycle->on_remove; + } + if (src_is_var) { + if (flecs_query_compile_lookup(query, op.src.var, ctx, cond_write)) { + write_state |= (1ull << op.src.var); // lookups are resolved inline } - } else { - ti->component = component; - ti->lifecycle = *lifecycle; - ti->lifecycle_set = true; - ti->size = size; - ti->alignment = alignment; + } + if (second_is_var) { + if (flecs_query_compile_lookup(query, op.second.var, ctx, cond_write)) { + write_state |= (1ull << op.second.var); // lookups are resolved inline + } + } - /* If no constructor is set, invoking any of the other lifecycle actions - * is not safe as they will potentially access uninitialized memory. For - * ease of use, if no constructor is specified, set a default one that - * initializes the component to 0. */ - if (!lifecycle->ctor && - (lifecycle->dtor || lifecycle->copy || lifecycle->move)) - { - ti->lifecycle.ctor = ecs_default_ctor; + if (builtin_pred) { + if (flecs_query_compile_builtin_pred(q, term, &op, write_state)) { + goto error; + } + } + + /* If we're writing the $this variable, filter out disabled/prefab entities + * unless the query explicitly matches them. + * This could've been done with regular With instructions, but since + * filtering out disabled/prefab entities is the default and this check is + * cheap to perform on table flags, it's worth special casing. */ + if (!src_written && op.src.var == 0) { + op.other = flecs_itolbl(flecs_query_to_table_flags(q)); + } + + /* After evaluating a term, a used variable is always written */ + if (src_is_var) { + flecs_query_write(op.src.var, &op.written); + flecs_query_write_ctx(op.src.var, ctx, cond_write); + } + if (first_is_var) { + flecs_query_write(op.first.var, &op.written); + flecs_query_write_ctx(op.first.var, ctx, cond_write); + } + if (second_is_var) { + flecs_query_write(op.second.var, &op.written); + flecs_query_write_ctx(op.second.var, ctx, cond_write); + } + + flecs_query_op_insert(&op, ctx); + + ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); + if (is_or && !member_term) { + flecs_query_mark_last_or_op(ctx); + } + + /* Handle self-references between src and first/second variables */ + if (src_is_var) { + if (first_is_var) { + flecs_query_insert_contains(query, op.src.var, op.first.var, ctx); + } + if (second_is_var && op.first.var != op.second.var) { + flecs_query_insert_contains(query, op.src.var, op.second.var, ctx); } + } - /* Set default copy ctor, move ctor and merge */ - if (lifecycle->copy && !lifecycle->copy_ctor) { - ti->lifecycle.copy_ctor = default_copy_ctor; + /* Handle self references between first and second variables */ + if (!ecs_id_is_wildcard(first_id)) { + if (first_is_var && !first_written && (op.first.var == op.second.var)) { + flecs_query_insert_pair_eq(term->field_index, ctx); } + } - if (lifecycle->move && !lifecycle->move_ctor) { - ti->lifecycle.move_ctor = default_move_ctor; - } + /* Handle closing of Not, Optional and Or operators */ + if (is_not) { + flecs_query_end_block(ctx, true); + } else if (is_optional) { + flecs_query_end_block(ctx, true); + } - if (!lifecycle->ctor_move_dtor) { - if (lifecycle->move) { - if (lifecycle->dtor) { - if (lifecycle->move_ctor) { - /* If an explicit move ctor has been set, use callback - * that uses the move ctor vs. using a ctor+move */ - ti->lifecycle.ctor_move_dtor = - default_move_ctor_w_dtor; - } else { - /* If no explicit move_ctor has been set, use - * combination of ctor + move + dtor */ - ti->lifecycle.ctor_move_dtor = - default_ctor_w_move_w_dtor; - } - } else { - /* If no dtor has been set, this is just a move ctor */ - ti->lifecycle.ctor_move_dtor = - ti->lifecycle.move_ctor; - } - } + /* Now that the term is resolved, evaluate member of component */ + if (member_term) { + flecs_query_compile_end_member_term(world, query, &op, term, ctx, + term_id, first_id, second_id, cond_write); + if (is_or) { + flecs_query_mark_last_or_op(ctx); } + } - if (!lifecycle->move_dtor) { - if (lifecycle->move) { - if (lifecycle->dtor) { - ti->lifecycle.move_dtor = default_move_w_dtor; - } else { - ti->lifecycle.move_dtor = default_move; - } - } else { - if (lifecycle->dtor) { - ti->lifecycle.move_dtor = default_dtor; - } - } + if (last_or) { + flecs_query_end_block_or(query, ctx); + } + + /* Handle closing of conditional evaluation */ + if (ctx->cur->lbl_cond_eval && (first_is_var || second_is_var || src_is_var)) { + if (!is_or || last_or) { + flecs_query_end_block_cond_eval(ctx); } + } - /* Ensure that no tables have yet been created for the component */ - ecs_assert( ecs_id_in_use(world, component) == false, - ECS_ALREADY_IN_USE, ecs_get_name(world, component)); - ecs_assert( ecs_id_in_use(world, - ecs_pair(component, EcsWildcard)) == false, - ECS_ALREADY_IN_USE, ecs_get_name(world, component)); + /* Ensure that term id is set after evaluating Not */ + if (term->flags_ & EcsTermIdInherited) { + if (is_not) { + ecs_query_op_t set_id = {0}; + set_id.kind = EcsQuerySetId; + set_id.first.entity = term->id; + set_id.flags = (EcsQueryIsEntity << EcsQueryFirst); + set_id.field_index = flecs_ito(int8_t, term->field_index); + flecs_query_op_insert(&set_id, ctx); + } } -error: - return; -} -bool ecs_component_has_actions( - const ecs_world_t *world, - ecs_entity_t component) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(component != 0, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - const ecs_type_info_t *ti = flecs_get_type_info(world, component); - return (ti != NULL) && ti->lifecycle_set; + return 0; error: - return false; + return -1; } -void ecs_atfini( - ecs_world_t *world, - ecs_fini_action_t action, - void *ctx) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_action_elem_t *elem = ecs_vector_add(&world->fini_actions, - ecs_action_elem_t); - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); +/** + * @file query/engine/eval.c + * @brief Query engine implementation. + */ - elem->action = action; - elem->ctx = ctx; -error: - return; -} -void ecs_run_post_frame( - ecs_world_t *world, - ecs_fini_action_t action, - void *ctx) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_action_elem_t *elem = ecs_vector_add(&stage->post_frame_actions, - ecs_action_elem_t); - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); +// #define FLECS_QUERY_TRACE - elem->action = action; - elem->ctx = ctx; -error: - return; -} +#ifdef FLECS_QUERY_TRACE +static int flecs_query_trace_indent = 0; +#endif -/* Unset data in tables */ static -void fini_unset_tables( - ecs_world_t *world) -{ - ecs_sparse_t *tables = &world->store.tables; - int32_t i, count = flecs_sparse_count(tables); - - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense(tables, ecs_table_t, i); - flecs_table_remove_actions(world, table); - } -} +bool flecs_query_dispatch( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); -/* Invoke fini actions */ -static -void fini_actions( - ecs_world_t *world) +bool flecs_query_select_w_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_id_t id, + ecs_flags32_t filter_mask) { - ecs_vector_each(world->fini_actions, ecs_action_elem_t, elem, { - elem->action(world, elem->ctx); - }); + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_component_record_t *cr = op_ctx->cr; + const ecs_table_record_t *tr; + ecs_table_t *table; - ecs_vector_free(world->fini_actions); -} + if (!redo) { + if (!cr || cr->id != id) { + cr = op_ctx->cr = flecs_components_get(ctx->world, id); + if (!cr) { + return false; + } + } -/* Cleanup component lifecycle callbacks & systems */ -static -void fini_component_lifecycle( - ecs_world_t *world) -{ - flecs_sparse_free(world->type_info); -} + if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&cr->cache, &op_ctx->it)) { + return false; + } + } else { + if (!flecs_table_cache_iter(&cr->cache, &op_ctx->it)) { + return false; + } + } + } -/* Cleanup queries */ -static -void fini_queries( - ecs_world_t *world) -{ - monitors_fini(world); - - int32_t i, count = flecs_sparse_count(world->queries); - for (i = 0; i < count; i ++) { - ecs_query_t *query = flecs_sparse_get_dense(world->queries, ecs_query_t, 0); - ecs_query_fini(query); +repeat: + if (!redo || (op_ctx->remaining <= 0)) { + tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } + + op_ctx->column = flecs_ito(int16_t, tr->index); + op_ctx->remaining = flecs_ito(int16_t, tr->count - 1); + table = tr->hdr.table; + flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); + } else { + tr = (const ecs_table_record_t*)op_ctx->it.cur; + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + table = tr->hdr.table; + op_ctx->column = flecs_query_next_column(table, cr->id, op_ctx->column); + op_ctx->remaining --; } - flecs_sparse_free(world->queries); -} -static -void fini_observers( - ecs_world_t *world) -{ - flecs_sparse_free(world->observers); -} + if (flecs_query_table_filter(table, op->other, filter_mask)) { + goto repeat; + } -/* Cleanup stages */ -static -void fini_stages( - ecs_world_t *world) -{ - flecs_stage_deinit(world, &world->stage); - ecs_set_stages(world, 0); + flecs_query_set_match(op, table, op_ctx->column, ctx); + return true; } -ecs_entity_t flecs_get_oneof( - const ecs_world_t *world, - ecs_entity_t e) +bool flecs_query_select( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - if (ecs_has_id(world, e, EcsOneOf)) { - return e; - } else { - return ecs_get_object(world, e, EcsOneOf, 0); + ecs_id_t id = 0; + if (!redo) { + id = flecs_query_op_get_id(op, ctx); } + return flecs_query_select_w_id(op, redo, ctx, id, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } -static -ecs_id_record_t* new_id_record( - ecs_world_t *world, - ecs_id_t id) +bool flecs_query_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_id_record_t *idr = ecs_os_calloc_t(ecs_id_record_t); - ecs_table_cache_init(&idr->cache); + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_component_record_t *cr = op_ctx->cr; + const ecs_table_record_t *tr; - ecs_entity_t rel = 0, obj = 0; - if (ECS_HAS_ROLE(id, PAIR)) { - rel = ecs_pair_first(world, id); - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); + if (!table) { + return false; + } - /* Relation object can be 0, as tables without a ChildOf relation are - * added to the (ChildOf, 0) id record */ - obj = ECS_PAIR_SECOND(id); - if (obj) { - obj = ecs_get_alive(world, obj); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - } - - /* If id is a pair, inherit flags from relation id record */ - ecs_id_record_t *idr_r = flecs_get_id_record( - world, ECS_PAIR_FIRST(id)); - if (idr_r) { - idr->flags = (idr_r->flags & ~ECS_TYPE_INFO_INITIALIZED); + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (!cr || cr->id != id) { + cr = op_ctx->cr = flecs_components_get(ctx->world, id); + if (!cr) { + return false; + } } - /* Check constraints */ - if (obj && !ecs_id_is_wildcard(obj)) { - ecs_entity_t oneof = flecs_get_oneof(world, rel); - ecs_check( !oneof || ecs_has_pair(world, obj, EcsChildOf, oneof), - ECS_CONSTRAINT_VIOLATED, NULL); - (void)oneof; + tr = flecs_component_get_table(cr, table); + if (!tr) { + return false; } + op_ctx->column = flecs_ito(int16_t, tr->index); + op_ctx->remaining = flecs_ito(int16_t, tr->count); + op_ctx->it.cur = &tr->hdr; } else { - rel = id & ECS_COMPONENT_MASK; - rel = ecs_get_alive(world, rel); - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - } - - /* Mark entities that are used as component/pair ids. When a tracked - * entity is deleted, cleanup policies are applied so that the store - * won't contain any tables with deleted ids. */ - - /* Flag for OnDelete policies */ - flecs_add_flag(world, rel, ECS_FLAG_OBSERVED_ID); - if (obj) { - /* Flag for OnDeleteObject policies */ - flecs_add_flag(world, obj, ECS_FLAG_OBSERVED_OBJECT); - if (ecs_has_id(world, rel, EcsAcyclic)) { - /* Flag used to determine if object should be traversed when - * propagating events or with super/subset queries */ - flecs_add_flag(world, obj, ECS_FLAG_OBSERVED_ACYCLIC); + ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(op_ctx->remaining >= 0, ECS_INTERNAL_ERROR, NULL); + if (--op_ctx->remaining <= 0) { + return false; } - } - if (ecs_should_log_1()) { - char *id_str = ecs_id_str(world, id); - ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str); - ecs_os_free(id_str); + op_ctx->column = flecs_query_next_column(table, cr->id, op_ctx->column); + ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); } - /* Update counters */ - world->info.id_create_total ++; - - if (!ecs_id_is_wildcard(id)) { - world->info.id_count ++; + flecs_query_set_match(op, table, op_ctx->column, ctx); + return true; +} - /* if id is component, attaching type info will update counters */ - world->info.tag_id_count ++; +static +bool flecs_query_all( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + int8_t field_index = op->field_index; + ecs_iter_t *it = ctx->it; - if (ECS_HAS_ROLE(id, PAIR)) { - world->info.pair_id_count ++; + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + if (field_index != -1) { + it->ids[field_index] = EcsWildcard; } + return !redo; } else { - world->info.wildcard_id_count ++; - } + ecs_query_all_ctx_t *op_ctx = flecs_op_ctx(ctx, all); + ecs_world_t *world = ctx->world; + ecs_sparse_t *tables = &world->store.tables; + bool match_empty = ctx->query->pub.flags & EcsQueryMatchEmptyTables; + ecs_table_t *table; - return idr; -error: - return NULL; -} + if (!redo) { + op_ctx->cur = 0; + op_ctx->dummy_tr.column = -1; + op_ctx->dummy_tr.index = -1; + op_ctx->dummy_tr.count = 0; + op_ctx->dummy_tr.hdr.cr = NULL; + if (field_index != -1) { + it->ids[field_index] = EcsWildcard; + it->trs[field_index] = &op_ctx->dummy_tr; + } + table = &world->store.root; + } else if (op_ctx->cur < flecs_sparse_count(tables)) { + table = flecs_sparse_get_dense_t( + tables, ecs_table_t, op_ctx->cur); + } else { + return false; + } +repeat: + op_ctx->cur ++; -/* Cleanup id index */ -static -bool free_id_record( - ecs_world_t *world, - ecs_id_t id, - ecs_id_record_t *idr) -{ - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_poly_assert(world, ecs_world_t); - (void)id; + if (match_empty || ecs_table_count(table)) { + if (!flecs_query_table_filter(table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + op_ctx->dummy_tr.hdr.table = table; + flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); + return true; + } + } - /* Force the empty table administration to be consistent if the non-empty - * list of the id record has elements */ - if (ecs_table_cache_count(&idr->cache)) { - ecs_force_aperiodic(world); - } + if (op_ctx->cur < flecs_sparse_count(tables)) { + table = flecs_sparse_get_dense_t( + tables, ecs_table_t, op_ctx->cur); + goto repeat; + } - /* If there are still tables in the non-empty list they're really not empty. - * We can't free the record yet. */ - if (ecs_table_cache_count(&idr->cache)) { return false; } +} - /* If id record contains no more empty tables, free it */ - if (ecs_table_cache_empty_count(&idr->cache) == 0) { - if (ecs_should_log_1()) { - char *id_str = ecs_id_str(world, id); - ecs_dbg_1("#[green]id#[normal] %s #[red]deleted", id_str); - ecs_os_free(id_str); - } - - /* Update counters */ - world->info.id_delete_total ++; +bool flecs_query_and( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_with(op, redo, ctx); + } else { + return flecs_query_select(op, redo, ctx); + } +} - if (!ecs_id_is_wildcard(id)) { - world->info.id_count --; +bool flecs_query_select_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_flags32_t table_filter) +{ + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_iter_t *it = ctx->it; + int8_t field = op->field_index; + ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); - if (ECS_HAS_ROLE(id, PAIR)) { - world->info.pair_id_count --; + if (!redo) { + ecs_id_t id = it->ids[field]; + ecs_component_record_t *cr = op_ctx->cr; + if (!cr || cr->id != id) { + cr = op_ctx->cr = flecs_components_get(ctx->world, id); + if (!cr) { + return false; } + } - if (idr->type_info) { - world->info.component_id_count --; - } else { - world->info.tag_id_count --; + if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&cr->cache, &op_ctx->it)) { + return false; } } else { - world->info.wildcard_id_count --; + if (!flecs_table_cache_iter(&cr->cache, &op_ctx->it)) { + return false; + } } - - ecs_table_cache_fini(&idr->cache); - flecs_name_index_free(idr->name_index); - ecs_os_free(idr); - return true; } - /* Delete empty tables */ - ecs_table_cache_iter_t cache_it; - flecs_table_cache_empty_iter(&idr->cache, &cache_it); - - const ecs_table_record_t *tr; - int32_t unreleased_count = 0; - while ((tr = flecs_table_cache_next(&cache_it, ecs_table_record_t))) { - if (!flecs_table_release(world, tr->hdr.table)) { - /* Releasing the table did not free it, which means that something - * is keeping the table alive. Cleanup of the id record will happen - * when the last reference(s) to the table are released */ - unreleased_count ++; - } - } - if (unreleased_count) { +repeat: {} + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { return false; } - /* If all tables were deleted for this id record, the last deleted table - * should have removed the record from the world. */ - ecs_assert(flecs_get_id_record(world, id) == NULL, - ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = tr->hdr.table; + if (flecs_query_table_filter(table, op->other, table_filter)) { + goto repeat; + } + flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); + flecs_query_it_set_tr(it, field, tr); return true; } -static -void fini_id_index( - ecs_world_t *world) +bool flecs_query_with_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_map_iter_t it = ecs_map_iter(&world->id_index); - ecs_id_record_t *idr; - ecs_map_key_t key; - while ((idr = ecs_map_next_ptr(&it, ecs_id_record_t*, &key))) { - free_id_record(world, key, idr); + if (redo) { + return false; } - ecs_map_fini(&world->id_index); - flecs_sparse_free(world->pending_tables); - flecs_sparse_free(world->pending_buffer); -} + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + ecs_iter_t *it = ctx->it; + int8_t field = op->field_index; + ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); -/* Cleanup misc structures */ -static -void fini_misc( - ecs_world_t *world) -{ - ecs_map_fini(&world->type_handles); - ecs_vector_free(world->fini_tasks); -} + ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -/* The destroyer of worlds */ -int ecs_fini( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(!world->is_readonly, ECS_INVALID_OPERATION, NULL); - ecs_assert(!world->is_fini, ECS_INVALID_OPERATION, NULL); + ecs_id_t id = it->ids[field]; + ecs_component_record_t *cr = op_ctx->cr; + if (!cr || cr->id != id) { + cr = op_ctx->cr = flecs_components_get(ctx->world, id); + if (!cr) { + return false; + } + } - ecs_trace("#[bold]shutting down world"); - ecs_log_push(); + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + return false; + } - world->is_fini = true; + flecs_query_it_set_tr(it, field, tr); + return true; +} - /* Operations invoked during UnSet/OnRemove/destructors are deferred and - * will be discarded after world cleanup */ - ecs_defer_begin(world); +bool flecs_query_and_any( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_flags16_t match_flags = op->match_flags; + if (redo) { + if (match_flags & EcsTermMatchAnySrc) { + return false; + } + } - /* Run UnSet/OnRemove actions for components while the store is still - * unmodified by cleanup. */ - fini_unset_tables(world); - - /* Run fini actions (simple callbacks ran when world is deleted) before - * destroying the storage */ - fini_actions(world); + uint64_t written = ctx->written[ctx->op_index]; + int32_t remaining = 1; + bool result; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + result = flecs_query_with(op, redo, ctx); + } else { + result = flecs_query_select(op, redo, ctx); + remaining = 0; + } - /* This will destroy all entities and components. After this point no more - * user code is executed. */ - fini_store(world); + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - /* Purge deferred operations from the queue. This discards operations but - * makes sure that any resources in the queue are freed */ - flecs_defer_purge(world, &world->stage); + if (match_flags & EcsTermMatchAny && op_ctx->remaining) { + op_ctx->remaining = flecs_ito(int16_t, remaining); + } - /* Entity index is kept alive until this point so that user code can do - * validity checks on entity ids, even though after store cleanup the index - * will be empty, so all entity ids are invalid. */ - flecs_sparse_fini(&world->store.entity_index); - - if (world->locking_enabled) { - ecs_os_mutex_free(world->mutex); + int32_t field = op->field_index; + if (field != -1) { + ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); } - ecs_trace("table store deinitialized"); + ctx->it->trs[field] = (const ecs_table_record_t*)op_ctx->it.cur; + + return result; +} - fini_stages(world); +static +bool flecs_query_and_wctgt( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + if (!redo) { + op_ctx->non_fragmenting = false; + } + + bool sparse_redo = true; - fini_component_lifecycle(world); + if (!op_ctx->non_fragmenting) { + bool result = flecs_query_and(op, redo, ctx); + if (result) { + return true; + } - fini_queries(world); + ecs_component_record_t *cr = op_ctx->cr; - fini_observers(world); + if (!cr) { + return false; + } - fini_id_index(world); + if (!(cr->flags & EcsIdMatchDontFragment) && + (cr->id != ecs_pair(EcsWildcard, EcsWildcard))) + { + return false; + } - flecs_observable_fini(&world->observable); + op_ctx->non_fragmenting = true; + sparse_redo = false; + } - flecs_sparse_free(world->triggers); + return flecs_query_sparse(op, sparse_redo, ctx); +} - flecs_name_index_fini(&world->aliases); - flecs_name_index_fini(&world->symbols); +static +bool flecs_query_with_wctgt( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); + if (!redo) { + op_ctx->non_fragmenting = false; + } - fini_misc(world); + bool sparse_redo = true; - ecs_os_enable_high_timer_resolution(false); + if (!op_ctx->non_fragmenting) { + bool result = flecs_query_with(op, redo, ctx); + if (result) { + return true; + } - /* End of the world */ - ecs_poly_free(world, ecs_world_t); + if (!op_ctx->cr) { + return false; + } - ecs_os_fini(); + if (!(op_ctx->cr->flags & EcsIdMatchDontFragment)) { + return false; + } - ecs_trace("world destroyed, bye!"); - ecs_log_pop(); + op_ctx->non_fragmenting = true; + sparse_redo = false; + } - return 0; + return flecs_query_sparse_with(op, sparse_redo, ctx, false); } -bool ecs_is_fini( - const ecs_world_t *world) +static +bool flecs_query_triv( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return world->is_fini; + ecs_query_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); + ecs_flags64_t termset = op->src.entity; + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + flecs_query_set_iter_this(ctx->it, ctx); + return flecs_query_trivial_test(ctx, redo, termset); + } else { + return flecs_query_trivial_search(ctx, op_ctx, redo, termset); + } } -void ecs_dim( - ecs_world_t *world, - int32_t entity_count) +static +bool flecs_query_cache( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); - ecs_eis_set_size(world, entity_count + ECS_HI_COMPONENT_ID); -} + (void)op; + (void)redo; -void flecs_eval_component_monitors( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - flecs_process_pending_tables(world); - eval_component_monitor(world); + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + return flecs_query_cache_test(ctx, redo); + } else { + return flecs_query_cache_search(ctx); + } } -void ecs_measure_frame_time( - ecs_world_t *world, - bool enable) +static +bool flecs_query_is_cache( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + (void)op; - if (world->info.target_fps == 0.0f || enable) { - world->measure_frame_time = enable; + uint64_t written = ctx->written[ctx->op_index]; + ctx->written[ctx->op_index + 1] |= 1ull; + if (written & 1ull) { + return flecs_query_is_cache_test(ctx, redo); + } else { + return flecs_query_is_cache_search(ctx); } -error: - return; } -void ecs_measure_system_time( +static +int32_t flecs_query_next_inheritable_id( ecs_world_t *world, - bool enable) + ecs_type_t *type, + int32_t index) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); - world->measure_system_time = enable; -error: - return; + int32_t i; + for (i = index; i < type->count; i ++) { + ecs_component_record_t *cr = flecs_components_get(world, type->array[i]); + if (!(cr->flags & EcsIdOnInstantiateDontInherit)) { + return i; + } + } + return -1; } -void ecs_set_target_fps( - ecs_world_t *world, - FLECS_FLOAT fps) +static +bool flecs_query_x_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_oper_kind_t oper) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); + ecs_query_xfrom_ctx_t *op_ctx = flecs_op_ctx(ctx, xfrom); + ecs_world_t *world = ctx->world; + ecs_type_t *type; + ecs_entity_t type_id; + int32_t i; - ecs_measure_frame_time(world, true); - world->info.target_fps = fps; - ecs_os_enable_high_timer_resolution(fps >= 60.0f); -error: - return; -} + if (!redo) { + /* Find entity that acts as the template from which we match the ids */ + type_id = flecs_query_op_get_id(op, ctx); + op_ctx->type_id = type_id; + ecs_assert(ecs_is_alive(world, type_id), ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, type_id); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -void* ecs_get_context( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return world->context; -error: - return NULL; -} + /* Find first id to test against. Skip ids with DontInherit flag. */ + type = op_ctx->type = &table->type; + op_ctx->first_id_index = flecs_query_next_inheritable_id( + world, type, 0); + op_ctx->cur_id_index = op_ctx->first_id_index; -void ecs_set_context( - ecs_world_t *world, - void *context) -{ - ecs_poly_assert(world, ecs_world_t); - world->context = context; -} + if (op_ctx->cur_id_index == -1) { + return false; /* No ids to filter on */ + } + } else { + type_id = op_ctx->type_id; + type = op_ctx->type; + } -void ecs_set_entity_range( - ecs_world_t *world, - ecs_entity_t id_start, - ecs_entity_t id_end) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_check(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); - ecs_check(!id_end || id_end > world->info.last_id, - ECS_INVALID_PARAMETER, NULL); + ecs_id_t *ids = type->array; - if (world->info.last_id < id_start) { - world->info.last_id = id_start - 1; + /* Check if source is variable, and if it's already written */ + bool src_written = true; + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + uint64_t written = ctx->written[ctx->op_index]; + src_written = written & (1ull << op->src.var); } - world->info.min_id = id_start; - world->info.max_id = id_end; -error: - return; -} - -bool ecs_enable_range_check( - ecs_world_t *world, - bool enable) -{ - ecs_poly_assert(world, ecs_world_t); - bool old_value = world->range_check_enabled; - world->range_check_enabled = enable; - return old_value; -} + do { + int32_t id_index = op_ctx->cur_id_index; -void ecs_set_entity_generation( - ecs_world_t *world, - ecs_entity_t entity_with_generation) -{ - flecs_sparse_set_generation( - &world->store.entity_index, entity_with_generation); -} + /* If source is not yet written, find tables with first id */ + if (!src_written) { + ecs_entity_t first_id = ids[id_index]; -int32_t ecs_get_threads( - ecs_world_t *world) -{ - return ecs_vector_count(world->worker_stages); -} + if (!flecs_query_select_w_id(op, redo, ctx, + first_id, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + if (oper == EcsOrFrom) { + id_index = flecs_query_next_inheritable_id( + world, type, id_index + 1); + if (id_index != -1) { + op_ctx->cur_id_index = id_index; + redo = false; + continue; + } + } -bool ecs_enable_locking( - ecs_world_t *world, - bool enable) -{ - ecs_poly_assert(world, ecs_world_t); + return false; + } - if (enable) { - if (!world->locking_enabled) { - world->mutex = ecs_os_mutex_new(); - world->thr_sync = ecs_os_mutex_new(); - world->thr_cond = ecs_os_cond_new(); + id_index ++; /* First id got matched */ + } else if (redo && src_written) { + return false; } - } else { - if (world->locking_enabled) { - ecs_os_mutex_free(world->mutex); - ecs_os_mutex_free(world->thr_sync); - ecs_os_cond_free(world->thr_cond); + + ecs_table_t *src_table = flecs_query_get_table( + op, &op->src, EcsQuerySrc, ctx); + if (!src_table) { + continue; } - } - bool old = world->locking_enabled; - world->locking_enabled = enable; - return old; -} + redo = true; -void ecs_lock( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); - ecs_os_mutex_lock(world->mutex); -} + if (!src_written && oper == EcsOrFrom) { + /* Eliminate duplicate matches from tables that have multiple + * components from the type list */ + if (op_ctx->cur_id_index != op_ctx->first_id_index) { + for (i = op_ctx->first_id_index; i < op_ctx->cur_id_index; i ++) { + ecs_component_record_t *cr = flecs_components_get(world, ids[i]); + if (!cr) { + continue; + } -void ecs_unlock( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); - ecs_os_mutex_unlock(world->mutex); -} + if (cr->flags & EcsIdOnInstantiateDontInherit) { + continue; + } + + if (flecs_component_get_table(cr, src_table) != NULL) { + /* Already matched */ + break; + } + } + if (i != op_ctx->cur_id_index) { + continue; + } + } + goto match; + } -void ecs_begin_wait( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); - ecs_os_mutex_lock(world->thr_sync); - ecs_os_cond_wait(world->thr_cond, world->thr_sync); -} + if (oper == EcsAndFrom || oper == EcsNotFrom || src_written) { + for (i = id_index; i < type->count; i ++) { + ecs_component_record_t *cr = flecs_components_get(world, ids[i]); + if (!cr) { + if (oper == EcsAndFrom) { + return false; + } else { + continue; + } + } -void ecs_end_wait( - ecs_world_t *world) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(world->locking_enabled, ECS_INVALID_PARAMETER, NULL); - ecs_os_mutex_unlock(world->thr_sync); -} + if (cr->flags & EcsIdOnInstantiateDontInherit) { + continue; + } -const ecs_type_info_t* flecs_get_type_info( - const ecs_world_t *world, - ecs_entity_t component) -{ - ecs_poly_assert(world, ecs_world_t); + if (flecs_component_get_table(cr, src_table) == NULL) { + if (oper == EcsAndFrom) { + break; /* Must have all ids */ + } + } else { + if (oper == EcsNotFrom) { + break; /* Must have none of the ids */ + } else if (oper == EcsOrFrom) { + goto match; /* Single match is enough */ + } + } + } - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!(component & ECS_ROLE_MASK), ECS_INTERNAL_ERROR, NULL); + if (i == type->count) { + if (oper == EcsAndFrom || oper == EcsNotFrom) { + break; /* All ids matched */ + } + } + } + } while (true); - return flecs_sparse_get(world->type_info, ecs_type_info_t, component); +match: + ctx->it->ids[op->field_index] = type_id; + return true; } -ecs_type_info_t* flecs_ensure_type_info( - ecs_world_t *world, - ecs_entity_t component) +static +bool flecs_query_and_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - - const ecs_type_info_t *ti = flecs_get_type_info(world, component); - ecs_type_info_t *ti_mut = NULL; - if (!ti) { - ti_mut = flecs_sparse_ensure( - world->type_info, ecs_type_info_t, component); - ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); - } else { - ti_mut = (ecs_type_info_t*)ti; - } - - return ti_mut; + return flecs_query_x_from(op, redo, ctx, EcsAndFrom); } -void flecs_init_type_info( - ecs_world_t *world, - ecs_entity_t component, - ecs_size_t size, - ecs_size_t alignment) +static +bool flecs_query_not_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_type_info_t *ti = flecs_ensure_type_info(world, component); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ti->size == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ti->alignment == 0, ECS_INTERNAL_ERROR, NULL); - ti->size = size; - ti->alignment = alignment; + return flecs_query_x_from(op, redo, ctx, EcsNotFrom); } static -FLECS_FLOAT insert_sleep( - ecs_world_t *world, - ecs_time_t *stop) +bool flecs_query_or_from( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); - - ecs_time_t start = *stop; - FLECS_FLOAT delta_time = (FLECS_FLOAT)ecs_time_measure(stop); - - if (world->info.target_fps == (FLECS_FLOAT)0.0) { - return delta_time; - } - - FLECS_FLOAT target_delta_time = - ((FLECS_FLOAT)1.0 / (FLECS_FLOAT)world->info.target_fps); + return flecs_query_x_from(op, redo, ctx, EcsOrFrom); +} - /* Calculate the time we need to sleep by taking the measured delta from the - * previous frame, and subtracting it from target_delta_time. */ - FLECS_FLOAT sleep = target_delta_time - delta_time; +static +bool flecs_query_ids_check( + ecs_component_record_t *cur) +{ + if (!cur->cache.tables.count) { + if (!(cur->flags & EcsIdOrderedChildren)) { + return false; + } - /* Pick a sleep interval that is 4 times smaller than the time one frame - * should take. */ - FLECS_FLOAT sleep_time = sleep / (FLECS_FLOAT)4.0; + ecs_assert(cur->pair != NULL, ECS_INTERNAL_ERROR, NULL); - do { - /* Only call sleep when sleep_time is not 0. On some platforms, even - * a sleep with a timeout of 0 can cause stutter. */ - if (sleep_time != 0) { - ecs_sleepf((double)sleep_time); + if (!ecs_vec_count(&cur->pair->ordered_children)) { + return false; } + } - ecs_time_t now = start; - delta_time = (FLECS_FLOAT)ecs_time_measure(&now); - } while ((target_delta_time - delta_time) > - (sleep_time / (FLECS_FLOAT)2.0)); - - return delta_time; + return true; } static -FLECS_FLOAT start_measure_frame( - ecs_world_t *world, - FLECS_FLOAT user_delta_time) +bool flecs_query_ids( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); + if (redo) { + return false; + } - FLECS_FLOAT delta_time = 0; + ecs_component_record_t *cur; + ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (world->measure_frame_time || (user_delta_time == 0)) { - ecs_time_t t = world->frame_start_time; - do { - if (world->frame_start_time.nanosec || world->frame_start_time.sec){ - delta_time = insert_sleep(world, &t); + { + cur = flecs_components_get(ctx->world, id); + if (!cur) { + return false; + } - ecs_time_measure(&t); - } else { - ecs_time_measure(&t); - if (world->info.target_fps != 0) { - delta_time = (FLECS_FLOAT)1.0 / world->info.target_fps; - } else { - /* Best guess */ - delta_time = (FLECS_FLOAT)1.0 / (FLECS_FLOAT)60.0; - } - } - - /* Keep trying while delta_time is zero */ - } while (delta_time == 0); + if (!flecs_query_ids_check(cur)) { + return false; + } + } - world->frame_start_time = t; + flecs_query_set_vars(op, cur->id, ctx); - /* Keep track of total time passed in world */ - world->info.world_time_total_raw += (FLECS_FLOAT)delta_time; + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + it->trs[op->field_index] = NULL; /* Mark field as set */ } - return (FLECS_FLOAT)delta_time; + return true; } static -void stop_measure_frame( - ecs_world_t* world) +bool flecs_query_idsright( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); + ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); + ecs_component_record_t *cur; + ecs_iter_t *it = ctx->it; - if (world->measure_frame_time) { - ecs_time_t t = world->frame_start_time; - world->info.frame_time_total += (FLECS_FLOAT)ecs_time_measure(&t); + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + cur = op_ctx->cur = flecs_components_get(ctx->world, id); + if (!ecs_id_is_wildcard(id)) { + /* If id is not a wildcard, we can directly return it. This can + * happen if a variable was constrained by an iterator. */ + op_ctx->cur = NULL; + flecs_query_set_vars(op, id, ctx); + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); + return flecs_query_ids_check(cur); + } + + if (!cur) { + return false; + } + } else { + if (!op_ctx->cur) { + return false; + } } -} -FLECS_FLOAT ecs_frame_begin( - ecs_world_t *world, - FLECS_FLOAT user_delta_time) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_check(world->is_readonly == false, ECS_INVALID_OPERATION, NULL); - ecs_check(user_delta_time != 0 || ecs_os_has_time(), - ECS_MISSING_OS_API, "get_time"); +next: + do { + cur = op_ctx->cur = flecs_component_first_next(op_ctx->cur); + } while (cur && !flecs_query_ids_check(cur)); /* Skip empty ids */ - if (world->locking_enabled) { - ecs_lock(world); + if (!cur) { + return false; } - /* Start measuring total frame time */ - FLECS_FLOAT delta_time = start_measure_frame(world, user_delta_time); - if (user_delta_time == 0) { - user_delta_time = delta_time; - } - - world->info.delta_time_raw = user_delta_time; - world->info.delta_time = user_delta_time * world->info.time_scale; + if (cur->id == ecs_pair(EcsChildOf, 0)) { + /* Skip the special (ChildOf, 0) entry for root entities, as 0 is + * not a valid target and could be matched by (ChildOf, *) */ + goto next; + } - /* Keep track of total scaled time passed in world */ - world->info.world_time_total += world->info.delta_time; + flecs_query_set_vars(op, cur->id, ctx); - ecs_force_aperiodic(world); + if (op->field_index != -1) { + ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); + } - return world->info.delta_time; -error: - return (FLECS_FLOAT)0; + return true; } -void ecs_frame_end( - ecs_world_t *world) +static +bool flecs_query_idsleft( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(world->is_readonly == false, ECS_INVALID_OPERATION, NULL); + ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); + ecs_component_record_t *cur; - world->info.frame_count_total ++; + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (!ecs_id_is_wildcard(id)) { + /* If id is not a wildcard, we can directly return it. This can + * happen if a variable was constrained by an iterator. */ + op_ctx->cur = NULL; + flecs_query_set_vars(op, id, ctx); + return true; + } - ecs_vector_each(world->worker_stages, ecs_stage_t, stage, { - flecs_stage_merge_post_frame(world, stage); - }); + cur = op_ctx->cur = flecs_components_get(ctx->world, id); + if (!cur) { + return false; + } + } else { + if (!op_ctx->cur) { + return false; + } + } - if (world->locking_enabled) { - ecs_unlock(world); + do { + cur = op_ctx->cur = flecs_component_second_next(op_ctx->cur); + } while (cur && !cur->cache.tables.count); /* Skip empty ids */ - ecs_os_mutex_lock(world->thr_sync); - ecs_os_cond_broadcast(world->thr_cond); - ecs_os_mutex_unlock(world->thr_sync); + if (!cur) { + return false; } - stop_measure_frame(world); -error: - return; -} + flecs_query_set_vars(op, cur->id, ctx); -const ecs_world_info_t* ecs_get_world_info( - const ecs_world_t *world) -{ - world = ecs_get_world(world); - return &world->info; + if (op->field_index != -1) { + ecs_iter_t *it = ctx->it; + ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); + it->ids[op->field_index] = id; + it->sources[op->field_index] = EcsWildcard; + ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); + } + + return true; } -void flecs_notify_queries( - ecs_world_t *world, - ecs_query_event_t *event) +static +bool flecs_query_each( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); + ecs_query_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); + int32_t row; - int32_t i, count = flecs_sparse_count(world->queries); - for (i = 0; i < count; i ++) { - ecs_query_t *query = flecs_sparse_get_dense( - world->queries, ecs_query_t, i); - if (query->flags & EcsQueryIsSubquery) { - continue; + ecs_table_range_t range = flecs_query_var_get_range(op->first.var, ctx); + ecs_table_t *table = range.table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (!redo) { + if (!ecs_table_count(table)) { + return false; } - - flecs_query_notify(world, query, event); - } + row = op_ctx->row = range.offset; + } else { + int32_t end = range.count; + if (end) { + end += range.offset; + } else { + end = ecs_table_count(table); + } + row = ++ op_ctx->row; + if (op_ctx->row >= end) { + return false; + } + } + + ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = ecs_table_entities(table); + flecs_query_var_set_entity(op, op->src.var, entities[row], ctx); + + return true; } -void flecs_delete_table( - ecs_world_t *world, - ecs_table_t *table) +static +bool flecs_query_store( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); - flecs_table_release(world, table); + if (!redo) { + flecs_query_var_set_entity(op, op->src.var, op->first.entity, ctx); + return true; + } else { + return false; + } } -/** Walk over tables that had a state change which requires bookkeeping */ -void flecs_process_pending_tables( - const ecs_world_t *world_r) +static +bool flecs_query_reset( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world_r, ecs_world_t); - - /* We can't update the administration while in readonly mode, but we can - * ensure that when this function is called there are no pending events. */ - if (world_r->is_readonly) { - ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, - ECS_INTERNAL_ERROR, NULL); - return; + if (!redo) { + return true; + } else { + flecs_query_var_reset(op->src.var, ctx); + return false; } +} - /* Safe to cast, world is not readonly */ - ecs_world_t *world = (ecs_world_t*)world_r; - - /* If pending buffer is NULL there already is a stackframe that's iterating - * the table list. This can happen when a trigger for a table event results - * in a mutation that causes another table to change state. A typical - * example of this is a system that becomes active/inactive as the result of - * a query (and as a result, its matched tables) becoming empty/non empty */ - if (!world->pending_buffer) { - return; +static +bool flecs_query_lookup( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; } - /* Swap buffer. The logic could in theory have been implemented with a - * single sparse set, but that would've complicated (and slowed down) the - * iteration. Additionally, by using a double buffer approach we can still - * keep most of the original ordering of events intact, which is desirable - * as it means that the ordering of tables in the internal datastructures is - * more predictable. */ - int32_t i, count = flecs_sparse_count(world->pending_tables); - if (!count) { - return; + const ecs_query_impl_t *query = ctx->query; + ecs_entity_t first = flecs_query_var_get_entity(op->first.var, ctx); + ecs_query_var_t *var = &query->vars[op->src.var]; + + ecs_entity_t result = ecs_lookup_path_w_sep(ctx->world, first, var->lookup, + NULL, NULL, false); + if (!result) { + flecs_query_var_set_entity(op, op->src.var, EcsWildcard, ctx); + return false; } - do { - ecs_sparse_t *pending_tables = world->pending_tables; - world->pending_tables = world->pending_buffer; - world->pending_buffer = NULL; + flecs_query_var_set_entity(op, op->src.var, result, ctx); - for (i = 0; i < count; i ++) { - ecs_table_t *table = flecs_sparse_get_dense( - pending_tables, ecs_table_t*, i)[0]; - if (!table->id) { - /* Table is being deleted, ignore empty events */ - continue; - } + return true; +} - /* For each id in the table, add it to the empty/non empty list - * based on its current state */ - if (flecs_table_records_update_empty(table)) { - /* Only emit an event when there was a change in the - * administration. It is possible that a table ended up in the - * pending_tables list by going from empty->non-empty, but then - * became empty again. By the time we run this code, no changes - * in the administration would actually be made. */ - ecs_ids_t ids = { - .array = ecs_vector_first(table->type, ecs_id_t), - .count = ecs_vector_count(table->type) - }; +static +bool flecs_query_setvars( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + (void)op; - int32_t table_count = ecs_table_count(table); + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + ecs_var_id_t *src_vars = query->src_vars; + ecs_iter_t *it = ctx->it; - flecs_emit(world, world, &(ecs_event_desc_t) { - .event = table_count - ? EcsOnTableFill - : EcsOnTableEmpty - , - .table = table, - .ids = &ids, - .observable = world, - .table_event = true - }); + if (redo) { + return false; + } - world->info.empty_table_count += (table_count == 0) * 2 - 1; - } + int32_t i; + ecs_flags32_t up_fields = it->up_fields; + for (i = 0; i < q->field_count; i ++) { + ecs_var_id_t var_id = src_vars[i]; + if (!var_id) { + continue; } - flecs_sparse_clear(pending_tables); - world->pending_buffer = pending_tables; - } while ((count = flecs_sparse_count(world->pending_tables))); -} - -void flecs_table_set_empty( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_poly_assert(world, ecs_world_t); - ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); + if (up_fields & (1u << i)) { + continue; + } - if (ecs_table_count(table)) { - table->generation = 0; + it->sources[i] = flecs_query_var_get_entity(var_id, ctx); } - flecs_sparse_set_generation(world->pending_tables, (uint32_t)table->id); - flecs_sparse_ensure(world->pending_tables, ecs_table_t*, - (uint32_t)table->id)[0] = table; + return true; } -ecs_id_record_t* flecs_ensure_id_record( - ecs_world_t *world, - ecs_id_t id) +static +bool flecs_query_setthis( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); + ecs_query_setthis_ctx_t *op_ctx = flecs_op_ctx(ctx, setthis); + ecs_var_t *vars = ctx->vars; + ecs_var_t *this_var = &vars[op->first.var]; - ecs_id_record_t **idr_ptr = ecs_map_ensure(&world->id_index, - ecs_id_record_t*, ecs_strip_generation(id)); - ecs_id_record_t *idr = idr_ptr[0]; - if (!idr) { - idr_ptr[0] = idr = new_id_record(world, id); - } + if (!redo) { + /* Save values so we can restore them later */ + op_ctx->range = vars[0].range; - return idr; + /* Constrain This table variable to a single entity from the table */ + vars[0].range = flecs_range_from_entity(ctx->world, this_var->entity); + vars[0].entity = this_var->entity; + return true; + } else { + /* Restore previous values, so that instructions that are operating on + * the table variable use all the entities in the table. */ + vars[0].range = op_ctx->range; + vars[0].entity = 0; + return false; + } } -void flecs_register_for_id_record( - ecs_world_t *world, - ecs_id_t id, - const ecs_table_t *table, - ecs_table_record_t *tr) +static +bool flecs_query_setfixed( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); + (void)op; + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + ecs_iter_t *it = ctx->it; - ecs_id_record_t *idr = flecs_ensure_id_record(world, id); - if (!idr) { - return; + if (redo) { + return false; } - ecs_table_cache_insert(&idr->cache, table, &tr->hdr); - - /* When id record is used by table, make sure type info is initialized */ - if (!(idr->flags & ECS_TYPE_INFO_INITIALIZED)) { - ecs_entity_t type = ecs_get_typeid(world, id); - if (type) { - idr->type_info = flecs_get_type_info(world, type); - ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - - world->info.tag_id_count --; - world->info.component_id_count ++; + int32_t i; + for (i = 0; i < q->term_count; i ++) { + const ecs_term_t *term = &q->terms[i]; + const ecs_term_ref_t *src = &term->src; + if (src->id & EcsIsEntity) { + it->sources[term->field_index] = ECS_TERM_REF_ID(src); } - idr->flags |= ECS_TYPE_INFO_INITIALIZED; } -} - -ecs_id_record_t* flecs_get_id_record( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_poly_assert(world, ecs_world_t); - return ecs_map_get_ptr(&world->id_index, ecs_id_record_t*, - ecs_strip_generation(id)); + return true; } -ecs_hashmap_t* flecs_ensure_id_name_index( - ecs_world_t *world, - ecs_id_t id) +bool flecs_query_setids( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); + (void)op; + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + ecs_iter_t *it = ctx->it; - ecs_id_record_t *idr = flecs_get_id_record(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + if (redo) { + return false; + } - ecs_hashmap_t *map = idr->name_index; - if (!map) { - map = idr->name_index = flecs_name_index_new(); + int32_t i; + for (i = 0; i < q->term_count; i ++) { + const ecs_term_t *term = &q->terms[i]; + it->ids[term->field_index] = term->id; } - return map; + return true; } -ecs_hashmap_t* flecs_get_id_name_index( - const ecs_world_t *world, - ecs_id_t id) +static +bool flecs_query_setid( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return NULL; + if (redo) { + return false; } - return idr->name_index; + ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); + ctx->it->ids[op->field_index] = op->first.entity; + return true; } -ecs_table_record_t* flecs_get_table_record( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) +/* Check if entity is stored in table */ +static +bool flecs_query_contain( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t* idr = flecs_get_id_record(world, id); - if (!idr) { - return NULL; + if (redo) { + return false; } - return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); + ecs_var_id_t src_id = op->src.var; + ecs_var_id_t first_id = op->first.var; + + ecs_table_t *table = flecs_query_var_get_table(src_id, ctx); + + ecs_entity_t e = flecs_query_var_get_entity(first_id, ctx); + return table == ecs_get_table(ctx->world, e); } -void flecs_remove_id_record( - ecs_world_t *world, - ecs_id_t id, - ecs_id_record_t *idr) +/* Check if first and second id of pair from last operation are the same */ +static +bool flecs_query_pair_eq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); - - /* Free id record resources */ - if (free_id_record(world, id, idr)) { - /* Remove record from world index */ - ecs_map_remove(&world->id_index, ecs_strip_generation(id)); + if (redo) { + return false; } + + ecs_iter_t *it = ctx->it; + ecs_id_t id = it->ids[op->field_index]; + return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); } -void flecs_clear_id_record( - ecs_world_t *world, - ecs_id_t id, - ecs_id_record_t *idr) +static +void flecs_query_reset_after_block( + const ecs_query_op_t *start_op, + ecs_query_run_ctx_t *ctx, + ecs_query_ctrl_ctx_t *op_ctx, + bool result) { - ecs_poly_assert(world, ecs_world_t); - - if (world->is_fini) { + ecs_query_lbl_t op_index = start_op->next; + const ecs_query_op_t *op = &ctx->qit->ops[op_index]; + + int32_t field = op->field_index; + if (field == -1) { + goto done; + } + + /* Set/unset field */ + ecs_iter_t *it = ctx->it; + if (result) { + ECS_TERMSET_SET(it->set_fields, 1u << field); return; } - ecs_table_cache_fini_delete_all(world, &idr->cache); + /* Reset state after a field was not matched */ + ctx->written[op_index] = ctx->written[ctx->op_index]; + ctx->op_index = op_index; + ECS_TERMSET_CLEAR(it->set_fields, 1u << field); - flecs_remove_id_record(world, id, idr); -} + /* Ignore variables written by Not operation */ + uint64_t *written = ctx->written; + uint64_t written_cur = written[op->prev + 1]; + ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); -bool ecs_id_in_use( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return false; + /* Overwrite id with cleared out variables */ + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (id) { + it->ids[field] = id; } - return (ecs_table_cache_count(&idr->cache) != 0) || - (ecs_table_cache_empty_count(&idr->cache) != 0); -} -const ecs_table_record_t* flecs_id_record_table( - ecs_id_record_t *idr, - ecs_table_t *table) -{ - if (!idr) { - return NULL; + it->trs[field] = NULL; + + /* Reset variables */ + if (flags_1st & EcsQueryIsVar) { + if (!flecs_ref_is_written(op, &op->first, EcsQueryFirst, written_cur)){ + flecs_query_var_reset(op->first.var, ctx); + } + } + if (flags_2nd & EcsQueryIsVar) { + if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written_cur)){ + flecs_query_var_reset(op->second.var, ctx); + } + } + + /* If term has entity src, set it because no other instruction might */ + if (op->flags & (EcsQueryIsEntity << EcsQuerySrc)) { + it->sources[field] = op->src.entity; } - return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); + +done: + op_ctx->op_index = op_index; } -ecs_id_record_t* flecs_table_iter( - ecs_world_t *world, - ecs_id_t id, - ecs_table_cache_iter_t *out) +static +bool flecs_query_run_block( + bool redo, + ecs_query_run_ctx_t *ctx, + ecs_query_ctrl_ctx_t *op_ctx) { - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return NULL; + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; + + if (!redo) { + op_ctx->op_index = flecs_itolbl(ctx->op_index + 1); + } else if (ctx->qit->ops[op_ctx->op_index].kind == EcsQueryEnd) { + return false; } - flecs_process_pending_tables(world); - ecs_assert( flecs_sparse_count(world->pending_tables) == 0, - ECS_INTERNAL_ERROR, NULL); + ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; - flecs_table_cache_iter(&idr->cache, out); - return idr; + const ecs_query_op_t *op = &ctx->qit->ops[ctx->op_index]; + bool result = flecs_query_run_until( + redo, ctx, qit->ops, ctx->op_index, op_ctx->op_index, op->next); + + op_ctx->op_index = flecs_itolbl(ctx->op_index - 1); + return result; } -ecs_id_record_t* flecs_empty_table_iter( - ecs_world_t *world, - ecs_id_t id, - ecs_table_cache_iter_t *out) +static +ecs_query_lbl_t flecs_query_last_op_for_or_cond( + const ecs_query_op_t *ops, + ecs_query_lbl_t cur, + ecs_query_lbl_t last) { - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return NULL; - } + const ecs_query_op_t *cur_op, *last_op = &ops[last]; - flecs_process_pending_tables(world); - ecs_assert( flecs_sparse_count(world->pending_tables) == 0, - ECS_INTERNAL_ERROR, NULL); + do { + cur_op = &ops[cur]; + cur ++; + } while (cur_op->next != last && cur_op != last_op); - flecs_table_cache_empty_iter(&idr->cache, out); - return idr; + return cur; } -void ecs_force_aperiodic( - ecs_world_t *world) +static +bool flecs_query_run_until_for_select_or( + bool redo, + ecs_query_run_ctx_t *ctx, + const ecs_query_op_t *ops, + ecs_query_lbl_t first, + ecs_query_lbl_t cur, + int32_t last) { - ecs_poly_assert(world, ecs_world_t); - - flecs_process_pending_tables(world); - flecs_eval_component_monitors(world); + ecs_query_lbl_t last_for_cur = flecs_query_last_op_for_or_cond( + ops, cur, flecs_itolbl(last)); + if (redo) { + /* If redoing, start from the last instruction of the last executed + * sequence */ + cur = flecs_itolbl(last_for_cur - 1); + } + + flecs_query_run_until(redo, ctx, ops, first, cur, last_for_cur); +#ifdef FLECS_QUERY_TRACE + printf("%*s%s (or)\n", (flecs_query_trace_indent + 1)*2, "", + ctx->op_index == last ? "true" : "false"); +#endif + return ctx->op_index == last; } -int32_t ecs_delete_empty_tables( - ecs_world_t *world, - ecs_id_t id, - uint16_t clear_generation, - uint16_t delete_generation, - int32_t min_id_count, - double time_budget_seconds) +static +bool flecs_query_select_or( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_poly_assert(world, ecs_world_t); - - ecs_time_t start = {0}, cur = {0}; - int32_t delete_count = 0, clear_count = 0; - bool time_budget = false; + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - ecs_time_measure(&start); - if (time_budget_seconds != 0) { - time_budget = true; + ecs_query_lbl_t first = flecs_itolbl(ctx->op_index + 1); + if (!redo) { + op_ctx->op_index = first; } - if (!id) { - id = EcsAny; /* Iterate all empty tables */ - } + const ecs_query_op_t *ops = qit->ops; + const ecs_query_op_t *first_op = &ops[first - 1]; + ecs_query_lbl_t last = first_op->next; + const ecs_query_op_t *last_op = &ops[last]; + const ecs_query_op_t *cur_op = &ops[op_ctx->op_index]; + bool result = false; - ecs_table_cache_iter_t it; - if (flecs_empty_table_iter(world, id, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - if (time_budget) { - cur = start; - if (ecs_time_measure(&cur) > time_budget_seconds) { - goto done; + do { + ecs_query_lbl_t cur = op_ctx->op_index; + ctx->op_index = cur; + ctx->written[cur] = op->written; + + result = flecs_query_run_until_for_select_or( + redo, ctx, ops, flecs_itolbl(first - 1), cur, last); + + if (result) { + if (first == cur) { + break; + } + + /* If a previous operation in the OR chain returned a result for the + * same matched source, skip it so we don't yield for each matching + * element in the chain. */ + + /* Copy written status so that the variables we just wrote will show + * up as written for the test. This ensures that the instructions + * match against the result we already found, vs. starting a new + * search (the difference between select & with). */ + ecs_query_lbl_t prev = first; + bool dup_found = false; + + /* While terms of an OR chain always operate on the same source, it + * is possible that a table variable is resolved to an entity + * variable. When checking for duplicates, copy the entity variable + * to the table, to ensure we're only testing the found entity. */ + const ecs_query_op_t *prev_op = &ops[cur - 1]; + ecs_var_t old_table_var; + ecs_os_memset_t(&old_table_var, 0, ecs_var_t); + bool restore_table_var = false; + + if (prev_op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + if (first_op->src.var != prev_op->src.var) { + restore_table_var = true; + old_table_var = ctx->vars[first_op->src.var]; + ctx->vars[first_op->src.var] = + ctx->vars[prev_op->src.var]; } } - ecs_table_t *table = tr->hdr.table; - ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); - if (table->refcount > 1) { - /* Don't delete claimed tables */ - continue; + int16_t field_index = op->field_index; + ecs_id_t prev_id = it->ids[field_index]; + const ecs_table_record_t *prev_tr = it->trs[field_index]; + + do { + ctx->written[prev] = ctx->written[last]; + + ecs_query_op_ctx_t *op_ctx_ptr = &ctx->op_ctx[first]; + ecs_query_op_ctx_t tmp_op_ctx = *op_ctx_ptr; + + ecs_os_zeromem(op_ctx_ptr); + + flecs_query_run_until(false, ctx, ops, flecs_itolbl(first - 1), + prev, cur); + + flecs_query_op_ctx_fini(ctx->it, &ops[first], op_ctx_ptr); + + ecs_os_memcpy_t(op_ctx_ptr, &tmp_op_ctx, ecs_query_op_ctx_t); + + if (ctx->op_index == last) { + /* Duplicate match was found, find next result */ + redo = true; + dup_found = true; + break; + } + + break; + } while (true); + + /* Restore table variable to full range for next result */ + if (restore_table_var) { + ctx->vars[first_op->src.var] = old_table_var; } - if (ecs_vector_count(table->type) < min_id_count) { + if (dup_found) { continue; } - uint16_t gen = ++ table->generation; - if (delete_generation && (gen > delete_generation)) { - if (flecs_table_release(world, table)) { - delete_count ++; - } - } else if (clear_generation && (gen > clear_generation)) { - if (flecs_table_shrink(world, table)) { - clear_count ++; - } - } + /* Restore id in case op set it */ + it->ids[field_index] = prev_id; + it->trs[field_index] = prev_tr; + break; } - } -done: - if (delete_count) { - ecs_dbg_1("#[red]deleted#[normal] %d empty tables in %.2fs", - delete_count, ecs_time_measure(&start)); - } - if (clear_count) { - ecs_dbg_1("#[red]cleared#[normal] %d empty tables in %.2fs", - clear_count, ecs_time_measure(&start)); - } - return delete_count; -} + /* No result was found, go to next operation in chain */ + op_ctx->op_index = flecs_query_last_op_for_or_cond( + ops, op_ctx->op_index, last); + cur_op = &qit->ops[op_ctx->op_index]; + redo = false; + } while (cur_op != last_op); -void flecs_observable_init( - ecs_observable_t *observable) -{ - observable->events = ecs_sparse_new(ecs_event_record_t); + return result; } -void flecs_observable_fini( - ecs_observable_t *observable) +static +bool flecs_query_with_or( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_sparse_t *triggers = observable->events; - int32_t i, count = flecs_sparse_count(triggers); - - for (i = 0; i < count; i ++) { - ecs_event_record_t *et = - ecs_sparse_get_dense(triggers, ecs_event_record_t, i); - ecs_assert(et != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - ecs_map_iter_t it = ecs_map_iter(&et->event_ids); - ecs_event_id_record_t *idt; - while ((idt = ecs_map_next(&it, ecs_event_id_record_t, NULL))) { - ecs_map_fini(&idt->triggers); - ecs_map_fini(&idt->set_triggers); - } - ecs_map_fini(&et->event_ids); + bool result = flecs_query_run_block(redo, ctx, op_ctx); + if (result) { + /* If a match was found, no need to keep searching for this source */ + op_ctx->op_index = op->next; } - flecs_sparse_free(observable->events); + return result; } static -void notify_subset( - ecs_world_t *world, - ecs_iter_t *it, - ecs_observable_t *observable, - ecs_entity_t entity, - ecs_entity_t event, - ecs_ids_t *ids) +bool flecs_query_or( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - ecs_id_t pair = ecs_pair(EcsWildcard, entity); - ecs_table_cache_iter_t idt; - ecs_id_record_t *idr = flecs_table_iter(world, pair, &idt); - if (!idr) { - return; + if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { + uint64_t written = ctx->written[ctx->op_index]; + if (!(written & (1ull << op->src.var))) { + return flecs_query_select_or(op, redo, ctx); + } } - const ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - ecs_id_t id = ecs_vector_get(table->type, ecs_id_t, tr->column)[0]; - ecs_entity_t rel = ECS_PAIR_FIRST(id); + return flecs_query_with_or(op, redo, ctx); +} - if (ecs_is_valid(world, rel) && !ecs_has_id(world, rel, EcsAcyclic)) { - /* Only notify for acyclic relations */ - continue; - } +static +bool flecs_query_run_block_w_reset( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - int32_t e, entity_count = ecs_table_count(table); - it->table = table; - it->type = table->type; - it->other_table = NULL; - it->offset = 0; - it->count = entity_count; + bool result = flecs_query_run_block(redo, ctx, op_ctx); + flecs_query_reset_after_block(op, ctx, op_ctx, result); + return result; +} - /* Treat as new event as this could trigger observers again for - * different tables. */ - world->event_id ++; +static +bool flecs_query_not( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; + } - flecs_set_triggers_notify(it, observable, ids, event, - ecs_pair(rel, EcsWildcard)); + return !flecs_query_run_block_w_reset(op, redo, ctx); +} - ecs_entity_t *entities = ecs_vector_first( - table->storage.entities, ecs_entity_t); - ecs_record_t **records = ecs_vector_first( - table->storage.record_ptrs, ecs_record_t*); +static +bool flecs_query_optional( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + bool result = flecs_query_run_block_w_reset(op, redo, ctx); - for (e = 0; e < entity_count; e ++) { - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[e]->row); - if (flags & ECS_FLAG_OBSERVED_ACYCLIC) { - /* Only notify for entities that are used in pairs with - * acyclic relations */ - notify_subset(world, it, observable, entities[e], event, ids); + ecs_query_optional_ctx_t *op_ctx = flecs_op_ctx(ctx, optional); + + if (!redo) { + op_ctx->range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + return true; /* Return at least once */ + } else { + if (!result) { + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + if (range.offset != op_ctx->range.offset) { + /* Different range is returned, so yield again. */ + result = true; + op_ctx->range = range; } } + + return result; } } -void flecs_emit( - ecs_world_t *world, - ecs_world_t *stage, - ecs_event_desc_t *desc) +static +bool flecs_query_eval_if( + const ecs_query_op_t *op, + ecs_query_run_ctx_t *ctx, + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind) { - ecs_poly_assert(world, ecs_world_t); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->event != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->event != EcsWildcard, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->ids != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->ids->count != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_ids_t *ids = desc->ids; - ecs_entity_t event = desc->event; - ecs_table_t *table = desc->table; - int32_t row = desc->offset; - int32_t i, count = desc->count; - ecs_entity_t relation = desc->relation; + bool result = true; + if (flecs_query_ref_flags(op->flags, ref_kind) == EcsQueryIsVar) { + result = ctx->vars[ref->var].entity != EcsWildcard; + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + flecs_query_reset_after_block(op, ctx, op_ctx, result); + return result; + } + return true; +} - if (!count) { - count = ecs_table_count(table) - row; +static +bool flecs_query_if_var( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + if (!redo) { + if (!flecs_query_eval_if(op, ctx, &op->src, EcsQuerySrc) || + !flecs_query_eval_if(op, ctx, &op->first, EcsQueryFirst) || + !flecs_query_eval_if(op, ctx, &op->second, EcsQuerySecond)) + { + return true; + } } - ecs_iter_t it = { - .world = stage, - .real_world = world, - .table = table, - .type = table->type, - .term_count = 1, - .other_table = desc->other_table, - .offset = row, - .count = count, - .param = (void*)desc->param, - .flags = desc->table_event ? EcsIterTableOnly : 0 - }; + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + return flecs_query_run_block(redo, ctx, op_ctx); +} - world->event_id ++; +static +bool flecs_query_if_set( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + ecs_iter_t *it = ctx->it; + int8_t field_index = flecs_ito(int8_t, op->other); - ecs_observable_t *observable = ecs_get_observable(desc->observable); - ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); + if (!redo) { + op_ctx->is_set = ecs_field_is_set(it, field_index); + } - if (!desc->relation) { - flecs_triggers_notify(&it, observable, ids, event); - } else { - flecs_set_triggers_notify(&it, observable, ids, event, - ecs_pair(relation, EcsWildcard)); + if (!op_ctx->is_set) { + return !redo; } - if (count && !desc->table_event) { - ecs_record_t **recs = ecs_vector_get( - table->storage.record_ptrs, ecs_record_t*, row); + return flecs_query_run_block(redo, ctx, op_ctx); +} - for (i = 0; i < count; i ++) { - ecs_record_t *r = recs[i]; - if (!r) { - /* If the event is emitted after a bulk operation, it's possible - * that it hasn't been populate with entities yet. */ - continue; - } +static +bool flecs_query_end( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + (void)op; (void)ctx; + return !redo; +} - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(recs[i]->row); - if (flags & ECS_FLAG_OBSERVED_ACYCLIC) { - notify_subset(world, &it, observable, ecs_vector_first( - table->storage.entities, ecs_entity_t)[row + i], - event, ids); - } - } +static +bool flecs_query_dispatch( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + switch(op->kind) { + case EcsQueryAll: return flecs_query_all(op, redo, ctx); + case EcsQueryAnd: return flecs_query_and(op, redo, ctx); + case EcsQueryAndAny: return flecs_query_and_any(op, redo, ctx); + case EcsQueryAndWcTgt: return flecs_query_and_wctgt(op, redo, ctx); + case EcsQueryTriv: return flecs_query_triv(op, redo, ctx); + case EcsQueryCache: return flecs_query_cache(op, redo, ctx); + case EcsQueryIsCache: return flecs_query_is_cache(op, redo, ctx); + case EcsQueryUp: return flecs_query_up(op, redo, ctx); + case EcsQuerySelfUp: return flecs_query_self_up(op, redo, ctx); + case EcsQueryWith: return flecs_query_with(op, redo, ctx); + case EcsQueryWithWcTgt: return flecs_query_with_wctgt(op, redo, ctx); + case EcsQueryTrav: return flecs_query_trav(op, redo, ctx); + case EcsQueryAndFrom: return flecs_query_and_from(op, redo, ctx); + case EcsQueryNotFrom: return flecs_query_not_from(op, redo, ctx); + case EcsQueryOrFrom: return flecs_query_or_from(op, redo, ctx); + case EcsQueryIds: return flecs_query_ids(op, redo, ctx); + case EcsQueryIdsRight: return flecs_query_idsright(op, redo, ctx); + case EcsQueryIdsLeft: return flecs_query_idsleft(op, redo, ctx); + case EcsQueryEach: return flecs_query_each(op, redo, ctx); + case EcsQueryStore: return flecs_query_store(op, redo, ctx); + case EcsQueryReset: return flecs_query_reset(op, redo, ctx); + case EcsQueryOr: return flecs_query_or(op, redo, ctx); + case EcsQueryOptional: return flecs_query_optional(op, redo, ctx); + case EcsQueryIfVar: return flecs_query_if_var(op, redo, ctx); + case EcsQueryIfSet: return flecs_query_if_set(op, redo, ctx); + case EcsQueryEnd: return flecs_query_end(op, redo, ctx); + case EcsQueryNot: return flecs_query_not(op, redo, ctx); + case EcsQueryPredEq: return flecs_query_pred_eq(op, redo, ctx); + case EcsQueryPredNeq: return flecs_query_pred_neq(op, redo, ctx); + case EcsQueryPredEqName: return flecs_query_pred_eq_name(op, redo, ctx); + case EcsQueryPredNeqName: return flecs_query_pred_neq_name(op, redo, ctx); + case EcsQueryPredEqMatch: return flecs_query_pred_eq_match(op, redo, ctx); + case EcsQueryPredNeqMatch: return flecs_query_pred_neq_match(op, redo, ctx); + case EcsQueryMemberEq: return flecs_query_member_eq(op, redo, ctx); + case EcsQueryMemberNeq: return flecs_query_member_neq(op, redo, ctx); + case EcsQueryToggle: return flecs_query_toggle(op, redo, ctx); + case EcsQueryToggleOption: return flecs_query_toggle_option(op, redo, ctx); + case EcsQuerySparse: return flecs_query_sparse(op, redo, ctx); + case EcsQuerySparseWith: return flecs_query_sparse_with(op, redo, ctx, false); + case EcsQuerySparseNot: return flecs_query_sparse_with(op, redo, ctx, true); + case EcsQuerySparseSelfUp: return flecs_query_sparse_self_up(op, redo, ctx); + case EcsQuerySparseUp: return flecs_query_sparse_up(op, redo, ctx); + case EcsQueryTree: return flecs_query_tree_and(op, redo, ctx); + case EcsQueryTreeWildcard: return flecs_query_tree_and_wildcard(op, redo, ctx, false); + case EcsQueryTreePre: return flecs_query_tree_pre(op, redo, ctx); + case EcsQueryTreePost: return flecs_query_tree_post(op, redo, ctx); + case EcsQueryTreeUpPre: return flecs_query_tree_up_pre(op, redo, ctx, false); + case EcsQueryTreeSelfUpPre: return flecs_query_tree_up_pre(op, redo, ctx, true); + case EcsQueryTreeUpPost: return flecs_query_tree_up_post(op, redo, ctx, false); + case EcsQueryTreeSelfUpPost: return flecs_query_tree_up_post(op, redo, ctx, true); + case EcsQueryChildrenWc: return flecs_query_tree_and_wildcard(op, redo, ctx, true); + case EcsQueryTreeWith: return flecs_query_tree_with(op, redo, ctx); + case EcsQueryChildren: return flecs_query_children(op, redo, ctx); + case EcsQueryLookup: return flecs_query_lookup(op, redo, ctx); + case EcsQuerySetVars: return flecs_query_setvars(op, redo, ctx); + case EcsQuerySetThis: return flecs_query_setthis(op, redo, ctx); + case EcsQuerySetFixed: return flecs_query_setfixed(op, redo, ctx); + case EcsQuerySetIds: return flecs_query_setids(op, redo, ctx); + case EcsQuerySetId: return flecs_query_setid(op, redo, ctx); + case EcsQueryContain: return flecs_query_contain(op, redo, ctx); + case EcsQueryPairEq: return flecs_query_pair_eq(op, redo, ctx); + case EcsQueryYield: return false; + case EcsQueryNothing: return false; } - -error: - return; + return false; } -void ecs_emit( - ecs_world_t *stage, - ecs_event_desc_t *desc) +bool flecs_query_run_until( + bool redo, + ecs_query_run_ctx_t *ctx, + const ecs_query_op_t *ops, + ecs_query_lbl_t first, + ecs_query_lbl_t cur, + int32_t last) { - ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage); - flecs_emit(world, stage, desc); -} + ecs_assert(first >= -1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cur > first, ECS_INTERNAL_ERROR, NULL); + ctx->op_index = cur; + const ecs_query_op_t *op = &ops[ctx->op_index]; + const ecs_query_op_t *last_op = &ops[last]; + ecs_assert(last > first, ECS_INTERNAL_ERROR, NULL); -#include +#ifdef FLECS_QUERY_TRACE + printf("%*sblock:\n", flecs_query_trace_indent*2, ""); + flecs_query_trace_indent ++; +#endif -static -void term_error( - const ecs_world_t *world, - const ecs_term_t *term, - const char *name, - const char *fmt, - ...) -{ - va_list args; - va_start(args, fmt); + do { + #ifdef FLECS_DEBUG + ctx->qit->profile[ctx->op_index].count[redo] ++; + #endif - char *expr = ecs_term_str(world, term); - ecs_parser_errorv(name, expr, 0, fmt, args); - ecs_os_free(expr); +#ifdef FLECS_QUERY_TRACE + printf("%*s%d: %s\n", flecs_query_trace_indent*2, "", + ctx->op_index, flecs_query_op_str(op->kind)); +#endif - va_end(args); + bool result = flecs_query_dispatch(op, redo, ctx); + cur = (&op->prev)[result]; + redo = cur < ctx->op_index; + + if (!redo) { + ctx->written[cur] |= ctx->written[ctx->op_index] | op->written; + } + + ctx->op_index = cur; + op = &ops[ctx->op_index]; + + if (cur <= first) { +#ifdef FLECS_QUERY_TRACE + printf("%*sfalse\n", flecs_query_trace_indent*2, ""); + flecs_query_trace_indent --; +#endif + return false; + } + } while (op < last_op); + +#ifdef FLECS_QUERY_TRACE + printf("%*strue\n", flecs_query_trace_indent*2, ""); + flecs_query_trace_indent --; +#endif + + return true; } +/** + * @file query/engine/eval_iter.c + * @brief Query iterator. + */ + + static -int finalize_term_set( - const ecs_world_t *world, - ecs_term_t *term, - ecs_term_id_t *identifier, - const char *name) +void flecs_query_iter_run_ctx_init( + ecs_iter_t *it, + ecs_query_run_ctx_t *ctx) { - if (identifier->set.mask & EcsParent) { - identifier->set.mask |= EcsSuperSet; - identifier->set.relation = EcsChildOf; - } + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, it->query); + ctx->world = it->real_world; + ctx->query = impl; + ctx->it = it; + ctx->vars = qit->vars; + ctx->query_vars = qit->query_vars; + ctx->written = qit->written; + ctx->op_ctx = qit->op_ctx; + ctx->qit = qit; +} - if (identifier->set.mask & EcsCascade && - !(identifier->set.mask & EcsSuperSet) && - !(identifier->set.mask & EcsSubSet)) - { - /* If cascade is used without specifying super or sub, assume - * super */ - identifier->set.mask |= EcsSuperSet; - } +void flecs_query_iter_constrain( + ecs_iter_t *it) +{ + ecs_query_run_ctx_t ctx; + flecs_query_iter_run_ctx_init(it, &ctx); + ecs_assert(ctx.written != NULL, ECS_INTERNAL_ERROR, NULL); + + const ecs_query_impl_t *query = ctx.query; + const ecs_query_t *q = &query->pub; + ecs_flags64_t it_written = it->constrained_vars; + ctx.written[0] = it_written; + if (it_written && ctx.query->src_vars) { + /* If variables were constrained, check if there are any table + * variables that have a constrained entity variable. */ + ecs_var_t *vars = ctx.vars; + int32_t i, count = q->field_count; + for (i = 0; i < count; i ++) { + ecs_var_id_t var_id = query->src_vars[i]; + ecs_query_var_t *var = &query->vars[var_id]; + + if (!(it_written & (1ull << var_id)) || + (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) + { + continue; + } - /* Default relation for superset/subset is EcsIsA */ - if (identifier->set.mask & (EcsSuperSet|EcsSubSet)) { - if (!identifier->set.relation) { - identifier->set.relation = EcsIsA; + /* Initialize table variable with constrained entity variable */ + ecs_var_t *tvar = &vars[var->table_id]; + tvar->range = flecs_range_from_entity(ctx.world, vars[var_id].entity); + ctx.written[0] |= (1ull << var->table_id); /* Mark as written */ } + } - if (!(identifier->set.mask & EcsSelf)) { - if (!identifier->set.min_depth) { - identifier->set.min_depth = 1; + /* This function can be called multiple times when setting variables, so + * reset flags before setting them. */ + it->flags &= ~(EcsIterTrivialTest|EcsIterTrivialCached| + EcsIterTrivialSearch); + + /* Figure out whether this query can utilize specialized iterator modes for + * improved performance. */ + ecs_flags32_t flags = q->flags; + ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; + ecs_query_cache_t *cache = query->cache; + + if (it_written) { + /* When we're testing against an entity or table, set the $this + * variable in advance since it won't change later on. This + * initializes it.count, it.entities and it.table. */ + flecs_query_set_iter_this(it, &ctx); + + if (!cache) { + if ((flags & (trivial_flags)) == trivial_flags) { + if (!(flags & EcsQueryMatchWildcards)) { + it->flags |= EcsIterTrivialTest; + flecs_query_setids(NULL, false, &ctx); + } + } + } else if (flags & EcsQueryIsCacheable) { + if (!query->ops) { + if (!cache->order_by_callback && + (cache->query->flags & EcsQueryTrivialCache && + !(query->pub.flags & EcsQueryHasChangeDetection))) + { + it->flags |= EcsIterTrivialTest|EcsIterTrivialCached| + EcsIterTrivialChangeDetection; + it->ids = cache->query->ids; + it->sources = cache->sources; + it->set_fields = flecs_uto(uint32_t, (1llu << it->field_count) - 1); + } else { + it->flags |= EcsIterTrivialTest|EcsIterCached; + } } } } else { - if (identifier->set.min_depth > 0) { - term_error(world, term, name, - "min depth cannnot be non-zero for Self term"); - return -1; - } - if (identifier->set.max_depth > 1) { - term_error(world, term, name, - "max depth cannnot be larger than 1 for Self term"); - return -1; + if (!cache) { + if ((flags & (trivial_flags)) == trivial_flags) { + if (!(flags & EcsQueryMatchWildcards)) { + it->flags |= EcsIterTrivialSearch| + EcsIterTrivialChangeDetection; + flecs_query_setids(NULL, false, &ctx); + } + } + } else if (flags & EcsQueryIsCacheable) { + if (!query->ops) { + if (!cache->order_by_callback && + (cache->query->flags & EcsQueryTrivialCache && + !(query->pub.flags & EcsQueryHasChangeDetection))) + { + it->flags |= EcsIterTrivialSearch|EcsIterTrivialCached| + EcsIterTrivialChangeDetection; + it->ids = cache->query->ids; + it->sources = cache->sources; + it->set_fields = flecs_uto(uint32_t, (1llu << it->field_count) - 1); + } else { + it->flags |= EcsIterTrivialSearch|EcsIterCached; + } + } } - - identifier->set.max_depth = 1; - } - - if ((identifier->set.mask != EcsNothing) && - (identifier->set.mask & EcsNothing)) - { - term_error(world, term, name, "invalid Nothing in set mask"); - return -1; } - - return 0; } static -void finalize_term_id_resources( - ecs_term_t *term, - ecs_term_id_t *id) -{ - if (id->entity) { - if (term->move) { - ecs_os_free(id->name); - } - id->name = NULL; - } else if (id->name) { - if (!term->move) { - id->name = ecs_os_strdup(id->name); +void flecs_query_change_detection( + ecs_iter_t *it, + ecs_query_iter_t *qit, + ecs_query_impl_t *impl) +{ + /* Change detection */ + if (!(it->flags & EcsIterSkip)) { + /* Mark table columns that are written to dirty */ + flecs_query_mark_fields_dirty(impl, it); + if (qit->elem) { + if (impl->pub.flags & EcsQueryHasChangeDetection) { + /* If this query uses change detection, synchronize the + * monitor for the iterated table with the query */ + flecs_query_sync_match_monitor(impl, qit->elem); + } } } } static -void finalize_term_resources( - ecs_term_t *term) +void flecs_query_self_change_detection( + ecs_iter_t *it, + ecs_query_iter_t *qit, + ecs_query_impl_t *impl) { - finalize_term_id_resources(term, &term->pred); - finalize_term_id_resources(term, &term->subj); - finalize_term_id_resources(term, &term->obj); - if (term->name) { - term->name = ecs_os_strdup(term->name); + if (!it->table->dirty_state) { + return; } - term->move = false; + + flecs_query_change_detection(it, qit, impl); } -static -int finalize_term_var( - const ecs_world_t *world, - ecs_term_t *term, - ecs_term_id_t *identifier, - const char *name) +bool ecs_query_next( + ecs_iter_t *it) { - if (identifier->var == EcsVarDefault && identifier->set.mask != EcsNothing){ - identifier->var = EcsVarIsEntity; - } + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->next == ecs_query_next || + it->next == flecs_query_trivial_cached_next || + it->next == flecs_default_next_callback, + ECS_INVALID_PARAMETER, NULL); - if (!identifier->name) { - return 0; + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, it->query); + ecs_assert(impl != NULL, ECS_INVALID_OPERATION, + "cannot call ecs_query_next on invalid iterator"); + + ecs_query_run_ctx_t ctx; + flecs_query_iter_run_ctx_init(it, &ctx); + + bool redo = it->flags & EcsIterIsValid; + if (redo) { + if (it->flags & EcsIterTrivialChangeDetection) { + flecs_query_self_change_detection(it, qit, impl); + } else { + flecs_query_change_detection(it, qit, impl); + } } - if (identifier->var != EcsVarIsVariable) { - if (ecs_identifier_is_0(identifier->name)) { - identifier->entity = 0; - } else if (identifier->name) { - ecs_entity_t oneof = 0; - ecs_entity_t pred = 0; - if (term->pred.var == EcsVarIsEntity) { - pred = term->pred.entity; - if (pred) { - oneof = flecs_get_oneof(world, pred); - } + it->flags &= ~(EcsIterSkip); + it->flags |= EcsIterIsValid; + it->frame_offset += it->count; + + /* Specialized iterator modes. When a query doesn't use any advanced + * features, it can call specialized iterator functions directly instead of + * going through the dispatcher of the query engine. + * The iterator mode is set during iterator initialization. Besides being + * determined by the query, there are different modes for searching and + * testing, where searching returns all matches for a query, whereas testing + * tests a single table or table range against the query. */ + + if (it->flags & EcsIterTrivialCached) { + /* Trivial cache iterator. Only supported for search */ + ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); + + if (it->flags & EcsIterTrivialSearch) { + if (flecs_query_is_trivial_cache_search(&ctx)) { + return true; } - - ecs_entity_t e = ecs_lookup_symbol(world, identifier->name, true); - if (oneof && identifier != &term->pred) { - if (!e) { - e = ecs_lookup_child(world, oneof, identifier->name); - } else if (e) { - if (!ecs_has_pair(world, e, EcsChildOf, oneof)) { - char *rel_str = ecs_get_fullpath(world, pred); - term_error(world, term, name, - "invalid object '%s' for relation %s", - identifier->name, rel_str); - ecs_os_free(rel_str); - return -1; - } - } + } else if (it->flags & EcsIterTrivialTest) { + if (flecs_query_is_trivial_cache_test(&ctx, redo)) { + return true; + } + } + } else if (it->flags & EcsIterCached) { + /* Cached iterator modes */ + if (it->flags & EcsIterTrivialSearch) { + ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); + if (flecs_query_is_cache_search(&ctx)) { + goto trivial_search_yield; } + } else if (it->flags & EcsIterTrivialTest) { + ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); + if (flecs_query_is_cache_test(&ctx, redo)) { + goto yield; + } + } + } else { + /* Uncached iterator modes */ + if (it->flags & EcsIterTrivialSearch) { + ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); - if (!e) { - term_error(world, term, name, - "unresolved identifier '%s'", identifier->name); - return -1; + ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; + if (flecs_query_is_trivial_search(&ctx, op_ctx, redo)) { + goto yield; } + } else if (it->flags & EcsIterTrivialTest) { + ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); - if (identifier->entity && identifier->entity != e) { - term_error(world, term, name, "name/entity mismatch"); - return -1; + int32_t fields = ctx.query->pub.term_count; + ecs_flags64_t mask = (2llu << (fields - 1)) - 1; + if (flecs_query_trivial_test(&ctx, redo, mask)) { + goto yield; } + } else { + const ecs_query_op_t *ops = qit->ops; - identifier->entity = e; + /* Default iterator mode. This enters the query VM dispatch loop. */ + if (flecs_query_run_until( + redo, &ctx, ops, -1, qit->op, impl->op_count - 1)) + { + ecs_assert(ops[ctx.op_index].kind == EcsQueryYield, + ECS_INTERNAL_ERROR, NULL); + flecs_query_set_iter_this(it, &ctx); + ecs_assert(it->count >= 0, ECS_INTERNAL_ERROR, NULL); + qit->op = flecs_itolbl(ctx.op_index - 1); + goto yield; + } } } - if ((identifier->set.mask == EcsNothing) && - (identifier->var != EcsVarDefault)) - { - term_error(world, term, name, "Invalid Nothing with entity"); - return -1; + /* Done iterating */ + flecs_query_mark_fixed_fields_dirty(impl, it); + if (ctx.query->monitor) { + flecs_query_update_fixed_monitor( + ECS_CONST_CAST(ecs_query_impl_t*, ctx.query)); } - if (identifier->var == EcsVarIsEntity) { - if (identifier->entity && !ecs_is_alive(world, identifier->entity)) { - term_error(world, term, name, - "cannot use not alive entity %u in query", - (uint32_t)identifier->entity); - return -1; - } - } + it->flags |= EcsIterSkip; /* Prevent change detection on fini */ - return 0; -} + ecs_iter_fini(it); + ecs_os_linc(&it->real_world->info.queries_ran_total); + return false; -static -int finalize_term_identifier( - const ecs_world_t *world, - ecs_term_t *term, - ecs_term_id_t *identifier, - const char *name) -{ - if (finalize_term_set(world, term, identifier, name)) { - return -1; - } - if (finalize_term_var(world, term, identifier, name)) { - return -1; - } - return 0; -} +trivial_search_yield: + it->table = ctx.vars[0].range.table; + it->count = ecs_table_count(it->table); + it->entities = ecs_table_entities(it->table); -static -bool term_can_inherit( - ecs_term_t *term) -{ - /* Hardcoded components that can't be inherited. TODO: replace with - * relationship property. */ - if (term->pred.entity == EcsChildOf || - (term->id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) || - (term->id == EcsPrefab) || - (term->id == EcsDisabled)) - { - return false; - } +yield: return true; } -static -ecs_entity_t term_id_entity( - const ecs_world_t *world, - ecs_term_id_t *term_id) +bool flecs_query_trivial_cached_next( + ecs_iter_t *it) { - if (term_id->entity && term_id->entity != EcsThis && - term_id->entity != EcsWildcard && term_id->entity != EcsAny) - { - if (!(term_id->entity & ECS_ROLE_MASK)) { - return term_id->entity; - } else { - return 0; - } - } else if (term_id->name) { - if (term_id->var == EcsVarIsEntity || - (term_id->var == EcsVarDefault && - !ecs_identifier_is_var(term_id->name))) - { - ecs_entity_t e = ecs_lookup_fullpath(world, term_id->name); - if (e != EcsWildcard && e != EcsThis && e != EcsAny) { - return e; - } - return 0; - } else { - return 0; - } - } else { - return 0; + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_query_iter_t *qit = &it->priv_.iter.query; + ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, it->query); + ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_query_run_ctx_t ctx; + flecs_query_iter_run_ctx_init(it, &ctx); + + bool redo = it->flags & EcsIterIsValid; + if (redo) { + flecs_query_self_change_detection(it, qit, impl); + } + + it->flags &= ~(EcsIterSkip); + it->flags |= EcsIterIsValid; + it->frame_offset += it->count; + + ecs_assert(it->flags & EcsIterTrivialCached, ECS_INVALID_OPERATION, + "query does not have trivial cache, use ecs_query_next instead"); + ecs_assert(it->flags & EcsIterTrivialSearch, ECS_INVALID_OPERATION, + "iterator has constrained variables, use ecs_query_next instead"); + ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); + + if (flecs_query_is_trivial_cache_search(&ctx)) { + return true; } + + it->flags |= EcsIterSkip; /* Prevent change detection on fini */ + + ecs_iter_fini(it); + ecs_os_linc(&it->real_world->info.queries_ran_total); + return false; } -static -int finalize_term_vars( - const ecs_world_t *world, - ecs_term_t *term, - const char *name) +void flecs_query_op_ctx_fini( + ecs_iter_t *it, + const ecs_query_op_t *op, + ecs_query_op_ctx_t *ctx) { - if (finalize_term_var(world, term, &term->pred, name)) { - return -1; + switch(op->kind) { + case EcsQueryTrav: { + ecs_allocator_t *a = flecs_query_get_allocator(it); + flecs_query_trav_cache_fini(a, &ctx->is.trav.cache); + break; } - if (finalize_term_var(world, term, &term->subj, name)) { - return -1; + case EcsQueryUp: + case EcsQuerySelfUp: + case EcsQueryTreeUp: + case EcsQueryTreeSelfUp: + case EcsQueryTreeUpPre: + case EcsQueryTreeSelfUpPre: + case EcsQueryTreeUpPost: + case EcsQueryTreeSelfUpPost: + case EcsQuerySparseUp: + case EcsQuerySparseSelfUp: { + ecs_allocator_t *a = flecs_query_get_allocator(it); + ecs_query_up_ctx_t *op_ctx = &ctx->is.up; + ecs_query_up_impl_t *impl = op_ctx->impl; + if (impl) { + ecs_trav_up_cache_t *cache = &impl->cache; + if (cache->dir == EcsTravDown) { + flecs_query_down_cache_fini(a, cache); + } else { + flecs_query_up_cache_fini(cache); + } + flecs_free_t(a, ecs_query_up_impl_t, impl); + } + break; } - if (finalize_term_var(world, term, &term->obj, name)) { - return -1; + default: + break; } - return 0; } static -bool entity_is_var( - ecs_entity_t e) +void flecs_query_iter_fini_ctx( + ecs_iter_t *it, + ecs_query_iter_t *qit) { - if (e == EcsThis || e == EcsWildcard || e == EcsAny || e == EcsVariable) { - return true; + const ecs_query_impl_t *query = flecs_query_impl(it->query); + int32_t i, count = query->op_count; + ecs_query_op_t *ops = query->ops; + ecs_query_op_ctx_t *ctx = qit->op_ctx; + + for (i = 0; i < count; i ++) { + ecs_query_op_t *op = &ops[i]; + flecs_query_op_ctx_fini(it, op, &ctx[i]); } - return false; } static -int finalize_term_identifiers( - const ecs_world_t *world, - ecs_term_t *term, - const char *name) +void flecs_query_iter_fini( + ecs_iter_t *it) { - /* By default select subsets for predicates. For example, when the term - * matches "Tree", also include "Oak", "Pine", "Elm". */ - if (term->pred.set.mask == EcsDefaultSet) { - ecs_entity_t e = term_id_entity(world, &term->pred); + ecs_query_iter_t *qit = &it->priv_.iter.query; + const ecs_query_t *q = it->query; + ecs_assert(q != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_poly_assert(q, ecs_query_t); + int32_t op_count = flecs_query_impl(q)->op_count; + int32_t var_count = flecs_query_impl(q)->var_count; - if (e && !ecs_has_id(world, e, EcsFinal)) { - term->pred.set.mask = EcsSelf|EcsSubSet; - } else { - /* If predicate is final, don't search subsets */ - term->pred.set.mask = EcsSelf; - } + if (it->flags & EcsIterIsValid) { + flecs_query_change_detection(it, qit, flecs_query_impl(q)); } - /* By default select supersets for subjects. For example, when an entity has - * (IsA, SpaceShip), also search the components of SpaceShip. */ - if (term->subj.set.mask == EcsDefaultSet) { - ecs_entity_t e = term_id_entity(world, &term->pred); - - /* If the component has the DontInherit tag, use EcsSelf */ - if (!e || !ecs_has_id(world, e, EcsDontInherit)) { - term->subj.set.mask = EcsSelf|EcsSuperSet; - } else { - term->subj.set.mask = EcsSelf; - } +#ifdef FLECS_DEBUG + if (it->flags & EcsIterProfile) { + char *str = ecs_query_plan_w_profile(q, it); + printf("%s\n", str); + ecs_os_free(str); } - /* By default select self for objects. */ - if (term->obj.set.mask == EcsDefaultSet) { - term->obj.set.mask = EcsSelf; - } + flecs_iter_free_n(qit->profile, ecs_query_op_profile_t, op_count); +#endif - if (finalize_term_set(world, term, &term->pred, name)) { - return -1; - } - if (finalize_term_set(world, term, &term->subj, name)) { - return -1; - } - if (finalize_term_set(world, term, &term->obj, name)) { - return -1; - } + flecs_query_iter_fini_ctx(it, qit); + flecs_iter_free_n(qit->vars, ecs_var_t, var_count); + flecs_iter_free_n(qit->written, ecs_write_flags_t, op_count); + flecs_iter_free_n(qit->op_ctx, ecs_query_op_ctx_t, op_count); - if (term->pred.set.mask & EcsNothing) { - term_error(world, term, name, - "invalid Nothing value for predicate set mask"); - return -1; - } + qit->vars = NULL; + qit->written = NULL; + qit->op_ctx = NULL; + it->query = NULL; +} - if (term->obj.set.mask & EcsNothing) { - term_error(world, term, name, - "invalid Nothing value for object set mask"); - return -1; - } +static +void flecs_query_validate_final_fields( + const ecs_query_t *q) +{ + (void)q; +#ifdef FLECS_DEBUG + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_world_t *world = q->real_world; - if (!(term->subj.set.mask & EcsNothing) && - !term->subj.entity && - term->subj.var == EcsVarIsEntity) - { - term->subj.entity = EcsThis; + if (!impl->final_terms) { + return; } - if (entity_is_var(term->pred.entity)) { - term->pred.var = EcsVarIsVariable; + if (!world->cr_isa_wildcard) { + return; } - if (term->pred.entity == EcsVariable) { - term_error(world, term, name, - "invalid usage of Variable ($) as predicate"); - return -1; - } + int32_t i, count = impl->pub.term_count; + ecs_term_t *terms = impl->pub.terms; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_id_t id = term->id; - /* If EcsVariable is used by itself, assign to predicate (singleton) */ - if (term->subj.entity == EcsVariable) { - term->subj.entity = term->pred.entity; - term->subj.var = term->pred.var; - } - if (term->obj.entity == EcsVariable) { - term->obj.entity = term->pred.entity; - term->obj.var = term->pred.var; - } - - if (entity_is_var(term->subj.entity)) { - term->subj.var = EcsVarIsVariable; - } - if (entity_is_var(term->obj.entity)) { - term->obj.var = EcsVarIsVariable; - } + if (!(impl->final_terms & (1ull << i))) { + continue; + } - finalize_term_resources(term); + if (ECS_IS_PAIR(id)) { + id = ECS_PAIR_FIRST(id); + } - return 0; -} + if (ecs_id_is_wildcard(id)) { + continue; + } -static -ecs_entity_t entity_from_identifier( - const ecs_term_id_t *identifier) -{ - if (identifier->var == EcsVarDefault) { - return 0; - } else if (identifier->var == EcsVarIsEntity) { - return identifier->entity; - } else if (identifier->var == EcsVarIsVariable) { - if (identifier->entity != EcsAny) { - /* Any variable should not use wildcard, as this would return all - * ids matching a wildcard, whereas Any returns the first match */ - return EcsWildcard; - } else { - return EcsAny; + if (flecs_components_get(world, ecs_pair(EcsIsA, id))) { + char *query_str = ecs_query_str(q); + char *id_str = ecs_id_str(world, id); + ecs_abort(ECS_INVALID_OPERATION, + "query '%s' was created before '(IsA, %s)' relationship, " + "create query after adding inheritance relationship " + "or add 'Inheritable' trait to '%s'", + query_str, id_str, id_str); + ecs_os_free(id_str); + ecs_os_free(query_str); } - } else { - /* This should've been caught earlier */ - ecs_abort(ECS_INTERNAL_ERROR, NULL); } +#endif } -static -int finalize_term_id( +ecs_iter_t flecs_query_iter( const ecs_world_t *world, - ecs_term_t *term, - const char *name) + const ecs_query_t *q) { - ecs_entity_t pred = entity_from_identifier(&term->pred); - ecs_entity_t obj = entity_from_identifier(&term->obj); - ecs_id_t role = term->role; + ecs_iter_t it = {0}; + ecs_query_iter_t *qit = &it.priv_.iter.query; + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); - if (ECS_HAS_ROLE(pred, PAIR)) { - if (obj) { - term_error(world, term, name, - "cannot set term.pred to a pair and term.obj at the same time"); - return -1; - } +#ifdef FLECS_DEBUG + flecs_check_exclusive_world_access_write(q->real_world); +#endif - obj = ECS_PAIR_SECOND(pred); - pred = ECS_PAIR_FIRST(pred); + flecs_query_validate_final_fields(q); + + flecs_poly_assert(q, ecs_query_t); + ecs_query_impl_t *impl = flecs_query_impl(q); - term->pred.entity = pred; - term->obj.entity = obj; + int32_t i, var_count = impl->var_count; + int32_t op_count = impl->op_count ? impl->op_count : 1; + it.world = ECS_CONST_CAST(ecs_world_t*, world); - if (finalize_term_identifier(world, term, &term->obj, name)) { - return -1; - } + /* If world passed to iterator is the real world, but query was created from + * a stage, stage takes precedence. */ + if (flecs_poly_is(it.world, ecs_world_t) && + flecs_poly_is(q->world, ecs_stage_t)) + { + it.world = ECS_CONST_CAST(ecs_world_t*, q->world); } - if (!obj && role != ECS_PAIR) { - term->id = pred | role; - } else { - if (role) { - if (role && role != ECS_PAIR && role != ECS_CASE) { - term_error(world, term, name, "invalid role for pair"); - return -1; - } - - term->role = role; - } else { - term->role = ECS_PAIR; - } + it.real_world = q->real_world; + ecs_assert(flecs_poly_is(it.real_world, ecs_world_t), + ECS_INTERNAL_ERROR, NULL); + ecs_check(!(it.real_world->flags & EcsWorldMultiThreaded) || + it.world != it.real_world, ECS_INVALID_PARAMETER, + "create iterator for stage when world is in multithreaded mode"); - term->id = term->role | ecs_entity_t_comb(obj, pred); - } + it.query = q; + it.system = q->entity; + it.next = ecs_query_next; + it.fini = flecs_query_iter_fini; + it.field_count = q->field_count; + it.sizes = q->sizes; + it.set_fields = q->set_fields; + it.ref_fields = q->fixed_fields | q->row_fields | q->var_fields; + it.row_fields = q->row_fields; + it.up_fields = 0; + flecs_query_apply_iter_flags(&it, q); - return 0; -} + bool fully_cached = (q->flags & EcsQueryIsCacheable) && !(q->flags & EcsQueryCacheWithFilter); + flecs_iter_init(it.world, &it, !impl->cache || !fully_cached); -static -int populate_from_term_id( - const ecs_world_t *world, - ecs_term_t *term, - const char *name) -{ - ecs_entity_t pred = 0; - ecs_entity_t obj = 0; - ecs_id_t role = term->id & ECS_ROLE_MASK; + qit->query_vars = impl->vars; + qit->ops = impl->ops; - if (!role && term->role) { - role = term->role; - term->id |= role; + if (q->flags & EcsQueryMatchEmptyTables) { + it.flags |= EcsIterMatchEmptyTables; } - if (term->role && term->role != role) { - term_error(world, term, name, "mismatch between term.id & term.role"); - return -1; + flecs_query_cache_iter_init(&it, qit, impl); + + if (var_count) { + qit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); } - term->role = role; + if (op_count) { + qit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); + qit->op_ctx = flecs_iter_calloc_n(&it, ecs_query_op_ctx_t, op_count); + } - if (ECS_HAS_ROLE(term->id, PAIR) || ECS_HAS_ROLE(term->id, CASE)) { - pred = ECS_PAIR_FIRST(term->id); - obj = ECS_PAIR_SECOND(term->id); +#ifdef FLECS_DEBUG + qit->profile = flecs_iter_calloc_n(&it, ecs_query_op_profile_t, op_count); +#endif - if (!pred) { - term_error(world, term, name, "missing predicate in term.id pair"); - return -1; - } - if (!obj) { - if (pred != EcsChildOf) { - term_error(world, term, name, "missing object in term.id pair"); - return -1; - } - } - } else { - pred = term->id & ECS_COMPONENT_MASK; - if (!pred) { - term_error(world, term, name, "missing predicate in term.id"); - return -1; - } + for (i = 1; i < var_count; i ++) { + qit->vars[i].entity = EcsWildcard; } - ecs_entity_t term_pred = entity_from_identifier(&term->pred); - if (term_pred) { - if (term_pred != pred) { - term_error(world, term, name, - "mismatch between term.id and term.pred"); - return -1; - } - } else { - term->pred.entity = pred; - if (finalize_term_identifier(world, term, &term->pred, name)) { - return -1; - } - } + /* Set flags for unconstrained query iteration. Can be reinitialized when + * variables are constrained on iterator. */ + flecs_query_iter_constrain(&it); +error: + return it; +} - ecs_entity_t term_obj = entity_from_identifier(&term->obj); - if (term_obj) { - if (ecs_entity_t_lo(term_obj) != obj) { - term_error(world, term, name, - "mismatch between term.id and term.obj"); - return -1; - } - } else { - term->obj.entity = obj; - if (finalize_term_identifier(world, term, &term->obj, name)) { - return -1; +ecs_iter_t ecs_query_iter( + const ecs_world_t *world, + const ecs_query_t *q) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); + + /* Ok, only for stats */ + ecs_os_linc(&ECS_CONST_CAST(ecs_query_t*, q)->eval_count); + + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_query_cache_t *cache = impl->cache; + if (cache) { + /* If monitors changed, do query rematching */ + ecs_flags32_t flags = q->flags; + if (!(ecs_world_get_flags(world) & EcsWorldReadonly) && + (flags & EcsQueryHasRefs)) + { + flecs_eval_component_monitors(q->world); } } - return 0; + return flecs_query_iter(world, q); } +/** + * @file query/engine/eval_member.c + * @brief Component member evaluation. + */ + + static -int verify_term_consistency( - const ecs_world_t *world, - const ecs_term_t *term, - const char *name) +bool flecs_query_member_cmp( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx, + bool neq) { - ecs_entity_t pred = entity_from_identifier(&term->pred); - ecs_entity_t obj = entity_from_identifier(&term->obj); - ecs_id_t role = term->role; - ecs_id_t id = term->id; - bool wildcard = pred == EcsWildcard || obj == EcsWildcard; - - if (obj && (!role || (role != ECS_PAIR && role != ECS_CASE))) { - term_error(world, term, name, - "invalid role for term with pair (expected ECS_PAIR)"); - return -1; + ecs_table_range_t range; + if (op->other) { + ecs_var_id_t table_var = flecs_itovar(op->other - 1); + range = flecs_query_var_get_range(table_var, ctx); + } else { + range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); } - if (role == ECS_CASE && !obj) { - term_error(world, term, name, - "missing object for term with ECS_CASE role"); - return -1; + ecs_table_t *table = range.table; + if (!table) { + return false; } - if (!pred) { - term_error(world, term, name, "missing predicate for term"); - return -1; - } + ecs_query_membereq_ctx_t *op_ctx = flecs_op_ctx(ctx, membereq); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; - if (role != (id & ECS_ROLE_MASK)) { - term_error(world, term, name, "mismatch between term.role & term.id"); - return -1; + if (!range.count) { + range.count = ecs_table_count(range.table); } - if (obj && !ECS_HAS_ROLE(id, PAIR) && !ECS_HAS_ROLE(id, CASE)) { - term_error(world, term, name, "term has object but id is not a pair"); - return -1; + int32_t row, end = range.count; + if (end) { + end += range.offset; + } else { + end = ecs_table_count(range.table); } - if (ECS_HAS_ROLE(id, PAIR) || ECS_HAS_ROLE(id, CASE)) { - if (!wildcard) { - role = ECS_ROLE_MASK & id; - if (id != (role | ecs_entity_t_comb( - term->obj.entity, term->pred.entity))) - { - char *id_str = ecs_id_str(world, ecs_pair(pred, obj)); - term_error(world, term, name, - "term id does not match pred/obj (%s)", id_str); - ecs_os_free(id_str); - return -1; - } - } - } else if (term->pred.entity != (id & ECS_COMPONENT_MASK)) { - if (!wildcard) { - char *pred_str = ecs_get_fullpath(world, term->pred.entity); - term_error(world, term, name, "term id does not match pred '%s'", - pred_str); - ecs_os_free(pred_str); - return -1; + void *data; + if (!redo) { + row = op_ctx->each.row = range.offset; + + /* Get data ptr starting from offset 0 so we can use row to index */ + range.offset = 0; + + /* Populate data field so we have the array we can compare the member + * value against. */ + data = op_ctx->data = + ecs_table_get_column(range.table, it->trs[field_index]->column, 0); + + it->ids[field_index] = ctx->query->pub.terms[op->term_index].id; + } else { + row = ++ op_ctx->each.row; + if (op_ctx->each.row >= end) { + return false; } + + data = op_ctx->data; } - if (term->pred.var == EcsVarIsEntity) { - const ecs_term_id_t *tsubj = &term->subj; - const ecs_term_id_t *tobj = &term->obj; + int32_t offset = (int32_t)op->first.entity; + int32_t size = (int32_t)(op->first.entity >> 32); + const ecs_entity_t *entities = ecs_table_entities(table); + ecs_entity_t e = 0; + ecs_entity_t *val; - if (ecs_term_id_is_set(tsubj) && ecs_term_id_is_set(tobj)) { - if (tsubj->var == tobj->var) { - bool is_same = false; + ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); /* Must be written */ + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - if (tsubj->var == EcsVarIsEntity) { - is_same = tsubj->entity == tobj->entity; - } else if (tsubj->name && tobj->name) { - is_same = !ecs_os_strcmp(tsubj->name, tobj->name); - } + bool second_written = true; + if (op->flags & (EcsQueryIsVar << EcsQuerySecond)) { + uint64_t written = ctx->written[ctx->op_index]; + second_written = written & (1ull << op->second.var); + } - if (is_same && ecs_has_id(world, term->pred.entity, EcsAcyclic) - && !ecs_has_id(world, term->pred.entity, EcsReflexive)) - { - char *pred_str = ecs_get_fullpath(world, term->pred.entity); - term_error(world, term, name, "term with acyclic relation" - " '%s' cannot have same subject and object", - pred_str); - ecs_os_free(pred_str); - return -1; + if (second_written) { + ecs_flags16_t second_flags = flecs_query_ref_flags( + op->flags, EcsQuerySecond); + ecs_entity_t second = flecs_get_ref_entity( + &op->second, second_flags, ctx); + + do { + e = entities[row]; + + val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); + if (val[0] == second || second == EcsWildcard) { + if (!neq) { + goto match; + } + } else { + if (neq) { + goto match; } } - } + + row ++; + } while (row < end); + + return false; + } else { + e = entities[row]; + val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); + flecs_query_var_set_entity(op, op->second.var, val[0], ctx); } - if (term->subj.set.relation && !term->subj.set.max_depth) { - if (!ecs_has_id(world, term->subj.set.relation, EcsAcyclic)) { - char *r_str = ecs_get_fullpath(world, term->subj.set.relation); - term_error(world, term, name, - "relation '%s' is used with SuperSet/SubSet but is not acyclic", - r_str); - ecs_os_free(r_str); - return -1; - } +match: + if (op->other) { + ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + flecs_query_var_set_entity(op, op->src.var, e, ctx); } - return 0; + ecs_entity_t mbr = ECS_PAIR_FIRST(it->ids[field_index]); + it->ids[field_index] = ecs_pair(mbr, val[0]); + + op_ctx->each.row = row; + + return true; } -bool ecs_identifier_is_0( - const char *id) +bool flecs_query_member_eq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - return id[0] == '0' && !id[1]; + return flecs_query_member_cmp(op, redo, ctx, false); } -const char* ecs_identifier_is_var( - const char *id) +bool flecs_query_member_neq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - if (!id) { - return NULL; - } + return flecs_query_member_cmp(op, redo, ctx, true); +} - /* Variable identifiers cannot start with a number */ - if (isdigit(id[0])) { - return NULL; - } +/** + * @file query/engine/eval_pred.c + * @brief Equality predicate evaluation. + */ - /* Identifiers that start with _ are variables */ - if (id[0] == '_' && id[1] != 0) { - return &id[1]; - } - return NULL; +static +const char* flecs_query_name_arg( + const ecs_query_op_t *op, + ecs_query_run_ctx_t *ctx) +{ + int8_t term_index = op->term_index; + const ecs_term_t *term = &ctx->query->pub.terms[term_index]; + return term->second.name; } -bool ecs_id_match( - ecs_id_t id, - ecs_id_t pattern) +static +bool flecs_query_compare_range( + const ecs_table_range_t *l, + const ecs_table_range_t *r) { - if (id == pattern) { - return true; + if (l->table != r->table) { + return false; } - if (ECS_HAS_ROLE(pattern, PAIR)) { - if (!ECS_HAS_ROLE(id, PAIR)) { + if (l->count) { + int32_t l_end = l->offset + l->count; + int32_t r_end = r->offset + r->count; + if (r->offset < l->offset) { + return false; + } + if (r_end > l_end) { return false; } + } else { + /* Entire table is matched */ + } - ecs_entity_t id_rel = ECS_PAIR_FIRST(id); - ecs_entity_t id_obj = ECS_PAIR_SECOND(id); - ecs_entity_t pattern_rel = ECS_PAIR_FIRST(pattern); - ecs_entity_t pattern_obj = ECS_PAIR_SECOND(pattern); + return true; +} - ecs_check(id_rel != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(id_obj != 0, ECS_INVALID_PARAMETER, NULL); +static +bool flecs_query_pred_eq_w_range( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx, + ecs_table_range_t r) +{ + if (redo) { + return false; + } - ecs_check(pattern_rel != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(pattern_obj != 0, ECS_INVALID_PARAMETER, NULL); - - if (pattern_rel == EcsWildcard) { - if (pattern_obj == EcsWildcard || pattern_obj == id_obj) { - return true; - } - } else if (pattern_obj == EcsWildcard) { - if (pattern_rel == id_rel) { - return true; - } + uint64_t written = ctx->written[ctx->op_index]; + ecs_var_id_t src_var = op->src.var; + if (!(written & (1ull << src_var))) { + /* left = unknown, right = known. Assign right-hand value to left */ + ecs_var_id_t l = src_var; + ctx->vars[l].range = r; + if (r.count == 1) { + ctx->vars[l].entity = ecs_table_entities(r.table)[r.offset]; } + return true; } else { - if ((id & ECS_ROLE_MASK) != (pattern & ECS_ROLE_MASK)) { + ecs_table_range_t l = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + + if (!flecs_query_compare_range(&l, &r)) { return false; } - if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { - return true; - } + ctx->vars[src_var].range.offset = r.offset; + ctx->vars[src_var].range.count = r.count; + return true; } - -error: - return false; } -bool ecs_id_is_pair( - ecs_id_t id) +bool flecs_query_pred_eq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - return ECS_HAS_ROLE(id, PAIR); + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized eq operand"); + + ecs_table_range_t r = flecs_query_get_range( + op, &op->second, EcsQuerySecond, ctx); + return flecs_query_pred_eq_w_range(op, redo, ctx, r); } -bool ecs_id_is_wildcard( - ecs_id_t id) +bool flecs_query_pred_eq_name( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - return - (id == EcsWildcard) || (ECS_HAS_ROLE(id, PAIR) && ( - (ECS_PAIR_FIRST(id) == EcsWildcard) || - (ECS_PAIR_SECOND(id) == EcsWildcard) - )) || - (id == EcsAny) || (ECS_HAS_ROLE(id, PAIR) && ( - (ECS_PAIR_FIRST(id) == EcsAny) || - (ECS_PAIR_SECOND(id) == EcsAny) - )); + const char *name = flecs_query_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return false; + } + + ecs_table_range_t r = flecs_range_from_entity(ctx->world, e); + return flecs_query_pred_eq_w_range(op, redo, ctx, r); } -bool ecs_id_is_valid( - const ecs_world_t *world, - ecs_id_t id) +bool flecs_query_pred_neq_w_range( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx, + ecs_table_range_t r) { - if (!id) { - return false; + ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + ecs_var_id_t src_var = op->src.var; + ecs_table_range_t l = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + + /* If tables don't match, neq always returns once */ + if (l.table != r.table) { + return true && !redo; } - if (ecs_id_is_wildcard(id)) { + + int32_t l_offset; + int32_t l_count; + if (!redo) { + /* Make sure we're working with the correct table count */ + if (!l.count && l.table) { + l.count = ecs_table_count(l.table); + } + + l_offset = l.offset; + l_count = l.count; + + /* Cache old value */ + op_ctx->range = l; + } else { + l_offset = op_ctx->range.offset; + l_count = op_ctx->range.count; + } + + /* If the table matches, a Neq returns twice: once for the slice before the + * excluded slice, once for the slice after the excluded slice. If the right + * hand range starts & overlaps with the left hand range, there is only + * one slice. */ + ecs_var_t *var = &ctx->vars[src_var]; + if (!redo && r.offset > l_offset) { + int32_t end = r.offset; + if (end > l_count) { + end = l_count; + } + + /* Return first slice */ + var->range.table = l.table; + var->range.offset = l_offset; + var->range.count = end - l_offset; + op_ctx->redo = false; + return true; + } else if (!op_ctx->redo) { + int32_t l_end = op_ctx->range.offset + l_count; + int32_t r_end = r.offset + r.count; + + if (l_end <= r_end) { + /* If end of existing range falls inside the excluded range, there's + * nothing more to return */ + var->range = l; + return false; + } + + /* Return second slice */ + var->range.table = l.table; + var->range.offset = r_end; + var->range.count = l_end - r_end; + + /* Flag so we know we're done the next redo */ + op_ctx->redo = true; + return true; + } else { + /* Restore previous value */ + var->range = l; return false; } - if (ECS_HAS_ROLE(id, PAIR)) { - if (!ECS_PAIR_FIRST(id)) { +} + +static +bool flecs_query_pred_match( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx, + bool is_neq) +{ + ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); + uint64_t written = ctx->written[ctx->op_index]; + ecs_assert(flecs_ref_is_written(op, &op->src, EcsQuerySrc, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized match operand"); + (void)written; + + ecs_var_id_t src_var = op->src.var; + const char *match = flecs_query_name_arg(op, ctx); + ecs_table_range_t l; + if (!redo) { + l = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + if (!l.table) { return false; } - if (!ECS_PAIR_SECOND(id)) { - return false; + + if (!l.count) { + l.count = ecs_table_count(l.table); } - } else if (id & ECS_ROLE_MASK) { - if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { + + op_ctx->range = l; + op_ctx->index = l.offset; + op_ctx->name_col = flecs_ito(int16_t, + ecs_table_get_type_index(ctx->world, l.table, + ecs_pair(ecs_id(EcsIdentifier), EcsName))); + if (op_ctx->name_col == -1) { + return is_neq; + } + op_ctx->name_col = flecs_ito(int16_t, + l.table->column_map[op_ctx->name_col]); + ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); + } else { + if (op_ctx->name_col == -1) { + /* Table has no name */ return false; } + + l = op_ctx->range; + } + + const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].data; + int32_t count = l.offset + l.count, offset = -1; + for (; op_ctx->index < count; op_ctx->index ++) { + const char *name = names[op_ctx->index].value; + bool result = strstr(name, match); + if (is_neq) { + result = !result; + } + + if (!result) { + if (offset != -1) { + break; + } + } else { + if (offset == -1) { + offset = op_ctx->index; + } + } + } + + if (offset == -1) { + ctx->vars[src_var].range = op_ctx->range; + return false; } + ctx->vars[src_var].range.offset = offset; + ctx->vars[src_var].range.count = (op_ctx->index - offset); return true; } -bool ecs_term_id_is_set( - const ecs_term_id_t *id) +bool flecs_query_pred_eq_match( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - return id->entity != 0 || id->name != NULL; + return flecs_query_pred_match(op, redo, ctx, false); } -bool ecs_term_is_initialized( - const ecs_term_t *term) +bool flecs_query_pred_neq_match( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - return term->id != 0 || ecs_term_id_is_set(&term->pred); + return flecs_query_pred_match(op, redo, ctx, true); } -bool ecs_term_is_trivial( - const ecs_term_t *term) +bool flecs_query_pred_neq( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - if (term->inout != EcsInOutDefault) { - return false; - } + uint64_t written = ctx->written[ctx->op_index]; (void)written; + ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), + ECS_INTERNAL_ERROR, + "invalid instruction sequence: uninitialized neq operand"); - if (term->subj.entity != EcsThis) { - return false; + ecs_table_range_t r = flecs_query_get_range( + op, &op->second, EcsQuerySecond, ctx); + return flecs_query_pred_neq_w_range(op, redo, ctx, r); +} + +bool flecs_query_pred_neq_name( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + const char *name = flecs_query_name_arg(op, ctx); + ecs_entity_t e = ecs_lookup(ctx->world, name); + if (!e) { + /* Entity doesn't exist */ + return true && !redo; } - if (term->subj.set.mask && (term->subj.set.mask != EcsSelf)) { + ecs_table_range_t r = flecs_range_from_entity(ctx->world, e); + return flecs_query_pred_neq_w_range(op, redo, ctx, r); +} + +/** + * @file query/engine/eval_sparse.c + * @brief Sparse component evaluation. + */ + + +static +bool flecs_query_sparse_init_sparse( + ecs_query_sparse_ctx_t *op_ctx, + ecs_component_record_t *cr) +{ + if (!cr) { return false; } - if (term->oper != EcsAnd && term->oper != EcsAndFrom) { + ecs_assert(cr->flags & EcsIdDontFragment, ECS_INTERNAL_ERROR, NULL); + + op_ctx->sparse = cr->sparse; + if (!op_ctx->sparse) { return false; } - if (term->name != NULL) { + int32_t count = flecs_sparse_count(op_ctx->sparse); + if (!count) { return false; } + op_ctx->cur = count; + return true; } -int ecs_term_finalize( - const ecs_world_t *world, - const char *name, - ecs_term_t *term) +static +void flecs_query_sparse_init_range( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_query_sparse_ctx_t *op_ctx) { - if (finalize_term_vars(world, term, name)) { - return -1; + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + if (!range.count) { + range.count = ecs_table_count(range.table); } - if (!term->id) { - if (finalize_term_id(world, term, name)) { - return -1; - } + op_ctx->range = range; + op_ctx->cur = range.offset - 1; +} + +static +bool flecs_query_sparse_next_entity( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_query_sparse_ctx_t *op_ctx, + bool not, + void **ptr_out) +{ + int32_t end = op_ctx->range.count + op_ctx->range.offset; + +next: + op_ctx->cur ++; + + if (op_ctx->cur == end) { + flecs_query_src_set_range(op, &op_ctx->range, ctx); + return false; + } + + ecs_entity_t e = ecs_table_entities(op_ctx->range.table)[op_ctx->cur]; + bool result; + + if (ptr_out) { + void *ptr = flecs_sparse_get(op_ctx->sparse, 0, e); + result = ptr != NULL; + *ptr_out = ptr; } else { - if (populate_from_term_id(world, term, name)) { - return -1; - } + result = flecs_sparse_has(op_ctx->sparse, e); } - if (finalize_term_identifiers(world, term, name)) { - return -1; + if (not) { + result = !result; } - if (!term_can_inherit(term)) { - if (term->subj.set.relation == EcsIsA) { - term->subj.set.relation = 0; - term->subj.set.mask = EcsSelf; - } + if (!result) { + goto next; } - if (term->role == ECS_AND || term->role == ECS_OR || term->role == ECS_NOT){ - /* AND/OR terms match >1 component, which is only valid as filter */ - if (term->inout != EcsInOutDefault && term->inout != EcsInOutFilter) { - term_error(world, term, name, "AND/OR terms must be filters"); - return -1; - } + flecs_query_src_set_single(op, op_ctx->cur, ctx); - term->inout = EcsInOutFilter; + return true; +} - /* Translate role to operator */ - if (term->role == ECS_AND) { - term->oper = EcsAndFrom; - } else - if (term->role == ECS_OR) { - term->oper = EcsOrFrom; - } else - if (term->role == ECS_NOT) { - term->oper = EcsNotFrom; - } +static +bool flecs_query_sparse_select_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_flags32_t table_mask, + ecs_id_t id) +{ + ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; - /* Zero out role & strip from id */ - term->id &= ECS_COMPONENT_MASK; - term->role = 0; + if (!redo) { + ecs_component_record_t *cr = flecs_components_get(ctx->world, id); + if (!flecs_query_sparse_init_sparse(op_ctx, cr)) { + return false; + } } - if (verify_term_consistency(world, term, name)) { - return -1; +next: + if (!(op_ctx->cur--)) { + return false; } - return 0; -} + ecs_assert(op_ctx->cur >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(op_ctx->cur < flecs_sparse_count(op_ctx->sparse), + ECS_INVALID_OPERATION, "sparse iterator invalidated while iterating"); -ecs_term_t ecs_term_copy( - const ecs_term_t *src) -{ - ecs_term_t dst = *src; - dst.name = ecs_os_strdup(src->name); - dst.pred.name = ecs_os_strdup(src->pred.name); - dst.subj.name = ecs_os_strdup(src->subj.name); - dst.obj.name = ecs_os_strdup(src->obj.name); - return dst; -} + ecs_entity_t e = flecs_sparse_ids(op_ctx->sparse)[op_ctx->cur]; + ecs_table_range_t range = flecs_range_from_entity(ctx->world, e); -ecs_term_t ecs_term_move( - ecs_term_t *src) -{ - if (src->move) { - ecs_term_t dst = *src; - src->name = NULL; - src->pred.name = NULL; - src->subj.name = NULL; - src->obj.name = NULL; - dst.move = false; - return dst; - } else { - ecs_term_t dst = ecs_term_copy(src); - dst.move = false; - return dst; + if (flecs_query_table_filter(range.table, op->other, table_mask)) { + goto next; } -} -void ecs_term_fini( - ecs_term_t *term) -{ - ecs_os_free(term->pred.name); - ecs_os_free(term->subj.name); - ecs_os_free(term->obj.name); - ecs_os_free(term->name); + flecs_query_var_set_range(op, op->src.var, + range.table, range.offset, range.count, ctx); + it->ids[field_index] = id; + flecs_query_set_vars(op, it->ids[field_index], ctx); - term->pred.name = NULL; - term->subj.name = NULL; - term->obj.name = NULL; - term->name = NULL; + return true; } -int ecs_filter_finalize( - const ecs_world_t *world, - ecs_filter_t *f) +static +bool flecs_query_sparse_select_wildcard( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_flags32_t table_mask, + ecs_id_t id) { - int32_t i, term_count = f->term_count, actual_count = 0; - ecs_term_t *terms = f->terms; - bool is_or = false, prev_or = false; - int32_t filter_terms = 0; - - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; + ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); - if (ecs_term_finalize(world, f->name, term)) { - return -1; + if (!redo) { + ecs_component_record_t *cr = flecs_components_get(ctx->world, id); + if (!cr) { + return false; } - is_or = term->oper == EcsOr; - actual_count += !(is_or && prev_or); - term->index = actual_count - 1; - prev_or = is_or; - - if (term->subj.entity == EcsThis) { - f->match_this = true; - if (term->subj.set.mask != EcsSelf) { - f->match_only_this = false; - } + if (ECS_PAIR_FIRST(id) == EcsWildcard) { + op_ctx->cr = cr->pair->second.next; } else { - f->match_only_this = false; + ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + op_ctx->cr = cr->pair->first.next; } + } else { + goto next_select; + } - if (term->id == EcsPrefab) { - f->match_prefab = true; - } - if (term->id == EcsDisabled) { - f->match_disabled = true; +next: + if (!flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { + return false; + } + +next_select: + if (flecs_query_sparse_select_id(op, true, ctx, table_mask, 0)) { + ecs_id_t actual_id = op_ctx->cr->id; + ctx->it->ids[op->field_index] = actual_id; + flecs_query_set_vars(op, actual_id, ctx); + + if (op->match_flags & EcsTermMatchAny) { + ctx->it->ids[op->field_index] = id; } - if (f->filter) { - term->inout = EcsInOutFilter; + return true; + } + +next_component: { + ecs_component_record_t *cr = op_ctx->cr; + if (ECS_PAIR_FIRST(id) == EcsWildcard) { + cr = op_ctx->cr = cr->pair->second.next; + } else { + ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + cr = op_ctx->cr = cr->pair->first.next; } - if (term->inout == EcsInOutFilter) { - filter_terms ++; + if (!cr) { + return false; } - if (term->oper != EcsNot || term->subj.entity != EcsThis) { - f->match_anything = false; + if (!(cr->flags & EcsIdDontFragment)) { + goto next_component; } } - f->term_count_actual = actual_count; + goto next; +} + +static +bool flecs_query_sparse_next_wildcard_pair( + ecs_query_sparse_ctx_t *op_ctx) +{ + ecs_component_record_t *cr = op_ctx->cr; + ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); + + do { + cr = cr->non_fragmenting.next; + } while (cr && (!ECS_IS_PAIR(cr->id) || ecs_id_is_wildcard(cr->id))); - if (filter_terms == term_count) { - f->filter = true; + if (!cr) { + return false; } - return 0; + op_ctx->cr = cr; + + ecs_assert(cr->flags & EcsIdDontFragment, ECS_INTERNAL_ERROR, NULL); + + return true; } -/* Implementation for iterable mixin */ static -void filter_iter_init( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) +bool flecs_query_sparse_select_all_wildcard_pairs( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_flags32_t table_mask) { - ecs_poly_assert(poly, ecs_filter_t); + ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); + + if (!redo) { + ecs_component_record_t *cr = op_ctx->cr = + ctx->world->cr_non_fragmenting_head; + if (!cr) { + return false; + } + + if (!ECS_IS_PAIR(cr->id)|| ecs_id_is_wildcard(cr->id)) { + goto next_component; + } + } else { + goto next_select; + } + +next: + if (!flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { + return false; + } + +next_select: + if (flecs_query_sparse_select_id(op, true, ctx, table_mask, 0)) { + ecs_id_t actual_id = op_ctx->cr->id; + ctx->it->ids[op->field_index] = actual_id; + flecs_query_set_vars(op, actual_id, ctx); + return true; + } + +next_component: + if (!flecs_query_sparse_next_wildcard_pair(op_ctx)) { + return false; + } + + goto next; +} - if (filter) { - iter[1] = ecs_filter_iter(world, (ecs_filter_t*)poly); - iter[0] = ecs_term_chain_iter(&iter[1], filter); +bool flecs_query_sparse_select( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_flags32_t table_mask) +{ + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (ecs_id_is_wildcard(id)) { + if (id == ecs_pair(EcsWildcard, EcsWildcard)) { + return flecs_query_sparse_select_all_wildcard_pairs( + op, redo, ctx, table_mask); + } else { + return flecs_query_sparse_select_wildcard( + op, redo, ctx, table_mask, id); + } } else { - iter[0] = ecs_filter_iter(world, (ecs_filter_t*)poly); + return flecs_query_sparse_select_id(op, redo, ctx, table_mask, id); } } -int ecs_filter_init( - const ecs_world_t *stage, - ecs_filter_t *filter_out, - const ecs_filter_desc_t *desc) +static +bool flecs_query_sparse_with_id( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool not, + ecs_id_t id, + void **ptr_out) { - ecs_filter_t f; - ecs_poly_init(&f, ecs_filter_t); + ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(filter_out != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); + if (!redo) { + ecs_component_record_t *cr = flecs_components_get(ctx->world, id); + if (!cr) { + return false; + } - const ecs_world_t *world = ecs_get_world(stage); + op_ctx->sparse = cr->sparse; + if (!op_ctx->sparse) { + return false; + } - int i, term_count = 0; - ecs_term_t *terms = desc->terms_buffer; - const char *name = desc->name; - const char *expr = desc->expr; + flecs_query_sparse_init_range(op, ctx, op_ctx); + } - /* Temporarily set the fields to the values provided in desc, until the - * filter has been validated. */ - f.name = (char*)name; - f.filter = desc->filter; - f.instanced = desc->instanced; - f.match_empty_tables = desc->match_empty_tables; - f.match_anything = true; + return flecs_query_sparse_next_entity(op, ctx, op_ctx, not, ptr_out); +} - if (terms) { - term_count = desc->terms_buffer_count; - } else { - terms = (ecs_term_t*)desc->terms; - for (i = 0; i < ECS_TERM_DESC_CACHE_SIZE; i ++) { - if (!ecs_term_is_initialized(&terms[i])) { - break; - } +static +bool flecs_query_sparse_with_exclusive( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool not, + ecs_id_t id) +{ + ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); - term_count ++; + if (!redo) { + if (!flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { + return false; } } - /* Temporarily set array from desc to filter, until the filter has been - * validated. */ - f.terms = terms; - f.term_count = term_count; - - if (expr) { -#ifdef FLECS_PARSER - int32_t buffer_count = 0; + ecs_id_t actual_id = op_ctx->cr->id; + void *tgt_ptr = NULL; + if (flecs_query_sparse_with_id(op, redo, ctx, not, actual_id, &tgt_ptr)) { + if (!not) { + ecs_entity_t tgt = *(ecs_entity_t*)tgt_ptr; + actual_id = ctx->it->ids[op->field_index] = + ecs_pair(ECS_PAIR_FIRST(actual_id), tgt); + } - /* If terms have already been set, copy buffer to allocated one */ - if (terms && term_count) { - terms = ecs_os_memdup(terms, term_count * ECS_SIZEOF(ecs_term_t)); - buffer_count = term_count; - } else { - terms = NULL; + if (not || (op->match_flags & EcsTermMatchAny)) { + ctx->it->ids[op->field_index] = id; } - /* Parse expression into array of terms */ - const char *ptr = desc->expr; - ecs_term_t term = {0}; - while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ - if (!ecs_term_is_initialized(&term)) { - break; - } - - if (term_count == buffer_count) { - buffer_count = buffer_count ? buffer_count * 2 : 8; - terms = ecs_os_realloc(terms, - buffer_count * ECS_SIZEOF(ecs_term_t)); - } + flecs_query_set_vars(op, actual_id, ctx); + return true; + } - /* Check for identifiers that have a name that starts with _. If the - * variable kind is left to Default, the kind should be set to - * variable and the _ prefix should be removed. */ - finalize_term_vars(world, &term, name); + return false; +} - terms[term_count] = term; - term_count ++; +static +bool flecs_query_sparse_with_wildcard( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool not, + ecs_id_t id) +{ + ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); + bool with_redo = false; - if (ptr[0] == '\n') { - break; - } + if (!redo) { + ecs_component_record_t *cr = flecs_components_get(ctx->world, id); + if (!cr) { + return false; } - f.terms = terms; - f.term_count = term_count; - - if (!ptr) { - goto error; + if (cr->flags & EcsIdExclusive) { + op_ctx->cr = cr; + op_ctx->exclusive = true; + return flecs_query_sparse_with_exclusive(op, false, ctx, not, id); } -#else - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); -#endif - } - /* Copy term resources. */ - if (term_count) { - ecs_term_t *dst_terms = terms; - if (!expr) { - if (term_count <= ECS_TERM_CACHE_SIZE) { - dst_terms = f.term_cache; - f.term_cache_used = true; + if (!not) { + if (ECS_PAIR_FIRST(id) == EcsWildcard) { + op_ctx->cr = cr->pair->second.next; } else { - dst_terms = ecs_os_malloc_n(ecs_term_t, term_count); + ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + op_ctx->cr = cr->pair->first.next; } + } else { + op_ctx->cr = cr; } - if (terms != dst_terms) { - ecs_os_memcpy_n(dst_terms, terms, ecs_term_t, term_count); + if (!op_ctx->cr) { + return not; } - - f.terms = dst_terms; } else { - f.terms = NULL; - } - - /* Ensure all fields are consistent and properly filled out */ - if (ecs_filter_finalize(world, &f)) { - goto error; + if (op_ctx->exclusive) { + return flecs_query_sparse_with_exclusive(op, true, ctx, not, id); + } + with_redo = true; + goto next_select; } - *filter_out = f; - if (f.term_cache_used) { - filter_out->terms = filter_out->term_cache; +next: + if (!flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { + return not; } - filter_out->name = ecs_os_strdup(desc->name); - filter_out->variable_names[0] = (char*)"."; +next_select: { + ecs_id_t actual_id = op_ctx->cr->id; - ecs_assert(!filter_out->term_cache_used || - filter_out->terms == filter_out->term_cache, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(filter_out->term_count == f.term_count, - ECS_INTERNAL_ERROR, NULL); + if (flecs_query_sparse_with_id(op, with_redo, ctx, not, actual_id, NULL)) { + ctx->it->ids[op->field_index] = actual_id; + flecs_query_set_vars(op, actual_id, ctx); - filter_out->iterable.init = filter_iter_init; + if (op->match_flags & EcsTermMatchAny) { + ctx->it->ids[op->field_index] = id; + } - return 0; -error: - if (f.name == desc->name) { - f.name = NULL; + return true; + } } - ecs_filter_fini(&f); - - return -1; -} - -void ecs_filter_copy( - ecs_filter_t *dst, - const ecs_filter_t *src) -{ - if (src) { - *dst = *src; - - int32_t term_count = src->term_count; - - if (src->term_cache_used) { - dst->terms = dst->term_cache; +next_component: { + ecs_component_record_t *cr = op_ctx->cr; + if (!not) { + if (ECS_PAIR_FIRST(id) == EcsWildcard) { + cr = op_ctx->cr = cr->pair->second.next; + } else { + ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + cr = op_ctx->cr = cr->pair->first.next; + } } else { - dst->terms = ecs_os_memdup_n(src->terms, ecs_term_t, term_count); + cr = NULL; } - int i; - for (i = 0; i < term_count; i ++) { - dst->terms[i] = ecs_term_copy(&src->terms[i]); + if (!cr) { + return false; + } + + if (!(cr->flags & EcsIdDontFragment)) { + goto next_component; } - } else { - ecs_os_memset_t(dst, 0, ecs_filter_t); } + + with_redo = false; + goto next; } -void ecs_filter_move( - ecs_filter_t *dst, - ecs_filter_t *src) +static +bool flecs_query_sparse_with_all_wildcard_pairs( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool not) { - if (src) { - *dst = *src; + ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); + bool with_redo = false; - if (src->term_cache_used) { - dst->terms = dst->term_cache; + if (!redo) { + ecs_component_record_t *cr = op_ctx->cr = + ctx->world->cr_non_fragmenting_head; + if (!cr) { + return false; } - if (dst != src) { - src->terms = NULL; - src->term_count = 0; + if (!ECS_IS_PAIR(cr->id) || ecs_id_is_wildcard(cr->id)) { + goto next_component; } } else { - ecs_os_memset_t(dst, 0, ecs_filter_t); + with_redo = true; + goto next_select; } -} -void ecs_filter_fini( - ecs_filter_t *filter) -{ - if (filter->terms) { - int i, count = filter->term_count; - for (i = 0; i < count; i ++) { - ecs_term_fini(&filter->terms[i]); - } +next: + if (!flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { + return false; + } - if (!filter->term_cache_used) { - ecs_os_free(filter->terms); +next_select: { + ecs_id_t actual_id = op_ctx->cr->id; + if (flecs_query_sparse_with_id(op, with_redo, ctx, not, actual_id, NULL)) { + ctx->it->ids[op->field_index] = actual_id; + flecs_query_set_vars(op, actual_id, ctx); + return true; } } - ecs_os_free(filter->name); +next_component: + if (!flecs_query_sparse_next_wildcard_pair(op_ctx)) { + return false; + } - filter->terms = NULL; - filter->name = NULL; + with_redo = false; + goto next; } -static -void filter_str_add_id( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_term_id_t *id, - bool is_subject, - uint8_t default_set_mask) -{ - if (id->name) { - ecs_strbuf_appendstr(buf, id->name); - } else if (id->entity) { - bool id_added = false; - if (!is_subject || id->entity != EcsThis) { - char *path = ecs_get_fullpath(world, id->entity); - ecs_strbuf_appendstr(buf, path); - ecs_os_free(path); - id_added = true; +bool flecs_query_sparse_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool not) +{ + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (ecs_id_is_wildcard(id)) { + if (id == ecs_pair(EcsWildcard, EcsWildcard)) { + return flecs_query_sparse_with_all_wildcard_pairs(op, redo, ctx, not); + } else { + return flecs_query_sparse_with_wildcard(op, redo, ctx, not, id); } + } else { + return flecs_query_sparse_with_id(op, redo, ctx, not, id, NULL); + } +} - if (id->set.mask != default_set_mask) { - if (id_added) { - ecs_strbuf_list_push(buf, ":", "|"); - } else { - ecs_strbuf_list_push(buf, "", "|"); - } - if (id->set.mask & EcsSelf) { - ecs_strbuf_list_appendstr(buf, "self"); - } - if (id->set.mask & EcsSuperSet) { - ecs_strbuf_list_appendstr(buf, "superset"); - } - if (id->set.mask & EcsSubSet) { - ecs_strbuf_list_appendstr(buf, "subset"); - } - - if (id->set.relation != EcsIsA) { - ecs_strbuf_list_push(buf, "(", ""); - - char *rel_path = ecs_get_fullpath(world, id->set.relation); - ecs_strbuf_appendstr(buf, rel_path); - ecs_os_free(rel_path); - - ecs_strbuf_list_pop(buf, ")"); +bool flecs_query_sparse_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + if (!redo) { + /* Can use regular up traversal since sparse components are resolved + * by the traversal cache */ + if (!flecs_query_up_with(op, redo, ctx)) { + return false; } - ecs_strbuf_list_pop(buf, ""); + return true; + } else { + return false; } } else { - ecs_strbuf_appendstr(buf, "0"); + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectUp, FlecsQueryUpSelectSparse); } } -static -void term_str_w_strbuf( - const ecs_world_t *world, - const ecs_term_t *term, - ecs_strbuf_t *buf) +bool flecs_query_sparse_self_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - const ecs_term_id_t *subj = &term->subj; - const ecs_term_id_t *obj = &term->obj; + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); - const uint8_t def_pred_mask = EcsSelf|EcsSubSet; - const uint8_t def_subj_mask = EcsSelf|EcsSuperSet; - const uint8_t def_obj_mask = EcsSelf; + if (!redo) { + op_ctx->self = true; + } - bool pred_set = ecs_term_id_is_set(&term->pred); - bool subj_set = ecs_term_id_is_set(subj); - bool obj_set = ecs_term_id_is_set(obj); + if (op_ctx->self) { + bool result = flecs_query_sparse_with(op, redo, ctx, false); + if (result) { + return true; + } - if (term->oper == EcsNot) { - ecs_strbuf_appendstr(buf, "!"); - } else if (term->oper == EcsOptional) { - ecs_strbuf_appendstr(buf, "?"); - } + op_ctx->self = false; - if (!subj_set) { - filter_str_add_id(world, buf, &term->pred, false, def_pred_mask); - ecs_strbuf_appendstr(buf, "()"); - } else if (subj_set && subj->entity == EcsThis && - subj->set.mask == def_subj_mask) - { - if (term->id) { - char *str = ecs_id_str(world, term->id); - ecs_strbuf_appendstr(buf, str); - ecs_os_free(str); - } else if (pred_set) { - filter_str_add_id(world, buf, &term->pred, false, def_pred_mask); + if (!flecs_query_up_with(op, false, ctx)) { + return false; + } + + flecs_query_sparse_init_range(op, ctx, op_ctx); } + + return flecs_query_sparse_next_entity(op, ctx, op_ctx, true, NULL); } else { - if (term->role && term->role != ECS_PAIR) { - ecs_strbuf_appendstr(buf, ecs_role_str(term->role)); - ecs_strbuf_appendch(buf, '|'); - } + ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); - filter_str_add_id(world, buf, &term->pred, false, def_pred_mask); - ecs_strbuf_appendstr(buf, "("); - filter_str_add_id(world, buf, &term->subj, true, def_subj_mask); - if (obj_set) { - ecs_strbuf_appendstr(buf, ","); - filter_str_add_id(world, buf, &term->obj, false, def_obj_mask); + if (!redo) { + op_ctx->self = true; } - ecs_strbuf_appendstr(buf, ")"); - } -} - -char* ecs_term_str( - const ecs_world_t *world, - const ecs_term_t *term) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; - term_str_w_strbuf(world, term, &buf); - return ecs_strbuf_get(&buf); -} -char* ecs_filter_str( - const ecs_world_t *world, - const ecs_filter_t *filter) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; + bool up_redo = true; - ecs_check(!filter->term_cache_used || filter->terms == filter->term_cache, - ECS_INVALID_PARAMETER, NULL); + if (op_ctx->self) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + if (flecs_query_sparse_select_id(op, redo, ctx, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled), id)) + { + return true; + } - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; - int32_t or_count = 0; + op_ctx->self = false; + up_redo = false; - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; + goto next_table; + } - if (i) { - if (terms[i - 1].oper == EcsOr && term->oper == EcsOr) { - ecs_strbuf_appendstr(&buf, " || "); - } else { - ecs_strbuf_appendstr(&buf, ", "); +next_entity: { + bool result = flecs_query_sparse_next_entity( + op, ctx, op_ctx, true, NULL); + if (!result) { + op_ctx->cur = op_ctx->prev_cur; + op_ctx->range = op_ctx->prev_range; + goto next_table; } - } - if (term->oper != EcsOr) { - or_count = 0; + return true; } - if (or_count < 1) { - if (term->inout == EcsIn) { - ecs_strbuf_appendstr(&buf, "[in] "); - } else if (term->inout == EcsInOut) { - ecs_strbuf_appendstr(&buf, "[inout] "); - } else if (term->inout == EcsOut) { - ecs_strbuf_appendstr(&buf, "[out] "); - } else if (term->inout == EcsInOutFilter) { - ecs_strbuf_appendstr(&buf, "[filter] "); +next_table: { + if (!flecs_query_up_select(op, up_redo, ctx, + FlecsQueryUpSelectUp, FlecsQueryUpSelectSparse)) + { + return false; } - } - if (term->oper == EcsOr) { - or_count ++; - } + op_ctx->prev_cur = op_ctx->cur; + op_ctx->prev_range = op_ctx->range; - term_str_w_strbuf(world, term, &buf); - } + flecs_query_sparse_init_range(op, ctx, op_ctx); - return ecs_strbuf_get(&buf); -error: - return NULL; + goto next_entity; + } + } } -int32_t ecs_filter_find_this_var( - const ecs_filter_t *filter) +bool flecs_query_sparse( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); - - if (filter->match_this) { - /* Filters currently only support the This variable at index 0. Only - * return 0 if filter actually has terms for the This variable. */ - return 0; + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_sparse_with(op, redo, ctx, false); + } else { + return flecs_query_sparse_select(op, redo, ctx, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } - -error: - return -1; } -/* Check if the id is a pair that has Any as first or second element. Any - * pairs behave just like Wildcard pairs and reuses the same data structures, - * with as only difference that the number of results returned for an Any pair - * is never more than one. This function is used to tell the difference. */ -static -bool is_any_pair( - ecs_id_t id) -{ - if (!ECS_HAS_ROLE(id, PAIR)) { - return false; - } +/** + * @file query/engine/eval_toggle.c + * @brief Bitset toggle evaluation. + */ - if (ECS_PAIR_FIRST(id) == EcsAny) { - return true; - } - if (ECS_PAIR_SECOND(id) == EcsAny) { - return true; - } - return false; +typedef struct { + ecs_flags64_t mask; + bool has_bitset; +} flecs_query_row_mask_t; + +/* Portable count-trailing-zeros for 64-bit values. Input must be nonzero. */ +static inline int32_t flecs_ctz64(uint64_t v) { +#if defined(__clang__) || defined(__GNUC__) + return (int32_t)__builtin_ctzll(v); +#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + unsigned long idx; + _BitScanForward64(&idx, v); + return (int32_t)idx; +#else + int32_t count = 0; + while ((v & 1u) == 0u) { + v >>= 1; + count ++; + } + return count; +#endif } static -ecs_id_t actual_match_id( - ecs_id_t id) -{ - /* Table types don't store CASE, so replace it with corresponding SWITCH */ - if (ECS_HAS_ROLE(id, CASE)) { - return ECS_SWITCH | ECS_PAIR_FIRST(id); - } +flecs_query_row_mask_t flecs_query_get_row_mask( + ecs_iter_t *it, + ecs_table_t *table, + int32_t block_index, + ecs_flags64_t and_fields, + ecs_flags64_t not_fields, + ecs_query_toggle_ctx_t *op_ctx) +{ + ecs_flags64_t mask = UINT64_MAX; + int32_t i, field_count = it->field_count; + ecs_flags64_t fields = and_fields | not_fields; + bool has_bitset = false; + + for (i = 0; i < field_count; i ++) { + uint64_t field_bit = 1llu << i; + if (!(fields & field_bit)) { + continue; + } - /* If the id is a pair and contains Any wildcards, replace them with * */ - if (ECS_HAS_ROLE(id, PAIR)) { - if (ECS_PAIR_FIRST(id) == EcsAny) { - id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); + if (not_fields & field_bit) { + it->set_fields &= (ecs_termset_t)~field_bit; + } else if (and_fields & field_bit) { + ecs_assert(it->set_fields & field_bit, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); } - if (ECS_PAIR_SECOND(id) == EcsAny) { - id = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); + + ecs_id_t id = it->ids[i]; + ecs_bitset_t *bs = flecs_table_get_toggle(table, id); + if (!bs) { + if (not_fields & field_bit) { + if (op_ctx->prev_set_fields & field_bit) { + has_bitset = false; + break; + } + } + continue; + } + + ecs_assert((64 * block_index) < bs->size, ECS_INTERNAL_ERROR, NULL); + ecs_flags64_t block = bs->data[block_index]; + + if (not_fields & field_bit) { + block = ~block; } + mask &= block; + has_bitset = true; } - return id; + return (flecs_query_row_mask_t){ mask, has_bitset }; } static -bool flecs_n_term_match_table( - ecs_world_t *world, - const ecs_term_t *term, - const ecs_table_t *table, - ecs_type_t type, - ecs_id_t *id_out, - int32_t *column_out, - ecs_entity_t *subject_out, - int32_t *match_index_out, - bool first, - ecs_flags32_t iter_flags) +bool flecs_query_toggle_for_up( + ecs_iter_t *it, + ecs_flags64_t and_fields, + ecs_flags64_t not_fields) { - (void)column_out; - - ecs_entity_t type_id = term->id; - ecs_oper_kind_t oper = term->oper; + int32_t i, field_count = it->field_count; + ecs_flags64_t fields = (and_fields | not_fields) & it->up_fields; - const EcsType *term_type = ecs_get(world, type_id, EcsType); - ecs_check(term_type != NULL, ECS_INVALID_PARAMETER, NULL); + for (i = 0; i < field_count; i ++) { + uint64_t field_bit = 1llu << i; + if (!(fields & field_bit)) { + continue; + } - ecs_id_t *ids = ecs_vector_first(term_type->normalized->type, ecs_id_t); - int32_t i, count = ecs_vector_count(term_type->normalized->type); - ecs_term_t temp = *term; - temp.oper = EcsAnd; + bool match = false; + if ((it->set_fields & field_bit)) { + ecs_entity_t src = it->sources[i]; + ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); + match = ecs_is_enabled_id(it->world, src, it->ids[i]); + } - for (i = 0; i < count; i ++) { - temp.id = ids[i]; - bool result = flecs_term_match_table(world, &temp, table, type, id_out, - 0, subject_out, match_index_out, first, iter_flags); - if (!result && oper == EcsAndFrom) { - return false; - } else - if (result && oper == EcsOrFrom) { - return true; + if (field_bit & not_fields) { + match = !match; } - } - if (oper == EcsAndFrom) { - if (id_out) { - id_out[0] = type_id; + if (!match) { + return false; } - return true; - } else - if (oper == EcsOrFrom) { - return false; } -error: - return false; + return true; } -bool flecs_term_match_table( - ecs_world_t *world, - const ecs_term_t *term, - const ecs_table_t *table, - ecs_type_t type, - ecs_id_t *id_out, - int32_t *column_out, - ecs_entity_t *subject_out, - int32_t *match_index_out, - bool first, - ecs_flags32_t iter_flags) +static +bool flecs_query_toggle_cmp( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx, + ecs_flags64_t and_fields, + ecs_flags64_t not_fields) { - const ecs_term_id_t *subj = &term->subj; - ecs_oper_kind_t oper = term->oper; - const ecs_table_t *match_table = table; - ecs_type_t match_type = type; - ecs_id_t id = term->id; + ecs_iter_t *it = ctx->it; + ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + ecs_table_t *table = range.table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t subj_entity = subj->entity; - if (!subj_entity) { - id_out[0] = id; /* no source corresponds with Nothing set mask */ - return true; + if ((and_fields & op_ctx->prev_set_fields) != and_fields) { + /* If not all fields matching and toggles are set, table can't match */ + return false; } - if (oper == EcsAndFrom || oper == EcsOrFrom) { - return flecs_n_term_match_table(world, term, table, type, id_out, column_out, - subject_out, match_index_out, first, iter_flags); - } + ecs_flags32_t up_fields = it->up_fields; + if (!redo) { + if (up_fields & (and_fields|not_fields)) { + /* If there are toggle fields that were matched with query + * traversal, evaluate those separately. */ + if (!flecs_query_toggle_for_up(it, and_fields, not_fields)) { + return false; + } - /* If source is not This, search in table of source */ - if (subj_entity != EcsThis) { - if (iter_flags & EcsIterEntityOptional) { - /* Treat entity terms as optional */ - oper = EcsOptional; + it->set_fields &= (ecs_termset_t)~(not_fields & up_fields); } + } + + /* Shared fields are evaluated, can be ignored from now on */ + // and_fields &= ~up_fields; + not_fields &= flecs_uto(uint64_t, ~up_fields); - match_table = ecs_get_table(world, subj_entity); - if (match_table) { - match_type = match_table->type; - } else if (oper != EcsOptional) { + if (!(table->flags & EcsTableHasToggle)) { + if (not_fields) { + /* If any of the toggle fields with a not operator are for fields + * that are set, without a bitset those fields can't match. */ return false; + } else { + /* If table doesn't have toggles but query matched toggleable + * components, all entities match. */ + if (!redo) { + return true; + } else { + return false; + } } - } else { - /* If filter contains This terms, a table must be provided */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); } - if (!match_type) { - return false; + if (table && !range.count) { + range.count = ecs_table_count(table); + if (!range.count) { + return false; + } } - ecs_entity_t source = 0; + int32_t last, block_index, cur; + uint64_t block = 0; + if (!redo) { + op_ctx->range = range; + cur = op_ctx->cur = range.offset; + block_index = op_ctx->block_index = -1; + last = range.offset + range.count; + } else { + if (!op_ctx->has_bitset) { + goto done; + } - /* If first = false, we're searching from an offset. This supports returning - * multiple results when using wildcard filters. */ - int32_t column = 0; - if (!first && column_out && column_out[0] != 0) { - column = column_out[0]; - if (column < 0) { - /* In case column is not from This, flip sign */ - column = -column; + last = op_ctx->range.offset + op_ctx->range.count; + cur = op_ctx->cur; + ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); + if (cur == last) { + goto done; } - /* Remove base 1 offset */ - column --; + block_index = op_ctx->block_index; + block = op_ctx->block; } - /* Find location, source and id of match in table type */ - ecs_table_record_t *tr = 0; - bool is_any = is_any_pair(id); + /* If end of last iteration is start of new block, compute new block */ + int32_t new_block_index = cur / 64; + if (new_block_index != block_index) { +compute_block: + block_index = op_ctx->block_index = new_block_index; - column = ecs_search_relation(world, match_table, - column, actual_match_id(id), subj->set.relation, subj->set.min_depth, - subj->set.max_depth, &source, id_out, 0, &tr); + flecs_query_row_mask_t row_mask = flecs_query_get_row_mask( + it, table, block_index, and_fields, not_fields, op_ctx); - if (tr && match_index_out) { - if (!is_any) { - match_index_out[0] = tr->count; - } else { - match_index_out[0] = 1; + /* If table doesn't have bitset columns, all columns match */ + if (!(op_ctx->has_bitset = row_mask.has_bitset)) { + if (!not_fields) { + return true; + } else { + goto done; + } } - } - bool result = column != -1; + /* No enabled bits */ + block = row_mask.mask; + if (!block) { +next_block: + new_block_index ++; + cur = new_block_index * 64; + if (cur >= last) { + /* No more rows */ + goto done; + } - if (oper == EcsNot) { - if (match_index_out) { - match_index_out[0] = 1; + op_ctx->cur = cur; + goto compute_block; } - result = !result; - } - if (oper == EcsOptional) { - result = true; + op_ctx->block = block; } - if (!result) { - return false; - } + /* Find first enabled bit using bit operations */ + int32_t first_bit = cur - (block_index * 64); + int32_t last_bit = ECS_MIN(64, last - (block_index * 64)); + ecs_assert(first_bit >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(first_bit < 64, ECS_INTERNAL_ERROR, NULL); + ecs_assert(last_bit >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(last_bit <= 64, ECS_INTERNAL_ERROR, NULL); + ecs_assert(last_bit >= first_bit, ECS_INTERNAL_ERROR, NULL); - if (subj_entity != EcsThis) { - if (!source) { - source = subj_entity; - } - } + uint64_t low_mask = (first_bit == 0) ? UINT64_MAX : (~0ull << first_bit); + uint64_t high_mask = (last_bit == 64) ? UINT64_MAX : ((1ull << last_bit) - 1ull); + uint64_t masked = block & low_mask & high_mask; - if (id_out && column < 0) { - id_out[0] = id; + if (!masked) { + goto next_block; } - if (column_out) { - if (column >= 0) { - column ++; - if (source != 0) { - column *= -1; - } - column_out[0] = column; - } else { - column_out[0] = 0; - } + int32_t tz = flecs_ctz64(masked); + uint64_t run = masked >> tz; /* ones from start of run */ + int32_t run_len; + if (~run == 0ull) { + run_len = 64 - tz; + } else { + run_len = flecs_ctz64(~run); } - - if (subject_out) { - subject_out[0] = source; + int32_t max_len = last_bit - tz; + if (run_len > max_len) { + run_len = max_len; } - return result; -} - -bool flecs_filter_match_table( - ecs_world_t *world, - const ecs_filter_t *filter, - const ecs_table_t *table, - ecs_id_t *ids, - int32_t *columns, - ecs_entity_t *subjects, - int32_t *match_indices, - int32_t *matches_left, - bool first, - int32_t skip_term, - ecs_flags32_t iter_flags) -{ - ecs_assert(!filter->term_cache_used || filter->terms == filter->term_cache, - ECS_INTERNAL_ERROR, NULL); - - ecs_type_t type = NULL; - if (table) { - type = table->type; - } + int32_t row = tz + (block_index * 64); + cur = tz + run_len + (block_index * 64); - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; + ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); - bool is_or = false; - bool or_result = false; - int32_t match_count = 1; - if (matches_left) { - match_count = *matches_left; + if (!(cur - row)) { + goto done; } - for (i = 0; i < count; i ++) { - if (i == skip_term) { - continue; - } - - ecs_term_t *term = &terms[i]; - ecs_term_id_t *subj = &term->subj; - ecs_oper_kind_t oper = term->oper; - const ecs_table_t *match_table = table; - ecs_type_t match_type = type; - int32_t t_i = term->index; - - if (!is_or && oper == EcsOr) { - is_or = true; - or_result = false; - } else if (is_or && oper != EcsOr) { - if (!or_result) { - return false; - } - - is_or = false; - } - - ecs_entity_t subj_entity = subj->entity; - if (!subj_entity) { - if (ids) { - ids[t_i] = term->id; - } - continue; - } - - if (subj_entity != EcsThis) { - match_table = ecs_get_table(world, subj_entity); - if (match_table) { - match_type = match_table->type; - } else { - match_type = NULL; - } - } else { - /* If filter contains This terms, table must be provided */ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - } + flecs_query_src_set_range(op, &(ecs_table_range_t){ + .table = table, .offset = row, .count = cur - row + }, ctx); + op_ctx->cur = cur; - int32_t match_index = 0; + return true; - bool result = flecs_term_match_table(world, term, match_table, - match_type, - ids ? &ids[t_i] : NULL, - columns ? &columns[t_i] : NULL, - subjects ? &subjects[t_i] : NULL, - &match_index, - first, - iter_flags); +done: + /* Restore range & set fields */ + flecs_query_src_set_range(op, &op_ctx->range, ctx); + it->set_fields = op_ctx->prev_set_fields; - if (is_or) { - or_result |= result; - if (result) { - /* If Or term matched, skip following Or terms */ - for (; i < count && terms[i].oper == EcsOr; i ++) { } - i -- ; - } - } else if (!result) { - return false; - } + return false; +} - if (first && match_index) { - match_count *= match_index; - } - if (match_indices) { - match_indices[t_i] = match_index; - } +bool flecs_query_toggle( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + ecs_iter_t *it = ctx->it; + ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); + if (!redo) { + op_ctx->prev_set_fields = it->set_fields; } - if (matches_left) { - *matches_left = match_count; - } + ecs_flags64_t and_fields = op->first.entity; + ecs_flags64_t not_fields = op->second.entity & op_ctx->prev_set_fields; - return !is_or || or_result; + return flecs_query_toggle_cmp( + op, redo, ctx, and_fields, not_fields); } -static -void term_iter_init_no_data( - ecs_term_iter_t *iter) +bool flecs_query_toggle_option( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) { - iter->term = (ecs_term_t){ .index = -1 }; - iter->self_index = NULL; - iter->index = 0; -} + ecs_iter_t *it = ctx->it; + ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); + if (!redo) { + op_ctx->prev_set_fields = it->set_fields; + op_ctx->optional_not = false; + op_ctx->has_bitset = false; + } -static -void term_iter_init_w_idr( - ecs_term_iter_t *iter, - ecs_id_record_t *idr, - bool empty_tables) -{ - if (idr) { - if (empty_tables) { - if ((empty_tables = flecs_table_cache_empty_iter( - &idr->cache, &iter->it))) - { - iter->empty_tables = true; - } - } +repeat: {} + ecs_flags64_t and_fields = 0, not_fields = 0; + if (op_ctx->optional_not) { + not_fields = op->first.entity & op_ctx->prev_set_fields; + } else { + and_fields = op->first.entity; + } - if (!empty_tables) { - flecs_table_cache_iter(&idr->cache, &iter->it); + bool result = flecs_query_toggle_cmp( + op, redo, ctx, and_fields, not_fields); + if (!result) { + if (!op_ctx->optional_not) { + /* Run the not-branch of optional fields */ + op_ctx->optional_not = true; + it->set_fields = op_ctx->prev_set_fields; + redo = false; + goto repeat; } - } else { - term_iter_init_no_data(iter); } - iter->index = 0; + return result; } -static -void term_iter_init_wildcard( - const ecs_world_t *world, - ecs_term_iter_t *iter, - bool empty_tables) -{ - iter->term = (ecs_term_t){ .index = -1 }; - iter->self_index = flecs_get_id_record(world, EcsAny); - ecs_id_record_t *idr = iter->cur = iter->self_index; - term_iter_init_w_idr(iter, idr, empty_tables); -} -static -void term_iter_init( - const ecs_world_t *world, - ecs_term_t *term, - ecs_term_iter_t *iter, - bool empty_tables) -{ - const ecs_term_id_t *subj = &term->subj; +/** + * @file query/engine/eval_trav.c + * @brief Transitive/reflexive relationship traversal. + */ - iter->term = *term; - if (subj->set.mask == EcsDefaultSet || subj->set.mask & EcsSelf) { - iter->self_index = flecs_get_id_record(world, - actual_match_id(term->id)); +static +bool flecs_query_trav_fixed_src_reflexive( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_table_range_t *range, + ecs_entity_t trav, + ecs_entity_t second) +{ + ecs_table_t *table = range->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t count = range->count; + if (!count) { + count = ecs_table_count(table); } - if (subj->set.mask & EcsSuperSet) { - iter->set_index = flecs_get_id_record(world, - ecs_pair(subj->set.relation, EcsWildcard)); + int32_t i = range->offset, end = i + count; + for (; i < end; i ++) { + if (entities[i] == second) { + /* Even though table doesn't have the specific relationship + * pair, the relationship is reflexive and the target entity + * is stored in the table. */ + break; + } } - - ecs_id_record_t *idr; - if (iter->self_index) { - idr = iter->cur = iter->self_index; - } else { - idr = iter->cur = iter->set_index; + if (i == end) { + /* Table didn't contain target entity */ + return false; + } + if (count > 1) { + /* If the range contains more than one entity, set the range to + * return only the entity matched by the reflexive property. */ + ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[op->src.var]; + ecs_table_range_t *var_range = &var->range; + var_range->offset = i; + var_range->count = 1; + var->entity = entities[i]; } - term_iter_init_w_idr(iter, idr, empty_tables); + flecs_query_set_trav_match(op, NULL, trav, second, ctx); + return true; } -ecs_iter_t ecs_term_iter( - const ecs_world_t *stage, - ecs_term_t *term) +static +bool flecs_query_trav_unknown_src_reflexive( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_entity_t trav, + ecs_entity_t second) { - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term->id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, + ECS_INTERNAL_ERROR, NULL); + ecs_var_id_t src_var = op->src.var; + flecs_query_var_set_entity(op, src_var, second, ctx); + flecs_query_var_get_table(src_var, ctx); - const ecs_world_t *world = ecs_get_world(stage); + ecs_table_t *table = ctx->vars[src_var].range.table; + if (table) { + if (flecs_query_table_filter(table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + return false; + } + } - flecs_process_pending_tables(world); + flecs_query_set_trav_match(op, NULL, trav, second, ctx); + return true; +} - if (ecs_term_finalize(world, NULL, term)) { - ecs_throw(ECS_INVALID_PARAMETER, NULL); +static +bool flecs_query_trav_fixed_src_up_fixed_second( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; /* If everything's fixed, can only have a single result */ } - ecs_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = (ecs_world_t*)stage, - .term_count = 1, - .next = ecs_term_next - }; + ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); + ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); + ecs_table_t *table = range.table; - /* Term iter populates the iterator with arrays from its own cache, ensure - * they don't get overwritten by flecs_iter_validate. - * - * Note: the reason the term iterator doesn't use the iterator cache itself - * (which could easily accomodate a single term) is that the filter iterator - * is built on top of the term iterator. The private cache of the term - * iterator keeps the filter iterator code simple, as it doesn't need to - * worry about the term iter overwriting the iterator fields. */ - flecs_iter_init(&it, 0); + /* Check if table has transitive relationship by traversing upwards */ + ecs_table_record_t *tr = NULL; + ecs_search_relation(ctx->world, table, 0, + ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, &tr); - term_iter_init(world, term, &it.priv.iter.term, false); + if (!tr) { + if (op->match_flags & EcsTermReflexive) { + return flecs_query_trav_fixed_src_reflexive(op, ctx, + &range, trav, second); + } else { + return false; + } + } - return it; -error: - return (ecs_iter_t){ 0 }; + flecs_query_set_trav_match(op, tr, trav, second, ctx); + return true; } -ecs_iter_t ecs_term_chain_iter( - const ecs_iter_t *chain_it, - ecs_term_t *term) +static +bool flecs_query_trav_unknown_src_up_fixed_second( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_check(chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_world_t *world = chain_it->real_world; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); + ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); - if (ecs_term_finalize(world, NULL, term)) { - ecs_throw(ECS_INVALID_PARAMETER, NULL); - } - - ecs_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = chain_it->world, - .terms = term, - .term_count = 1, - .chain_it = (ecs_iter_t*)chain_it, - .next = ecs_term_next - }; + if (!redo) { + ecs_record_t *r_second = flecs_entities_get(ctx->world, second); + bool traversable = r_second && r_second->row & EcsEntityIsTraversable; + bool reflexive = op->match_flags & EcsTermReflexive; + if (!traversable && !reflexive) { + trav_ctx->cache.id = 0; - flecs_iter_init(&it, flecs_iter_cache_all); + /* If there's no record for the entity, it can't have a subtree so + * forward operation to a regular select. */ + return flecs_query_select(op, redo, ctx); + } - term_iter_init(world, term, &it.priv.iter.term, false); + /* Entity is traversable, which means it could have a subtree */ + flecs_query_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); + trav_ctx->index = 0; - return it; -error: - return (ecs_iter_t){ 0 }; -} + if (op->match_flags & EcsTermReflexive) { + trav_ctx->index = -1; + if(flecs_query_trav_unknown_src_reflexive( + op, ctx, trav, second)) + { + /* It's possible that we couldn't return the entity required for + * reflexive matching, like when it's a prefab or disabled. */ + return true; + } + } + } else { + if (!trav_ctx->cache.id) { + /* No traversal cache, which means this is a regular select */ + return flecs_query_select(op, redo, ctx); + } + } -static -const ecs_table_record_t *next_table( - ecs_term_iter_t *iter) -{ - ecs_id_record_t *idr = iter->cur; - if (!idr) { - return NULL; + if (trav_ctx->index == -1) { + redo = false; /* First result after handling reflexive relationship */ + trav_ctx->index = 0; } - const ecs_table_record_t *tr; - if (!(tr = flecs_table_cache_next(&iter->it, ecs_table_record_t))) { - if (iter->empty_tables) { - iter->empty_tables = false; - flecs_table_cache_iter(&idr->cache, &iter->it); - tr = flecs_table_cache_next(&iter->it, ecs_table_record_t); + /* Forward to select */ + int32_t count = ecs_vec_count(&trav_ctx->cache.entities); + ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); + for (; trav_ctx->index < count; trav_ctx->index ++) { + ecs_trav_elem_t *el = &elems[trav_ctx->index]; + trav_ctx->and.cr = el->cr; /* prevents lookup by select */ + if (flecs_query_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + return true; } + + redo = false; } - return tr; + return false; } static -bool iter_find_superset( - ecs_world_t *world, - ecs_table_t *table, - ecs_term_t *term, - ecs_entity_t *source, - ecs_id_t *id, - int32_t *column) +bool flecs_query_trav_yield_reflexive_src( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_table_range_t *range, + ecs_entity_t trav) { - ecs_term_id_t *subj = &term->subj; + ecs_var_t *vars = ctx->vars; + ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); + int32_t offset = trav_ctx->offset, count = trav_ctx->count; + bool src_is_var = op->flags & (EcsQueryIsVar << EcsQuerySrc); - /* Test if following the relation finds the id */ - int32_t index = ecs_search_relation(world, table, 0, - term->id, subj->set.relation, subj->set.min_depth, - subj->set.max_depth, source, id, 0, 0); - - if (index == -1) { - *source = 0; + if (trav_ctx->index >= (offset + count)) { + /* Restore previous offset, count */ + if (src_is_var) { + ecs_var_id_t src_var = op->src.var; + vars[src_var].range.offset = offset; + vars[src_var].range.count = count; + vars[src_var].entity = 0; + } return false; } - ecs_assert(*source != 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t entity = ecs_table_entities(range->table)[trav_ctx->index]; + flecs_query_set_trav_match(op, NULL, trav, entity, ctx); - *column = (index + 1) * -1; + /* Hijack existing variable to return one result at a time */ + if (src_is_var) { + ecs_var_id_t src_var = op->src.var; + ecs_table_t *table = vars[src_var].range.table; + ecs_assert(!table || table == ecs_get_table(ctx->world, entity), + ECS_INTERNAL_ERROR, NULL); + (void)table; + vars[src_var].entity = entity; + vars[src_var].range = flecs_range_from_entity(ctx->world, entity); + } return true; } static -bool term_iter_next( - ecs_world_t *world, - ecs_term_iter_t *iter, - bool match_prefab, - bool match_disabled) +bool flecs_query_trav_fixed_src_up_unknown_second( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_table_t *table = iter->table; - ecs_entity_t source = 0; - const ecs_table_record_t *tr; - ecs_term_t *term = &iter->term; + ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); + ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); + ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); + ecs_table_t *table = range.table; + ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); - do { - if (table) { - iter->cur_match ++; - if (iter->cur_match >= iter->match_count) { - table = NULL; - } else { - iter->last_column = ecs_search_offset( - world, table, iter->last_column + 1, term->id, 0); - iter->column = iter->last_column + 1; - if (iter->last_column >= 0) { - iter->id = ecs_vector_get( - table->type, ecs_id_t, iter->last_column)[0]; - } - } + if (!redo) { + flecs_query_get_trav_up_cache(ctx, &trav_ctx->cache, trav, table); + trav_ctx->index = 0; + if (op->match_flags & EcsTermReflexive) { + trav_ctx->yield_reflexive = true; + trav_ctx->index = range.offset; + trav_ctx->offset = range.offset; + trav_ctx->count = range.count ? range.count : ecs_table_count(table); } + } else { + trav_ctx->index ++; + } - if (!table) { - if (!(tr = next_table(iter))) { - if (iter->cur != iter->set_index && iter->set_index != NULL) { - iter->cur = iter->set_index; - flecs_table_cache_iter(&iter->set_index->cache, &iter->it); - iter->index = 0; - tr = next_table(iter); - } - - if (!tr) { - return false; - } - } + if (trav_ctx->yield_reflexive) { + if (flecs_query_trav_yield_reflexive_src(op, ctx, &range, trav)) { + return true; + } + trav_ctx->yield_reflexive = false; + trav_ctx->index = 0; + } - table = tr->hdr.table; + if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { + return false; + } - if (!match_prefab && (table->flags & EcsTableIsPrefab)) { - continue; - } + ecs_trav_elem_t *el = ecs_vec_get_t( + &trav_ctx->cache.entities, ecs_trav_elem_t, trav_ctx->index); + flecs_query_set_trav_match(op, el->tr, trav, el->entity, ctx); + return true; +} - if (!match_disabled && (table->flags & EcsTableIsDisabled)) { - continue; - } +bool flecs_query_trav( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; - iter->table = table; - iter->match_count = tr->count; - if (is_any_pair(term->id)) { - iter->match_count = 1; - } - iter->cur_match = 0; - iter->last_column = tr->column; - iter->column = tr->column + 1; - iter->id = ecs_vector_get(table->type, ecs_id_t, tr->column)[0]; + if (!flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { + /* This can't happen, src or second should have been resolved */ + ecs_abort(ECS_INTERNAL_ERROR, + "invalid instruction sequence: unconstrained traversal"); + } else { + return flecs_query_trav_unknown_src_up_fixed_second(op, redo, ctx); } - - if (iter->cur == iter->set_index) { - if (iter->self_index) { - if (flecs_id_record_table(iter->self_index, table) != NULL) { - /* If the table has the id itself and this term matched Self - * we already matched it */ - continue; - } - } - - if (!iter_find_superset( - world, table, term, &source, &iter->id, &iter->column)) - { - continue; - } + } else { + if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { + return flecs_query_trav_fixed_src_up_unknown_second(op, redo, ctx); + } else { + return flecs_query_trav_fixed_src_up_fixed_second(op, redo, ctx); } + } +} - break; - } while (true); +/** + * @file query/engine/eval_tree.c + * @brief Hierarchy evaluation (ChildOf pairs/Parent components). + */ - iter->subject = source; - return true; +const EcsParent* flecs_query_tree_get_parents( + ecs_table_range_t range) +{ + int32_t parent_column = range.table->component_map[ecs_id(EcsParent)]; + ecs_assert(parent_column != -1, ECS_INTERNAL_ERROR, NULL); + return ecs_table_get_column(range.table, parent_column - 1, range.offset); } static -bool term_iter_set_table( - ecs_world_t *world, - ecs_term_iter_t *iter, - ecs_table_t *table) +bool flecs_query_tree_select_tgt( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - const ecs_table_record_t *tr = NULL; - const ecs_id_record_t *idr = iter->self_index; - if (idr) { - tr = ecs_table_cache_get(&idr->cache, table); - if (tr) { - iter->match_count = tr->count; - iter->last_column = tr->column; - iter->column = tr->column + 1; - iter->id = ecs_vector_get(table->type, ecs_id_t, tr->column)[0]; - } - } - - if (!tr) { - idr = iter->set_index; - if (idr) { - tr = ecs_table_cache_get(&idr->cache, table); - if (!iter_find_superset(world, table, &iter->term, &iter->subject, - &iter->id, &iter->column)) - { - return false; - } - iter->match_count = 1; + ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); + + if (redo) { + op_ctx->cur ++; + if (op_ctx->cur >= op_ctx->range.count) { + return false; } } - if (!tr) { - return false; - } + ecs_entity_t child = op_ctx->entities[op_ctx->cur]; + ecs_assert(child != 0, ECS_INTERNAL_ERROR, NULL); - /* Populate fields as usual */ - iter->table = table; - iter->cur_match = 0; + ecs_table_range_t range = flecs_range_from_entity(ctx->world, child); + flecs_query_var_set_range(op, op->src.var, + range.table, range.offset, range.count, ctx); return true; } -bool ecs_term_next( - ecs_iter_t *it) +static +bool flecs_query_tree_with_parent( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_term_next, ECS_INVALID_PARAMETER, NULL); - - flecs_iter_validate(it); - - ecs_term_iter_t *iter = &it->priv.iter.term; - ecs_term_t *term = &iter->term; - ecs_world_t *world = it->real_world; - ecs_table_t *table; + ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); + ecs_table_range_t range = op_ctx->range; - it->ids = &iter->id; - it->subjects = &iter->subject; - it->columns = &iter->column; - it->terms = &iter->term; - it->sizes = &iter->size; +repeat: + if (redo) { + op_ctx->cur ++; + } - if (term->inout != EcsInOutFilter) { - it->ptrs = &iter->ptr; - } else { - it->ptrs = NULL; + if ((op_ctx->cur - range.offset) >= range.count) { + flecs_query_src_set_range(op, &op_ctx->range, ctx); + return false; } - ecs_iter_t *chain_it = it->chain_it; - if (chain_it) { - ecs_iter_next_action_t next = chain_it->next; - bool match; + ecs_assert(op_ctx->cur >= range.offset, ECS_INTERNAL_ERROR, NULL); + ecs_assert(op_ctx->cur < (range.offset + range.count), ECS_INTERNAL_ERROR, NULL); - do { - if (!next(chain_it)) { - goto done; - } + ecs_entity_t tgt = op_ctx->tgt; + ecs_entity_t parent = op_ctx->parents[op_ctx->cur - range.offset].value; - table = chain_it->table; - match = flecs_term_match_table(world, term, table, table->type, - it->ids, it->columns, it->subjects, it->match_indices, true, - it->flags); - } while (!match); - goto yield; + ecs_iter_t *it = ctx->it; + flecs_query_iter_set_id(it, op->field_index, ecs_childof(parent)); - } else { - if (!term_iter_next(world, iter, false, false)) { - goto done; + if (tgt != EcsWildcard) { + if (parent != op_ctx->tgt) { + redo = true; + goto repeat; } - - table = iter->table; - - /* Source must either be 0 (EcsThis) or nonzero in case of substitution */ - ecs_assert(iter->subject || iter->cur != iter->set_index, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(iter->table != NULL, ECS_INTERNAL_ERROR, NULL); } -yield: - flecs_iter_populate_data(world, it, table, 0, 0, it->ptrs, it->sizes); - ECS_BIT_SET(it->flags, EcsIterIsValid); + flecs_query_src_set_single(op, op_ctx->cur, ctx); + return true; -done: -error: - return false; } static -const ecs_filter_t* init_filter_iter( - const ecs_world_t *world, - ecs_iter_t *it, - const ecs_filter_t *filter) +bool flecs_query_tree_select( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_filter_iter_t *iter = &it->priv.iter.filter; + ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); - if (filter) { - iter->filter = *filter; + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, + ECS_INTERNAL_ERROR, NULL); + op_ctx->tgt = ECS_PAIR_SECOND(id); + op_ctx->cur = 0; - if (filter->term_cache_used) { - iter->filter.terms = iter->filter.term_cache; + ecs_component_record_t *cr = flecs_components_get(ctx->world, id); + if (!cr) { + return false; } - int filter_invalid = ecs_filter_finalize(world, &iter->filter); - ecs_assert(!filter_invalid, ECS_INTERNAL_ERROR, NULL); - (void)filter_invalid; + if (!(cr->flags & EcsIdOrderedChildren)) { + op_ctx->tgt = 0; + return flecs_query_and(op, redo, ctx); + } - ecs_assert(!filter->term_cache_used || - filter->terms == filter->term_cache, ECS_INTERNAL_ERROR, NULL); + ecs_assert(op_ctx->tgt != EcsWildcard, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_t *v_children = &cr->pair->ordered_children; + op_ctx->entities = ecs_vec_first_t(v_children, ecs_entity_t); + op_ctx->range.count = ecs_vec_count(v_children); + if (!op_ctx->range.count) { + return false; + } } else { - ecs_filter_init(world, &iter->filter, &(ecs_filter_desc_t) { - .terms = {{ .id = EcsAny }} - }); - - filter = &iter->filter; + if (!op_ctx->tgt) { + return flecs_query_and(op, redo, ctx); + } } - it->term_count = filter->term_count_actual; - - return filter; + return flecs_query_tree_select_tgt(op, redo, ctx); } -int32_t ecs_filter_pivot_term( - const ecs_world_t *world, - const ecs_filter_t *filter) +static +bool flecs_query_tree_select_any( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_id_t pair) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_query_tree_wildcard_ctx_t *op_ctx = flecs_op_ctx(ctx, tree_wildcard); + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_term_t *terms = filter->terms; - int32_t i, term_count = filter->term_count; - int32_t pivot_term = -1, min_count = -1, self_pivot_term = -1; - - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_id_t id = term->id; - - if (term->oper != EcsAnd) { - continue; - } - - if (term->subj.entity != EcsThis) { - continue; - } + if (!redo) { + op_ctx->cr = ctx->world->cr_childof_wildcard; + ecs_assert(op_ctx->cr != NULL, ECS_INTERNAL_ERROR, NULL); + op_ctx->state = EcsQueryTreeIterEntities; + } - ecs_id_record_t *idr = flecs_get_id_record(world, - actual_match_id(id)); - if (!idr) { - /* If one of the terms does not match with any data, iterator - * should not return anything */ - return -2; /* -2 indicates filter doesn't match anything */ +next: + switch(op_ctx->state) { + case EcsQueryTreeIterEntities: { + bool result = flecs_query_select_w_id( + op, redo, ctx, ecs_id(EcsParent), + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + if (!result) { + op_ctx->state = EcsQueryTreeIterTables; + redo = false; + goto next; } - - int32_t table_count = ecs_table_cache_count(&idr->cache); - if (min_count == -1 || table_count < min_count) { - min_count = table_count; - pivot_term = i; - if (term->subj.set.mask == EcsSelf) { - self_pivot_term = i; - } + it->ids[field_index] = ecs_pair(EcsChildOf, EcsWildcard); + return true; + } + case EcsQueryTreeIterTables: { + bool result = flecs_query_select_w_id( + op, redo, ctx, pair, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + if (!result) { + return false; } + it->ids[field_index] = pair; + return true; } - - if (self_pivot_term != -1) { - pivot_term = self_pivot_term; + case EcsQueryTreeIterNext: + default: + return false; } - - return pivot_term; -error: - return -2; } -ecs_iter_t ecs_filter_iter( - const ecs_world_t *stage, - const ecs_filter_t *filter) +static +bool flecs_query_tree_select_wildcard( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool bulk_return) { - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - - const ecs_world_t *world = ecs_get_world(stage); - - flecs_process_pending_tables(world); - - bool instanced = filter ? filter->instanced : false; + ecs_iter_t *it = ctx->it; + int8_t field_index = op->field_index; + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_iter_t it = { - .real_world = (ecs_world_t*)world, - .world = (ecs_world_t*)stage, - .terms = filter ? filter->terms : NULL, - .next = ecs_filter_next, - .flags = instanced ? EcsIterIsInstanced : 0 - }; + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, + ECS_INTERNAL_ERROR, NULL); + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + if (tgt != EcsWildcard) { + it->ids[field_index] = id; - ecs_filter_iter_t *iter = &it.priv.iter.filter; + /* Happens when variable has been constrained */ + if (bulk_return) { + return flecs_query_children(op, redo, ctx); + } else { + return flecs_query_tree_select(op, redo, ctx); + } + } - filter = init_filter_iter(world, &it, filter); + ecs_query_tree_wildcard_ctx_t *op_ctx = flecs_op_ctx(ctx, tree_wildcard); - /* Find term that represents smallest superset */ - if (filter->match_this) { - ecs_term_t *terms = filter->terms; - int32_t pivot_term = -1; - ecs_check(terms != NULL, ECS_INVALID_PARAMETER, NULL); + if (!redo) { + op_ctx->cr = ctx->world->cr_childof_wildcard; + ecs_assert(op_ctx->cr != NULL, ECS_INTERNAL_ERROR, NULL); + op_ctx->state = EcsQueryTreeIterNext; + } - iter->kind = EcsIterEvalTables; +next: + switch(op_ctx->state) { - pivot_term = ecs_filter_pivot_term(world, filter); - iter->pivot_term = pivot_term; + /* Select next (ChildOf, parent) pair */ + case EcsQueryTreeIterNext: { + ecs_component_record_t *cr = op_ctx->cr = + flecs_component_first_next(op_ctx->cr); + if (!cr) { + return false; + } - if (pivot_term == -2) { - /* One or more terms have no matching results */ - term_iter_init_no_data(&iter->term_iter); - } else if (pivot_term == -1) { - /* No terms meet the criteria to be a pivot term, evaluate filter - * against all tables */ - term_iter_init_wildcard(world, &iter->term_iter, - filter->match_empty_tables); + flecs_query_var_reset(0, ctx); + if (op->match_flags & EcsTermMatchAny) { + it->ids[field_index] = ecs_childof(EcsWildcard); } else { - ecs_assert(pivot_term >= 0, ECS_INTERNAL_ERROR, NULL); - term_iter_init(world, &terms[pivot_term], &iter->term_iter, - filter->match_empty_tables); + it->ids[field_index] = op_ctx->cr->id; } - } else { - if (!filter->match_anything) { - iter->kind = EcsIterEvalCondition; - term_iter_init_no_data(&iter->term_iter); + + if (cr->flags & EcsIdOrderedChildren) { + ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_t *v_children = &cr->pair->ordered_children; + if (bulk_return) { + op_ctx->state = EcsQueryTreeIterNext; + it->entities = ecs_vec_first_t(v_children, ecs_entity_t); + it->count = ecs_vec_count(v_children); + goto done; + } else { + op_ctx->entities = ecs_vec_first_t(v_children, ecs_entity_t); + op_ctx->count = ecs_vec_count(v_children); + op_ctx->cur = -1; + op_ctx->state = EcsQueryTreeIterEntities; + goto next; + } } else { - iter->kind = EcsIterEvalNone; + if (!flecs_component_iter(cr, &op_ctx->it)) { + op_ctx->state = EcsQueryTreeIterNext; + } else { + op_ctx->state = EcsQueryTreeIterTables; + } } - iter->pivot_term = -1; + goto next; } - if (filter->terms == filter->term_cache) { - /* Because we're returning the iterator by value, the address of the - * term cache changes. The ecs_filter_next function will set the correct - * address when it detects that terms is set to NULL */ - iter->filter.terms = NULL; - } + /* Iterate tables for (ChildOf, parent) */ + case EcsQueryTreeIterTables: { + const ecs_table_record_t *tr = flecs_component_next(&op_ctx->it); + if (!tr) { + op_ctx->state = EcsQueryTreeIterNext; + goto next; + } else { + ecs_table_t *table = tr->hdr.table; + if (!ecs_table_count(table) || (table->flags & EcsTableNotQueryable)) { + goto next; + } - if (filter->filter) { - ECS_BIT_SET(it.flags, EcsIterIsFilter); + it->trs[field_index] = tr; + flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); + goto done; + } } - if (filter->match_this) { - /* Make space for one variable if the filter has terms for This var */ - it.variable_count = 1; - - /* Set variable name array */ - it.variable_names = (char**)filter->variable_names; + /* Return ordered entities for (ChildOf, parent) one by one */ + case EcsQueryTreeIterEntities: { + op_ctx->cur ++; + if (op_ctx->cur >= op_ctx->count) { + op_ctx->state = EcsQueryTreeIterNext; + goto next; + } else { + ecs_entity_t child = op_ctx->entities[op_ctx->cur]; + ecs_assert(child != 0, ECS_INTERNAL_ERROR, NULL); + ecs_table_range_t range = flecs_range_from_entity(ctx->world, child); + flecs_query_var_set_range(op, op->src.var, + range.table, range.offset, range.count, ctx); + goto done; + } + } } - flecs_iter_init(&it, flecs_iter_cache_all); - - return it; -error: - return (ecs_iter_t){ 0 }; +done: + flecs_query_set_vars(op, it->ids[field_index], ctx); + return true; } -ecs_iter_t ecs_filter_chain_iter( - const ecs_iter_t *chain_it, - const ecs_filter_t *filter) +bool flecs_query_tree_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_iter_t it = { - .terms = filter->terms, - .term_count = filter->term_count, - .world = chain_it->world, - .real_world = chain_it->real_world, - .chain_it = (ecs_iter_t*)chain_it, - .next = ecs_filter_next - }; + ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); - flecs_iter_init(&it, flecs_iter_cache_all); - ecs_filter_iter_t *iter = &it.priv.iter.filter; - init_filter_iter(it.world, &it, filter); + if (redo) { + if (op_ctx->tgt) { + return flecs_query_tree_with_parent(op, redo, ctx); + } - iter->kind = EcsIterEvalChain; + flecs_query_src_set_range(op, &op_ctx->range, ctx); + return false; + } - if (filter->terms == filter->term_cache) { - /* See ecs_filter_iter */ - iter->filter.terms = NULL; + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + if (!range.count) { + range.count = ecs_table_count(range.table); } - return it; -} + op_ctx->range = range; -bool ecs_filter_next( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); + ecs_table_t *table = range.table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (flecs_iter_next_row(it)) { - return true; + /* Fragmenting childof */ + if (table->flags & EcsTableHasChildOf) { + if (op->match_flags & EcsTermMatchAny) { + return flecs_query_and_any(op, redo, ctx); + } else { + return flecs_query_with(op, redo, ctx); + } } - return flecs_iter_next_instanced(it, ecs_filter_next_instanced(it)); -error: - return false; -} - -bool ecs_filter_next_instanced( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != it, ECS_INVALID_PARAMETER, NULL); - - ecs_filter_iter_t *iter = &it->priv.iter.filter; - ecs_filter_t *filter = &iter->filter; - ecs_world_t *world = it->real_world; - ecs_table_t *table = NULL; - bool match; + ecs_iter_t *it = ctx->it; + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_assert(ECS_IS_PAIR(id), ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t tgt = ECS_PAIR_SECOND(id); - if (!filter->terms) { - filter->terms = filter->term_cache; + /* Root */ + if (!(table->flags & EcsTableHasParent)) { + if (!tgt) { + int8_t field_index = op->field_index; + it->set_fields &= ~(1u << field_index); + flecs_query_iter_set_id(it, op->field_index, ecs_childof(EcsWildcard)); + return true; + } + return false; } - flecs_iter_validate(it); + /* Non-fragmenting childof */ + const EcsParent *parents = flecs_query_tree_get_parents(range); - ecs_iter_t *chain_it = it->chain_it; - ecs_iter_kind_t kind = iter->kind; + /* Evaluating a single entity */ + if (range.count == 1) { + ecs_entity_t parent = parents[0].value; - if (chain_it) { - ecs_assert(kind == EcsIterEvalChain, ECS_INVALID_PARAMETER, NULL); - - ecs_iter_next_action_t next = chain_it->next; - do { - if (!next(chain_it)) { - goto done; + /* Wildcard query */ + if (tgt == EcsWildcard) { + if (op->match_flags & EcsTermMatchAny) { + flecs_query_iter_set_id(it, op->field_index, ecs_childof(EcsWildcard)); + } else { + ecs_id_t actual_id = flecs_query_iter_set_id(it, op->field_index, + ecs_childof(parent)); + flecs_query_set_vars(op, actual_id, ctx); + } + + /* Parent query */ + } else { + if ((uint32_t)parent != tgt) { + return false; } - table = chain_it->table; - match = flecs_filter_match_table(world, filter, table, - it->ids, it->columns, it->subjects, it->match_indices, NULL, - true, -1, it->flags); - } while (!match); + ecs_id_t actual_id = flecs_query_iter_set_id(it, op->field_index, + ecs_childof(parent)); + flecs_query_set_vars(op, actual_id, ctx); + } - goto yield; - } else if (kind == EcsIterEvalTables || kind == EcsIterEvalCondition) { - ecs_term_iter_t *term_iter = &iter->term_iter; - ecs_term_t *term = &term_iter->term; - int32_t pivot_term = iter->pivot_term; - bool first; + /* Evaluating an entity range */ + } else { + /* Wildcard query */ + if (tgt == EcsWildcard) { + if (op->match_flags & EcsTermMatchAny) { + flecs_query_iter_set_id(it, op->field_index, ecs_childof(EcsWildcard)); + } else { + op_ctx->parents = parents; + op_ctx->tgt = tgt; + op_ctx->cur = range.offset; + return flecs_query_tree_with_parent(op, redo, ctx); + } - /* Check if the This variable has been set on the iterator. If set, - * the filter should only be applied to the variable value */ - ecs_var_t *this_var = NULL; - ecs_table_t *this_table = NULL; - if (it->variable_count) { - if (ecs_iter_var_is_constrained(it, 0)) { - this_var = it->variables; - this_table = this_var->range.table; + /* Parent query */ + } else { + ecs_component_record_t *cr = flecs_components_get( + ctx->world, ecs_pair(EcsChildOf, tgt)); + if (!cr) { + return false; + } - /* If variable is constrained, make sure it's a value that's - * pointing to a table, as a filter can't iterate single - * entities (yet) */ - ecs_assert(this_table != NULL, ECS_INVALID_OPERATION, NULL); + ecs_parent_record_t *pr = flecs_component_get_parent_record( + cr, range.table); + if (!pr) { + /* Table doesn't have entities with parent */ + return false; + } - /* Can't set variable for filter that does not iterate tables */ - ecs_assert(kind == EcsIterEvalTables, - ECS_INVALID_OPERATION, NULL); + ecs_entity_t child = pr->entity; + if (child) { + ecs_assert(pr->count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get_any(ctx->world, child); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == range.table, ECS_INTERNAL_ERROR, NULL); + int32_t cur = ECS_RECORD_TO_ROW(r->row); + + /* Table contains a single entity for parent */ + if ((range.offset > cur) || ((range.offset + range.count) <= cur)) { + /* Entity does not fall within range */ + return false; + } + + flecs_query_iter_set_id(it, op->field_index, ecs_childof(tgt)); + flecs_query_src_set_single(op, cur, ctx); + return true; } + + /* Table contains multiple entities for same parent, scan */ + op_ctx->parents = parents; + op_ctx->tgt = tgt; + op_ctx->cur = range.offset; + + return flecs_query_tree_with_parent(op, redo, ctx); } + } - do { - /* If there are no matches left for the previous table, this is the - * first match of the next table. */ - first = iter->matches_left == 0; - - if (first) { - if (kind != EcsIterEvalCondition) { - /* Check if this variable was constrained */ - if (this_table != NULL) { - /* If this is the first match of a new result and the - * previous result was equal to the value of a - * constrained var, there's nothing left to iterate */ - if (it->table == this_table) { - goto done; - } + return true; +} - /* If table doesn't match term iterator, it doesn't - * match filter. */ - if (!term_iter_set_table(world, term_iter, this_table)){ - goto done; - } +static +bool flecs_query_tree_with_pre( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; + } - /* But if it does, forward it to filter matching */ - ecs_assert(term_iter->table == this_table, - ECS_INTERNAL_ERROR, NULL); + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); - /* If This variable is not constrained, iterate as usual */ - } else { - /* Find new match, starting with the leading term */ - if (!term_iter_next(world, term_iter, - filter->match_prefab, filter->match_disabled)) - { - goto done; - } - } + if (range.table->flags & EcsTableHasChildOf) { + bool result = flecs_query_with(op, redo, ctx); + if (op->match_flags & EcsTermMatchAny) { + flecs_query_iter_set_id( + ctx->it, op->field_index, ecs_childof(EcsWildcard)); + } + return result; + } - ecs_assert(term_iter->match_count != 0, - ECS_INTERNAL_ERROR, NULL); + if (range.table->flags & EcsTableHasParent) { + flecs_query_iter_set_id( + ctx->it, op->field_index, ecs_pair(EcsChildOf, EcsWildcard)); + return true; + } - if (pivot_term == -1) { - /* Without a pivot term, we're iterating all tables with - * a wildcard, so the match count is meaningless. */ - term_iter->match_count = 1; - } + return false; +} - iter->matches_left = term_iter->match_count; +static +bool flecs_query_children_select( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); + ecs_assert(op_ctx->tgt == 0, ECS_INTERNAL_ERROR, NULL); + ecs_iter_t *it = ctx->it; - /* Filter iterator takes control over iterating all the - * permutations that match the wildcard. */ - term_iter->match_count = 1; + if (!redo) { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, + ECS_INTERNAL_ERROR, NULL); - table = term_iter->table; + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + ecs_assert(tgt != EcsWildcard, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tgt != EcsAny, ECS_INTERNAL_ERROR, NULL); + (void)tgt; - if (pivot_term != -1) { - int32_t index = term->index; - it->ids[index] = term_iter->id; - it->subjects[index] = term_iter->subject; - it->columns[index] = term_iter->column; - } - } else { - /* Progress iterator to next match for table, if any */ - table = it->table; - if (term_iter->index == 0) { - iter->matches_left = 1; - term_iter->index = 1; /* prevents looping again */ - } else { - goto done; - } - } + ecs_component_record_t *cr = flecs_components_get(ctx->world, id); + if (!cr) { + return false; + } - /* Match the remainder of the terms */ - match = flecs_filter_match_table(world, filter, table, - it->ids, it->columns, it->subjects, - it->match_indices, &iter->matches_left, first, - pivot_term, it->flags); - if (!match) { - it->table = table; - iter->matches_left = 0; - continue; - } + op_ctx->tgt = 0; + if (!(cr->flags & EcsIdOrderedChildren)) { + /* No vector with ordered children, forward to regular search. */ + op_ctx->state = EcsQueryTreeIterTables; + goto next; + } - /* Table got matched, set This variable */ - if (table) { - ecs_assert(it->variable_count == 1, ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); - it->variables[0].range.table = table; - } + ecs_pair_record_t *pr = cr->pair; + ecs_assert(pr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_t *v_children = &pr->ordered_children; + uint32_t filter = flecs_ito(uint32_t, op->other); - ecs_assert(iter->matches_left != 0, ECS_INTERNAL_ERROR, NULL); - } + if ((!pr->disabled_tables || !(filter & EcsTableIsDisabled)) && + (!pr->prefab_tables || !(filter & EcsTableIsPrefab))) + { + it->entities = ecs_vec_first_t(v_children, ecs_entity_t); + it->count = ecs_vec_count(v_children); + return true; + } else { + /* Flags that we're going to iterate each entity separately because we + * need to filter out disabled entities. */ + op_ctx->state = EcsQueryTreeIterEntities; + op_ctx->entities = ecs_vec_first_t(v_children, ecs_entity_t); + op_ctx->cur = -1; + op_ctx->range.count = ecs_vec_count(v_children); + op_ctx->cr = cr; + } + } else { + if (!op_ctx->state) { + return false; + } + } - /* If this is not the first result for the table, and the table - * is matched more than once, iterate remaining matches */ - if (!first && (iter->matches_left > 0)) { - table = it->table; - - /* Find first term that still has matches left */ - int32_t i, j, count = it->term_count; - for (i = count - 1; i >= 0; i --) { - int32_t mi = -- it->match_indices[i]; - if (mi) { - break; - } - } +next: + if (op_ctx->state == EcsQueryTreeIterEntities) { + int32_t cur = ++ op_ctx->cur; + if (cur >= op_ctx->range.count) { + return false; + } - /* Progress first term to next match (must be at least one) */ - int32_t column = it->columns[i]; - if (column < 0) { - /* If this term was matched on a non-This entity, reconvert - * the column back to a positive value */ - column = -column; - } + ecs_entity_t *e = &op_ctx->entities[cur]; + ecs_record_t *r = flecs_entities_get(ctx->world, *e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - it->columns[i] = column + 1; - flecs_term_match_table(world, &filter->terms[i], table, - table->type, &it->ids[i], &it->columns[i], &it->subjects[i], - &it->match_indices[i], false, it->flags); + if (flecs_query_table_filter(r->table, op->other, + (EcsTableIsDisabled|EcsTableIsPrefab))) + { + /* Skip disabled/prefab entities */ + goto next; + } - /* Reset remaining terms (if any) to first match */ - for (j = i + 1; j < count; j ++) { - flecs_term_match_table(world, &filter->terms[j], table, - table->type, &it->ids[j], &it->columns[j], - &it->subjects[j], &it->match_indices[j], true, - it->flags); - } - } + it->entities = e; + it->count = 1; + return true; + } else { + ecs_assert(op_ctx->state == EcsQueryTreeIterTables, + ECS_INTERNAL_ERROR, NULL); + return flecs_query_and(op, redo, ctx); + } +} - match = iter->matches_left != 0; - iter->matches_left --; +static +bool flecs_query_children_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + if (redo) { + return false; + } - ecs_assert(iter->matches_left >= 0, ECS_INTERNAL_ERROR, NULL); - } while (!match); + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + if (range.table->flags & EcsTableHasChildOf) { + return flecs_query_and(op, redo, ctx); + } - goto yield; + if (!(range.table->flags & EcsTableHasParent)) { + /* If table doesn't have ChildOf or Parent its entities don't have + * parents. */ + return false; } -done: -error: - ecs_iter_fini(it); - return false; + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); -yield: - it->offset = 0; - flecs_iter_populate_data(world, it, table, 0, 0, it->ptrs, it->sizes); - ECS_BIT_SET(it->flags, EcsIterIsValid); - return true; -} + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + if (tgt == EcsWildcard || tgt == EcsAny) { + /* Entities in table have parents, so wildcard will always match. */ + return true; + } + /* TODO: if a ChildOf query is constrained to a table with Parent component, + * only some entities in the table may match the query. TBD on what the + * behavior should be in this case. For now the query engine only supports + * constraining the query to a single entity or an entire table. */ + ecs_assert(range.count < 2, ECS_UNSUPPORTED, + "can only use set_var() to a single entity for ChildOf($this, parent) terms"); -static -int32_t type_search( - const ecs_table_t *table, - ecs_id_record_t *idr, - ecs_id_t *ids, - ecs_id_t *id_out, - ecs_table_record_t **tr_out) -{ - ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); - if (tr) { - int32_t r = tr->column; - if (tr_out) tr_out[0] = tr; - if (id_out) id_out[0] = ids[r]; - return r; + if (range.count == 0) { + /* If matching the entire table, return true. Even though not all + * entities in the table may match, this lets us add tables with the + * Parent component to query caches. */ + return true; } - return -1; + if (flecs_query_table_filter(range.table, op->other, + EcsTableIsDisabled|EcsTableIsPrefab)) + { + return false; + } + + const EcsParent *parents = flecs_query_tree_get_parents(range); + if ((uint32_t)parents->value == tgt) { + return true; + } + + return false; } -static -int32_t type_offset_search( - int32_t offset, - ecs_id_t id, - ecs_id_t *ids, - int32_t count, - ecs_id_t *id_out) +bool flecs_query_children( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!ECS_HAS_ROLE(id, CASE), ECS_INVALID_PARAMETER, NULL); - - while (offset < count) { - ecs_id_t type_id = ids[offset ++]; - if (ecs_id_match(type_id, id)) { - if (id_out) id_out[0] = type_id; - return offset - 1; - } + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_children_with(op, redo, ctx); + } else { + return flecs_query_children_select(op, redo, ctx); } - - return -1; } -static -bool type_can_inherit_id( - const ecs_world_t *world, - const ecs_table_t *table, - const ecs_id_record_t *idr, - ecs_id_t id) +bool flecs_query_tree_and( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - if (idr->flags & ECS_ID_DONT_INHERIT) { - return false; + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_tree_with(op, redo, ctx); + } else { + return flecs_query_tree_select(op, redo, ctx); } - if (idr->flags & ECS_ID_EXCLUSIVE) { - if (ECS_HAS_ROLE(id, PAIR)) { - ecs_entity_t er = ECS_PAIR_FIRST(id); - if (flecs_get_table_record( - world, table, ecs_pair(er, EcsWildcard))) - { - return false; - } +} + +bool flecs_query_tree_and_wildcard( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool bulk_return) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_tree_with(op, redo, ctx); + } else { + if (op->match_flags & EcsTermMatchAny) { + return flecs_query_tree_select_any( + op, redo, ctx, ecs_pair(EcsChildOf, EcsWildcard)); + } else { + return flecs_query_tree_select_wildcard(op, redo, ctx, bulk_return); } } - return true; } -static -int32_t type_search_relation( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_id_record_t *idr, - ecs_id_t rel, - ecs_id_record_t *idr_r, - int32_t min_depth, - int32_t max_depth, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - int32_t *depth_out, - ecs_table_record_t **tr_out) +bool flecs_query_tree_pre( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_type_t type = table->type; - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t count = ecs_vector_count(type); + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + return flecs_query_tree_with_pre(op, redo, ctx); + } else { + ecs_id_t id = flecs_query_op_get_id(op, ctx); + return flecs_query_tree_select_any(op, redo, ctx, id); + } +} - if (min_depth <= 0) { - if (offset) { - int32_t r = type_offset_search(offset, id, ids, count, id_out); - if (r != -1) { - return r; - } - } else { - int32_t r = type_search(table, idr, ids, id_out, tr_out); - if (r != -1) { - return r; - } - } +bool flecs_query_tree_post( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + /* Source should have been written as this instruction can only be inserted + * after a cache instruction has been evaluated. */ + uint64_t written = ctx->written[ctx->op_index]; + ecs_assert(written & (1ull << op->src.var), ECS_INTERNAL_ERROR, NULL); + (void)written; + + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + + if (range.table->flags & (EcsTableHasChildOf)) { + return !redo; } - ecs_flags32_t flags = table->flags; - if ((flags & EcsTableHasPairs) && max_depth && rel) { - bool is_a = rel == ecs_pair(EcsIsA, EcsWildcard); - if (is_a) { - if (!(flags & EcsTableHasIsA)) { - return -1; - } - if (!type_can_inherit_id(world, table, idr, id)) { - return -1; - } - idr_r = world->idr_isa_wildcard; + /* Shouldn't have gotten here if the table has neither ChildOf or Parent */ + ecs_assert(range.table->flags & EcsTableHasParent, ECS_INTERNAL_ERROR, NULL); + + return flecs_query_tree_with(op, redo, ctx); +} + +bool flecs_query_tree_up_pre( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool self) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + if (self) { + return flecs_query_self_up(op, redo, ctx); + } else { + return flecs_query_up(op, redo, ctx); + } + } else { + ecs_query_tree_pre_ctx_t *op_ctx = flecs_op_ctx(ctx, tree_pre); + if (!redo) { + op_ctx->state = EcsQueryTreeIterTables; } - if (!idr_r) { - idr_r = flecs_get_id_record(world, rel); - if (!idr_r) { - return -1; + if (op_ctx->state == EcsQueryTreeIterTables) { + bool result; +retry: + if (self) { + result = flecs_query_self_up(op, redo, ctx); + } else { + result = flecs_query_up(op, redo, ctx); } - } - ecs_id_t id_r; - int32_t r, r_column; - if (offset) { - r_column = type_offset_search(offset, rel, ids, count, &id_r); - } else { - r_column = type_search(table, idr_r, ids, &id_r, 0); - } - while (r_column != -1) { - ecs_entity_t obj = ECS_PAIR_SECOND(id_r); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_record_t *rec = ecs_eis_get_any(world, obj); - ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_table_t *obj_table = rec->table; - if (obj_table) { - r = type_search_relation(world, obj_table, 0, id, idr, - rel, idr_r, min_depth - 1, max_depth - 1, subject_out, - id_out, depth_out, tr_out); - if (r != -1) { - if (subject_out && !subject_out[0]) { - subject_out[0] = ecs_get_alive(world, obj); - } - if (depth_out) { - depth_out[0] ++; - } - return r_column; + if (!result) { + op_ctx->state = EcsQueryTreeIterEntities; + redo = false; + } else { + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); + if (range.table->flags & EcsTableHasParent) { + /* Skip tables with Parent, since we'll yield all tables + * with Parent in the second phase. */ + redo = true; + goto retry; } - if (!is_a) { - r = type_search_relation(world, obj_table, 0, id, idr, - ecs_pair(EcsIsA, EcsWildcard), world->idr_isa_wildcard, - 1, INT_MAX, subject_out, id_out, depth_out, tr_out); - if (r != -1) { - if (subject_out && !subject_out[0]) { - subject_out[0] = ecs_get_alive(world, obj); - } - if (depth_out) { - depth_out[0] ++; - } - return r_column; - } - } + return true; } + } - r_column = type_offset_search(r_column + 1, rel, ids, count, &id_r); + bool result = flecs_query_select_w_id(op, redo, ctx, ecs_id(EcsParent), + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + if (result) { + /* Signal this table needs post processing */ + ctx->it->sources[op->field_index] = EcsWildcard; } - } - return -1; + return result; + } } -int32_t ecs_search_relation( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_entity_t rel, - int32_t min_depth, - int32_t max_depth, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - int32_t *depth_out, - struct ecs_table_record_t **tr_out) +bool flecs_query_tree_up_post( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + bool self) { - if (!table) return -1; + ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); - ecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + /* Source should have been written as this instruction can only be inserted + * after a cache instruction has been evaluated. */ + uint64_t written = ctx->written[ctx->op_index]; + ecs_assert(written & (1ull << op->src.var), ECS_INTERNAL_ERROR, NULL); + (void)written; - bool is_case = ECS_HAS_ROLE(id, CASE); - id = is_case * (ECS_SWITCH | ECS_PAIR_FIRST(id)) + !is_case * id; + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return -1; + /* Passthrough tables with ChildOf pair */ + if (!(range.table->flags & EcsTableHasParent)) { + return !redo; } - max_depth = INT_MAX * !max_depth + max_depth * !!max_depth; + if (!redo) { + op_ctx->tgt = ctx->it->sources[op->field_index]; + } - int32_t result = type_search_relation(world, table, offset, id, idr, - ecs_pair(rel, EcsWildcard), NULL, min_depth, max_depth, subject_out, - id_out, depth_out, tr_out); + /* Passthrough tables that own the component */ + if (op_ctx->tgt != EcsWildcard) { + return !redo; + } - return result; -} + /* Shouldn't have gotten here if the table has neither ChildOf or Parent */ + ecs_assert(range.table->flags & EcsTableHasParent, ECS_INTERNAL_ERROR, NULL); -int32_t ecs_search( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id, - ecs_id_t *id_out) -{ - if (!table) return -1; + const ecs_term_t *term = &ctx->query->pub.terms[op->term_index]; - ecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + if (term->oper == EcsOptional) { + if (!redo) { + if (!range.count) { + range.count = ecs_table_count(range.table); + } - ecs_id_record_t *idr = flecs_get_id_record(world, id); - if (!idr) { - return -1; - } + op_ctx->range = range; + op_ctx->cur = range.offset - 1; + } - ecs_type_t type = table->type; - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - return type_search(table, idr, ids, id_out, NULL); -} + op_ctx->cur ++; -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) -{ - if (!offset) { - return ecs_search(world, table, id, id_out); - } + if (op_ctx->cur >= op_ctx->range.count) { + flecs_query_src_set_range(op, &op_ctx->range, ctx); + return false; + } - if (!table) return -1; + bool result = false; - ecs_poly_assert(world, ecs_world_t); + flecs_query_src_set_single(op, op_ctx->cur, ctx); - ecs_type_t type = table->type; - ecs_id_t *ids = ecs_vector_first(type, ecs_id_t); - int32_t count = ecs_vector_count(type); - return type_offset_search(offset, id, ids, count, id_out); -} + if (self) { + result = flecs_query_self_up_with(op, false, ctx); + } else { + result = flecs_query_up_with(op, false, ctx); + } -int32_t ecs_search_relation_last( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_entity_t rel, - int32_t min_depth, - int32_t max_depth, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - int32_t *depth_out, - struct ecs_table_record_t **tr_out) -{ - int32_t depth = 0; - ecs_entity_t subj = 0; - int32_t cur, result = ecs_search_relation( - world, table, offset, id, rel, min_depth, max_depth, &subj, - id_out, &depth, tr_out); - if (result == -1) { - return -1; - } + uint64_t field_bit = 1llu << op->field_index; + if (!result) { + ctx->it->set_fields &= (ecs_termset_t)~field_bit; + } else { + ctx->it->set_fields |= (ecs_termset_t)field_bit; + } - if (!subj) { - cur = ecs_search_relation( - world, table, offset, id, rel, 1, max_depth, &subj, - id_out, &depth, tr_out); - if (cur == -1) { - goto done; + return true; + } else { + if (self) { + return flecs_query_self_up_with(op, redo, ctx); + } else { + return flecs_query_up_with(op, redo, ctx); } } +} + +/** + * @file query/engine/eval.c + * @brief Query engine implementation. + */ + + +/* Find tables with requested component that has traversable entities. */ +static +bool flecs_query_up_select_table( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_query_up_select_trav_kind_t trav_kind, + ecs_query_up_select_kind_t kind) +{ + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_query_up_impl_t *impl = op_ctx->impl; + ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_iter_t *it = ctx->it; + bool self = trav_kind == FlecsQueryUpSelectSelfUp; + ecs_table_range_t range; do { - ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); - table = ecs_get_table(world, subj); - int32_t cur_depth = 0; - ecs_entity_t cur_subj = 0; - cur = ecs_search_relation( - world, table, 0, id, rel, 1, max_depth, &cur_subj, - id_out, &cur_depth, tr_out); - if (cur == -1) { - break; + bool result; + if (ECS_PAIR_FIRST(impl->with) == EcsChildOf) { + if (impl->with == ecs_childof(EcsWildcard)) { + result = flecs_query_tree_and_wildcard(op, redo, ctx, false); + } else { + result = flecs_query_tree_and(op, redo, ctx); + } + } else if (kind == FlecsQueryUpSelectId) { + result = flecs_query_select_id(op, redo, ctx, 0); + } else if (kind == FlecsQueryUpSelectDefault) { + result = flecs_query_select_w_id(op, redo, ctx, + impl->with, 0); + } else if (kind == FlecsQueryUpSelectSparse) { + result = flecs_query_sparse_select(op, redo, ctx, 0); + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); } - ecs_assert(subj != cur_subj, ECS_INTERNAL_ERROR, NULL); - - int32_t actual_depth = depth + cur_depth; - if (max_depth && (actual_depth > max_depth)) { - break; + if (!result) { + /* No remaining tables with component found. */ + return false; } - subj = cur_subj; - depth = actual_depth; - } while(true); + redo = true; -done: - if (depth_out) depth_out[0] = depth; - if (subject_out) subject_out[0] = subj; - return result; -} + range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); + ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Keep searching until we find a table that has the requested component, + * with traversable entities */ + } while (!self && range.table->_->traversable_count == 0); + + if (!range.count) { + range.count = ecs_table_count(range.table); + } + + impl->table = range.table; + impl->row = range.offset; + impl->end = range.offset + range.count; + impl->matched = it->ids[op->field_index]; + return true; +} +/* Find next traversable entity in table. */ static -bool observer_run(ecs_iter_t *it) { - ecs_observer_t *o = it->ctx; - ecs_world_t *world = it->real_world; +ecs_trav_down_t* flecs_query_up_find_next_traversable( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx, + ecs_query_up_select_trav_kind_t trav_kind) +{ + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_query_up_impl_t *impl = op_ctx->impl; + ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = ctx->world; + ecs_iter_t *it = ctx->it; + const ecs_query_t *q = &ctx->query->pub; + ecs_table_t *table = impl->table; + bool self = trav_kind == FlecsQueryUpSelectSelfUp; - ecs_assert(o->callback != NULL, ECS_INVALID_PARAMETER, NULL); + if (table->_->traversable_count == 0) { + /* No traversable entities in table */ + impl->table = NULL; + return NULL; + } else { + int32_t row; + ecs_entity_t entity = 0; + const ecs_entity_t *entities = ecs_table_entities(table); + + for (row = impl->row; row < impl->end; row ++) { + entity = entities[row]; + ecs_record_t *record = flecs_entities_get(world, entity); + if (record->row & EcsEntityIsTraversable) { + /* Found traversable entity */ + it->sources[op->field_index] = entity; + break; + } + } - if (o->last_event_id == world->event_id) { - /* Already handled this event */ - return false; - } + if (row == impl->end) { + /* No traversable entities remaining in table */ + impl->table = NULL; + return NULL; + } - o->last_event_id = world->event_id; + impl->row = row; - ecs_iter_t user_it = *it; - user_it.term_count = o->filter.term_count_actual; - user_it.terms = o->filter.terms; - user_it.flags = o->filter.filter ? EcsIterIsFilter : 0; - user_it.ids = NULL; - user_it.columns = NULL; - user_it.subjects = NULL; - user_it.sizes = NULL; - user_it.ptrs = NULL; - flecs_iter_init(&user_it, flecs_iter_cache_all); + /* Get down cache entry for traversable entity */ + bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; + impl->down = flecs_query_get_down_cache(ctx, &impl->cache, + impl->trav, entity, impl->cr_with, self, match_empty); + impl->cache_elem = -1; + } - ecs_table_t *table = it->table; - ecs_table_t *prev_table = it->other_table; - int32_t pivot_term = it->term_index; - ecs_term_t *term = &o->filter.terms[pivot_term]; + return impl->down; +} - if (term->oper == EcsNot) { - table = it->other_table; - prev_table = it->table; +/* Select all tables that can reach the target component through the traversal + * relationship. */ +bool flecs_query_up_select( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx, + ecs_query_up_select_trav_kind_t trav_kind, + ecs_query_up_select_kind_t kind) +{ + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_query_up_impl_t *impl = op_ctx->impl; + + if (!impl) { + ecs_iter_t *it = ctx->it; + ecs_allocator_t *a = flecs_query_get_allocator(it); + impl = op_ctx->impl = flecs_calloc_t(a, ecs_query_up_impl_t); } - if (!table) { - table = &world->store.root; + ecs_iter_t *it = ctx->it; + bool redo_select = redo; + const ecs_query_t *q = &ctx->query->pub; + bool self = trav_kind == FlecsQueryUpSelectSelfUp; + + impl->trav = q->terms[op->term_index].trav; + + /* Reuse component record from previous iteration if possible*/ + if (!impl->cr_trav) { + impl->cr_trav = flecs_components_get(ctx->world, + ecs_pair(impl->trav, EcsWildcard)); } - if (!prev_table) { - prev_table = &world->store.root; + + /* If component record is not found, or if it doesn't have any tables, revert to + * iterating owned components (no traversal) */ + if (!impl->cr_trav || + !flecs_table_cache_count(&impl->cr_trav->cache)) + { + if (!self) { + /* If operation does not match owned components, return false */ + return false; + } else if (kind == FlecsQueryUpSelectId) { + return flecs_query_select_id(op, redo, ctx, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + } else if (kind == FlecsQueryUpSelectDefault) { + return flecs_query_select(op, redo, ctx); + } else if (kind == FlecsQueryUpSelectSparse) { + return flecs_query_sparse_select(op, redo, ctx, 0); + } else { + /* Invalid select kind */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } } - static int obs_count = 0; - obs_count ++; + if (!redo) { + /* Get component id to match */ + impl->with = flecs_query_op_get_id(op, ctx); - /* Populate the column for the term that triggered. This will allow the - * matching algorithm to pick the right column in case the term is a - * wildcard matching multiple columns. */ - user_it.columns[0] = 0; + /* Get component record for component to match */ + impl->cr_with = flecs_components_get(ctx->world, impl->with); + if (!impl->cr_with) { + /* If component record does not exist, there can't be any results */ + return false; + } - /* Normalize id */ - int32_t column = it->columns[0]; - if (column < 0) { - column = -column; + impl->down = NULL; + impl->cache_elem = 0; } - user_it.columns[pivot_term] = column; + /* Get last used entry from down traversal cache. Cache entries in the down + * traversal cache contain a list of tables that can reach the requested + * component through the traversal relationship, for a traversable entity + * which acts as the key for the cache. */ + ecs_trav_down_t *down = impl->down; - if (flecs_filter_match_table(world, &o->filter, table, user_it.ids, - user_it.columns, user_it.subjects, NULL, NULL, false, -1, - user_it.flags)) - { - /* Monitor observers only trigger when the filter matches for the first - * time with an entity */ - if (o->is_monitor) { - if (flecs_filter_match_table(world, &o->filter, prev_table, - NULL, NULL, NULL, NULL, NULL, true, -1, user_it.flags)) +next_down_entry: + /* Get (next) entry in down traversal cache */ + while (!down) { + ecs_table_t *table = impl->table; + + /* Get (next) table with traversable entities that have the + * requested component. We'll traverse downwards from the + * traversable entities in the table to find all entities that can + * reach the component through the traversal relationship. */ + if (!table) { + /* Reset source, in case we have to return a component matched + * by the entity in the found table. */ + it->sources[op->field_index] = 0; + + if (!flecs_query_up_select_table( + op, redo_select, ctx, trav_kind, kind)) { - goto done; + return false; } - if (term->oper == EcsNot) { - /* Flip event if this is a Not, so OnAdd and OnRemove can be - * reliably used to check if we're entering or leaving the - * monitor */ - if (it->event == EcsOnAdd) { - user_it.event = EcsOnRemove; - } else if (it->event == EcsOnRemove) { - user_it.event = EcsOnAdd; + table = impl->table; + + /* If 'self' is true, we're evaluating a term with self|up. This + * means that before traversing downwards, we should also return + * the current table as result. */ + if (self) { + if (!flecs_query_table_filter(table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + flecs_reset_source_set_flag(it, op->field_index); + impl->row --; + return true; } } - } - - flecs_iter_populate_data(world, &user_it, - it->table, it->offset, it->count, user_it.ptrs, user_it.sizes); - user_it.ids[it->term_index] = it->event_id; - user_it.system = o->entity; - user_it.term_index = it->term_index; - user_it.self = o->self; - user_it.ctx = o->ctx; - user_it.term_count = o->filter.term_count_actual; - flecs_iter_validate(&user_it); - - o->callback(&user_it); + redo_select = true; + } else { + /* Evaluate next entity in table */ + impl->row ++; + } - ecs_iter_fini(&user_it); - return true; + /* Get down cache entry for next traversable entity in table */ + down = flecs_query_up_find_next_traversable(op, ctx, trav_kind); + if (!down) { + goto next_down_entry; + } } -done: - ecs_iter_fini(&user_it); - return false; -} - -bool ecs_observer_default_run_action(ecs_iter_t *it) { - return observer_run(it); -} +next_down_elem: + /* Get next element (table) in cache entry */ + if ((++ impl->cache_elem) >= ecs_vec_count(&down->elems)) { + /* No more elements in cache entry, find next.*/ + down = NULL; + goto next_down_entry; + } -static -void default_observer_run_callback(ecs_iter_t *it) { - observer_run(it); -} + ecs_trav_down_elem_t *elem = ecs_vec_get_t( + &down->elems, ecs_trav_down_elem_t, impl->cache_elem); + flecs_query_var_set_range(op, op->src.var, elem->range.table, + elem->range.offset, elem->range.count, ctx); + flecs_query_set_vars(op, impl->matched, ctx); -/* For convenience, so applications can (in theory) use a single run callback - * that uses ecs_iter_next to iterate results */ -static -bool default_observer_next_callback(ecs_iter_t *it) { - if (it->interrupted_by) { - return false; - } else { - it->interrupted_by = it->system; - return true; + if (flecs_query_table_filter(elem->range.table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + /* Go to next table if table contains prefabs, disabled entities or + * entities that are not queryable. */ + goto next_down_elem; } -} -static -void observer_run_callback(ecs_iter_t *it) { - ecs_observer_t *o = it->ctx; - ecs_run_action_t run = o->run; + flecs_set_source_set_flag(it, op->field_index); - if (run) { - it->next = default_observer_next_callback; - it->callback = default_observer_run_callback; - it->interrupted_by = 0; - run(it); - } else { - observer_run(it); - } + return true; } -static -void observer_yield_existing( - ecs_world_t *world, - ecs_observer_t *observer) +/* Check if a table can reach the target component through the traversal + * relationship. */ +bool flecs_query_up_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - ecs_run_action_t run = observer->run; - if (!run) { - run = default_observer_run_callback; - } + const ecs_query_t *q = &ctx->query->pub; + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_query_up_impl_t *impl = op_ctx->impl; + ecs_iter_t *it = ctx->it; - int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter); - if (pivot_term < 0) { - return; + if (!impl) { + ecs_allocator_t *a = flecs_query_get_allocator(it); + impl = op_ctx->impl = flecs_calloc_t(a, ecs_query_up_impl_t); } - /* If yield existing is enabled, trigger for each thing that matches - * the event, if the event is iterable. */ - int i, count = observer->event_count; - for (i = 0; i < count; i ++) { - ecs_entity_t evt = observer->events[i]; - const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); - if (!iterable) { - continue; - } + impl->trav = q->terms[op->term_index].trav; + if (!impl->cr_trav) { + impl->cr_trav = flecs_components_get(ctx->world, + ecs_pair(impl->trav, EcsWildcard)); + } - ecs_iter_t it; - iterable->init(world, world, &it, &observer->filter.terms[pivot_term]); - it.terms = observer->filter.terms; - it.term_count = 1; - it.term_index = pivot_term; - it.system = observer->entity; - it.ctx = observer; - it.binding_ctx = observer->binding_ctx; - it.event = evt; - - ecs_iter_next_action_t next = it.next; - ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); - while (next(&it)) { - run(&it); - world->event_id ++; - } + if (!impl->cr_trav || + !flecs_table_cache_count(&impl->cr_trav->cache)) + { + /* If there are no tables with traversable relationship, there are no + * matches. */ + return false; } -} -ecs_entity_t ecs_observer_init( - ecs_world_t *world, - const ecs_observer_desc_t *desc) -{ - ecs_entity_t entity = 0; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(!world->is_fini, ECS_INVALID_OPERATION, NULL); - ecs_check(desc->callback != NULL || desc->run != NULL, - ECS_INVALID_OPERATION, NULL); + if (!redo) { + impl->trav = q->terms[op->term_index].trav; + impl->with = flecs_query_op_get_id(op, ctx); + impl->cr_with = flecs_components_get(ctx->world, impl->with); - /* If entity is provided, create it */ - ecs_entity_t existing = desc->entity.entity; - entity = ecs_entity_init(world, &desc->entity); - if (!existing && !desc->entity.name) { - ecs_add_pair(world, entity, EcsChildOf, EcsFlecsHidden); - } + /* If component record for component doesn't exist, there are no matches */ + if (!impl->cr_with) { + return false; + } - bool added = false; - EcsObserver *comp = ecs_get_mut(world, entity, EcsObserver, &added); - if (added) { - ecs_observer_t *observer = flecs_sparse_add( - world->observers, ecs_observer_t); - ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); - observer->id = flecs_sparse_last_id(world->observers); - comp->observer = observer; - - /* Make writeable copy of filter desc so that we can set name. This will - * make debugging easier, as any error messages related to creating the - * filter will have the name of the observer. */ - ecs_filter_desc_t filter_desc = desc->filter; - filter_desc.name = desc->entity.name; - - /* Parse filter */ - ecs_filter_t *filter = &observer->filter; - if (ecs_filter_init(world, filter, &filter_desc)) { - flecs_observer_fini(world, observer); - return 0; + /* Get the range (table) that is currently being evaluated. In most + * cases the range will cover the entire table, but in some cases it + * can only cover a subset of the entities in the table. */ + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + if (!range.table) { + return false; } - /* Creating an observer with no terms has no effect */ - ecs_assert(observer->filter.term_count != 0, - ECS_INVALID_PARAMETER, NULL); + op_ctx->range = range; + if (!op_ctx->range.count) { + op_ctx->range.count = ecs_table_count(op_ctx->range.table); + } - int i, e; - for (i = 0; i < ECS_TRIGGER_DESC_EVENT_COUNT_MAX; i ++) { - ecs_entity_t event = desc->events[i]; - if (!event) { - break; - } + op_ctx->cur = -1; - if (event == EcsMonitor) { - /* Monitor event must be first and last event */ - ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL); + /* Handle tables with non-fragmenting ChildOf */ + if (impl->trav == EcsChildOf) { + if (range.table->flags & EcsTableHasParent) { + if (q->flags & EcsQueryNested) { + /* If this is a nested query (used to populate a cache), + * don't store entries for individual entities in the cache. + * Instead, match the entire table, and figure out from + * which parent the entity gets the component in an uncached + * operation. */ - observer->events[0] = EcsOnAdd; - observer->events[1] = EcsOnRemove; - observer->event_count ++; - observer->is_monitor = true; - } else { - observer->events[i] = event; + /* Signal that the uncached instruction needs to search. + * This helps distinguish between tables with a Parent + * component that own the component vs. those that don't. */ + it->sources[op->field_index] = EcsWildcard; + return true; + } + + /* Signals that we need to evaluate individual rows. */ + op_ctx->cur = 0; + flecs_query_src_set_single(op, range.offset, ctx); } + } + } else { +next_row: + if (op_ctx->cur == -1) { + /* The table either can or can't reach the component, nothing to do + * for a second evaluation of this operation.*/ + return false; + } - observer->event_count ++; + /* Evaluate next row. Only necessary for non-fragmenting ChildOf since + * in that case different table rows can have different parents. */ + op_ctx->cur ++; + if (op_ctx->cur >= op_ctx->range.count) { + return false; } - /* Observer must have at least one event */ - ecs_check(observer->event_count != 0, ECS_INVALID_PARAMETER, NULL); + flecs_query_src_set_single(op, op_ctx->range.offset + op_ctx->cur, ctx); + } - observer->callback = desc->callback; - observer->run = desc->run; - observer->self = desc->self; - observer->ctx = desc->ctx; - observer->binding_ctx = desc->binding_ctx; - observer->ctx_free = desc->ctx_free; - observer->binding_ctx_free = desc->binding_ctx_free; - observer->entity = entity; - comp->observer = observer; + /* Get entry from up traversal cache. The up traversal cache contains + * the entity on which the component was found, with additional metadata + * on where it is stored. */ + ecs_trav_up_t *up = flecs_query_get_up_cache(ctx, &impl->cache, + op_ctx->range.table, op_ctx->range.offset + op_ctx->cur, impl->with, + impl->trav, impl->cr_with, impl->cr_trav); - /* Create a trigger for each term in the filter */ - ecs_trigger_desc_t tdesc = { - .callback = observer_run_callback, - .ctx = observer, - .binding_ctx = desc->binding_ctx, - .match_prefab = observer->filter.match_prefab, - .match_disabled = observer->filter.match_disabled, - .last_event_id = &observer->last_event_id - }; + if (!up) { + /* Component is not reachable from table */ + goto next_row; + } - for (i = 0; i < filter->term_count; i ++) { - tdesc.term = filter->terms[i]; - ecs_oper_kind_t oper = tdesc.term.oper; - ecs_id_t id = tdesc.term.id; + it->sources[op->field_index] = flecs_entities_get_alive( + ctx->world, up->src); + it->trs[op->field_index] = up->tr; + it->ids[op->field_index] = up->id; + if (op->match_flags & EcsTermMatchAny) { + it->ids[op->field_index] = ecs_pair(impl->trav, EcsWildcard); + } + + flecs_query_set_vars(op, up->id, ctx); + flecs_set_source_set_flag(it, op->field_index); - bool is_tag = ecs_id_is_tag(world, id); + return true; +} - if (is_tag) { - /* If id is a tag, convert OnSet/UnSet to OnAdd/OnRemove. This - * allows for creating OnSet observers with both components and - * tags that only fire when the entity has all ids */ - for (e = 0; e < observer->event_count; e ++) { - if (observer->events[e] == EcsOnSet) { - tdesc.events[e] = EcsOnAdd; - } else - if (observer->events[e] == EcsUnSet) { - tdesc.events[e] = EcsOnRemove; - } else { - tdesc.events[e] = observer->events[e]; - } - } - } else { - ecs_os_memcpy_n(tdesc.events, observer->events, ecs_entity_t, - observer->event_count); - } - - /* AndFrom & OrFrom terms insert multiple triggers */ - if (oper == EcsAndFrom || oper == EcsOrFrom) { - const EcsType *type = ecs_get(world, id, EcsType); - int32_t ti, ti_count = ecs_vector_count(type->normalized->type); - ecs_id_t *ti_ids = ecs_vector_first( - type->normalized->type, ecs_id_t); - - /* Correct operator will be applied when a trigger occurs, and - * the observer is evaluated on the trigger source */ - tdesc.term.oper = EcsAnd; - for (ti = 0; ti < ti_count; ti ++) { - tdesc.term.pred.name = NULL; - tdesc.term.pred.entity = ti_ids[ti]; - tdesc.term.id = ti_ids[ti]; - ecs_entity_t t = ecs_vector_add(&observer->triggers, - ecs_entity_t)[0] = ecs_trigger_init(world, &tdesc); - if (!t) { - goto error; - } - } - continue; - } +/* Check if a table can reach the target component through the traversal + * relationship, or if the table has the target component itself. */ +bool flecs_query_self_up_with( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); + ecs_query_up_impl_t *impl = op_ctx->impl; + ecs_iter_t *it = ctx->it; - ecs_entity_t t = ecs_vector_add(&observer->triggers, ecs_entity_t) - [0] = ecs_trigger_init(world, &tdesc); - if (!t) { - goto error; - } + if (!redo) { + if (!op_ctx->impl) { + ecs_allocator_t *a = flecs_query_get_allocator(it); + impl = op_ctx->impl = flecs_calloc_t(a, ecs_query_up_impl_t); } - if (desc->entity.name) { - ecs_trace("#[green]observer#[reset] %s created", - ecs_get_name(world, entity)); - } + flecs_reset_source_set_flag(ctx->it, op->field_index); + op_ctx->cur = -1; + impl->trav = 0; + impl->with = flecs_query_op_get_id(op, ctx); + } - if (desc->yield_existing) { - observer_yield_existing(world, observer); + /* First check if table has the component */ + if (op_ctx->impl->trav == 0) { + bool result; + ecs_id_t with = impl->with; + if (ECS_PAIR_FIRST(with) == EcsChildOf) { + if (with == ecs_childof(EcsWildcard)) { + result = flecs_query_tree_and_wildcard(op, redo, ctx, false); + } else { + result = flecs_query_tree_and(op, redo, ctx); + } + } else { + result = flecs_query_with(op, redo, ctx); } - } else { - ecs_assert(comp->observer != NULL, ECS_INTERNAL_ERROR, NULL); - /* If existing entity handle was provided, override existing params */ - if (existing) { - if (desc->callback) { - ((ecs_observer_t*)comp->observer)->callback = desc->callback; - } - if (desc->ctx) { - ((ecs_observer_t*)comp->observer)->ctx = desc->ctx; - } - if (desc->binding_ctx) { - ((ecs_observer_t*)comp->observer)->binding_ctx = - desc->binding_ctx; + if (result) { + /* Table has component, no need to traverse*/ + if (flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar) { + /* Matching self, so set sources to 0 */ + it->sources[op->field_index] = 0; } - } + return true; + } } - return entity; -error: - if (entity) { - ecs_delete(world, entity); + /* Table doesn't have component, traverse relationship */ + return flecs_query_up_with(op, redo, ctx); +} + +bool flecs_query_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + return flecs_query_up_with(op, redo, ctx); + } else { + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectUp, FlecsQueryUpSelectDefault); } - return 0; } -void flecs_observer_fini( - ecs_world_t *world, - ecs_observer_t *observer) +bool flecs_query_self_up( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) { - /* Cleanup triggers */ - int i, count = ecs_vector_count(observer->triggers); - ecs_entity_t *triggers = ecs_vector_first(observer->triggers, ecs_entity_t); - for (i = 0; i < count; i ++) { - ecs_entity_t t = triggers[i]; - if (!t) continue; - ecs_delete(world, triggers[i]); + uint64_t written = ctx->written[ctx->op_index]; + if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { + return flecs_query_self_up_with(op, redo, ctx); + } else { + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectDefault); } - ecs_vector_free(observer->triggers); +} - /* Cleanup filters */ - ecs_filter_fini(&observer->filter); +/** + * @file query/engine/eval_utils.c + * @brief Query engine evaluation utilities. + */ - /* Cleanup context */ - if (observer->ctx_free) { - observer->ctx_free(observer->ctx); - } - if (observer->binding_ctx_free) { - observer->binding_ctx_free(observer->binding_ctx); +void flecs_query_set_iter_this( + ecs_iter_t *it, + const ecs_query_run_ctx_t *ctx) +{ + const ecs_var_t *var = &ctx->vars[0]; + const ecs_table_range_t *range = &var->range; + ecs_table_t *table = range->table; + int32_t count = range->count; + if (table) { + if (!count) { + count = ecs_table_count(table); + } + it->table = table; + it->offset = range->offset; + it->count = count; + it->entities = ecs_table_entities(table); + if (it->entities) { + it->entities += it->offset; + } + } else if (count == 1) { + it->count = 1; + it->entities = &ctx->vars[0].entity; } - - /* Cleanup observer storage */ - flecs_sparse_remove(world->observers, observer->id); } -void* ecs_get_observer_ctx( - const ecs_world_t *world, - ecs_entity_t observer) +ecs_query_op_ctx_t* flecs_op_ctx_( + const ecs_query_run_ctx_t *ctx) { - const EcsObserver *o = ecs_get(world, observer, EcsObserver); - if (o) { - return o->observer->ctx; - } else { - return NULL; - } + return &ctx->op_ctx[ctx->op_index]; } -void* ecs_get_observer_binding_ctx( - const ecs_world_t *world, - ecs_entity_t observer) +#define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) + +void flecs_reset_source_set_flag( + ecs_iter_t *it, + int32_t field_index) { - const EcsObserver *o = ecs_get(world, observer, EcsObserver); - if (o) { - return o->observer->binding_ctx; - } else { - return NULL; - } + ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); + ECS_TERMSET_CLEAR(it->up_fields, 1u << field_index); } - -static -void table_cache_list_remove( - ecs_table_cache_t *cache, - ecs_table_cache_hdr_t *elem) +void flecs_set_source_set_flag( + ecs_iter_t *it, + int32_t field_index) { - ecs_table_cache_hdr_t *next = elem->next; - ecs_table_cache_hdr_t *prev = elem->prev; + ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); + ECS_TERMSET_SET(it->up_fields, 1u << field_index); +} - if (next) { - next->prev = prev; +ecs_table_range_t flecs_query_var_get_range( + int32_t var_id, + const ecs_query_run_ctx_t *ctx) +{ + ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + ecs_table_t *table = var->range.table; + if (table) { + return var->range; } - if (prev) { - prev->next = next; + + ecs_entity_t entity = var->entity; + if (entity && entity != EcsWildcard) { + var->range = flecs_range_from_entity(ctx->world, entity); + return var->range; } - cache->empty_tables.count -= !!elem->empty; - cache->tables.count -= !elem->empty; + return (ecs_table_range_t){ 0 }; +} - if (cache->empty_tables.first == elem) { - cache->empty_tables.first = next; - } else if (cache->tables.first == elem) { - cache->tables.first = next; +ecs_table_t* flecs_query_var_get_table( + int32_t var_id, + const ecs_query_run_ctx_t *ctx) +{ + ecs_var_t *var = &ctx->vars[var_id]; + ecs_table_t *table = var->range.table; + if (table) { + return table; } - if (cache->empty_tables.last == elem) { - cache->empty_tables.last = prev; + + ecs_entity_t entity = var->entity; + if (entity && entity != EcsWildcard) { + var->range = flecs_range_from_entity(ctx->world, entity); + return var->range.table; } - if (cache->tables.last == elem) { - cache->tables.last = prev; + + return NULL; +} + +ecs_table_t* flecs_query_get_table( + const ecs_query_op_t *op, + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_query_run_ctx_t *ctx) +{ + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); + if (flags & EcsQueryIsEntity) { + return ecs_get_table(ctx->world, ref->entity); + } else { + return flecs_query_var_get_table(ref->var, ctx); } } -static -void table_cache_list_insert( - ecs_table_cache_t *cache, - ecs_table_cache_hdr_t *elem) +ecs_table_range_t flecs_query_get_range( + const ecs_query_op_t *op, + const ecs_query_ref_t *ref, + ecs_flags16_t ref_kind, + const ecs_query_run_ctx_t *ctx) { - ecs_table_cache_hdr_t *last; - if (elem->empty) { - last = cache->empty_tables.last; - cache->empty_tables.last = elem; - if ((++ cache->empty_tables.count) == 1) { - cache->empty_tables.first = elem; - } + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); + if (flags & EcsQueryIsEntity) { + ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); + return flecs_range_from_entity(ctx->world, ref->entity); } else { - last = cache->tables.last; - cache->tables.last = elem; - if ((++ cache->tables.count) == 1) { - cache->tables.first = elem; + ecs_var_t *var = &ctx->vars[ref->var]; + if (var->range.table) { + return ctx->vars[ref->var].range; + } else if (var->entity) { + return flecs_range_from_entity(ctx->world, var->entity); } } + return (ecs_table_range_t){0}; +} - elem->next = NULL; - elem->prev = last; - - if (last) { - last->next = elem; +ecs_entity_t flecs_query_var_get_entity( + ecs_var_id_t var_id, + const ecs_query_run_ctx_t *ctx) +{ + ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + ecs_entity_t entity = var->entity; + if (entity) { + return entity; } + + ecs_assert(var->range.count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = var->range.table; + const ecs_entity_t *entities = ecs_table_entities(table); + var->entity = entities[var->range.offset]; + return var->entity; } -void ecs_table_cache_init( - ecs_table_cache_t *cache) +void flecs_query_var_reset( + ecs_var_id_t var_id, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_init(&cache->index, ecs_table_cache_hdr_t*, 0); + ctx->vars[var_id].entity = EcsWildcard; + ctx->vars[var_id].range.table = NULL; } -void ecs_table_cache_fini( - ecs_table_cache_t *cache) +void flecs_query_var_set_range( + const ecs_query_op_t *op, + ecs_var_id_t var_id, + ecs_table_t *table, + int32_t offset, + int32_t count, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_fini(&cache->index); + (void)op; + ecs_assert(ctx->query_vars[var_id].kind == EcsVarTable, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_query_is_written(var_id, op->written), + ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + var->entity = 0; + var->range = (ecs_table_range_t){ + .table = table, + .offset = offset, + .count = count + }; } -bool ecs_table_cache_is_empty( - const ecs_table_cache_t *cache) +void flecs_query_src_set_single( + const ecs_query_op_t *op, + int32_t row, + const ecs_query_run_ctx_t *ctx) { - return ecs_map_count(&cache->index) == 0; + if (!(op->flags & (EcsQueryIsVar << EcsQuerySrc))) { + return; + } + + ecs_var_id_t src = op->src.var; + ecs_var_t *var = &ctx->vars[src]; + + ecs_assert(var->range.table != NULL, ECS_INTERNAL_ERROR, NULL); + var->range.offset = row; + var->range.count = 1; + + var->entity = ecs_table_entities(var->range.table)[row]; } -void ecs_table_cache_insert( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *result) +void flecs_query_src_set_range( + const ecs_query_op_t *op, + const ecs_table_range_t *range, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_table_cache_get(cache, table) == NULL, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - - bool empty; - if (!table) { - empty = false; - } else { - empty = ecs_table_count(table) == 0; + if (!(op->flags & (EcsQueryIsVar << EcsQuerySrc))) { + return; } - result->cache = cache; - result->table = (ecs_table_t*)table; - result->empty = empty; - - table_cache_list_insert(cache, result); + ecs_var_id_t src = op->src.var; + ecs_var_t *var = &ctx->vars[src]; + + var->entity = 0; + ecs_assert(var->range.table == range->table, ECS_INTERNAL_ERROR, NULL); + var->range = *range; - if (table) { - ecs_map_set_ptr(&cache->index, table->id, result); + if (ctx->query_vars[src].kind != EcsVarTable) { + ecs_assert(range->count == 1, ECS_INTERNAL_ERROR, NULL); + var->entity = ecs_table_entities(range->table)[range->offset]; } +} - ecs_assert(empty || cache->tables.first != NULL, +void flecs_query_var_set_entity( + const ecs_query_op_t *op, + ecs_var_id_t var_id, + ecs_entity_t entity, + const ecs_query_run_ctx_t *ctx) +{ + (void)op; + ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!empty || cache->empty_tables.first != NULL, + ecs_assert(flecs_query_is_written(var_id, op->written), ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &ctx->vars[var_id]; + var->range.table = NULL; + var->entity = entity; } -void* ecs_table_cache_get( - const ecs_table_cache_t *cache, - const ecs_table_t *table) +void flecs_query_set_vars( + const ecs_query_op_t *op, + ecs_id_t id, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - if (table) { - return ecs_map_get_ptr(&cache->index, ecs_table_cache_hdr_t*, table->id); - } else { - ecs_table_cache_hdr_t *elem = cache->tables.first; - ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL); - return elem; + ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + + if (flags_1st & EcsQueryIsVar) { + ecs_var_id_t var = op->first.var; + if (op->written & (1ull << var)) { + if (ECS_IS_PAIR(id)) { + flecs_query_var_set_entity( + op, var, ecs_get_alive(ctx->world, ECS_PAIR_FIRST(id)), ctx); + } else { + flecs_query_var_set_entity(op, var, id, ctx); + } + } + } + + if (flags_2nd & EcsQueryIsVar) { + ecs_var_id_t var = op->second.var; + if (op->written & (1ull << var)) { + flecs_query_var_set_entity( + op, var, ecs_get_alive(ctx->world, ECS_PAIR_SECOND(id)), ctx); + } } } -void* ecs_table_cache_remove( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *elem) +ecs_table_range_t flecs_get_ref_range( + const ecs_query_ref_t *ref, + ecs_flags16_t flag, + const ecs_query_run_ctx_t *ctx) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (flag & EcsQueryIsEntity) { + return flecs_range_from_entity(ctx->world, ref->entity); + } else if (flag & EcsQueryIsVar) { + return flecs_query_var_get_range(ref->var, ctx); + } + return (ecs_table_range_t){0}; +} - if (!ecs_map_is_initialized(&cache->index)) { - return NULL; +ecs_entity_t flecs_get_ref_entity( + const ecs_query_ref_t *ref, + ecs_flags16_t flag, + const ecs_query_run_ctx_t *ctx) +{ + if (flag & EcsQueryIsEntity) { + return ref->entity; + } else if (flag & EcsQueryIsVar) { + return flecs_query_var_get_entity(ref->var, ctx); } + return 0; +} - if (!elem) { - elem = ecs_map_get_ptr( - &cache->index, ecs_table_cache_hdr_t*, table->id); - if (!elem) { - return false; +ecs_id_t flecs_query_op_get_id_w_written( + const ecs_query_op_t *op, + uint64_t written, + const ecs_query_run_ctx_t *ctx) +{ + ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); + ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); + ecs_entity_t first = 0, second = 0; + + if (flags_1st) { + if (flecs_ref_is_written(op, &op->first, EcsQueryFirst, written)) { + first = flecs_get_ref_entity(&op->first, flags_1st, ctx); + } else if (flags_1st & EcsQueryIsVar) { + first = EcsWildcard; + } + } + if (flags_2nd) { + if (flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { + second = flecs_get_ref_entity(&op->second, flags_2nd, ctx); + } else if (flags_2nd & EcsQueryIsVar) { + second = EcsWildcard; } } - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->table == table, ECS_INTERNAL_ERROR, NULL); + if (flags_2nd & (EcsQueryIsVar | EcsQueryIsEntity)) { + return ecs_pair(first, second); + } else { + return flecs_entities_get_alive(ctx->world, first); + } +} - table_cache_list_remove(cache, elem); +ecs_id_t flecs_query_op_get_id( + const ecs_query_op_t *op, + const ecs_query_run_ctx_t *ctx) +{ + uint64_t written = ctx->written[ctx->op_index]; + return flecs_query_op_get_id_w_written(op, written, ctx); +} - ecs_map_remove(&cache->index, table->id); +int16_t flecs_query_next_column( + ecs_table_t *table, + ecs_id_t id, + int32_t column) +{ + if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { + column = column + 1; + } else { + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + column = ecs_search_offset(NULL, table, column + 1, id, NULL); + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); + } + return flecs_ito(int16_t, column); +} - return elem; +void flecs_query_it_set_tr( + ecs_iter_t *it, + int32_t field_index, + const ecs_table_record_t *tr) +{ + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); + it->trs[field_index] = tr; } -bool ecs_table_cache_set_empty( - ecs_table_cache_t *cache, - const ecs_table_t *table, - bool empty) +ecs_id_t flecs_query_it_set_id( + ecs_iter_t *it, + ecs_table_t *table, + int32_t field_index, + int32_t column) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column < table->type.count, ECS_INTERNAL_ERROR, NULL); + return it->ids[field_index] = table->type.array[column]; +} - ecs_table_cache_hdr_t *elem = ecs_map_get_ptr( - &cache->index, ecs_table_cache_hdr_t*, table->id); - if (!elem) { - return false; +void flecs_query_set_match( + const ecs_query_op_t *op, + ecs_table_t *table, + int32_t column, + const ecs_query_run_ctx_t *ctx) +{ + int32_t field_index = op->field_index; + if (field_index == -1 || column == -1) { + return; } - if (elem->empty == empty) { - return false; + ecs_iter_t *it = ctx->it; + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column < table->type.count, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = &table->_->records[column]; + flecs_query_it_set_tr(it, field_index, tr); + ecs_id_t matched = flecs_query_it_set_id(it, table, field_index, tr->index); + flecs_query_set_vars(op, matched, ctx); +} + +void flecs_query_set_trav_match( + const ecs_query_op_t *op, + const ecs_table_record_t *tr, + ecs_entity_t trav, + ecs_entity_t second, + const ecs_query_run_ctx_t *ctx) +{ + int32_t field_index = op->field_index; + if (field_index == -1) { + return; } - table_cache_list_remove(cache, elem); - elem->empty = empty; - table_cache_list_insert(cache, elem); + ecs_iter_t *it = ctx->it; + ecs_id_t matched = ecs_pair(trav, second); + it->ids[op->field_index] = matched; + flecs_query_it_set_tr(it, op->field_index, tr); + flecs_query_set_vars(op, matched, ctx); +} - return true; +bool flecs_query_table_filter( + ecs_table_t *table, + ecs_query_lbl_t other, + ecs_flags32_t filter_mask) +{ + uint32_t filter = flecs_ito(uint32_t, other); + return (table->flags & filter_mask & filter) != 0; } -void ecs_table_cache_fini_delete_all( +/** + * @file query/engine/trav_cache.c + * @brief Cache that stores the result of graph traversal. + */ + + +static +void flecs_query_build_down_cache( ecs_world_t *world, - ecs_table_cache_t *cache) + ecs_allocator_t *a, + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - if (!ecs_map_is_initialized(&cache->index)) { + ecs_component_record_t *cr = flecs_components_get(world, ecs_pair(trav, entity)); + if (!cr) { return; } - /* Temporarily set index to NULL, so that when the table tries to remove - * itself from the cache it won't be able to. This keeps the arrays we're - * iterating over consistent */ - ecs_map_t index = cache->index; - ecs_os_zeromem(&cache->index); + ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, + ecs_trav_elem_t); + elem->entity = entity; + elem->cr = cr; - ecs_table_cache_hdr_t *cur, *next = cache->tables.first; - while ((cur = next)) { - flecs_delete_table(world, cur->table); - next = cur->next; - } + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&cr->cache, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; + } - next = cache->empty_tables.first; - while ((cur = next)) { - flecs_delete_table(world, cur->table); - next = cur->next; + int32_t i, count = ecs_table_count(table); + const ecs_entity_t *entities = ecs_table_entities(table); + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + if (r->row & EcsEntityIsTraversable) { + flecs_query_build_down_cache( + world, a, ctx, cache, trav, entities[i]); + } + } + } } +} + +static +void flecs_query_build_up_cache( + ecs_world_t *world, + ecs_allocator_t *a, + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table, + const ecs_table_record_t *tr, + int32_t root_column) +{ + ecs_id_t *ids = table->type.array; + int32_t i = tr->index, end = i + tr->count; + bool is_root = root_column == -1; - cache->index = index; + for (; i < end; i ++) { + ecs_entity_t second = ecs_pair_second(world, ids[i]); + if (is_root) { + root_column = i; + } - ecs_table_cache_fini(cache); + ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, + ecs_trav_elem_t); + + el->entity = second; + el->tr = &table->_->records[i]; + el->cr = NULL; + + ecs_record_t *r = flecs_entities_get_any(world, second); + if (r->table) { + const ecs_table_record_t *r_tr = flecs_component_get_table( + cache->cr, r->table); + if (!r_tr) { + return; + } + flecs_query_build_up_cache(world, a, ctx, cache, trav, r->table, + r_tr, root_column); + } + } } -bool flecs_table_cache_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out) +void flecs_query_trav_cache_fini( + ecs_allocator_t *a, + ecs_trav_cache_t *cache) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - out->next = cache->tables.first; - out->cur = NULL; - return out->next != NULL; + ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); } -bool flecs_table_cache_empty_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out) +void flecs_query_get_trav_down_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t entity) { - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - out->next = cache->empty_tables.first; - out->cur = NULL; - return out->next != NULL; + if (cache->id != ecs_pair(trav, entity) || cache->up) { + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + flecs_query_build_down_cache(world, a, ctx, cache, trav, entity); + cache->id = ecs_pair(trav, entity); + cache->up = false; + } } -ecs_table_cache_hdr_t* _flecs_table_cache_next( - ecs_table_cache_iter_t *it) +void flecs_query_get_trav_up_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_cache_t *cache, + ecs_entity_t trav, + ecs_table_t *table) { - ecs_table_cache_hdr_t *next = it->next; - if (!next) { - return false; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + + ecs_component_record_t *cr = cache->cr; + if (!cr || cr->id != ecs_pair(trav, EcsWildcard)) { + cr = cache->cr = flecs_components_get(world, + ecs_pair(trav, EcsWildcard)); + if (!cr) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + return; + } } - it->cur = next; - it->next = next->next; - return next; -} + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + return; + } -#include -#include + ecs_id_t id = table->type.array[tr->index]; -void ecs_os_api_impl(ecs_os_api_t *api); + if (cache->id != id || !cache->up) { + ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); + flecs_query_build_up_cache(world, a, ctx, cache, trav, table, tr, -1); + cache->id = id; + cache->up = true; + } +} -static bool ecs_os_api_initialized = false; -static bool ecs_os_api_initializing = false; -static int ecs_os_api_init_count = 0; +/** + * @file query/engine/trav_down_cache.c + * @brief Compile query term. + */ -#ifndef __EMSCRIPTEN__ -ecs_os_api_t ecs_os_api = { - .log_with_color_ = true, - .log_level_ = -1 /* disable tracing by default, but enable >= warnings */ -}; -#else -/* Disable colors by default for emscripten */ -ecs_os_api_t ecs_os_api = { - .log_level_ = -1 -}; -#endif -int64_t ecs_os_api_malloc_count = 0; -int64_t ecs_os_api_realloc_count = 0; -int64_t ecs_os_api_calloc_count = 0; -int64_t ecs_os_api_free_count = 0; +static +void flecs_trav_entity_down_isa( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_component_record_t *cr_with, + bool self, + bool empty); -void ecs_os_set_api( - ecs_os_api_t *os_api) +static +void flecs_trav_entity_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_component_record_t *cr_trav, + ecs_component_record_t *cr_with, + bool self, + bool empty); + +static +ecs_trav_down_t* flecs_trav_down_ensure( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_entity_t entity) { - if (!ecs_os_api_initialized) { - ecs_os_api = *os_api; - ecs_os_api_initialized = true; + ecs_trav_down_t **trav = ecs_map_ensure_ref( + &cache->src, ecs_trav_down_t, entity); + if (!trav[0]) { + trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_down_t); + ecs_vec_init_t(NULL, &trav[0]->elems, ecs_trav_down_elem_t, 0); } + + return trav[0]; } -void ecs_os_init(void) +static +ecs_trav_down_t* flecs_trav_table_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + const ecs_table_range_t *range, + ecs_component_record_t *cr_with, + bool self, + bool empty) { - if (!ecs_os_api_initialized) { - ecs_os_set_api_defaults(); + ecs_table_t *table = range->table; + ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); + + if (!table->_->traversable_count) { + return dst; } - - if (!(ecs_os_api_init_count ++)) { - if (ecs_os_api.init_) { - ecs_os_api.init_(); + + ecs_assert(cr_with != NULL, ECS_INTERNAL_ERROR, NULL); + + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i = range->offset, end = i + range->count; + for (; i < end; i ++) { + ecs_entity_t entity = entities[i]; + ecs_record_t *record = flecs_entities_get(world, entity); + if (!record) { + continue; } - } -} -void ecs_os_fini(void) { - if (!--ecs_os_api_init_count) { - if (ecs_os_api.fini_) { - ecs_os_api.fini_(); + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + if (flags & EcsEntityIsTraversable) { + ecs_component_record_t *cr_trav = flecs_components_get(world, + ecs_pair(trav, entity)); + if (!cr_trav) { + continue; + } + + flecs_trav_entity_down(world, a, cache, dst, + trav, cr_trav, cr_with, self, empty); } } + + return dst; } -#if !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) -#include -#define ECS_BT_BUF_SIZE 100 static -void dump_backtrace( - FILE *stream) +void flecs_trav_entity_down_isa( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_entity_t entity, + ecs_component_record_t *cr_with, + bool self, + bool empty) { - int nptrs; - void *buffer[ECS_BT_BUF_SIZE]; - char **strings; - - nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); - - strings = backtrace_symbols(buffer, nptrs); - if (strings == NULL) { + if (trav == EcsIsA || !world->cr_isa_wildcard) { return; } - for (int j = 3; j < nptrs; j++) { - fprintf(stream, "%s\n", strings[j]); + ecs_component_record_t *cr_isa = flecs_components_get( + world, ecs_pair(EcsIsA, entity)); + if (!cr_isa) { + return; } - free(strings); -} -#else -static -void dump_backtrace( - FILE *stream) -{ - (void)stream; -} -#endif + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&cr_isa->cache, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!table->_->traversable_count) { + continue; + } -static -void log_msg( - int32_t level, - const char *file, - int32_t line, - const char *msg) -{ - FILE *stream; - if (level >= 0) { - stream = stdout; - } else { - stream = stderr; - } + if (ecs_table_has_id(world, table, cr_with->id)) { + /* Table owns component */ + continue; + } - if (level >= 0) { - if (level == 0) { - if (ecs_os_api.log_with_color_) fputs(ECS_MAGENTA, stream); - } else { - if (ecs_os_api.log_with_color_) fputs(ECS_GREY, stream); + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + ecs_record_t *record = flecs_entities_get(world, e); + if (!record) { + continue; + } + + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + if (flags & EcsEntityIsTraversable) { + ecs_component_record_t *cr_trav = flecs_components_get(world, + ecs_pair(trav, e)); + if (cr_trav) { + flecs_trav_entity_down(world, a, cache, dst, trav, + cr_trav, cr_with, self, empty); + } + + flecs_trav_entity_down_isa(world, a, cache, dst, trav, e, + cr_with, self, empty); + } + } } - fputs("info", stream); - } else if (level == -2) { - if (ecs_os_api.log_with_color_) fputs(ECS_YELLOW, stream); - fputs("warning", stream); - } else if (level == -3) { - if (ecs_os_api.log_with_color_) fputs(ECS_RED, stream); - fputs("error", stream); - } else if (level == -4) { - if (ecs_os_api.log_with_color_) fputs(ECS_RED, stream); - fputs("fatal", stream); } +} - if (ecs_os_api.log_with_color_) fputs(ECS_NORMAL, stream); - fputs(": ", stream); +static +void flecs_trav_entity_down_iter_children( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_component_record_t *cr_trav, + ecs_component_record_t *cr_with, + bool self, + bool empty) +{ + (void)cache; + (void)trav; + (void)empty; - if (level >= 0) { - if (ecs_os_api.log_indent_) { - char indent[32]; - int i, indent_count = ecs_os_api.log_indent_; - if (indent_count > 15) indent_count = 15; + ecs_vec_t *children = &cr_trav->pair->ordered_children; + int32_t i, count = ecs_vec_count(children); + ecs_entity_t *elems = ecs_vec_first(children); - for (i = 0; i < indent_count; i ++) { - indent[i * 2] = '|'; - indent[i * 2 + 1] = ' '; + for (i = 0; i < count; i ++) { + ecs_entity_t e = elems[i]; + ecs_record_t *r = flecs_entities_get(world, e); + bool leaf = false; + + /* Check if table has the component*/ + if (flecs_component_get_table(cr_with, r->table) != NULL) { + if (self) { + /* If matching self and the table has the component, entity + * shouldn't be matched through traversal and will instead + * be matched directly.*/ + continue; } - if (ecs_os_api.log_indent_ != indent_count) { - indent[i * 2 - 2] = '+'; - } + leaf = true; + } - indent[i * 2] = '\0'; + /* Add element to the cache for a single child */ + ecs_trav_down_elem_t *elem = ecs_vec_append_t( + a, &dst->elems, ecs_trav_down_elem_t); + elem->range.table = r->table; + elem->range.offset = ECS_RECORD_TO_ROW(r->row); + elem->range.count = 1; + elem->leaf = leaf; + } +} - fputs(indent, stream); - } +static +void flecs_trav_entity_down_iter_tables( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_component_record_t *cr_trav, + ecs_component_record_t *cr_with, + bool self, + bool empty) +{ + (void)cache; + + ecs_table_cache_iter_t it; + bool result; + if (empty) { + result = flecs_table_cache_all_iter(&cr_trav->cache, &it); + } else { + result = flecs_table_cache_iter(&cr_trav->cache, &it); } - if (level < 0) { - if (file) { - const char *file_ptr = strrchr(file, '/'); - if (!file_ptr) { - file_ptr = strrchr(file, '\\'); - } + if (result) { + ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = tr->hdr.table; + bool leaf = false; - if (file_ptr) { - file = file_ptr + 1; + if (flecs_component_get_table(cr_with, table) != NULL) { + if (self) { + continue; + } + leaf = true; } - fputs(file, stream); - fputs(": ", stream); - } + /* If record is not the first instance of (trav, *), don't add it + * to the cache. */ + int32_t index = tr->index; + if (index) { + ecs_id_t id = table->type.array[index - 1]; + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == trav) { + int32_t col = ecs_search_relation(world, table, 0, + cr_with->id, trav, EcsUp, NULL, NULL, &tr); + ecs_assert(col >= 0, ECS_INTERNAL_ERROR, NULL); + + if (col != index) { + /* First relationship through which the id is + * reachable is not the current one, so skip. */ + continue; + } + } + } - if (line) { - fprintf(stream, "%d: ", line); + ecs_trav_down_elem_t *elem = ecs_vec_append_t( + a, &dst->elems, ecs_trav_down_elem_t); + elem->range.table = table; + elem->range.offset = 0; + elem->range.count = ecs_table_count(table); + elem->leaf = leaf; } } +} - fputs(msg, stream); +static +void flecs_trav_entity_down( + ecs_world_t *world, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + ecs_trav_down_t *dst, + ecs_entity_t trav, + ecs_component_record_t *cr_trav, + ecs_component_record_t *cr_with, + bool self, + bool empty) +{ + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr_with != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr_trav != NULL, ECS_INTERNAL_ERROR, NULL); - fputs("\n", stream); + int32_t first = ecs_vec_count(&dst->elems); - if (level == -4) { - dump_backtrace(stream); + if (cr_trav->flags & EcsIdOrderedChildren) { + flecs_trav_entity_down_iter_children( + world, a, cache, dst, trav, cr_trav, cr_with, self, empty); + } else { + flecs_trav_entity_down_iter_tables( + world, a, cache, dst, trav, cr_trav, cr_with, self, empty); } -} -void ecs_os_dbg( - const char *file, - int32_t line, - const char *msg) -{ - if (ecs_os_api.log_) { - ecs_os_api.log_(1, file, line, msg); + /* Breadth first walk */ + int32_t t, last = ecs_vec_count(&dst->elems); + for (t = first; t < last; t ++) { + ecs_trav_down_elem_t *elem = ecs_vec_get_t( + &dst->elems, ecs_trav_down_elem_t, t); + if (!elem->leaf) { + flecs_trav_table_down(world, a, cache, dst, trav, + &elem->range, cr_with, self, empty); + } } } -void ecs_os_trace( - const char *file, - int32_t line, - const char *msg) +ecs_trav_down_t* flecs_query_get_down_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_entity_t trav, + ecs_entity_t e, + ecs_component_record_t *cr_with, + bool self, + bool empty) { - if (ecs_os_api.log_) { - ecs_os_api.log_(0, file, line, msg); + ecs_world_t *world = ctx->it->real_world; + ecs_assert(cache->dir != EcsTravUp, ECS_INTERNAL_ERROR, NULL); + cache->dir = EcsTravDown; + + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_map_init_if(&cache->src, a); + + ecs_trav_down_t *result = flecs_trav_down_ensure(ctx, cache, e); + if (result->ready) { + return result; } -} -void ecs_os_warn( - const char *file, - int32_t line, - const char *msg) -{ - if (ecs_os_api.log_) { - ecs_os_api.log_(-2, file, line, msg); + ecs_component_record_t *cr_trav = flecs_components_get(world, ecs_pair(trav, e)); + if (!cr_trav) { + if (trav != EcsIsA) { + if (cr_with->flags & EcsIdOnInstantiateInherit) { + flecs_trav_entity_down_isa( + world, a, cache, result, trav, e, cr_with, self, empty); + } + + } + result->ready = true; + return result; } -} -void ecs_os_err( - const char *file, - int32_t line, - const char *msg) -{ - if (ecs_os_api.log_) { - ecs_os_api.log_(-3, file, line, msg); + ecs_vec_init_t(a, &result->elems, ecs_trav_down_elem_t, 0); + + /* Cover IsA -> trav paths. If a parent inherits a component, then children + * of that parent should find the component through up traversal. */ + if (cr_with->flags & EcsIdOnInstantiateInherit) { + flecs_trav_entity_down_isa( + world, a, cache, result, trav, e, cr_with, self, empty); } + + flecs_trav_entity_down( + world, a, cache, result, trav, cr_trav, cr_with, self, empty); + result->ready = true; + + return result; } -void ecs_os_fatal( - const char *file, - int32_t line, - const char *msg) +void flecs_query_down_cache_fini( + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-4, file, line, msg); + ecs_map_iter_t it = ecs_map_iter(&cache->src); + while (ecs_map_next(&it)) { + ecs_trav_down_t *t = ecs_map_ptr(&it); + ecs_vec_fini_t(a, &t->elems, ecs_trav_down_elem_t); } + ecs_map_fini(&cache->src); } -static -void ecs_os_gettime(ecs_time_t *time) { - ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL); - - uint64_t now = ecs_os_now(); - uint64_t sec = now / 1000000000; - - assert(sec < UINT32_MAX); - assert((now - sec * 1000000000) < UINT32_MAX); +/** + * @file query/engine/trav_up_cache.c + * @brief Compile query term. + */ - time->sec = (uint32_t)sec; - time->nanosec = (uint32_t)(now - sec * 1000000000); -} static -void* ecs_os_api_malloc(ecs_size_t size) { - ecs_os_api_malloc_count ++; - ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - return malloc((size_t)size); +ecs_trav_up_t* flecs_trav_up_ensure( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_entity_t tgt) +{ + ecs_trav_up_t **trav = ecs_map_ensure_ref( + &cache->src, ecs_trav_up_t, tgt); + if (!trav[0]) { + trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); + } + + return trav[0]; } static -void* ecs_os_api_calloc(ecs_size_t size) { - ecs_os_api_calloc_count ++; - ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - return calloc(1, (size_t)size); +int32_t flecs_trav_type_search( + ecs_trav_up_t *up, + const ecs_table_t *table, + ecs_component_record_t *cr_with, + ecs_type_t *type) +{ + ecs_table_record_t *tr = ecs_table_cache_get(&cr_with->cache, table); + if (tr) { + up->id = type->array[tr->index]; + up->tr = tr; + return tr->index; + } + + return -1; } static -void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { - ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); +int32_t flecs_trav_type_offset_search( + ecs_trav_up_t *up, + const ecs_table_t *table, + int32_t offset, + ecs_id_t with, + ecs_type_t *type) +{ + ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); - if (ptr) { - ecs_os_api_realloc_count ++; - } else { - /* If not actually reallocing, treat as malloc */ - ecs_os_api_malloc_count ++; + while (offset < type->count) { + ecs_id_t type_id = type->array[offset ++]; + if (ecs_id_match(type_id, with)) { + up->id = type_id; + up->tr = &table->_->records[offset - 1]; + return offset - 1; + } } - - return realloc(ptr, (size_t)size); + + return -1; } static -void ecs_os_api_free(void *ptr) { - if (ptr) { - ecs_os_api_free_count ++; +ecs_trav_up_t* flecs_trav_table_up( + const ecs_query_run_ctx_t *ctx, + ecs_allocator_t *a, + ecs_trav_up_cache_t *cache, + const ecs_world_t *world, + ecs_entity_t src, + ecs_id_t with, + ecs_id_t rel, + ecs_component_record_t *cr_with, + ecs_component_record_t *cr_trav) +{ + ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); + if (up->ready) { + return up; } - free(ptr); -} -static -char* ecs_os_api_strdup(const char *str) { - if (str) { - int len = ecs_os_strlen(str); - char *result = ecs_os_malloc(len + 1); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_os_strcpy(result, str); - return result; - } else { - return NULL; + ecs_record_t *src_record = flecs_entities_get_any(world, src); + ecs_table_t *table = src_record->table; + if (!table) { + goto not_found; } -} -/* Replace dots with underscores */ -static -char *module_file_base(const char *module, char sep) { - char *base = ecs_os_strdup(module); - ecs_size_t i, len = ecs_os_strlen(base); - for (i = 0; i < len; i ++) { - if (base[i] == '.') { - base[i] = sep; + ecs_type_t type = table->type; + if (cr_with->flags & EcsIdDontFragment) { + if (flecs_sparse_has(cr_with->sparse, src)) { + up->src = src; + up->tr = NULL; + up->id = cr_with->id; + goto found; + } + } else { + if (flecs_trav_type_search(up, table, cr_with, &type) >= 0) { + up->src = src; + goto found; + } else if (ECS_PAIR_FIRST(with) == EcsChildOf) { + if (table->flags & EcsTableHasParent) { + const EcsParent *p = ecs_table_get_id( + world, table, ecs_id(EcsParent), + ECS_RECORD_TO_ROW(src_record->row)); + ecs_assert(p != NULL, ECS_INTERNAL_ERROR, NULL); + + if ((uint32_t)p->value == ECS_PAIR_SECOND(with)) { + up->src = src; + up->tr = NULL; + up->id = cr_with->id; + goto found; + } + } } } - return base; -} + ecs_flags32_t flags = table->flags; + if ((flags & EcsTableHasPairs) && rel) { + bool is_a = cr_trav == world->cr_isa_wildcard; + if (is_a) { + if (!(flags & EcsTableHasIsA)) { + goto not_found; + } -static -char* ecs_os_api_module_to_dl(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; + if (!flecs_type_can_inherit_id(world, table, cr_with, with)) { + goto not_found; + } + } - /* Best guess, use module name with underscores + OS library extension */ - char *file_base = module_file_base(module, '_'); + if ((rel == ecs_pair(EcsChildOf, EcsWildcard) && + (flags & EcsTableHasParent))) + { + const EcsParent *p = ecs_table_get_id( + world, table, ecs_id(EcsParent), + ECS_RECORD_TO_ROW(src_record->row)); + ecs_assert(p != NULL, ECS_INTERNAL_ERROR, NULL); -# if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) - ecs_strbuf_appendstr(&lib, "lib"); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, ".so"); -# elif defined(ECS_TARGET_DARWIN) - ecs_strbuf_appendstr(&lib, "lib"); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, ".dylib"); -# elif defined(ECS_TARGET_WINDOWS) - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, ".dll"); -# endif + ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, + world, p->value, with, rel, cr_with, cr_trav); + if (up_parent->tr) { + up->src = up_parent->src; + up->tr = up_parent->tr; + up->id = up_parent->id; + goto found; + } + } - ecs_os_free(file_base); + ecs_trav_up_t up_pair = {0}; + int32_t r_column = flecs_trav_type_search( + &up_pair, table, cr_trav, &type); - return ecs_strbuf_get(&lib); -} + while (r_column != -1) { + ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, + world, tgt, with, rel, cr_with, cr_trav); + if (up_parent->tr) { + up->src = up_parent->src; + up->tr = up_parent->tr; + up->id = up_parent->id; + goto found; + } -static -char* ecs_os_api_module_to_etc(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; + r_column = flecs_trav_type_offset_search( + &up_pair, table, r_column + 1, rel, &type); + } - /* Best guess, use module name with dashes + /etc */ - char *file_base = module_file_base(module, '-'); + if (!is_a && (cr_with->flags & EcsIdOnInstantiateInherit)) { + cr_trav = world->cr_isa_wildcard; + r_column = flecs_trav_type_search( + &up_pair, table, cr_trav, &type); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendstr(&lib, "/etc"); + while (r_column != -1) { + ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(file_base); + ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, + world, tgt, with, rel, cr_with, cr_trav); + if (up_parent->tr) { + up->src = up_parent->src; + up->tr = up_parent->tr; + up->id = up_parent->id; + goto found; + } - return ecs_strbuf_get(&lib); + r_column = flecs_trav_type_offset_search( + &up_pair, table, r_column + 1, rel, &type); + } + } + } + +not_found: + up->tr = NULL; +found: + up->ready = true; + return up; } -void ecs_os_set_api_defaults(void) +ecs_trav_up_t* flecs_query_get_up_cache( + const ecs_query_run_ctx_t *ctx, + ecs_trav_up_cache_t *cache, + ecs_table_t *table, + int32_t row, + ecs_id_t with, + ecs_entity_t trav, + ecs_component_record_t *cr_with, + ecs_component_record_t *cr_trav) { - /* Don't overwrite if already initialized */ - if (ecs_os_api_initialized != 0) { - return; + if (cache->with && cache->with != with) { + flecs_query_up_cache_fini(cache); } - if (ecs_os_api_initializing != 0) { - return; - } + ecs_world_t *world = ctx->it->real_world; + ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); + ecs_map_init_if(&cache->src, a); - ecs_os_api_initializing = true; - - /* Memory management */ - ecs_os_api.malloc_ = ecs_os_api_malloc; - ecs_os_api.free_ = ecs_os_api_free; - ecs_os_api.realloc_ = ecs_os_api_realloc; - ecs_os_api.calloc_ = ecs_os_api_calloc; + ecs_assert(cache->dir != EcsTravDown, ECS_INTERNAL_ERROR, NULL); + cache->dir = EcsTravUp; + cache->with = with; - /* Strings */ - ecs_os_api.strdup_ = ecs_os_api_strdup; + ecs_assert(cr_with != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cr_trav != NULL, ECS_INTERNAL_ERROR, NULL); - /* Time */ - ecs_os_api.get_time_ = ecs_os_gettime; + if (trav == EcsChildOf) { + if (table->flags & EcsTableHasParent) { + ecs_assert(row != -1, ECS_INTERNAL_ERROR, NULL); - /* Logging */ - ecs_os_api.log_ = log_msg; + const EcsParent *p = flecs_query_tree_get_parents((ecs_table_range_t){ + .table = table, + .offset = row, + .count = 1 + }); + ecs_assert(p != NULL, ECS_INTERNAL_ERROR, NULL); - /* Modules */ - if (!ecs_os_api.module_to_dl_) { - ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; - } + ecs_entity_t tgt = (uint32_t)p->value; + ecs_trav_up_t *result = flecs_trav_table_up(ctx, a, cache, world, tgt, + with, ecs_pair(trav, EcsWildcard), cr_with, cr_trav); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + if (result->src != 0) { + return result; + } - if (!ecs_os_api.module_to_etc_) { - ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; + return NULL; + } } - ecs_os_api.abort_ = abort; + ecs_table_record_t *tr = ecs_table_cache_get(&cr_trav->cache, table); + if (!tr) { + return NULL; /* Table doesn't have the relationship */ + } -# ifdef FLECS_OS_API_IMPL - /* Initialize defaults to OS API IMPL addon, but still allow for overriding - * by the application */ - ecs_set_os_api_impl(); - ecs_os_api_initialized = false; -# endif + int32_t i = tr->index, end = i + tr->count; + for (; i < end; i ++) { + ecs_id_t id = table->type.array[i]; + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + ecs_trav_up_t *result = flecs_trav_table_up(ctx, a, cache, world, tgt, + with, ecs_pair(trav, EcsWildcard), cr_with, cr_trav); + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + if (result->src != 0) { + return result; + } + } - ecs_os_api_initializing = false; + return NULL; } -bool ecs_os_has_heap(void) { - return - (ecs_os_api.malloc_ != NULL) && - (ecs_os_api.calloc_ != NULL) && - (ecs_os_api.realloc_ != NULL) && - (ecs_os_api.free_ != NULL); +void flecs_query_up_cache_fini( + ecs_trav_up_cache_t *cache) +{ + ecs_map_fini(&cache->src); } -bool ecs_os_has_threading(void) { - return - (ecs_os_api.mutex_new_ != NULL) && - (ecs_os_api.mutex_free_ != NULL) && - (ecs_os_api.mutex_lock_ != NULL) && - (ecs_os_api.mutex_unlock_ != NULL) && - (ecs_os_api.cond_new_ != NULL) && - (ecs_os_api.cond_free_ != NULL) && - (ecs_os_api.cond_wait_ != NULL) && - (ecs_os_api.cond_signal_ != NULL) && - (ecs_os_api.cond_broadcast_ != NULL) && - (ecs_os_api.thread_new_ != NULL) && - (ecs_os_api.thread_join_ != NULL); -} +/** + * @file query/engine/trivial_iter.c + * @brief Iterator for trivial queries. + */ -bool ecs_os_has_time(void) { - return - (ecs_os_api.get_time_ != NULL) && - (ecs_os_api.sleep_ != NULL) && - (ecs_os_api.now_ != NULL) && - (ecs_os_api.enable_high_timer_resolution_ != NULL); -} -bool ecs_os_has_logging(void) { - return (ecs_os_api.log_ != NULL); -} +static +bool flecs_query_trivial_search_init( + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + const ecs_query_t *query, + bool redo, + ecs_flags64_t term_set) +{ + if (!redo) { + /* Find first trivial term*/ + int32_t t = 0; + if (term_set) { + for (; t < query->term_count; t ++) { + if (term_set & (1llu << t)) { + break; + } + } + } -bool ecs_os_has_dl(void) { - return - (ecs_os_api.dlopen_ != NULL) && - (ecs_os_api.dlproc_ != NULL) && - (ecs_os_api.dlclose_ != NULL); -} + ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); + op_ctx->start_from = t; -bool ecs_os_has_modules(void) { - return - (ecs_os_api.module_to_dl_ != NULL) && - (ecs_os_api.module_to_etc_ != NULL); -} + ecs_component_record_t *cr = flecs_components_get(ctx->world, query->ids[t]); + if (!cr) { + return false; + } -void ecs_os_enable_high_timer_resolution(bool enable) { - if (ecs_os_api.enable_high_timer_resolution_) { - ecs_os_api.enable_high_timer_resolution_(enable); - } else { - ecs_assert(enable == false, ECS_MISSING_OS_API, - "enable_high_timer_resolution"); + if (query->flags & EcsQueryMatchEmptyTables) { + if (!flecs_table_cache_all_iter(&cr->cache, &op_ctx->it)){ + return false; + } + } else { + if (!flecs_table_cache_iter(&cr->cache, &op_ctx->it)) { + return false; + } + } + + /* Find next term to evaluate once */ + + for (t = t + 1; t < query->term_count; t ++) { + if (term_set & (1llu << t)) { + break; + } + } + + op_ctx->first_to_eval = t; } + + return true; } -#if defined(ECS_TARGET_WINDOWS) -static char error_str[255]; -#endif +bool flecs_query_trivial_search( + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + bool redo, + ecs_flags64_t term_set) +{ + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + const ecs_term_t *terms = q->terms; + ecs_iter_t *it = ctx->it; + int32_t t, term_count = query->pub.term_count; -const char* ecs_os_strerror(int err) { -# if defined(ECS_TARGET_WINDOWS) - strerror_s(error_str, 255, err); - return error_str; -# else - return strerror(err); -# endif -} + if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, term_set)) { + return false; + } + uint64_t q_filter = q->bloom_filter; -#ifdef FLECS_SYSTEM -#endif + do { + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; + } -static -void compute_group_id( - ecs_query_t *query, - ecs_query_table_match_t *match) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = tr->hdr.table; + if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { + continue; + } - if (query->group_by) { - ecs_table_t *table = match->table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (!flecs_table_bloom_filter_test(table, q_filter)) { + continue; + } - match->group_id = query->group_by(query->world, table, - query->group_by_id, query->group_by_ctx); - } else { - match->group_id = 0; - } -} + for (t = op_ctx->first_to_eval; t < term_count; t ++) { + if (!(term_set & (1llu << t))) { + continue; + } -static -ecs_query_table_list_t* get_group( - ecs_query_t *query, - uint64_t group_id) -{ - return ecs_map_get(&query->groups, ecs_query_table_list_t, group_id); -} + const ecs_term_t *term = &terms[t]; + ecs_component_record_t *cr = flecs_components_get(ctx->world, term->id); + if (!cr) { + break; + } -static -ecs_query_table_list_t* ensure_group( - ecs_query_t *query, - uint64_t group_id) -{ - return ecs_map_ensure(&query->groups, ecs_query_table_list_t, group_id); + const ecs_table_record_t *tr_with = flecs_component_get_table( + cr, table); + if (!tr_with) { + break; + } + + it->trs[term->field_index] = tr_with; + } + + if (t == term_count) { + ctx->vars[0].range.table = table; + ctx->vars[0].range.count = 0; + ctx->vars[0].range.offset = 0; + it->trs[op_ctx->start_from] = tr; + break; + } + } while (true); + + return true; } -/* Find the last node of the group after which this group should be inserted */ -static -ecs_query_table_node_t* find_group_insertion_node( - ecs_query_t *query, - uint64_t group_id) +bool flecs_query_is_trivial_search( + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + bool redo) { - /* Grouping must be enabled */ - ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + const ecs_id_t *ids = q->ids; + ecs_iter_t *it = ctx->it; + int32_t t, term_count = query->pub.term_count; - ecs_map_iter_t it = ecs_map_iter(&query->groups); - ecs_query_table_list_t *list, *closest_list = NULL; - uint64_t id, closest_id = 0; + if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, 0)) { + return false; + } - /* Find closest smaller group id */ - while ((list = ecs_map_next(&it, ecs_query_table_list_t, &id))) { - if (id >= group_id) { - continue; - } + uint64_t q_filter = q->bloom_filter; - if (!list->last) { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - continue; +next: + { + const ecs_table_record_t *tr = flecs_table_cache_next( + &op_ctx->it, ecs_table_record_t); + if (!tr) { + return false; } - if (!closest_list || ((group_id - id) < (group_id - closest_id))) { - closest_id = id; - closest_list = list; + ecs_table_t *table = tr->hdr.table; + if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { + goto next; } - } - if (closest_list) { - return closest_list->last; - } else { - return NULL; /* Group should be first in query */ - } -} + if (!flecs_table_bloom_filter_test(table, q_filter)) { + goto next; + } -/* Initialize group with first node */ -static -void create_group( - ecs_query_t *query, - ecs_query_table_node_t *node) -{ - ecs_query_table_match_t *match = node->match; - uint64_t group_id = match->group_id; + for (t = 1; t < term_count; t ++) { + ecs_component_record_t *cr = flecs_components_get(ctx->world, ids[t]); + if (!cr) { + return false; + } - /* If query has grouping enabled & this is a new/empty group, find - * the insertion point for the group */ - ecs_query_table_node_t *insert_after = find_group_insertion_node( - query, group_id); + const ecs_table_record_t *tr_with = flecs_component_get_table( + cr, table); + if (!tr_with) { + goto next; + } - if (!insert_after) { - /* This group should appear first in the query list */ - ecs_query_table_node_t *query_first = query->list.first; - if (query_first) { - /* If this is not the first match for the query, insert before it */ - node->next = query_first; - query_first->prev = node; - query->list.first = node; - } else { - /* If this is the first match of the query, initialize its list */ - ecs_assert(query->list.last == NULL, ECS_INTERNAL_ERROR, NULL); - query->list.first = node; - query->list.last = node; - } - } else { - ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - - /* This group should appear after another group */ - ecs_query_table_node_t *insert_before = insert_after->next; - node->prev = insert_after; - insert_after->next = node; - node->next = insert_before; - if (insert_before) { - insert_before->prev = node; - } else { - ecs_assert(query->list.last == insert_after, - ECS_INTERNAL_ERROR, NULL); - - /* This group should appear last in the query list */ - query->list.last = node; + it->trs[t] = tr_with; } + + it->table = table; + it->count = ecs_table_count(table); + it->entities = ecs_table_entities(table); + it->trs[0] = tr; } -} -static -void remove_group( - ecs_query_t *query, - uint64_t group_id) -{ - ecs_map_remove(&query->groups, group_id); + return true; } -/* Find the list the node should be part of */ -static -ecs_query_table_list_t* get_node_list( - ecs_query_t *query, - ecs_query_table_node_t *node) +bool flecs_query_trivial_test( + const ecs_query_run_ctx_t *ctx, + bool redo, + ecs_flags64_t term_set) { - ecs_query_table_match_t *match = node->match; - if (query->group_by) { - return get_group(query, match->group_id); + if (redo) { + return false; } else { - return &query->list; - } -} + const ecs_query_impl_t *impl = ctx->query; + const ecs_query_t *q = &impl->pub; + const ecs_term_t *terms = q->terms; + ecs_iter_t *it = ctx->it; + int32_t t, term_count = impl->pub.term_count; -/* Find or create the list the node should be part of */ -static -ecs_query_table_list_t* ensure_node_list( - ecs_query_t *query, - ecs_query_table_node_t *node) -{ - ecs_query_table_match_t *match = node->match; - if (query->group_by) { - return ensure_group(query, match->group_id); - } else { - return &query->list; + ecs_table_t *table = it->table; + ecs_assert(table != NULL, ECS_INVALID_OPERATION, + "the variable set on the iterator is missing a table"); + + if (!flecs_table_bloom_filter_test(table, q->bloom_filter)) { + return false; + } + + for (t = 0; t < term_count; t ++) { + if (!(term_set & (1llu << t))) { + continue; + } + + const ecs_term_t *term = &terms[t]; + ecs_component_record_t *cr = flecs_components_get(q->world, term->id); + if (!cr) { + return false; + } + + const ecs_table_record_t *tr = flecs_component_get_table(cr, table); + if (!tr) { + return false; + } + + it->trs[term->field_index] = tr; + } + + it->entities = ecs_table_entities(table); + if (it->entities) { + it->entities = &it->entities[it->offset]; + } + + return true; } } -/* Remove node from list */ -static -void remove_table_node( - ecs_query_t *query, - ecs_query_table_node_t *node) -{ - ecs_query_table_node_t *prev = node->prev; - ecs_query_table_node_t *next = node->next; +/** + * @file addons/meta/type_support/array_ts.c + * @brief Array type support. + */ - ecs_assert(prev != node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); - ecs_query_table_list_t *list = get_node_list(query, node); +#ifdef FLECS_META - if (!list || !list->first) { - /* If list contains no nodes, the node must be empty */ - ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); - return; - } +static +void flecs_set_array(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsArray *array = ecs_field(it, EcsArray, 0); - ecs_assert(prev != NULL || query->list.first == node, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != NULL || query->list.last == node, - ECS_INTERNAL_ERROR, NULL); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t elem_type = array[i].type; + int32_t elem_count = array[i].count; - if (prev) { - prev->next = next; - } - if (next) { - next->prev = prev; - } + if (!elem_type) { + ecs_err("array '%s' has no element type", ecs_get_name(world, e)); + continue; + } + + if (!elem_count) { + ecs_err("array '%s' has size 0", ecs_get_name(world, e)); + continue; + } - ecs_assert(list->count > 0, ECS_INTERNAL_ERROR, NULL); - list->count --; + if (!ecs_is_alive(world, elem_type)) { + ecs_err("array '%s' has invalid element type", ecs_get_name(world, e)); + continue; + } - if (query->group_by) { - ecs_query_table_match_t *match = node->match; - uint64_t group_id = match->group_id; + const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); + if (!elem_ptr) { + ecs_err("array '%s' has invalid element type", ecs_get_name(world, e)); + continue; + } - /* Make sure query.list is updated if this is the first or last group */ - if (query->list.first == node) { - ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); - query->list.first = next; - prev = next; + if (!ecs_get_type_info(world, elem_type)) { + ecs_err("array '%s' element type has no type info", ecs_get_name(world, e)); + continue; } - if (query->list.last == node) { - ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); - query->list.last = prev; - next = prev; + + if (flecs_init_type(world, e, EcsArrayType, + elem_ptr->size * elem_count, elem_ptr->alignment)) + { + continue; } + } +} - ecs_assert(query->list.count > 0, ECS_INTERNAL_ERROR, NULL); - query->list.count --; +static +void flecs_set_vector(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsVector *array = ecs_field(it, EcsVector, 0); + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t elem_type = array[i].type; - /* Make sure group list only contains nodes that belong to the group */ - if (prev && prev->match->group_id != group_id) { - /* The previous node belonged to another group */ - prev = next; + if (!elem_type) { + ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); + continue; } - if (next && next->match->group_id != group_id) { - /* The next node belonged to another group */ - next = prev; + + if (!ecs_is_alive(world, elem_type)) { + ecs_err("vector '%s' has invalid element type", ecs_get_name(world, e)); + continue; } - /* Do check again, in case both prev & next belonged to another group */ - if (prev && prev->match->group_id != group_id) { - /* There are no more matches left in this group */ - remove_group(query, group_id); - list = NULL; + if (!ecs_get(world, elem_type, EcsComponent)) { + ecs_err("vector '%s' has invalid element type", ecs_get_name(world, e)); + continue; } - } - if (list) { - if (list->first == node) { - list->first = next; + if (!ecs_get_type_info(world, elem_type)) { + ecs_err("vector '%s' element type has no type info", ecs_get_name(world, e)); + continue; } - if (list->last == node) { - list->last = prev; + + if (flecs_init_type_t(world, e, EcsVectorType, ecs_vec_t)) { + continue; } } +} - node->prev = NULL; - node->next = NULL; +ecs_entity_t ecs_array_init( + ecs_world_t *world, + const ecs_array_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); -#ifdef FLECS_SYSTEM - if (query->list.first == NULL && query->system && !query->world->is_fini) { - ecs_system_activate(query->world, query->system, false, NULL); + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } -#endif - query->match_count ++; + ecs_set(world, t, EcsArray, { + .type = desc->type, + .count = desc->count + }); + + flecs_resume_readonly(world, &rs); + + return t; } -/* Add node to list */ -static -void insert_table_node( - ecs_query_t *query, - ecs_query_table_node_t *node) +ecs_entity_t ecs_vector_init( + ecs_world_t *world, + const ecs_vector_desc_t *desc) { - /* Node should not be part of an existing list */ - ecs_assert(node->prev == NULL && node->next == NULL, - ECS_INTERNAL_ERROR, NULL); + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - /* If this is the first match, activate system */ -#ifdef FLECS_SYSTEM - if (!query->list.first && query->system) { - ecs_system_activate(query->world, query->system, true, NULL); + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } -#endif - compute_group_id(query, node->match); + ecs_set(world, t, EcsVector, { + .type = desc->type + }); - ecs_query_table_list_t *list = ensure_node_list(query, node); - if (list->last) { - ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_resume_readonly(world, &rs); - ecs_query_table_node_t *last = list->last; - ecs_query_table_node_t *last_next = last->next; + return t; +} - node->prev = last; - node->next = last_next; - last->next = node; +void flecs_meta_array_init( + ecs_world_t *world) +{ + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsArray), + .name = "array", .symbol = "EcsArray", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsArray), + .type.alignment = ECS_ALIGNOF(EcsArray) + }); - if (last_next) { - last_next->prev = node; - } + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsVector), + .name = "vector", .symbol = "EcsVector", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsVector), + .type.alignment = ECS_ALIGNOF(EcsVector) + }); - list->last = node; + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsArray) }, + .events = {EcsOnSet}, + .callback = flecs_set_array, + .global_observer = true + }); - if (query->group_by) { - /* Make sure to update query list if this is the last group */ - if (query->list.last == last) { - query->list.last = node; - } - } - } else { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsVector) }, + .events = {EcsOnSet}, + .callback = flecs_set_vector, + .global_observer = true + }); +} - list->first = node; - list->last = node; +#endif - if (query->group_by) { - /* Initialize group with its first node */ - create_group(query, node); - } - } +/** + * @file addons/meta/type_support/enum_ts.c + * @brief Enum type support. + */ - if (query->group_by) { - query->list.count ++; - } - list->count ++; - query->match_count ++; +#ifdef FLECS_META - ecs_assert(node->prev != node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(node->next != node, ECS_INTERNAL_ERROR, NULL); +/* EcsConstants lifecycle */ - ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last == node, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(query->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); +static void flecs_constants_dtor( + ecs_map_t *constants) +{ + ecs_map_iter_t it = ecs_map_iter(constants); + while (ecs_map_next(&it)) { + ecs_enum_constant_t *c = ecs_map_ptr(&it); + ecs_os_free(ECS_CONST_CAST(char*, c->name)); + ecs_os_free(c); + } + ecs_map_fini(constants); } -static -ecs_query_table_match_t* cache_add( - ecs_query_table_t *elem) +static void flecs_ordered_constants_dtor( + ecs_vec_t *ordered_constants) { - ecs_query_table_match_t *result = ecs_os_calloc_t(ecs_query_table_match_t); - ecs_query_table_node_t *node = &result->node; + /* shallow fini of is ok since map deallocs name c-string member */ + ecs_vec_fini_t(NULL, ordered_constants, ecs_enum_constant_t); +} - node->match = result; - if (!elem->first) { - elem->first = result; - elem->last = result; - } else { - ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); - elem->last->next_match = result; - elem->last = result; +static ECS_CTOR(EcsConstants, ptr, { + ptr->constants = ecs_os_malloc_t(ecs_map_t); + ecs_map_init(ptr->constants, NULL); + ecs_vec_init_t(NULL, &ptr->ordered_constants, ecs_enum_constant_t, 0); +}) + +static ECS_MOVE(EcsConstants, dst, src, { + if (dst->constants) { + flecs_constants_dtor(dst->constants); + ecs_os_free(dst->constants); } - return result; -} + dst->constants = src->constants; + src->constants = NULL; -typedef struct { - ecs_table_t *table; - int32_t *dirty_state; - int32_t column; -} table_dirty_state_t; + flecs_ordered_constants_dtor(&dst->ordered_constants); + dst->ordered_constants = src->ordered_constants; + ecs_os_zeromem(&src->ordered_constants); +}) + +static ECS_DTOR(EcsConstants, ptr, { + if (ptr->constants) { + flecs_constants_dtor(ptr->constants); + ecs_os_free(ptr->constants); + } + flecs_ordered_constants_dtor(&ptr->ordered_constants); +}) static -void get_dirty_state( - ecs_query_t *query, - ecs_query_table_match_t *match, - int32_t term, - table_dirty_state_t *out) +int flecs_add_constant_to_enum( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t e, + ecs_id_t constant_id) { - ecs_world_t *world = query->world; - ecs_entity_t subject = match->subjects[term]; - int32_t column; + EcsEnum *eptr = ecs_ensure(world, type, EcsEnum); + EcsConstants *ptr = ecs_ensure(world, type, EcsConstants); + ecs_entity_t ut = eptr->underlying_type; - if (!subject) { - out->table = match->table; - column = match->columns[term]; - if (column == -1) { - column = 0; + /* It's possible that a constant is added to an entity that didn't have an + * Enum component yet. In that case derive the underlying type from the + * first constant. */ + if (!ut) { + if (ecs_id_is_pair(constant_id)) { + ut = eptr->underlying_type = ecs_pair_second(world, constant_id); + } else { + /* Default to i32 */ + ut = ecs_id(ecs_i32_t); } - } else { - out->table = ecs_get_table(world, subject); - column = -match->columns[term]; } - out->dirty_state = flecs_table_get_dirty_state(out->table); + ecs_assert(ut != 0, ECS_INVALID_OPERATION, + "missing underlying type for enum"); - if (column) { - out->column = ecs_table_type_to_storage_index(out->table, column - 1); - } else { - out->column = -1; + const EcsPrimitive *p = ecs_get(world, ut, EcsPrimitive); + if (!p) { + char *path = ecs_get_path(world, ut); + ecs_err("underlying type '%s' must be a primitive type", path); + ecs_os_free(path); + return -1; } -} -/* Get match monitor. Monitors are used to keep track of whether components - * matched by the query in a table have changed. */ -static -bool get_match_monitor( - ecs_query_t *query, - ecs_query_table_match_t *match) -{ - if (match->monitor) { - return false; + bool ut_is_unsigned = false; + ecs_primitive_kind_t kind = p->kind; + if (kind == EcsU8 || kind == EcsU16 || kind == EcsU32 || kind == EcsU64) { + ut_is_unsigned = true; } - int32_t *monitor = ecs_os_calloc_n(int32_t, query->filter.term_count + 1); - - /* Mark terms that don't need to be monitored. This saves time when reading - * and/or updating the monitor. */ - const ecs_filter_t *f = &query->filter; - int32_t i, t = -1, term_count = f->term_count_actual; - table_dirty_state_t cur_dirty_state; + if (!ptr->constants) { + ptr->constants = ecs_os_malloc_t(ecs_map_t); + ecs_map_init(ptr->constants, NULL); + } - for (i = 0; i < term_count; i ++) { - if (t == f->terms[i].index) { - if (monitor[t + 1] != -1) { - continue; + /* Remove constant from map and vector if it was already added */ + ecs_map_iter_t it = ecs_map_iter(ptr->constants); + while (ecs_map_next(&it)) { + ecs_enum_constant_t *c = ecs_map_ptr(&it); + if (c->constant == e) { + ecs_os_free(ECS_CONST_CAST(char*, c->name)); + ecs_map_remove_free(ptr->constants, ecs_map_key(&it)); + + ecs_enum_constant_t* constants = ecs_vec_first_t( + &ptr->ordered_constants, ecs_enum_constant_t); + int32_t i, count = ecs_vec_count(&ptr->ordered_constants); + for (i = 0; i < count; i++) { + if (constants[i].constant == e) { + break; + } + } + if (i < count) { + for (int j = i; j < count - 1; j++) { + constants[j] = constants[j + 1]; + } + ecs_vec_remove_last(&ptr->ordered_constants); } } + } - t = f->terms[i].index; - monitor[t + 1] = -1; - - if (f->terms[i].inout != EcsIn && - f->terms[i].inout != EcsInOut && - f->terms[i].inout != EcsInOutDefault) { - continue; /* If term isn't read, don't monitor */ + /* Check if constant sets explicit value */ + int64_t value = 0; + uint64_t value_unsigned = 0; + bool value_set = false; + if (ecs_id_is_pair(constant_id)) { + ecs_value_t v = { .type = ut }; + v.ptr = ecs_get_mut_id(world, e, ecs_pair(EcsConstant, ut)); + + if (!v.ptr) { + char *has_pair = ecs_id_str(world, constant_id); + char *expect_pair = ecs_id_str(world, ecs_pair(EcsConstant, ut)); + char *path = ecs_get_path(world, e); + ecs_err( + "enum constant '%s' has incorrect value pair (expected %s, got %s)", + path, expect_pair, has_pair); + ecs_os_free(path); + ecs_os_free(has_pair); + ecs_os_free(expect_pair); + return -1; } - int32_t column = match->columns[t]; - if (column == 0) { - continue; /* Don't track terms that aren't matched */ + ecs_meta_cursor_t c; + if (ut_is_unsigned) { + c = ecs_meta_cursor(world, ecs_id(ecs_u64_t), &value_unsigned); + } else { + c = ecs_meta_cursor(world, ecs_id(ecs_i64_t), &value); } - get_dirty_state(query, match, t, &cur_dirty_state); - if (cur_dirty_state.column == -1) { - continue; /* Don't track terms that aren't stored */ + if (ecs_meta_set_value(&c, &v)) { + char *path = ecs_get_path(world, e); + ecs_err("failed to get constant value for '%s'", path); + ecs_os_free(path); + return -1; } - monitor[t + 1] = 0; + value_set = true; } - match->monitor = monitor; - - query->flags |= EcsQueryHasMonitor; - - return true; -} - -/* Synchronize match monitor with table dirty state */ -static -void sync_match_monitor( - ecs_query_t *query, - ecs_query_table_match_t *match) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - if (!match->monitor) { - if (query->flags & EcsQueryHasMonitor) { - get_match_monitor(query, match); + /* Make sure constant value doesn't conflict if set / find the next value */ + it = ecs_map_iter(ptr->constants); + while (ecs_map_next(&it)) { + ecs_enum_constant_t *c = ecs_map_ptr(&it); + if (ut_is_unsigned) { + if (value_set) { + if (c->value_unsigned == value_unsigned) { + char *path = ecs_get_path(world, e); + ecs_abort(ECS_INTERNAL_ERROR, + "conflicting constant value %u for '%s' (other is '%s')", + value_unsigned, path, c->name); + ecs_os_free(path); + + return -1; + } + } else { + if (c->value_unsigned >= value_unsigned) { + value_unsigned = c->value_unsigned + 1; + } + } } else { - return; + if (value_set) { + if (c->value == value) { + char *path = ecs_get_path(world, e); + ecs_err("conflicting constant value %d for '%s' (other is '%s')", + value, path, c->name); + ecs_os_free(path); + return -1; + } + } else { + if (c->value >= value) { + value = c->value + 1; + } + } } } - int32_t *monitor = match->monitor; - ecs_table_t *table = match->table; - int32_t *dirty_state = flecs_table_get_dirty_state(table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - table_dirty_state_t cur; + ecs_map_init_if(ptr->constants, &world->allocator); + ecs_enum_constant_t *c; + if (ut_is_unsigned) { + c = ecs_map_insert_alloc_t(ptr->constants, + ecs_enum_constant_t, value_unsigned); + c->value_unsigned = value_unsigned; + c->value = 0; + } else { + c = ecs_map_insert_alloc_t(ptr->constants, + ecs_enum_constant_t, (ecs_map_key_t)value); + c->value_unsigned = 0; + c->value = value; - monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ + } + c->name = ecs_os_strdup(ecs_get_name(world, e)); + c->constant = e; - int32_t i, term_count = query->filter.term_count_actual; - for (i = 0; i < term_count; i ++) { - int32_t t = query->filter.terms[i].index; - if (monitor[t + 1] == -1) { - continue; + ecs_vec_init_if_t(&ptr->ordered_constants, ecs_enum_constant_t); + ecs_enum_constant_t* ordered_c = ecs_vec_append_t(NULL, + &ptr->ordered_constants, ecs_enum_constant_t); + ordered_c->name = c->name; + ordered_c->value = value; + ordered_c->value_unsigned = value_unsigned; + ordered_c->constant = c->constant; + + if (!value_set) { + const ecs_type_info_t *ti = ecs_get_type_info(world, ut); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, + "underlying type is not a type"); + void *cptr = ecs_ensure_id( + world, e, ecs_pair(EcsConstant, ut), flecs_ito(size_t, ti->size)); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_meta_cursor_t cur = ecs_meta_cursor(world, ut, cptr); + if (ut_is_unsigned) { + if (ecs_meta_set_uint(&cur, value_unsigned)) { + char *path = ecs_get_path(world, e); + ecs_err("failed to assign value to constant '%s'", path); + ecs_os_free(path); + return -1; + } + } else { + if (ecs_meta_set_int(&cur, value)) { + char *path = ecs_get_path(world, e); + ecs_err("failed to assign value to constant '%s'", path); + ecs_os_free(path); + return -1; + } } - - get_dirty_state(query, match, t, &cur); - ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); - monitor[t + 1] = cur.dirty_state[cur.column + 1]; } + + ecs_modified(world, type, EcsConstants); + ecs_modified(world, type, EcsEnum); + return 0; } -/* Check if single match term has changed */ static -bool check_match_monitor_term( - ecs_query_t *query, - ecs_query_table_match_t *match, - int32_t term) +int flecs_add_constant_to_bitmask( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t e, + ecs_id_t constant_id) { - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_add(world, type, EcsBitmask); + EcsConstants *ptr = ecs_ensure(world, type, EcsConstants); + + /* Remove constant from map and vector if it was already added */ + ecs_map_iter_t it = ecs_map_iter(ptr->constants); + while (ecs_map_next(&it)) { + ecs_bitmask_constant_t *c = ecs_map_ptr(&it); + if (c->constant == e) { + ecs_os_free(ECS_CONST_CAST(char*, c->name)); + ecs_map_remove_free(ptr->constants, ecs_map_key(&it)); + + ecs_bitmask_constant_t* constants = ecs_vec_first_t( + &ptr->ordered_constants, ecs_bitmask_constant_t); + int32_t i, count = ecs_vec_count(&ptr->ordered_constants); + for (i = 0; i < count; i++) { + if (constants[i].constant == c->value) { + break; + } + } + if (i < count) { + for (int j = i; j < count - 1; j++) { + constants[j] = constants[j + 1]; + } + ecs_vec_remove_last(&ptr->ordered_constants); + } + } + } - if (get_match_monitor(query, match)) { - return true; + /* Check if constant sets explicit value */ + uint32_t value = 1; + if (ecs_id_is_pair(constant_id)) { + if (ecs_pair_second(world, constant_id) != ecs_id(ecs_u32_t)) { + char *path = ecs_get_path(world, e); + ecs_err("expected u32 type for bitmask constant '%s'", path); + ecs_os_free(path); + return -1; + } + + const uint32_t *value_ptr = ecs_get_pair_second( + world, e, EcsConstant, ecs_u32_t); + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + value = *value_ptr; + } else { + value = 1u << (ecs_u32_t)ecs_map_count(ptr->constants); } - - int32_t *monitor = match->monitor; - ecs_table_t *table = match->table; - int32_t *dirty_state = flecs_table_get_dirty_state(table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - table_dirty_state_t cur; - int32_t state = monitor[term]; - if (state == -1) { - return false; + if (!ptr->constants) { + ptr->constants = ecs_os_malloc_t(ecs_map_t); + ecs_map_init(ptr->constants, NULL); } - if (!term) { - return monitor[0] != dirty_state[0]; + /* Make sure constant value doesn't conflict */ + it = ecs_map_iter(ptr->constants); + while (ecs_map_next(&it)) { + ecs_bitmask_constant_t *c = ecs_map_ptr(&it); + if (c->value == value) { + char *path = ecs_get_path(world, e); + ecs_err("conflicting constant value for '%s' (other is '%s')", + path, c->name); + ecs_os_free(path); + return -1; + } } - get_dirty_state(query, match, term - 1, &cur); - ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_map_init_if(ptr->constants, &world->allocator); + + ecs_bitmask_constant_t *c = ecs_map_insert_alloc_t(ptr->constants, + ecs_bitmask_constant_t, value); + c->name = ecs_os_strdup(ecs_get_name(world, e)); + c->value = value; + c->constant = e; + + ecs_vec_init_if_t(&ptr->ordered_constants, ecs_bitmask_constant_t); + ecs_bitmask_constant_t* ordered_c = ecs_vec_append_t(NULL, + &ptr->ordered_constants, ecs_bitmask_constant_t); + ordered_c->name = c->name; + ordered_c->value = value; + ordered_c->constant = c->constant; + + ecs_u32_t *cptr = ecs_ensure_pair_second( + world, e, EcsConstant, ecs_u32_t); + ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); + cptr[0] = value; + + cptr = ecs_ensure_id(world, e, type, sizeof(uint32_t)); + cptr[0] = value; - return monitor[term] != cur.dirty_state[cur.column + 1]; + return 0; } -/* Check if any term for match has changed */ static -bool check_match_monitor( - ecs_query_t *query, - ecs_query_table_match_t *match) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); +void flecs_add_enum(ecs_iter_t *it) { + ecs_world_t *world = it->world; - if (get_match_monitor(query, match)) { - return true; - } + EcsEnum *data = ecs_field(it, EcsEnum, 0); - int32_t *monitor = match->monitor; - ecs_table_t *table = match->table; - int32_t *dirty_state = flecs_table_get_dirty_state(table); - ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); - table_dirty_state_t cur; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t underlying_type = data[i].underlying_type; - if (monitor[0] != dirty_state[0]) { - return true; - } + if (!underlying_type) { + underlying_type = data[i].underlying_type = ecs_id(ecs_i32_t); + } - ecs_filter_t *f = &query->filter; - int32_t i, term_count = f->term_count_actual; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &f->terms[i]; - int32_t t = term->index; - if (monitor[t + 1] == -1) { + const EcsComponent *uc = ecs_get(world, underlying_type, EcsComponent); + if (!uc) { + char *str = ecs_get_path(world, underlying_type); + ecs_err("underlying_type entity for enum '%s' is not a type", str); + ecs_os_free(str); continue; } - get_dirty_state(query, match, t, &cur); - ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); - - if (monitor[t + 1] != cur.dirty_state[cur.column + 1]) { - return true; + if (flecs_init_type(world, e, EcsEnumType, uc->size, uc->alignment)) { + continue; } - } - return false; + ecs_add_id(world, e, EcsExclusive); + ecs_add_id(world, e, EcsOneOf); + ecs_add_id(world, e, EcsPairIsTag); + } } -/* Check if any term for matched table has changed */ static -bool check_table_monitor( - ecs_query_t *query, - ecs_query_table_t *table, - int32_t term) -{ - ecs_query_table_node_t *cur, *end = table->last->node.next; - - for (cur = &table->first->node; cur != end; cur = cur->next) { - ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; - if (term == -1) { - if (check_match_monitor(query, match)) { - return true; - } - } else { - if (check_match_monitor_term(query, match, term)) { - return true; - } - } - } +void flecs_add_bitmask(ecs_iter_t *it) { + ecs_world_t *world = it->world; - return false; -} + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; -static -bool check_query_monitor( - ecs_query_t *query) -{ - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&query->cache, &it)) { - ecs_query_table_t *qt; - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - if (check_table_monitor(query, qt, -1)) { - return true; - } + if (flecs_init_type_t(world, e, EcsBitmaskType, ecs_u32_t)) { + continue; } } - - return false; } static -void init_query_monitors( - ecs_query_t *query) -{ - ecs_query_table_node_t *cur = query->list.first; - - /* Ensure each match has a monitor */ - for (; cur != NULL; cur = cur->next) { - ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; - get_match_monitor(query, match); - } -} +void flecs_add_constant(ecs_iter_t *it) { + ecs_world_t *world = it->world; -/* The group by function for cascade computes the tree depth for the table type. - * This causes tables in the query cache to be ordered by depth, which ensures - * breadth-first iteration order. */ -static -uint64_t group_by_cascade( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - void *ctx) -{ - ecs_term_t *term = ctx; - ecs_entity_t rel = term->subj.set.relation; - int32_t depth = 0; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); + if (!parent) { + ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); + continue; + } - if (-1 != ecs_search_relation_last( - world, table, 0, id, rel, 0, 0, 0, 0, &depth, 0)) - { - return flecs_ito(uint64_t, depth); - } else { - return 0; + if (ecs_has(world, parent, EcsEnum)) { + flecs_add_constant_to_enum(world, parent, e, it->event_id); + } else if (ecs_has(world, parent, EcsBitmask)) { + flecs_add_constant_to_bitmask(world, parent, e, it->event_id); + } } } -static -ecs_vector_t* add_ref( +ecs_entity_t ecs_enum_init( ecs_world_t *world, - ecs_query_t *query, - ecs_vector_t *references, - ecs_term_t *term, - ecs_entity_t component, - ecs_entity_t entity, - ecs_size_t size) + const ecs_enum_desc_t *desc) { - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_ref_t *ref = ecs_vector_add(&references, ecs_ref_t); - ecs_term_id_t *subj = &term->subj; - - if (!(subj->set.mask & EcsCascade)) { - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - } - - *ref = (ecs_ref_t){0}; - ref->entity = entity; + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - if (size) { - ref->component = component; - ecs_get_ref_id(world, ref, entity, component); - } else { - ref->component = 0; + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } - query->flags |= EcsQueryHasRefs; - - return references; -} - -static -ecs_query_table_match_t* add_table_match( - ecs_query_t *query, - ecs_query_table_t *qt, - ecs_table_t *table) -{ - ecs_filter_t *filter = &query->filter; - int32_t term_count = filter->term_count; - - /* Add match for table. One table can have more than one match, if - * the query contains wildcards. */ - ecs_query_table_match_t *qm = cache_add(qt); - qm->table = table; - qm->columns = ecs_os_malloc_n(int32_t, term_count); - qm->ids = ecs_os_malloc_n(ecs_id_t, term_count); - qm->subjects = ecs_os_malloc_n(ecs_entity_t, term_count); - qm->sizes = ecs_os_malloc_n(ecs_size_t, term_count); + ecs_entity_t underlying = desc->underlying_type; - /* Insert match to iteration list if table is not empty */ - if (!table || ecs_table_count(table) != 0) { - insert_table_node(query, &qm->node); + if (!underlying) { + underlying = ecs_id(ecs_i32_t); } - return qm; -} - -static -void set_table_match( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_table_t *qt, - ecs_query_table_match_t *qm, - ecs_table_t *table, - ecs_iter_t *it) -{ - (void)qt; - - ecs_filter_t *filter = &query->filter; - int32_t i, term_count = filter->term_count; - ecs_term_t *terms = filter->terms; + ecs_assert(ecs_is_valid(world, underlying), ECS_INVALID_PARAMETER, + "invalid underlying type for enum"); - /* Reset resources in case this is an existing record */ - if (qm->sparse_columns) { - ecs_vector_free(qm->sparse_columns); - qm->sparse_columns = NULL; - } - if (qm->bitset_columns) { - ecs_vector_free(qm->bitset_columns); - qm->bitset_columns = NULL; + const EcsPrimitive *p = ecs_get(world, underlying, EcsPrimitive); + if (!p) { + char *path = ecs_get_path(world, underlying); + ecs_err("underlying type '%s' must be a primitive type", path); + ecs_os_free(path); + return 0; } - if (qm->references) { - ecs_os_free(qm->references); - qm->references = NULL; + + bool ut_is_unsigned = false; + ecs_primitive_kind_t kind = p->kind; + if (kind == EcsU8 || kind == EcsU16 || kind == EcsU32 || kind == EcsU64) { + ut_is_unsigned = true; } - ecs_os_memcpy_n(qm->columns, it->columns, int32_t, term_count); - ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, term_count); - ecs_os_memcpy_n(qm->subjects, it->subjects, ecs_entity_t, term_count); - ecs_os_memcpy_n(qm->sizes, it->sizes, ecs_size_t, term_count); + EcsEnum *enum_data = ecs_ensure(world, t, EcsEnum); + enum_data->underlying_type = underlying; + ecs_modified(world, t, EcsEnum); - /* Initialize switch/case terms */ - for (i = 0; i < term_count; i ++) { - ecs_id_t id = terms[i].id; - if (ECS_HAS_ROLE(id, CASE) && terms[i].subj.set.mask != EcsNothing) { - flecs_switch_term_t *sc = ecs_vector_add( - &qm->sparse_columns, flecs_switch_term_t); - sc->signature_column_index = i; - sc->sw_case = ecs_pair_second(world, id); - sc->sw_column = NULL; - qm->ids[i] = id; + ecs_entity_t old_scope = ecs_set_scope(world, t); + + int i; + for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { + const ecs_enum_constant_t *m_desc = &desc->constants[i]; + if (!m_desc->name) { + break; } - } - /* Look for disabled terms */ - if (table) { - if (table->flags & EcsTableHasDisabled) { - for (i = 0; i < term_count; i ++) { - if (terms[i].subj.set.mask != EcsNothing) { - ecs_id_t id = it->ids[i]; - ecs_id_t bs_id = ECS_DISABLED | (id & ECS_COMPONENT_MASK); - int32_t bs_index = ecs_search(world, table, bs_id, 0); - if (bs_index != -1) { - flecs_bitset_term_t *bc = ecs_vector_add( - &qm->bitset_columns, flecs_bitset_term_t); - bc->column_index = bs_index; - bc->bs_column = NULL; - } + ecs_entity_t c = ecs_entity(world, { + .name = m_desc->name + }); + + if (!m_desc->value && !m_desc->value_unsigned) { + ecs_add_id(world, c, EcsConstant); + } else { + const ecs_type_info_t *ti = ecs_get_type_info(world, underlying); + ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, + "underlying type is not a type"); + void *ptr = ecs_ensure_id(world, c, + ecs_pair(EcsConstant, underlying), flecs_ito(size_t, ti->size)); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_meta_cursor_t cur = ecs_meta_cursor(world, underlying, ptr); + + int ret; + if (m_desc->value) { + if (ut_is_unsigned) { + char *path = ecs_get_path(world, c); + ecs_err("use desc::value_unsigned for constant '%s' which" + "has an unsigned underlying type", path); + ecs_os_free(path); + return 0; + } + ret = ecs_meta_set_int(&cur, m_desc->value); + } else { + if (!ut_is_unsigned) { + char *path = ecs_get_path(world, c); + ecs_err("use desc::value for constant '%s' which" + "has a signed underlying type", path); + ecs_os_free(path); + return 0; } + ret = ecs_meta_set_uint(&cur, m_desc->value_unsigned); } - } - } - /* Add references for non-This terms */ - if (!filter->match_only_this) { - ecs_vector_t *refs = NULL; - for (i = 0; i < term_count; i ++) { - ecs_entity_t src = it->subjects[i]; - ecs_size_t size = 0; - if (it->sizes) { - size = it->sizes[i]; + if (ret) { + char *type_str = ecs_get_path(world, t); + char *utype_str = ecs_get_path(world, underlying); + ecs_err("value for constant '%s' for enum '%s' is not valid " + "for underlying type '%s'", type_str, utype_str); + ecs_os_free(utype_str); + ecs_os_free(type_str); + continue; } - if (src) { - ecs_term_t *term = &terms[i]; - ecs_id_t id = it->ids[i]; - refs = add_ref(world, query, refs, term, id, src, size); - /* Use column index to bind term and ref */ - if (qm->columns[i] != 0) { - qm->columns[i] = -ecs_vector_count(refs); - } - } - } - if (refs) { - int32_t count = ecs_vector_count(refs); - ecs_ref_t *ptr = ecs_vector_first(refs, ecs_ref_t); - qm->references = ecs_os_memdup_n(ptr, ecs_ref_t, count); - ecs_vector_free(refs); + ecs_modified_id(world, c, ecs_pair(EcsConstant, underlying)); } } - ecs_assert(table == qt->hdr.table, ECS_INTERNAL_ERROR, NULL); -} - -/** Populate query cache with tables */ -static -void match_tables( - ecs_world_t *world, - ecs_query_t *query) -{ - ecs_table_t *table = NULL; - ecs_query_table_t *qt = NULL; - - ecs_iter_t it = ecs_filter_iter(world, &query->filter); - ECS_BIT_SET(it.flags, EcsIterIsInstanced); - ECS_BIT_SET(it.flags, EcsIterIsFilter); - ECS_BIT_SET(it.flags, EcsIterEntityOptional); - - while (ecs_filter_next(&it)) { - if ((table != it.table) || (!it.table && !qt)) { - /* New table matched, add record to cache */ - qt = ecs_os_calloc_t(ecs_query_table_t); - ecs_table_cache_insert(&query->cache, it.table, &qt->hdr); - table = it.table; - } + ecs_set_scope(world, old_scope); + flecs_resume_readonly(world, &rs); - ecs_query_table_match_t *qm = add_table_match(query, qt, table); - set_table_match(world, query, qt, qm, table, &it); + if (i == 0) { + ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; } + + return t; } -static -bool match_table( +ecs_entity_t ecs_bitmask_init( ecs_world_t *world, - ecs_query_t *query, - ecs_table_t *table) + const ecs_bitmask_desc_t *desc) { - if (!ecs_map_is_initialized(&query->cache.index)) { - return false; - } + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - ecs_query_table_t *qt = NULL; - int var_id = ecs_filter_find_this_var(&query->filter); - if (var_id == -1) { - /* If query doesn't match with This term, it can't match with tables */ - return false; + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } - ecs_iter_t it = ecs_filter_iter(world, &query->filter); - ECS_BIT_SET(it.flags, EcsIterIsInstanced); - ECS_BIT_SET(it.flags, EcsIterIsFilter); - ECS_BIT_SET(it.flags, EcsIterEntityOptional); - ecs_iter_set_var_as_table(&it, var_id, table); + ecs_add(world, t, EcsBitmask); + + ecs_entity_t old_scope = ecs_set_scope(world, t); + + int i; + for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { + const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; + if (!m_desc->name) { + break; + } + + ecs_entity_t c = ecs_entity(world, { + .name = m_desc->name + }); - while (ecs_filter_next(&it)) { - ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); - if (qt == NULL) { - qt = ecs_os_calloc_t(ecs_query_table_t); - ecs_table_cache_insert(&query->cache, it.table, &qt->hdr); - table = it.table; + if (!m_desc->value) { + ecs_add_id(world, c, EcsConstant); + } else { + ecs_set_pair_second(world, c, EcsConstant, ecs_u32_t, + { flecs_uto(uint32_t, m_desc->value) }); } + } + + ecs_set_scope(world, old_scope); + flecs_resume_readonly(world, &rs); - ecs_query_table_match_t *qm = add_table_match(query, qt, table); - set_table_match(world, query, qt, qm, table, &it); + if (i == 0) { + ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); + ecs_delete(world, t); + return 0; } - return qt != NULL; + return t; } -static -int32_t qsort_partition( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - ecs_entity_t *entities, - void *ptr, - int32_t elem_size, - int32_t lo, - int32_t hi, - ecs_order_by_action_t compare) +void flecs_meta_enum_init( + ecs_world_t *world) { - int32_t p = (hi + lo) / 2; - void *pivot = ECS_ELEM(ptr, elem_size, p); - ecs_entity_t pivot_e = entities[p]; - int32_t i = lo - 1, j = hi + 1; - void *el; + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsEnum), + .name = "enum", .symbol = "EcsEnum", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsEnum), + .type.alignment = ECS_ALIGNOF(EcsEnum) + }); -repeat: - { - do { - i ++; - el = ECS_ELEM(ptr, elem_size, i); - } while ( compare(entities[i], el, pivot_e, pivot) < 0); + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsBitmask), + .name = "bitmask", .symbol = "EcsBitmask", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsBitmask), + .type.alignment = ECS_ALIGNOF(EcsBitmask) + }); - do { - j --; - el = ECS_ELEM(ptr, elem_size, j); - } while ( compare(entities[j], el, pivot_e, pivot) > 0); + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsConstants), + .name = "constants", .symbol = "EcsConstants", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsConstants), + .type.alignment = ECS_ALIGNOF(EcsConstants) + }); - if (i >= j) { - return j; - } + ecs_set_hooks(world, EcsEnum, { + .ctor = flecs_default_ctor, + }); - flecs_table_swap(world, table, data, i, j); + ecs_set_hooks(world, EcsBitmask, { + .ctor = flecs_default_ctor, + }); - if (p == i) { - pivot = ECS_ELEM(ptr, elem_size, j); - pivot_e = entities[j]; - } else if (p == j) { - pivot = ECS_ELEM(ptr, elem_size, i); - pivot_e = entities[i]; - } + ecs_set_hooks(world, EcsConstants, { + .ctor = ecs_ctor(EcsConstants), + .move = ecs_move(EcsConstants), + .dtor = ecs_dtor(EcsConstants), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); - goto repeat; - } -} + ecs_add_pair(world, ecs_id(EcsEnum), EcsWith, ecs_id(EcsConstants)); + ecs_add_pair(world, ecs_id(EcsBitmask), EcsWith, ecs_id(EcsConstants)); -static -void qsort_array( - ecs_world_t *world, - ecs_table_t *table, - ecs_data_t *data, - ecs_entity_t *entities, - void *ptr, - int32_t size, - int32_t lo, - int32_t hi, - ecs_order_by_action_t compare) -{ - if ((hi - lo) < 1) { - return; - } + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsEnum) }, + .events = {EcsOnSet}, + .callback = flecs_add_enum, + .global_observer = true + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsBitmask) }, + .events = {EcsOnAdd}, + .callback = flecs_add_bitmask, + .global_observer = true + }); - int32_t p = qsort_partition( - world, table, data, entities, ptr, size, lo, hi, compare); + ecs_observer(world, { + .query.terms[0] = { .id = EcsConstant }, + .events = {EcsOnAdd}, + .callback = flecs_add_constant, + .global_observer = true + }); - qsort_array(world, table, data, entities, ptr, size, lo, p, compare); + ecs_observer(world, { + .query.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard) }, + .events = {EcsOnSet}, + .callback = flecs_add_constant, + .global_observer = true + }); - qsort_array(world, table, data, entities, ptr, size, p + 1, hi, compare); + ecs_set(world, ecs_id(EcsEnum), EcsDefaultChildComponent, {EcsConstant}); + ecs_set(world, ecs_id(EcsBitmask), EcsDefaultChildComponent, {EcsConstant}); } -static -void sort_table( - ecs_world_t *world, - ecs_table_t *table, - int32_t column_index, - ecs_order_by_action_t compare) -{ - ecs_data_t *data = &table->storage; - if (!data->entities) { - /* Nothing to sort */ - return; - } +#endif - int32_t count = flecs_table_data_count(data); - if (count < 2) { - return; - } +/** + * @file addons/meta/type_support/opaque_ts.c + * @brief Opaque type support. + */ - ecs_entity_t *entities = ecs_vector_first(data->entities, ecs_entity_t); - void *ptr = NULL; - int32_t size = 0; - if (column_index != -1) { - ecs_type_info_t *ti = &table->type_info[column_index]; - ecs_column_t *column = &data->columns[column_index]; - size = ti->size; - ptr = ecs_vector_first_t(column->data, size, ti->alignment); - } +#ifdef FLECS_META - qsort_array(world, table, data, entities, ptr, size, 0, count - 1, compare); -} +static +void flecs_set_opaque_type(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsOpaque *serialize = ecs_field(it, EcsOpaque, 0); -/* Helper struct for building sorted table ranges */ -typedef struct sort_helper_t { - ecs_query_table_match_t *match; - ecs_entity_t *entities; - const void *ptr; - int32_t row; - int32_t elem_size; - int32_t count; - bool shared; -} sort_helper_t; + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_entity_t as_type = serialize[i].as_type; -static -const void* ptr_from_helper( - sort_helper_t *helper) -{ - ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); - if (helper->shared) { - return helper->ptr; - } else { - return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); - } -} + if (!as_type) { + ecs_err( + "opaque type '%s' has no mapping type", ecs_get_name(world, e)); + continue; + } -static -ecs_entity_t e_from_helper( - sort_helper_t *helper) -{ - if (helper->row < helper->count) { - return helper->entities[helper->row]; - } else { - return 0; + /* If the as_type is anonymous and has no parent, parent it under the + * opaque type. That way we don't end up with a bunch of anonymous entities + * in the root scope. */ + if (!ecs_get_parent(world, as_type) && !ecs_get_name(world, as_type)) { + ecs_add_pair(world, as_type, EcsChildOf, e); + } + + const EcsComponent *comp = ecs_get(world, e, EcsComponent); + if (!comp || !comp->size || !comp->alignment) { + ecs_err( + "opaque type '%s' has no size/alignment, register as component first", + ecs_get_name(world, e)); + continue; + } + + if (flecs_init_type(world, e, EcsOpaqueType, comp->size, comp->alignment)) { + continue; + } } } -static -void build_sorted_table_range( - ecs_query_t *query, - ecs_query_table_list_t *list) +ecs_entity_t ecs_opaque_init( + ecs_world_t *world, + const ecs_opaque_desc_t *desc) { - ecs_world_t *world = query->world; - ecs_entity_t id = query->order_by_component; - ecs_order_by_action_t compare = query->order_by; - - if (!list->count) { - return; - } + ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL); - int to_sort = 0; + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - sort_helper_t *helper = ecs_os_malloc_n(sort_helper_t, list->count); - ecs_query_table_node_t *cur, *end = list->last->next; - for (cur = list->first; cur != end; cur = cur->next) { - ecs_query_table_match_t *match = cur->match; - ecs_table_t *table = match->table; - ecs_data_t *data = &table->storage; - ecs_vector_t *entities; - - ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } - entities = data->entities; + ecs_set_ptr(world, t, EcsOpaque, &desc->type); - int32_t index = -1; - if (id) { - index = ecs_search(world, table->storage_table, id, 0); - } + flecs_resume_readonly(world, &rs); - if (index != -1) { - ecs_type_info_t *ti = &table->type_info[index]; - ecs_column_t *column = &data->columns[index]; - int32_t size = ti->size; - int32_t align = ti->alignment; - helper[to_sort].ptr = ecs_vector_first_t(column->data, size, align); - helper[to_sort].elem_size = size; - helper[to_sort].shared = false; - } else if (id) { - /* Find component in prefab */ - ecs_entity_t base = 0; - ecs_search_relation(world, table, 0, id, - EcsIsA, 1, 0, &base, 0, 0, 0); - - /* If a base was not found, the query should not have allowed using - * the component for sorting */ - ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); + return t; +} - const EcsComponent *cptr = ecs_get(world, id, EcsComponent); - ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); +void flecs_meta_opaque_init( + ecs_world_t *world) +{ + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsOpaque), + .name = "opaque", .symbol = "EcsOpaque", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsOpaque), + .type.alignment = ECS_ALIGNOF(EcsOpaque) + }); - helper[to_sort].ptr = ecs_get_id(world, base, id); - helper[to_sort].elem_size = cptr->size; - helper[to_sort].shared = true; - } else { - helper[to_sort].ptr = NULL; - helper[to_sort].elem_size = 0; - helper[to_sort].shared = false; - } + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsOpaque) }, + .events = {EcsOnSet}, + .callback = flecs_set_opaque_type, + .global_observer = true + }); - helper[to_sort].match = match; - helper[to_sort].entities = ecs_vector_first(entities, ecs_entity_t); - helper[to_sort].row = 0; - helper[to_sort].count = ecs_table_count(table); - to_sort ++; - } +} - ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); +#endif - bool proceed; - do { - int32_t j, min = 0; - proceed = true; +/** + * @file addons/meta/type_support/primitive_ts.c + * @brief Primitives type support. + */ - ecs_entity_t e1; - while (!(e1 = e_from_helper(&helper[min]))) { - min ++; - if (min == to_sort) { - proceed = false; - break; - } - } - if (!proceed) { - break; - } +#ifdef FLECS_META - for (j = min + 1; j < to_sort; j++) { - ecs_entity_t e2 = e_from_helper(&helper[j]); - if (!e2) { - continue; - } +/* ecs_string_t lifecycle */ - const void *ptr1 = ptr_from_helper(&helper[min]); - const void *ptr2 = ptr_from_helper(&helper[j]); +static ECS_COPY(ecs_string_t, dst, src, { + ecs_os_free(*(ecs_string_t*)dst); + *(ecs_string_t*)dst = ecs_os_strdup(*(const ecs_string_t*)src); +}) - if (compare(e1, ptr1, e2, ptr2) > 0) { - min = j; - e1 = e_from_helper(&helper[min]); - } - } +static ECS_MOVE(ecs_string_t, dst, src, { + ecs_os_free(*(ecs_string_t*)dst); + *(ecs_string_t*)dst = *(ecs_string_t*)src; + *(ecs_string_t*)src = NULL; +}) - sort_helper_t *cur_helper = &helper[min]; - if (!cur || cur->match != cur_helper->match) { - cur = ecs_vector_add(&query->table_slices, ecs_query_table_node_t); - ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); - cur->match = cur_helper->match; - cur->offset = cur_helper->row; - cur->count = 1; - } else { - cur->count ++; - } +static ECS_DTOR(ecs_string_t, ptr, { + ecs_os_free(*(ecs_string_t*)ptr); + *(ecs_string_t*)ptr = NULL; +}) - cur_helper->row ++; - } while (proceed); +/* Primitive comparers */ - /* Iterate through the vector of slices to set the prev/next ptrs. This - * can't be done while building the vector, as reallocs may occur */ - int32_t i, count = ecs_vector_count(query->table_slices); - ecs_query_table_node_t *nodes = ecs_vector_first( - query->table_slices, ecs_query_table_node_t); - for (i = 0; i < count; i ++) { - nodes[i].prev = &nodes[i - 1]; - nodes[i].next = &nodes[i + 1]; - } +static +int flecs_compare_bool( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return + (int)(*((const ecs_bool_t*)a_ptr)) - (int)(*((const ecs_bool_t*)b_ptr)); +} - nodes[0].prev = NULL; - nodes[i - 1].next = NULL; +static +bool flecs_equals_bool( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_bool(a_ptr, b_ptr, ti) == 0; +} - ecs_os_free(helper); +static +int flecs_compare_char( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return + (int)(*((const ecs_char_t*)a_ptr)) - (int)(*((const ecs_char_t*)b_ptr)); } static -void build_sorted_tables( - ecs_query_t *query) +bool flecs_equals_char( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) { - ecs_vector_clear(query->table_slices); + return flecs_compare_char(a_ptr, b_ptr, ti) == 0; +} - if (query->group_by) { - /* Populate sorted node list in grouping order */ - ecs_query_table_node_t *cur = query->list.first; - if (cur) { - do { - /* Find list for current group */ - ecs_query_table_match_t *match = cur->match; - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - uint64_t group_id = match->group_id; - ecs_query_table_list_t *list = ecs_map_get(&query->groups, - ecs_query_table_list_t, group_id); - ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Sort tables in current group */ - build_sorted_table_range(query, list); - - /* Find next group to sort */ - cur = list->last->next; - } while (cur); - } - } else { - build_sorted_table_range(query, &query->list); - } +static +int flecs_compare_byte( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return + (int)(*((const ecs_byte_t*)a_ptr)) - (int)(*((const ecs_byte_t*)b_ptr)); } static -void sort_tables( - ecs_world_t *world, - ecs_query_t *query) +bool flecs_equals_byte( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) { - ecs_order_by_action_t compare = query->order_by; - if (!compare) { - return; - } - - ecs_entity_t order_by_component = query->order_by_component; - int32_t i, order_by_term = -1; + return flecs_compare_byte(a_ptr, b_ptr, ti) == 0; +} - /* Find term that iterates over component (must be at least one) */ - if (order_by_component) { - const ecs_filter_t *f = &query->filter; - int32_t term_count = f->term_count_actual; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &f->terms[i]; - if (term->subj.entity != EcsThis) { - continue; - } +static +int flecs_compare_u8( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_u8_t*)a_ptr)) - (int)(*((const ecs_u8_t*)b_ptr)); +} - if (term->id == order_by_component) { - order_by_term = i; - break; - } - } +static +bool flecs_equals_u8( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_u8(a_ptr, b_ptr, ti) == 0; +} - ecs_assert(order_by_term != -1, ECS_INTERNAL_ERROR, NULL); - } +static +int flecs_compare_u16( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_u16_t*)a_ptr)) - (int)(*((const ecs_u16_t*)b_ptr)); +} - /* Iterate over non-empty tables. Don't bother with empty tables as they - * have nothing to sort */ +static +bool flecs_equals_u16( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_u16(a_ptr, b_ptr, ti) == 0; +} - bool tables_sorted = false; +static +int flecs_compare_u32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_u32_t a = *((const ecs_u32_t*)a_ptr); + ecs_u32_t b = *((const ecs_u32_t*)b_ptr); + return (a > b) - (a < b); +} - ecs_table_cache_iter_t it; - ecs_query_table_t *qt; - flecs_table_cache_iter(&query->cache, &it); +static +bool flecs_equals_u32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_u32(a_ptr, b_ptr, ti) == 0; +} - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - ecs_table_t *table = qt->hdr.table; - bool dirty = false; +static +int flecs_compare_u64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_u64_t a = *((const ecs_u64_t*)a_ptr); + ecs_u64_t b = *((const ecs_u64_t*)b_ptr); + return (a > b) - (a < b); +} - if (check_table_monitor(query, qt, 0)) { - dirty = true; - } +static +bool flecs_equals_u64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_u64(a_ptr, b_ptr, ti) == 0; +} - int32_t column = -1; - if (order_by_component) { - if (check_table_monitor(query, qt, order_by_term + 1)) { - dirty = true; - } +static +int flecs_compare_uptr( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_uptr_t a = *((const ecs_uptr_t*)a_ptr); + ecs_uptr_t b = *((const ecs_uptr_t*)b_ptr); + return (a > b) - (a < b); +} - if (dirty) { - column = -1; +static +bool flecs_equals_uptr( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_uptr(a_ptr, b_ptr, ti) == 0; +} - ecs_table_t *storage_table = table->storage_table; - if (storage_table) { - column = ecs_search(world, storage_table, - order_by_component, NULL); - } +static +int flecs_compare_i8( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_i8_t*)a_ptr)) - + (int)(*((const ecs_i8_t*)b_ptr)); +} - if (column == -1) { - /* Component is shared, no sorting is needed */ - dirty = false; - } - } - } +static +bool flecs_equals_i8( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_i8(a_ptr, b_ptr, ti) == 0; +} - if (!dirty) { - continue; - } +static +int flecs_compare_i16( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_i16_t*)a_ptr)) - + (int)(*((const ecs_i16_t*)b_ptr)); +} - /* Something has changed, sort the table */ - sort_table(world, table, column, compare); - tables_sorted = true; - } +static +bool flecs_equals_i16( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_i16(a_ptr, b_ptr, ti) == 0; +} - if (tables_sorted || query->match_count != query->prev_match_count) { - build_sorted_tables(query); - query->match_count ++; /* Increase version if tables changed */ - } +static +int flecs_compare_i32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_i32_t a = *((const ecs_i32_t*)a_ptr); + ecs_i32_t b = *((const ecs_i32_t*)b_ptr); + return (a > b) - (a < b); } static -bool has_refs( - ecs_query_t *query) +bool flecs_equals_i32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; + return flecs_compare_i32(a_ptr, b_ptr, ti) == 0; +} - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *subj = &term->subj; +static +int flecs_compare_i64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_i64_t a = *((const ecs_i64_t*)a_ptr); + ecs_i64_t b = *((const ecs_i64_t*)b_ptr); + return (a > b) - (a < b); +} - if (term->oper == EcsNot && !subj->entity) { - /* Special case: if oper kind is Not and the query contained a - * shared expression, the expression is translated to FromEmpty to - * prevent resolving the ref */ - return true; - } else if (subj->entity && (subj->entity != EcsThis || subj->set.mask != EcsSelf)) { - /* If entity is not this, or if it can be substituted by other - * entities, the query can have references. */ - return true; - } - } +static +bool flecs_equals_i64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_i64(a_ptr, b_ptr, ti) == 0; +} - return false; +static +int flecs_compare_iptr( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_iptr_t a = *((const ecs_iptr_t*)a_ptr); + ecs_iptr_t b = *((const ecs_iptr_t*)b_ptr); + return (a > b) - (a < b); } static -void for_each_component_monitor( - ecs_world_t *world, - ecs_query_t *query, - void(*callback)( - ecs_world_t* world, - ecs_id_t id, - ecs_query_t *query)) +bool flecs_equals_iptr( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; + return flecs_compare_iptr(a_ptr, b_ptr, ti) == 0; +} - for (i = 0; i < count; i++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *subj = &term->subj; +static +int flecs_compare_f32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_f32_t a = *((const ecs_f32_t*)a_ptr); + ecs_f32_t b = *((const ecs_f32_t*)b_ptr); + if (a < b) return -1; + if (a > b) return 1; + return 0; +} - if (subj->set.mask & EcsSuperSet) { - callback(world, ecs_pair(subj->set.relation, EcsWildcard), query); - if (subj->set.relation != EcsIsA) { - callback(world, ecs_pair(EcsIsA, EcsWildcard), query); - } - callback(world, term->id, query); +static +bool flecs_equals_f32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + /* intentional equal check as if it was an integer */ + return flecs_compare_u32(a_ptr, b_ptr, ti) == 0; +} - } else if (subj->set.mask & EcsSelf && subj->entity != EcsThis) { - callback(world, term->id, query); - } - } +static +int flecs_compare_f64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_f64_t a = *((const ecs_f64_t*)a_ptr); + ecs_f64_t b = *((const ecs_f64_t*)b_ptr); + if (a < b) return -1; + if (a > b) return 1; + return 0; } static -bool is_term_id_supported( - ecs_term_id_t *term_id) +bool flecs_equals_f64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) { - if (term_id->var != EcsVarIsVariable) { - return true; - } - if ((term_id->entity == EcsWildcard) || (term_id->entity == EcsAny)) { - return true; - } - return false; + /* intentional equal check as if it was an integer */ + return flecs_compare_u64(a_ptr, b_ptr, ti) == 0; } static -void process_signature( - ecs_world_t *world, - ecs_query_t *query) +int flecs_compare_entity( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count; + (void)ti; + ecs_entity_t a = *((const ecs_entity_t*)a_ptr); + ecs_entity_t b = *((const ecs_entity_t*)b_ptr); + return (a > b) - (a < b); +} - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *pred = &term->pred; - ecs_term_id_t *subj = &term->subj; - ecs_term_id_t *obj = &term->obj; - ecs_inout_kind_t inout = term->inout; +static +bool flecs_equals_entity( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_entity(a_ptr, b_ptr, ti) == 0; +} - bool is_pred_supported = is_term_id_supported(pred); - bool is_subj_supported = is_term_id_supported(subj); - bool is_obj_supported = is_term_id_supported(obj); +static +int flecs_compare_id( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_id_t a = *((const ecs_id_t*)a_ptr); + ecs_id_t b = *((const ecs_id_t*)b_ptr); + return (a > b) - (a < b); +} - (void)pred; - (void)obj; - (void)is_pred_supported; - (void)is_subj_supported; - (void)is_obj_supported; +static +bool flecs_equals_id( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_id(a_ptr, b_ptr, ti) == 0; +} - /* Queries do not support named variables */ - ecs_check(is_pred_supported, ECS_UNSUPPORTED, NULL); - ecs_check(is_obj_supported, ECS_UNSUPPORTED, NULL); - ecs_check(is_subj_supported || subj->entity == EcsThis, - ECS_UNSUPPORTED, NULL); +int flecs_compare_string( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) { + (void)ti; + const ecs_string_t str_a = *((const ecs_string_t *) a_ptr); + const ecs_string_t str_b = *((const ecs_string_t *) b_ptr); + if(str_a == str_b) { + return 0; + } + if(str_a == NULL) { + return -1; + } + if(str_b == NULL) { + return 1; + } + return ecs_os_strcmp(str_a, str_b); +} - if (inout != EcsIn) { - query->flags |= EcsQueryHasOutColumns; - } +bool flecs_equals_string( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return flecs_compare_string(a_ptr, b_ptr, ti) == 0; +} - if (subj->set.mask & EcsCascade) { - /* Query can only have one cascade column */ - ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL); - query->cascade_by = i + 1; - } +static +void flecs_set_primitive(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsPrimitive *type = ecs_field(it, EcsPrimitive, 0); - if (subj->entity && subj->entity != EcsThis && - subj->set.mask == EcsSelf) - { - flecs_add_flag(world, term->subj.entity, ECS_FLAG_OBSERVED); + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + switch(type->kind) { + case EcsBool: + flecs_init_type_t(world, e, EcsPrimitiveType, bool); + break; + case EcsChar: + flecs_init_type_t(world, e, EcsPrimitiveType, char); + break; + case EcsByte: + flecs_init_type_t(world, e, EcsPrimitiveType, bool); + break; + case EcsU8: + flecs_init_type_t(world, e, EcsPrimitiveType, uint8_t); + break; + case EcsU16: + flecs_init_type_t(world, e, EcsPrimitiveType, uint16_t); + break; + case EcsU32: + flecs_init_type_t(world, e, EcsPrimitiveType, uint32_t); + break; + case EcsU64: + flecs_init_type_t(world, e, EcsPrimitiveType, uint64_t); + break; + case EcsI8: + flecs_init_type_t(world, e, EcsPrimitiveType, int8_t); + break; + case EcsI16: + flecs_init_type_t(world, e, EcsPrimitiveType, int16_t); + break; + case EcsI32: + flecs_init_type_t(world, e, EcsPrimitiveType, int32_t); + break; + case EcsI64: + flecs_init_type_t(world, e, EcsPrimitiveType, int64_t); + break; + case EcsF32: + flecs_init_type_t(world, e, EcsPrimitiveType, float); + break; + case EcsF64: + flecs_init_type_t(world, e, EcsPrimitiveType, double); + break; + case EcsUPtr: + flecs_init_type_t(world, e, EcsPrimitiveType, uintptr_t); + break; + case EcsIPtr: + flecs_init_type_t(world, e, EcsPrimitiveType, intptr_t); + break; + case EcsString: + flecs_init_type_t(world, e, EcsPrimitiveType, char*); + break; + case EcsEntity: + flecs_init_type_t(world, e, EcsPrimitiveType, ecs_entity_t); + break; + case EcsId: + flecs_init_type_t(world, e, EcsPrimitiveType, ecs_id_t); + break; } } +} - query->flags |= (ecs_flags32_t)(has_refs(query) * EcsQueryHasRefs); +ecs_entity_t ecs_primitive_init( + ecs_world_t *world, + const ecs_primitive_desc_t *desc) +{ + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - if (!(query->flags & EcsQueryIsSubquery)) { - for_each_component_monitor(world, query, flecs_monitor_register); + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } -error: - return; + + ecs_set(world, t, EcsPrimitive, { desc->kind }); + + flecs_resume_readonly(world, &rs); + return t; } -/** When a table becomes empty remove it from the query list, or vice versa. */ -static -void update_table( - ecs_query_t *query, - ecs_table_t *table, - bool empty) +void flecs_meta_primitives_init( + ecs_world_t *world) { - int32_t prev_count = ecs_query_table_count(query); - ecs_table_cache_set_empty(&query->cache, table, empty); - int32_t cur_count = ecs_query_table_count(query); + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsPrimitive), + .name = "primitive", .symbol = "EcsPrimitive", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsPrimitive), + .type.alignment = ECS_ALIGNOF(EcsPrimitive) + }); - if (prev_count != cur_count) { - ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table); - ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_query_table_match_t *cur, *next; + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsPrimitive) }, + .events = {EcsOnSet}, + .callback = flecs_set_primitive, + .global_observer = true + }); - for (cur = qt->first; cur != NULL; cur = next) { - next = cur->next_match; + /* Initialize primitive types */ + #define ECS_PRIMITIVE(world, type, primitive_kind)\ + ecs_entity_init(world, &(ecs_entity_desc_t){\ + .id = ecs_id(ecs_##type##_t),\ + .name = #type,\ + .symbol = #type });\ + ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ + .kind = primitive_kind\ + });\ + ecs_set_hooks(world, ecs_##type##_t, { \ + .cmp = flecs_compare_##type, \ + .equals = flecs_equals_##type \ + }) - if (empty) { - ecs_assert(ecs_table_count(table) == 0, - ECS_INTERNAL_ERROR, NULL); + ECS_PRIMITIVE(world, bool, EcsBool); + ECS_PRIMITIVE(world, char, EcsChar); + ECS_PRIMITIVE(world, byte, EcsByte); + ECS_PRIMITIVE(world, u8, EcsU8); + ECS_PRIMITIVE(world, u16, EcsU16); + ECS_PRIMITIVE(world, u32, EcsU32); + ECS_PRIMITIVE(world, u64, EcsU64); + ECS_PRIMITIVE(world, uptr, EcsUPtr); + ECS_PRIMITIVE(world, i8, EcsI8); + ECS_PRIMITIVE(world, i16, EcsI16); + ECS_PRIMITIVE(world, i32, EcsI32); + ECS_PRIMITIVE(world, i64, EcsI64); + ECS_PRIMITIVE(world, iptr, EcsIPtr); + ECS_PRIMITIVE(world, f32, EcsF32); + ECS_PRIMITIVE(world, f64, EcsF64); + ECS_PRIMITIVE(world, string, EcsString); + ECS_PRIMITIVE(world, entity, EcsEntity); + ECS_PRIMITIVE(world, id, EcsId); - remove_table_node(query, &cur->node); - } else { - ecs_assert(ecs_table_count(table) != 0, - ECS_INTERNAL_ERROR, NULL); - insert_table_node(query, &cur->node); - } - } - } + #undef ECS_PRIMITIVE - ecs_assert(cur_count || query->list.first == NULL, - ECS_INTERNAL_ERROR, NULL); + ecs_type_hooks_t string_hooks = *ecs_get_hooks(world, ecs_string_t); + string_hooks.ctor = flecs_default_ctor; + string_hooks.copy = ecs_copy(ecs_string_t); + string_hooks.move = ecs_move(ecs_string_t); + string_hooks.dtor = ecs_dtor(ecs_string_t); + string_hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; + ecs_set_hooks_id(world, ecs_id(ecs_string_t), &string_hooks); } -static -void add_subquery( - ecs_world_t *world, - ecs_query_t *parent, - ecs_query_t *subquery) -{ - ecs_query_t **elem = ecs_vector_add(&parent->subqueries, ecs_query_t*); - *elem = subquery; +#endif - ecs_table_cache_t *cache = &parent->cache; - ecs_table_cache_iter_t it; - ecs_query_table_t *qt; - flecs_table_cache_iter(cache, &it); - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - match_table(world, subquery, qt->hdr.table); - } - - flecs_table_cache_empty_iter(cache, &it); - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - match_table(world, subquery, qt->hdr.table); - } -} +/** + * @file addons/meta/type_support/struct_ts.c + * @brief Struct type support. + */ -static -void notify_subqueries( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_event_t *event) -{ - if (query->subqueries) { - ecs_query_t **queries = ecs_vector_first(query->subqueries, ecs_query_t*); - int32_t i, count = ecs_vector_count(query->subqueries); - ecs_query_event_t sub_event = *event; - sub_event.parent_query = query; +#ifdef FLECS_META - for (i = 0; i < count; i ++) { - ecs_query_t *sub = queries[i]; - flecs_query_notify(world, sub, &sub_event); - } +static void flecs_struct_dtor( + EcsStruct *ptr) +{ + ecs_member_t *members = ecs_vec_first_t(&ptr->members, ecs_member_t); + int32_t i, count = ecs_vec_count(&ptr->members); + for (i = 0; i < count; i ++) { + ecs_os_free(ECS_CONST_CAST(char*, members[i].name)); } + ecs_vec_fini_t(NULL, &ptr->members, ecs_member_t); } -/* Remove table */ -static -void query_table_match_free( - ecs_query_t *query, - ecs_query_table_t *elem, - ecs_query_table_match_t *first) -{ - ecs_query_table_match_t *cur, *next; - - for (cur = first; cur != NULL; cur = next) { - ecs_os_free(cur->columns); - ecs_os_free(cur->ids); - ecs_os_free(cur->subjects); - ecs_os_free(cur->sizes); - ecs_os_free(cur->references); - ecs_os_free(cur->sparse_columns); - ecs_os_free(cur->bitset_columns); - ecs_os_free(cur->monitor); +static ECS_COPY(EcsStruct, dst, src, { + flecs_struct_dtor(dst); - if (!elem->hdr.empty) { - remove_table_node(query, &cur->node); - } + dst->members = ecs_vec_copy_t(NULL, &src->members, ecs_member_t); - next = cur->next_match; + ecs_member_t *members = ecs_vec_first_t(&dst->members, ecs_member_t); + int32_t m, count = ecs_vec_count(&dst->members); - ecs_os_free(cur); + for (m = 0; m < count; m ++) { + members[m].name = ecs_os_strdup(members[m].name); } -} +}) + +static ECS_MOVE(EcsStruct, dst, src, { + flecs_struct_dtor(dst); + dst->members = src->members; + src->members = (ecs_vec_t){0}; +}) + +static ECS_DTOR(EcsStruct, ptr, { flecs_struct_dtor(ptr); }) static -void query_table_free( - ecs_query_t *query, - ecs_query_table_t *elem) +void flecs_set_struct_member( + ecs_member_t *member, + ecs_entity_t entity, + ecs_member_t *m, + ecs_entity_t unit) { - query_table_match_free(query, elem, elem->first); - ecs_os_free(elem); + member->member = entity; + member->type = m->type; + member->count = m->count; + member->unit = unit; + member->offset = m->offset; + + ecs_os_strset(ECS_CONST_CAST(char**, &member->name), m->name); + + member->range = m->range; + member->error_range = m->error_range; + member->warning_range = m->warning_range; } static -bool satisfy_constraints( +int flecs_add_member_to_struct( ecs_world_t *world, - const ecs_filter_t *filter) + ecs_entity_t struct_type, + ecs_entity_t member_entity, + const ecs_member_t *m_ptr) { - ecs_term_t *terms = filter->terms; - int32_t i, count = filter->term_count; + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(struct_type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(m_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_id_t *subj = &term->subj; - ecs_oper_kind_t oper = term->oper; + ecs_member_t m = *m_ptr; + const char *name = m.name; - if (oper == EcsOptional) { - continue; + if (!name) { + char *path = ecs_get_path(world, struct_type); + ecs_err("member for struct '%s' does not have a name", path); + ecs_os_free(path); + return -1; + } + + if (!m.type) { + char *path = ecs_get_path(world, struct_type); + ecs_err("member '%s.%s' does not have a type", path, name); + ecs_os_free(path); + return -1; + } + + if (ecs_get_typeid(world, m.type) == 0) { + char *path = ecs_get_path(world, struct_type); + char *ent_path = ecs_get_path(world, m.type); + ecs_err("member '%s.%s.type' is '%s' which is not a type", + path, name, ent_path); + ecs_os_free(path); + ecs_os_free(ent_path); + return -1; + } + + ecs_entity_t unit = m.unit; + if (unit) { + if (!ecs_has(world, unit, EcsUnit)) { + ecs_err("entity '%s' for member '%s' is not a unit", + ecs_get_name(world, unit), name); + return -1; } - if (subj->entity != EcsThis && subj->entity) { - ecs_table_t *table = ecs_get_table(world, subj->entity); - if (!table) { - goto no_match; + if (ecs_has(world, m.type, EcsUnit) && m.type != unit) { + ecs_err("unit mismatch for type '%s' and unit '%s' for member '%s'", + ecs_get_name(world, m.type), ecs_get_name(world, unit), name); + return -1; + } + } else { + if (ecs_has(world, m.type, EcsUnit)) { + ecs_entity_t unit_base = ecs_get_target_for( + world, m.type, EcsIsA, EcsUnit); + if (unit_base) { + unit = m.unit = unit_base; + } else { + unit = m.unit = m.type; } - if (!flecs_term_match_table(world, term, table, table->type, NULL, - NULL, NULL, NULL, true, 0)) - { - goto no_match; + if (member_entity) { + ecs_assert(ecs_owns(world, member_entity, EcsMember), + ECS_INTERNAL_ERROR, NULL); + ecs_get_mut(world, member_entity, EcsMember)->unit = m.type; } } } - return true; -no_match: - return false; -} + EcsStruct *s = ecs_ensure(world, struct_type, EcsStruct); + ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); -static -void unmatch_table( - ecs_query_t *query, - ecs_table_t *table) -{ - ecs_query_table_t *qt = ecs_table_cache_remove( - &query->cache, table, NULL); - if (qt) { - query_table_free(query, qt); + /* First check if member is already added to struct */ + ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); + int32_t i, count = ecs_vec_count(&s->members); + + bool has_member = false; + for (i = 0; i < count; i ++) { + if (member_entity && members[i].member) { + if (members[i].member == member_entity) { + flecs_set_struct_member(&members[i], member_entity, &m, unit); + break; + } + } else { + if (!ecs_os_strcmp(name, members[i].name)) { + flecs_set_struct_member(&members[i], member_entity, &m, unit); + break; + } + } } -} -/* Rematch system with tables after a change happened to a watched entity */ -static -void rematch_tables( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_t *parent_query) -{ - ecs_iter_t it, parent_it; - ecs_table_t *table = NULL; - ecs_query_table_t *qt = NULL; - ecs_query_table_match_t *qm = NULL; + has_member = i != count; - if (parent_query) { - parent_it = ecs_query_iter(world, parent_query); - it = ecs_filter_chain_iter(&parent_it, &query->filter); - } else { - it = ecs_filter_iter(world, &query->filter); + /* If member wasn't added yet, add a new element to vector */ + if (!has_member) { + ecs_vec_init_if_t(&s->members, ecs_member_t); + ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); + elem->name = NULL; + flecs_set_struct_member(elem, member_entity, &m, unit); + + /* Reobtain members array in case it was reallocated */ + members = ecs_vec_first_t(&s->members, ecs_member_t); + count ++; } - ECS_BIT_SET(it.flags, EcsIterIsInstanced); - ECS_BIT_SET(it.flags, EcsIterIsFilter); - ECS_BIT_SET(it.flags, EcsIterEntityOptional); + bool explicit_offset = m.offset || m.use_offset; + + /* Compute member offsets and size & alignment of struct */ + ecs_size_t size = 0; + ecs_size_t alignment = 0; + + if (!explicit_offset) { + for (i = 0; i < count; i ++) { + ecs_member_t *elem = &members[i]; + + ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); + + /* Get component of member type to get its size & alignment */ + const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); + if (!mbr_comp) { + char *path = ecs_get_path(world, elem->type); + ecs_err("member '%s' is not a type", path); + ecs_os_free(path); + return -1; + } + + ecs_size_t member_size = mbr_comp->size; + ecs_size_t member_alignment = mbr_comp->alignment; + + if (!member_size || !member_alignment) { + char *path = ecs_get_path(world, elem->type); + ecs_err("member '%s' has 0 size/alignment", path); + ecs_os_free(path); + return -1; + } + + member_size *= elem->count ? elem->count : 1; + size = ECS_ALIGN(size, member_alignment); + elem->size = member_size; + elem->offset = size; + + /* Synchronize offset with Member component */ + if (elem->member) { + EcsMember *member_data = ecs_ensure( + world, elem->member, EcsMember); + member_data->offset = elem->offset; + } - int32_t rematch_count = ++ query->rematch_count; + size += member_size; + + if (member_alignment > alignment) { + alignment = member_alignment; + } + } + } else { + /* If members have explicit offsets, we can't rely on computed + * size/alignment values. Calculate size as if this is the last member + * instead, since this will validate if the member fits in the struct. + * It doesn't matter if the size is smaller than the actual struct size + * because flecs_init_type function compares computed size with actual + * (component) size to determine if the type is partial. */ + ecs_member_t *elem = &members[i]; - while (ecs_iter_next(&it)) { - if ((table != it.table) || (!it.table && !qt)) { - if (qm && qm->next_match) { - query_table_match_free(query, qt, qm->next_match); - qm->next_match = NULL; - } + ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); - table = it.table; + /* Get component of member type to get its size & alignment */ + const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); + if (!mbr_comp) { + char *path = ecs_get_path(world, elem->type); + ecs_err("member '%s' is not a type", path); + ecs_os_free(path); + return -1; + } - qt = ecs_table_cache_get(&query->cache, table); - if (!qt) { - qt = ecs_os_calloc_t(ecs_query_table_t); - ecs_table_cache_insert(&query->cache, table, &qt->hdr); - } + ecs_size_t member_size = mbr_comp->size; + ecs_size_t member_alignment = mbr_comp->alignment; - ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - qt->rematch_count = rematch_count; - qm = NULL; - } - if (!qm) { - qm = qt->first; - } else { - qm = qm->next_match; - } - if (!qm) { - qm = add_table_match(query, qt, table); + if (!member_size || !member_alignment) { + char *path = ecs_get_path(world, elem->type); + ecs_err("member '%s' has 0 size/alignment", path); + ecs_os_free(path); + return -1; } - set_table_match(world, query, qt, qm, table, &it); + member_size *= elem->count ? elem->count : 1; + elem->size = member_size; + size = elem->offset + member_size; - if (table && ecs_table_count(table) && query->group_by) { - /* If grouping is enabled, make sure match is in the right place */ - remove_table_node(query, &qm->node); - insert_table_node(query, &qm->node); + const EcsComponent* comp = ecs_get(world, struct_type, EcsComponent); + if (comp) { + alignment = comp->alignment; + } else { + alignment = member_alignment; } } - if (qm && qm->next_match) { - query_table_match_free(query, qt, qm->next_match); - qm->next_match = NULL; + if (size == 0) { + ecs_err("struct '%s' has 0 size", ecs_get_name(world, struct_type)); + return -1; } - /* Iterate all tables in cache, remove ones that weren't just matched */ - ecs_table_cache_iter_t cache_it; - if (flecs_table_cache_iter(&query->cache, &cache_it)) { - while ((qt = flecs_table_cache_next(&cache_it, ecs_query_table_t))) { - if (qt->rematch_count != rematch_count) { - unmatch_table(query, qt->hdr.table); - } - } + if (alignment == 0) { + ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, struct_type)); + return -1; } - if (flecs_table_cache_empty_iter(&query->cache, &cache_it)) { - while ((qt = flecs_table_cache_next(&cache_it, ecs_query_table_t))) { - if (qt->rematch_count != rematch_count) { - unmatch_table(query, qt->hdr.table); - } - } - } - - /* Enable/disable system if constraints are (not) met. If the system is - * already dis/enabled this operation has no side effects. */ - query->constraints_satisfied = satisfy_constraints(world, &query->filter); -} - -static -void remove_subquery( - ecs_query_t *parent, - ecs_query_t *sub) -{ - ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(sub != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(parent->subqueries != NULL, ECS_INTERNAL_ERROR, NULL); + /* Align struct size to struct alignment */ + size = ECS_ALIGN(size, alignment); - int32_t i, count = ecs_vector_count(parent->subqueries); - ecs_query_t **sq = ecs_vector_first(parent->subqueries, ecs_query_t*); + ecs_modified(world, struct_type, EcsStruct); - for (i = 0; i < count; i ++) { - if (sq[i] == sub) { - break; - } + /* Do this last as it triggers the update of EcsTypeSerializer */ + if (flecs_init_type(world, struct_type, EcsStructType, size, alignment)) { + return -1; } - ecs_vector_remove(parent->subqueries, ecs_query_t*, i); -} - -/* -- Private API -- */ + /* If current struct is also a member, assign to itself */ + if (ecs_has(world, struct_type, EcsMember)) { + EcsMember *type_mbr = ecs_ensure(world, struct_type, EcsMember); + ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); -void flecs_query_notify( - ecs_world_t *world, - ecs_query_t *query, - ecs_query_event_t *event) -{ - bool notify = true; + type_mbr->type = struct_type; + type_mbr->count = 0; - switch(event->kind) { - case EcsQueryTableMatch: - /* Creation of new table */ - if (match_table(world, query, event->table)) { - if (query->subqueries) { - notify_subqueries(world, query, event); - } - } - notify = false; - break; - case EcsQueryTableUnmatch: - /* Deletion of table */ - unmatch_table(query, event->table); - break; - case EcsQueryTableRematch: - /* Rematch tables of query */ - rematch_tables(world, query, event->parent_query); - break; - case EcsQueryOrphan: - ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); - query->flags |= EcsQueryIsOrphaned; - query->parent = NULL; - break; + ecs_modified(world, struct_type, EcsMember); } - if (notify) { - notify_subqueries(world, query, event); - } + return 0; } static -void query_order_by( +void flecs_set_member_from_component( ecs_world_t *world, - ecs_query_t *query, - ecs_entity_t order_by_component, - ecs_order_by_action_t order_by) + ecs_entity_t e, + EcsMember *member, + EcsMemberRanges *ranges) { - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); - - query->order_by_component = order_by_component; - query->order_by = order_by; - - ecs_vector_free(query->table_slices); - query->table_slices = NULL; + ecs_entity_t struct_type = ecs_get_parent(world, e); + if (!struct_type) { + ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); + return; + } - sort_tables(world, query); + ecs_member_t m = { + .name = ecs_get_name(world, e), + .type = member->type, + .count = member->count, + .offset = member->offset, + .unit = member->unit + }; - if (!query->table_slices) { - build_sorted_tables(query); + if (ranges) { + m.range = ranges->value; + m.error_range = ranges->error; + m.warning_range = ranges->warning; } -error: - return; -} - -static -void query_group_by( - ecs_query_t *query, - ecs_entity_t sort_component, - ecs_group_by_action_t group_by) -{ - /* Cannot change grouping once a query has been created */ - ecs_check(query->group_by_id == 0, ECS_INVALID_OPERATION, NULL); - ecs_check(query->group_by == 0, ECS_INVALID_OPERATION, NULL); - query->group_by_id = sort_component; - query->group_by = group_by; - ecs_map_init(&query->groups, ecs_query_table_list_t, 16); -error: - return; + flecs_add_member_to_struct(world, struct_type, e, &m); } -/* Implementation for iterable mixin */ static -void query_iter_init( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter) -{ - ecs_poly_assert(poly, ecs_query_t); +void flecs_set_member(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsMember *member = ecs_field(it, EcsMember, 0); + EcsMemberRanges *ranges = ecs_table_get_id(world, it->table, + ecs_id(EcsMemberRanges), it->offset); - if (filter) { - iter[1] = ecs_query_iter(world, (ecs_query_t*)poly); - iter[0] = ecs_term_chain_iter(&iter[1], filter); - } else { - iter[0] = ecs_query_iter(world, (ecs_query_t*)poly); + int i, count = it->count; + for (i = 0; i < count; i ++) { + flecs_set_member_from_component(world, it->entities[i], + &member[i], ranges ? &ranges[i] : NULL); } } static -void query_on_event( - ecs_iter_t *it) -{ - /* Because this is the observer::run callback, checking if this is event is - * already handled is not done for us. */ +void flecs_set_member_ranges(ecs_iter_t *it) { ecs_world_t *world = it->world; - ecs_observer_t *o = it->ctx; - if (o->last_event_id == world->event_id) { + EcsMemberRanges *ranges = ecs_field(it, EcsMemberRanges, 0); + EcsMember *member = ecs_table_get_id(world, it->table, + ecs_id(EcsMember), it->offset); + if (!member) { return; } - o->last_event_id = world->event_id; - ecs_query_t *query = o->ctx; - ecs_table_t *table = it->table; - ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); + int i, count = it->count; + for (i = 0; i < count; i ++) { + flecs_set_member_from_component(world, it->entities[i], + &member[i], &ranges[i]); + } +} - /* The observer isn't doing the matching because the query can do it more - * efficiently by checking the table with the query cache. */ - if (ecs_table_cache_get(&query->cache, table) == NULL) { - return; +static +bool flecs_member_range_overlaps( + const ecs_member_value_range_t *range, + const ecs_member_value_range_t *with) +{ + if (ECS_EQ(with->min, with->max)) { + return false; } - ecs_entity_t event = it->event; - if (event == EcsOnTableEmpty) { - update_table(query, table, true); - } else - if (event == EcsOnTableFill) { - update_table(query, table, false); + if (ECS_EQ(range->min, range->max)) { + return false; } -} + if (range->min < with->min || + range->max > with->max) + { + return true; + } -/* -- Public API -- */ + return false; +} -ecs_query_t* ecs_query_init( +static +bool flecs_type_is_number( ecs_world_t *world, - const ecs_query_desc_t *desc) + ecs_entity_t type) { - ecs_query_t *result = NULL; - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(!world->is_fini, ECS_INVALID_OPERATION, NULL); - - /* Ensure that while initially populating the query with tables, they are - * in the right empty/non-empty list. This ensures the query won't miss - * empty/non-empty events for tables that are currently out of sync, but - * change back to being in sync before processing pending events. */ - ecs_force_aperiodic(world); - - result = flecs_sparse_add(world->queries, ecs_query_t); - ecs_poly_init(result, ecs_query_t); - result->id = flecs_sparse_last_id(world->queries); - - ecs_observer_desc_t observer_desc = { .filter = desc->filter }; - observer_desc.filter.match_empty_tables = true; - - if (ecs_filter_init(world, &result->filter, &observer_desc.filter)) { - goto error; + const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); + if (!p) { + return false; } - if (result->filter.term_count) { - observer_desc.run = query_on_event; - observer_desc.ctx = result; - observer_desc.events[0] = EcsOnTableEmpty; - observer_desc.events[1] = EcsOnTableFill; - observer_desc.filter.filter = true; + switch(p->kind) { + case EcsChar: + case EcsU8: + case EcsU16: + case EcsU32: + case EcsU64: + case EcsI8: + case EcsI16: + case EcsI32: + case EcsI64: + case EcsF32: + case EcsF64: + return true; - /* ecs_filter_init could have moved away resources from the terms array - * in the descriptor, so use the terms array from the filter. */ - observer_desc.filter.terms_buffer = result->filter.terms; - observer_desc.filter.terms_buffer_count = result->filter.term_count; - observer_desc.filter.expr = NULL; /* Already parsed */ + case EcsBool: + case EcsByte: + case EcsUPtr: + case EcsIPtr: + case EcsString: + case EcsEntity: + case EcsId: + return false; + default: + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } +} - result->observer = ecs_observer_init(world, &observer_desc); - if (!result->observer) { +static +int flecs_member_validate_ranges( + ecs_world_t *world, + ecs_entity_t type, + const ecs_member_t *member, + bool *ranges_set_out) +{ + const ecs_member_value_range_t *range = &member->range; + const ecs_member_value_range_t *error = &member->error_range; + const ecs_member_value_range_t *warning = &member->warning_range; + bool ranges_set = false; + + if (ECS_NEQ(range->min, range->max)) { + ranges_set = true; + if (range->min > range->max) { + ecs_err("member '%s.%s' has an invalid value range [%f..%f]", + flecs_errstr(ecs_get_path(world, type)), member->name, + range->min, range->max); goto error; } } - result->world = world; - result->iterable.init = query_iter_init; - result->system = desc->system; - result->prev_match_count = -1; - - process_signature(world, result); - - /* Group before matching so we won't have to move tables around later */ - int32_t cascade_by = result->cascade_by; - if (cascade_by) { - query_group_by(result, result->filter.terms[cascade_by - 1].id, - group_by_cascade); - result->group_by_ctx = &result->filter.terms[cascade_by - 1]; - } - - if (desc->group_by) { - /* Can't have a cascade term and group by at the same time, as cascade - * uses the group_by mechanism */ - ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, NULL); - query_group_by(result, desc->group_by_id, desc->group_by); - result->group_by_ctx = desc->group_by_ctx; - result->group_by_ctx_free = desc->group_by_ctx_free; - } - - if (desc->parent != NULL) { - result->flags |= EcsQueryIsSubquery; - } - - /* If a system is specified, ensure that if there are any subjects in the - * filter that refer to the system, the component is added */ - if (desc->system) { - int32_t t, term_count = result->filter.term_count; - ecs_term_t *terms = result->filter.terms; + if (ECS_NEQ(error->min, error->max)) { + ranges_set = true; + if (error->min > error->max) { + ecs_err("member '%s.%s' has an invalid error range [%f..%f]", + flecs_errstr(ecs_get_path(world, type)), member->name, + error->min, error->max); + goto error; + } - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &terms[t]; - if (term->subj.entity == desc->system) { - ecs_add_id(world, desc->system, term->id); - } - } + if (flecs_member_range_overlaps(error, range)) { + ecs_err("error range of member '%s.%s' overlaps with value range", + flecs_errstr(ecs_get_path(world, type)), member->name); + goto error; + } } - if (ecs_should_log_1()) { - char *filter_expr = ecs_filter_str(world, &result->filter); - ecs_dbg_1("#[green]query#[normal] [%s] created", filter_expr); - ecs_os_free(filter_expr); - } + if (ECS_NEQ(warning->min, warning->max)) { + ranges_set = true; + if (warning->min > warning->max) { + ecs_err("member '%s.%s' has an invalid warning range [%f..%f]", + flecs_errstr(ecs_get_path(world, type)), member->name, + warning->min, warning->max); + goto error; + } - ecs_table_cache_init(&result->cache); + if (flecs_member_range_overlaps(warning, range)) { + ecs_err("warning range of member '%s.%s' overlaps with value range", + flecs_errstr(ecs_get_path(world, type)), member->name); + goto error; + } - if (!desc->parent) { - match_tables(world, result); - } else { - add_subquery(world, desc->parent, result); - result->parent = desc->parent; + if (flecs_member_range_overlaps(warning, error)) { + ecs_err("warning range of member '%s.%s' overlaps with error range", + flecs_errstr(ecs_get_path(world, type)), member->name); + goto error; + } } - if (desc->order_by) { - query_order_by( - world, result, desc->order_by_component, desc->order_by); + if (ranges_set && !flecs_type_is_number(world, member->type)) { + ecs_err("member '%s.%s' has an value/error/warning range, but is " + "not a number", + flecs_errstr(ecs_get_path(world, type)), member->name); + goto error; } - result->constraints_satisfied = satisfy_constraints(world, &result->filter); + *ranges_set_out = ranges_set; - return result; + return 0; error: - if (result) { - ecs_filter_fini(&result->filter); - if (result->observer) { - ecs_delete(world, result->observer); - } - flecs_sparse_remove(world->queries, result->id); - } - return NULL; + return -1; } static -void table_cache_free( - ecs_query_t *query) +void flecs_struct_create_member_entity( + ecs_world_t *world, + ecs_entity_t type, + const ecs_member_t *member, + bool set_ranges) { - ecs_table_cache_iter_t it; - ecs_query_table_t *qt; + ecs_entity_t member_entity = ecs_new_from_path(world, type, member->name); - if (flecs_table_cache_iter(&query->cache, &it)) { - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - query_table_free(query, qt); - } - } + ecs_set(world, member_entity, EcsMember, { + .type = member->type, + .count = member->count, + .offset = member->offset, + .unit = member->unit + }); - if (flecs_table_cache_empty_iter(&query->cache, &it)) { - while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { - query_table_free(query, qt); - } + if (set_ranges) { + ecs_set(world, member_entity, EcsMemberRanges, { + .value = member->range, + .error = member->error_range, + .warning = member->warning_range + }); } - - ecs_table_cache_fini(&query->cache); } -void ecs_query_fini( - ecs_query_t *query) +ecs_entity_t ecs_struct_init( + ecs_world_t *world, + const ecs_struct_desc_t *desc) { - ecs_poly_assert(query, ecs_query_t); - ecs_world_t *world = query->world; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - if (!world->is_fini) { - if (query->observer) { - ecs_delete(world, query->observer); - } + ecs_entity_t type = desc->entity; + if (!type) { + type = ecs_new_low_id(world); } - if (query->group_by_ctx_free) { - if (query->group_by_ctx) { - query->group_by_ctx_free(query->group_by_ctx); + ecs_entity_t old_scope = ecs_set_scope(world, type); + + int i; + for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { + const ecs_member_t *m_desc = &desc->members[i]; + if (!m_desc->name) { + break; } - } - if ((query->flags & EcsQueryIsSubquery) && - !(query->flags & EcsQueryIsOrphaned)) - { - remove_subquery(query->parent, query); - } + if (!m_desc->type) { + ecs_err("member '%s' of struct '%s' does not have a type", + m_desc->name, ecs_get_name(world, type)); + goto error; + } - notify_subqueries(world, query, &(ecs_query_event_t){ - .kind = EcsQueryOrphan - }); + ecs_member_t member = *m_desc; + bool ranges_set = false; + bool create_member_entity = desc->create_member_entities; - for_each_component_monitor(world, query, flecs_monitor_unregister); +#ifdef FLECS_CREATE_MEMBER_ENTITIES + create_member_entity = true; +#endif + if (flecs_member_validate_ranges(world, type, &member, &ranges_set)) { + goto error; + } - table_cache_free(query); + if (!create_member_entity) { + if (flecs_add_member_to_struct(world, type, 0, &member)) { + goto error; + } + } else { + flecs_struct_create_member_entity(world, type, &member, ranges_set); + } + } - ecs_map_fini(&query->groups); + ecs_set_scope(world, old_scope); + flecs_resume_readonly(world, &rs); - ecs_vector_free(query->subqueries); - ecs_vector_free(query->table_slices); - ecs_filter_fini(&query->filter); + if (i == 0) { + EcsStruct *s = ecs_ensure(world, type, EcsStruct); + ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_init_t(NULL, &s->members, ecs_member_t, 0); + ecs_modified(world, type, EcsStruct); + } else if (!ecs_has(world, type, EcsStruct)) { + goto error; + } - ecs_poly_fini(query, ecs_query_t); - - /* Remove query from storage */ - flecs_sparse_remove(world->queries, query->id); + return type; error: - return; -} - -const ecs_filter_t* ecs_query_get_filter( - ecs_query_t *query) -{ - ecs_poly_assert(query, ecs_query_t); - return &query->filter; + flecs_resume_readonly(world, &rs); + if (type) { + ecs_delete(world, type); + } + return 0; } -/* Create query iterator */ -ecs_iter_t ecs_query_iter( - const ecs_world_t *stage, - ecs_query_t *query) +int ecs_struct_add_member( + ecs_world_t *world, + ecs_entity_t type, + const ecs_member_t *member) { - ecs_poly_assert(query, ecs_query_t); - ecs_check(!(query->flags & EcsQueryIsOrphaned), - ECS_INVALID_PARAMETER, NULL); - - query->constraints_satisfied = satisfy_constraints(query->world, &query->filter); + flecs_stage_from_world(&world); - ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage); + bool ranges_set; + if (flecs_member_validate_ranges(world, type, member, &ranges_set)) { + goto error; + } - flecs_process_pending_tables(world); +#ifdef FLECS_CREATE_MEMBER_ENTITIES + flecs_struct_create_member_entity(world, type, member, ranges_set); +#else + if (flecs_add_member_to_struct(world, type, 0, member)) { + goto error; + } +#endif - sort_tables(world, query); + return 0; +error: + return -1; +} - if (!world->is_readonly && query->flags & EcsQueryHasRefs) { - flecs_eval_component_monitors(world); +ecs_member_t* ecs_struct_get_member( + ecs_world_t *world, + ecs_entity_t type, + const char *name) +{ + const EcsStruct *s = ecs_get(world, type, EcsStruct); + if (!s) { + return NULL; } - query->prev_match_count = query->match_count; + ecs_member_t *members = ecs_vec_first(&s->members); + int32_t i, count = ecs_vec_count(&s->members); - int32_t table_count; - if (query->table_slices) { - table_count = ecs_vector_count(query->table_slices); - } else { - table_count = ecs_query_table_count(query); + for (i = 0; i < count; i ++) { + if (!ecs_os_strcmp(members[i].name, name)) { + return &members[i]; + } } - ecs_query_iter_t it = { - .query = query, - .node = query->list.first - }; + return NULL; +} - if (query->order_by && query->list.count) { - it.node = ecs_vector_first(query->table_slices, ecs_query_table_node_t); +ecs_member_t* ecs_struct_get_nth_member( + ecs_world_t *world, + ecs_entity_t type, + int32_t i) +{ + const EcsStruct *s = ecs_get(world, type, EcsStruct); + if (!s) { + return NULL; } - ecs_flags32_t flags = 0; - if (query->filter.filter) { - ECS_BIT_SET(flags, EcsIterIsFilter); - } - if (query->filter.instanced) { - ECS_BIT_SET(flags, EcsIterIsInstanced); + ecs_member_t *members = ecs_vec_first(&s->members); + int32_t count = ecs_vec_count(&s->members); + if (i >= count) { + return NULL; } - ecs_iter_t result = { - .real_world = world, - .world = (ecs_world_t*)stage, - .terms = query->filter.terms, - .term_count = query->filter.term_count_actual, - .table_count = table_count, - .flags = flags, - .priv.iter.query = it, - .next = ecs_query_next, - }; - - /* Query populates the iterator with arrays from the cache, ensure they - * don't get overwritten by flecs_iter_validate */ - flecs_iter_init(&result, flecs_iter_cache_ptrs); - - return result; -error: - return (ecs_iter_t){ 0 }; + return &members[i]; } -static -int find_smallest_column( - ecs_table_t *table, - ecs_query_table_match_t *table_data, - ecs_vector_t *sparse_columns) +void flecs_meta_struct_init( + ecs_world_t *world) { - flecs_switch_term_t *sparse_column_array = - ecs_vector_first(sparse_columns, flecs_switch_term_t); - int32_t i, count = ecs_vector_count(sparse_columns); - int32_t min = INT_MAX, index = 0; + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsMember), + .name = "member", .symbol = "EcsMember", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsMember), + .type.alignment = ECS_ALIGNOF(EcsMember) + }); - for (i = 0; i < count; i ++) { - /* The array with sparse queries for the matched table */ - flecs_switch_term_t *sparse_column = &sparse_column_array[i]; + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsMemberRanges), + .name = "member_ranges", .symbol = "EcsMemberRanges", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsMemberRanges), + .type.alignment = ECS_ALIGNOF(EcsMemberRanges) + }); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsStruct), + .name = "struct", .symbol = "EcsStruct", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }), + .type.size = sizeof(EcsStruct), + .type.alignment = ECS_ALIGNOF(EcsStruct) + }); - /* Pointer to the switch column struct of the table */ - ecs_sw_column_t *sc = sparse_column->sw_column; + ecs_set_hooks(world, EcsStruct, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsStruct), + .copy = ecs_copy(EcsStruct), + .dtor = ecs_dtor(EcsStruct) + }); - /* If the sparse column pointer hadn't been retrieved yet, do it now */ - if (!sc) { - /* Get the table column index from the signature column index */ - int32_t table_column_index = table_data->columns[ - sparse_column->signature_column_index]; + ecs_set_hooks(world, EcsMember, { + .ctor = flecs_default_ctor, + }); - /* Translate the table column index to switch column index */ - table_column_index -= table->sw_column_offset; - ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); + ecs_set_hooks(world, EcsMemberRanges, { + .ctor = flecs_default_ctor + }); - /* Get the sparse column */ - ecs_data_t *data = &table->storage; - sc = sparse_column->sw_column = - &data->sw_columns[table_column_index - 1]; - } + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsMember) }, + .events = {EcsOnSet}, + .callback = flecs_set_member, + .global_observer = true + }); - /* Find the smallest column */ - ecs_switch_t *sw = sc->data; - int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); - if (case_count < min) { - min = case_count; - index = i + 1; - } - } + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsMemberRanges) }, + .events = {EcsOnSet}, + .callback = flecs_set_member_ranges, + .global_observer = true + }); - return index; + ecs_set(world, ecs_id(EcsStruct), EcsDefaultChildComponent, {ecs_id(EcsMember)}); + ecs_add_pair(world, ecs_id(EcsStruct), EcsWith, ecs_id(EcsComponent)); + ecs_set(world, ecs_id(EcsMember), EcsDefaultChildComponent, {ecs_id(EcsMember)}); } -typedef struct { - int32_t first; - int32_t count; -} query_iter_cursor_t; - -static -int sparse_column_next( - ecs_table_t *table, - ecs_query_table_match_t *matched_table, - ecs_vector_t *sparse_columns, - ecs_query_iter_t *iter, - query_iter_cursor_t *cur, - bool filter) -{ - bool first_iteration = false; - int32_t sparse_smallest; +#endif - if (!(sparse_smallest = iter->sparse_smallest)) { - sparse_smallest = iter->sparse_smallest = find_smallest_column( - table, matched_table, sparse_columns); - first_iteration = true; - } +/** + * @file addons/meta/type_support/units_ts.c + * @brief Units type support. + */ - sparse_smallest -= 1; - flecs_switch_term_t *columns = ecs_vector_first( - sparse_columns, flecs_switch_term_t); - flecs_switch_term_t *column = &columns[sparse_smallest]; - ecs_switch_t *sw, *sw_smallest = column->sw_column->data; - ecs_entity_t case_smallest = column->sw_case; +#ifdef FLECS_META - /* Find next entity to iterate in sparse column */ - int32_t first, sparse_first = iter->sparse_first; +/* EcsUnit lifecycle */ - if (!filter) { - if (first_iteration) { - first = flecs_switch_first(sw_smallest, case_smallest); - } else { - first = flecs_switch_next(sw_smallest, sparse_first); - } - } else { - int32_t cur_first = cur->first, cur_count = cur->count; - first = cur_first; - while (flecs_switch_get(sw_smallest, first) != case_smallest) { - first ++; - if (first >= (cur_first + cur_count)) { - first = -1; - break; - } - } - } +static void flecs_unit_dtor( + EcsUnit *ptr) +{ + ecs_os_free(ptr->symbol); +} - if (first == -1) { - goto done; - } +static ECS_COPY(EcsUnit, dst, src, { + flecs_unit_dtor(dst); + dst->symbol = ecs_os_strdup(src->symbol); + dst->base = src->base; + dst->over = src->over; + dst->prefix = src->prefix; + dst->translation = src->translation; +}) - /* Check if entity matches with other sparse columns, if any */ - int32_t i, count = ecs_vector_count(sparse_columns); - do { - for (i = 0; i < count; i ++) { - if (i == sparse_smallest) { - /* Already validated this one */ - continue; - } +static ECS_MOVE(EcsUnit, dst, src, { + flecs_unit_dtor(dst); + dst->symbol = src->symbol; + dst->base = src->base; + dst->over = src->over; + dst->prefix = src->prefix; + dst->translation = src->translation; - column = &columns[i]; - sw = column->sw_column->data; + src->symbol = NULL; + src->base = 0; + src->over = 0; + src->prefix = 0; + src->translation = (ecs_unit_translation_t){0}; +}) - if (flecs_switch_get(sw, first) != column->sw_case) { - first = flecs_switch_next(sw_smallest, first); - if (first == -1) { - goto done; - } - } - } - } while (i != count); +static ECS_DTOR(EcsUnit, ptr, { flecs_unit_dtor(ptr); }) - cur->first = iter->sparse_first = first; - cur->count = 1; - return 0; -done: - /* Iterated all elements in the sparse list, we should move to the - * next matched table. */ - iter->sparse_smallest = 0; - iter->sparse_first = 0; +/* EcsUnitPrefix lifecycle */ - return -1; +static void flecs_unit_prefix_dtor( + EcsUnitPrefix *ptr) +{ + ecs_os_free(ptr->symbol); } -#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) +static ECS_COPY(EcsUnitPrefix, dst, src, { + flecs_unit_prefix_dtor(dst); + dst->symbol = ecs_os_strdup(src->symbol); + dst->translation = src->translation; +}) -static -int bitset_column_next( - ecs_table_t *table, - ecs_vector_t *bitset_columns, - ecs_query_iter_t *iter, - query_iter_cursor_t *cur) -{ - /* Precomputed single-bit test */ - static const uint64_t bitmask[64] = { - (uint64_t)1 << 0, (uint64_t)1 << 1, (uint64_t)1 << 2, (uint64_t)1 << 3, - (uint64_t)1 << 4, (uint64_t)1 << 5, (uint64_t)1 << 6, (uint64_t)1 << 7, - (uint64_t)1 << 8, (uint64_t)1 << 9, (uint64_t)1 << 10, (uint64_t)1 << 11, - (uint64_t)1 << 12, (uint64_t)1 << 13, (uint64_t)1 << 14, (uint64_t)1 << 15, - (uint64_t)1 << 16, (uint64_t)1 << 17, (uint64_t)1 << 18, (uint64_t)1 << 19, - (uint64_t)1 << 20, (uint64_t)1 << 21, (uint64_t)1 << 22, (uint64_t)1 << 23, - (uint64_t)1 << 24, (uint64_t)1 << 25, (uint64_t)1 << 26, (uint64_t)1 << 27, - (uint64_t)1 << 28, (uint64_t)1 << 29, (uint64_t)1 << 30, (uint64_t)1 << 31, - (uint64_t)1 << 32, (uint64_t)1 << 33, (uint64_t)1 << 34, (uint64_t)1 << 35, - (uint64_t)1 << 36, (uint64_t)1 << 37, (uint64_t)1 << 38, (uint64_t)1 << 39, - (uint64_t)1 << 40, (uint64_t)1 << 41, (uint64_t)1 << 42, (uint64_t)1 << 43, - (uint64_t)1 << 44, (uint64_t)1 << 45, (uint64_t)1 << 46, (uint64_t)1 << 47, - (uint64_t)1 << 48, (uint64_t)1 << 49, (uint64_t)1 << 50, (uint64_t)1 << 51, - (uint64_t)1 << 52, (uint64_t)1 << 53, (uint64_t)1 << 54, (uint64_t)1 << 55, - (uint64_t)1 << 56, (uint64_t)1 << 57, (uint64_t)1 << 58, (uint64_t)1 << 59, - (uint64_t)1 << 60, (uint64_t)1 << 61, (uint64_t)1 << 62, (uint64_t)1 << 63 - }; +static ECS_MOVE(EcsUnitPrefix, dst, src, { + flecs_unit_prefix_dtor(dst); + dst->symbol = src->symbol; + dst->translation = src->translation; - /* Precomputed test to verify if remainder of block is set (or not) */ - static const uint64_t bitmask_remain[64] = { - BS_MAX, BS_MAX - (BS_MAX >> 63), BS_MAX - (BS_MAX >> 62), - BS_MAX - (BS_MAX >> 61), BS_MAX - (BS_MAX >> 60), BS_MAX - (BS_MAX >> 59), - BS_MAX - (BS_MAX >> 58), BS_MAX - (BS_MAX >> 57), BS_MAX - (BS_MAX >> 56), - BS_MAX - (BS_MAX >> 55), BS_MAX - (BS_MAX >> 54), BS_MAX - (BS_MAX >> 53), - BS_MAX - (BS_MAX >> 52), BS_MAX - (BS_MAX >> 51), BS_MAX - (BS_MAX >> 50), - BS_MAX - (BS_MAX >> 49), BS_MAX - (BS_MAX >> 48), BS_MAX - (BS_MAX >> 47), - BS_MAX - (BS_MAX >> 46), BS_MAX - (BS_MAX >> 45), BS_MAX - (BS_MAX >> 44), - BS_MAX - (BS_MAX >> 43), BS_MAX - (BS_MAX >> 42), BS_MAX - (BS_MAX >> 41), - BS_MAX - (BS_MAX >> 40), BS_MAX - (BS_MAX >> 39), BS_MAX - (BS_MAX >> 38), - BS_MAX - (BS_MAX >> 37), BS_MAX - (BS_MAX >> 36), BS_MAX - (BS_MAX >> 35), - BS_MAX - (BS_MAX >> 34), BS_MAX - (BS_MAX >> 33), BS_MAX - (BS_MAX >> 32), - BS_MAX - (BS_MAX >> 31), BS_MAX - (BS_MAX >> 30), BS_MAX - (BS_MAX >> 29), - BS_MAX - (BS_MAX >> 28), BS_MAX - (BS_MAX >> 27), BS_MAX - (BS_MAX >> 26), - BS_MAX - (BS_MAX >> 25), BS_MAX - (BS_MAX >> 24), BS_MAX - (BS_MAX >> 23), - BS_MAX - (BS_MAX >> 22), BS_MAX - (BS_MAX >> 21), BS_MAX - (BS_MAX >> 20), - BS_MAX - (BS_MAX >> 19), BS_MAX - (BS_MAX >> 18), BS_MAX - (BS_MAX >> 17), - BS_MAX - (BS_MAX >> 16), BS_MAX - (BS_MAX >> 15), BS_MAX - (BS_MAX >> 14), - BS_MAX - (BS_MAX >> 13), BS_MAX - (BS_MAX >> 12), BS_MAX - (BS_MAX >> 11), - BS_MAX - (BS_MAX >> 10), BS_MAX - (BS_MAX >> 9), BS_MAX - (BS_MAX >> 8), - BS_MAX - (BS_MAX >> 7), BS_MAX - (BS_MAX >> 6), BS_MAX - (BS_MAX >> 5), - BS_MAX - (BS_MAX >> 4), BS_MAX - (BS_MAX >> 3), BS_MAX - (BS_MAX >> 2), - BS_MAX - (BS_MAX >> 1) - }; + src->symbol = NULL; + src->translation = (ecs_unit_translation_t){0}; +}) - int32_t i, count = ecs_vector_count(bitset_columns); - flecs_bitset_term_t *columns = ecs_vector_first( - bitset_columns, flecs_bitset_term_t); - int32_t bs_offset = table->bs_column_offset; +static ECS_DTOR(EcsUnitPrefix, ptr, { flecs_unit_prefix_dtor(ptr); }) - int32_t first = iter->bitset_first; - int32_t last = 0; +bool flecs_unit_validate( + ecs_world_t *world, + ecs_entity_t t, + EcsUnit *data) +{ + char *derived_symbol = NULL; + const char *symbol = data->symbol; - for (i = 0; i < count; i ++) { - flecs_bitset_term_t *column = &columns[i]; - ecs_bs_column_t *bs_column = columns[i].bs_column; + ecs_entity_t base = data->base; + ecs_entity_t over = data->over; + ecs_entity_t prefix = data->prefix; + ecs_unit_translation_t translation = data->translation; - if (!bs_column) { - int32_t index = column->column_index; - ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); - bs_column = &table->storage.bs_columns[index - bs_offset]; - columns[i].bs_column = bs_column; + if (base) { + if (!ecs_has(world, base, EcsUnit)) { + ecs_err("entity '%s' for unit '%s' used as base is not a unit", + ecs_get_name(world, base), ecs_get_name(world, t)); + goto error; } - - ecs_bitset_t *bs = &bs_column->data; - int32_t bs_elem_count = bs->count; - int32_t bs_block = first >> 6; - int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; + } - if (bs_block >= bs_block_count) { - goto done; + if (over) { + if (!base) { + ecs_err("invalid unit '%s': cannot specify over without base", + ecs_get_name(world, t)); + goto error; + } + if (!ecs_has(world, over, EcsUnit)) { + ecs_err("entity '%s' for unit '%s' used as over is not a unit", + ecs_get_name(world, over), ecs_get_name(world, t)); + goto error; } + } - uint64_t *data = bs->data; - int32_t bs_start = first & 0x3F; + if (prefix) { + if (!base) { + ecs_err("invalid unit '%s': cannot specify prefix without base", + ecs_get_name(world, t)); + goto error; + } + const EcsUnitPrefix *prefix_ptr = ecs_get(world, prefix, EcsUnitPrefix); + if (!prefix_ptr) { + ecs_err("entity '%s' for unit '%s' used as prefix is not a prefix", + ecs_get_name(world, over), ecs_get_name(world, t)); + goto error; + } - /* Step 1: find the first non-empty block */ - uint64_t v = data[bs_block]; - uint64_t remain = bitmask_remain[bs_start]; - while (!(v & remain)) { - /* If no elements are remaining, move to next block */ - if ((++bs_block) >= bs_block_count) { - /* No non-empty blocks left */ - goto done; + if (translation.factor || translation.power) { + if (prefix_ptr->translation.factor != translation.factor || + prefix_ptr->translation.power != translation.power) + { + ecs_err( + "factor for unit '%s' is inconsistent with prefix '%s'", + ecs_get_name(world, t), ecs_get_name(world, prefix)); + goto error; } - - bs_start = 0; - remain = BS_MAX; /* Test the full block */ - v = data[bs_block]; + } else { + translation = prefix_ptr->translation; } + } - /* Step 2: find the first non-empty element in the block */ - while (!(v & bitmask[bs_start])) { - bs_start ++; + if (base) { + bool must_match = false; /* Must base symbol match symbol? */ + ecs_strbuf_t sbuf = ECS_STRBUF_INIT; + if (prefix) { + const EcsUnitPrefix *ptr = ecs_get(world, prefix, EcsUnitPrefix); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (ptr->symbol) { + ecs_strbuf_appendstr(&sbuf, ptr->symbol); + must_match = true; + } + } - /* Block was not empty, so bs_start must be smaller than 64 */ - ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); + const EcsUnit *uptr = ecs_get(world, base, EcsUnit); + ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (uptr->symbol) { + ecs_strbuf_appendstr(&sbuf, uptr->symbol); } - - /* Step 3: Find number of contiguous enabled elements after start */ - int32_t bs_end = bs_start, bs_block_end = bs_block; - - remain = bitmask_remain[bs_end]; - while ((v & remain) == remain) { - bs_end = 0; - bs_block_end ++; - if (bs_block_end == bs_block_count) { - break; + if (over) { + uptr = ecs_get(world, over, EcsUnit); + ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (uptr->symbol) { + ecs_strbuf_appendch(&sbuf, '/'); + ecs_strbuf_appendstr(&sbuf, uptr->symbol); + must_match = true; } + } - v = data[bs_block_end]; - remain = BS_MAX; /* Test the full block */ + derived_symbol = ecs_strbuf_get(&sbuf); + if (derived_symbol && !ecs_os_strlen(derived_symbol)) { + ecs_os_free(derived_symbol); + derived_symbol = NULL; } - /* Step 4: find remainder of enabled elements in current block */ - if (bs_block_end != bs_block_count) { - while ((v & bitmask[bs_end])) { - bs_end ++; + if (derived_symbol && symbol && ecs_os_strcmp(symbol, derived_symbol)) { + if (must_match) { + ecs_err("symbol '%s' for unit '%s' does not match base" + " symbol '%s'", symbol, + ecs_get_name(world, t), derived_symbol); + goto error; } } + if (!symbol && derived_symbol && (prefix || over)) { + ecs_os_free(data->symbol); + data->symbol = derived_symbol; + } else { + ecs_os_free(derived_symbol); + } + } - /* Block was not 100% occupied, so bs_start must be smaller than 64 */ - ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); + data->base = base; + data->over = over; + data->prefix = prefix; + data->translation = translation; - /* Step 5: translate to element start/end and make sure that each column - * range is a subset of the previous one. */ - first = bs_block * 64 + bs_start; - int32_t cur_last = bs_block_end * 64 + bs_end; - - /* No enabled elements found in table */ - if (first == cur_last) { - goto done; - } + return true; +error: + ecs_os_free(derived_symbol); + return false; +} - /* If multiple bitsets are evaluated, make sure each subsequent range - * is equal or a subset of the previous range */ - if (i) { - /* If the first element of a subsequent bitset is larger than the - * previous last value, start over. */ - if (first >= last) { - i = -1; - continue; - } +static +void flecs_set_unit(ecs_iter_t *it) { + EcsUnit *u = ecs_field(it, EcsUnit, 0); - /* Make sure the last element of the range doesn't exceed the last - * element of the previous range. */ - if (cur_last > last) { - cur_last = last; - } - } + ecs_world_t *world = it->world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + flecs_unit_validate(world, e, &u[i]); + } +} - last = cur_last; - int32_t elem_count = last - first; +static +void flecs_unit_quantity_monitor(ecs_iter_t *it) { + ecs_world_t *world = it->world; - /* Make sure last element doesn't exceed total number of elements in - * the table */ - if (elem_count > (bs_elem_count - first)) { - elem_count = (bs_elem_count - first); - if (!elem_count) { - iter->bitset_first = 0; - goto done; - } + int i, count = it->count; + if (it->event == EcsOnAdd) { + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_add_pair(world, e, EcsQuantity, e); + } + } else { + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_remove_pair(world, e, EcsQuantity, e); } - - cur->first = first; - cur->count = elem_count; - iter->bitset_first = first; } - - /* Keep track of last processed element for iteration */ - iter->bitset_first = last; - - return 0; -done: - iter->sparse_smallest = 0; - iter->sparse_first = 0; - return -1; } -static -void mark_columns_dirty( - ecs_query_t *query, - ecs_query_table_match_t *table_data) +ecs_entity_t ecs_unit_init( + ecs_world_t *world, + const ecs_unit_desc_t *desc) { - ecs_table_t *table = table_data->table; + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - if (table && table->dirty_state) { - ecs_term_t *terms = query->filter.terms; - int32_t i, count = query->filter.term_count_actual; - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - int32_t ti = term->index; + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); + } - if (term->inout == EcsIn || term->inout == EcsInOutFilter) { - /* Don't mark readonly terms dirty */ - continue; - } + ecs_entity_t quantity = desc->quantity; + if (quantity) { + if (!ecs_has_id(world, quantity, EcsQuantity)) { + ecs_err("entity '%s' for unit '%s' is not a quantity", + ecs_get_name(world, quantity), ecs_get_name(world, t)); + goto error; + } - if (table_data->subjects[ti] != 0) { - /* Don't mark table dirty if term is not from the table */ - continue; - } + ecs_add_pair(world, t, EcsQuantity, desc->quantity); + } else { + ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); + } - int32_t index = table_data->columns[ti]; - if (index <= 0) { - /* If term is not set, there's nothing to mark dirty */ - continue; - } + EcsUnit *value = ecs_ensure(world, t, EcsUnit); + value->base = desc->base; + value->over = desc->over; + value->translation = desc->translation; + value->prefix = desc->prefix; + ecs_os_strset(&value->symbol, desc->symbol); - /* Potential candidate for marking table dirty, if a component */ - int32_t storage_index = ecs_table_type_to_storage_index( - table, index - 1); - if (storage_index >= 0) { - table->dirty_state[storage_index + 1] ++; - } - } + if (!flecs_unit_validate(world, t, value)) { + goto error; + } + + ecs_modified(world, t, EcsUnit); + + flecs_resume_readonly(world, &rs); + return t; +error: + if (t) { + ecs_delete(world, t); } + flecs_resume_readonly(world, &rs); + return 0; } -bool ecs_query_next( - ecs_iter_t *it) +ecs_entity_t ecs_unit_prefix_init( + ecs_world_t *world, + const ecs_unit_prefix_desc_t *desc) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - if (flecs_iter_next_row(it)) { - return true; + ecs_entity_t t = desc->entity; + if (!t) { + t = ecs_new_low_id(world); } - return flecs_iter_next_instanced(it, ecs_query_next_instanced(it)); -error: - return false; + ecs_set(world, t, EcsUnitPrefix, { + .symbol = ECS_CONST_CAST(char*, desc->symbol), + .translation = desc->translation + }); + + flecs_resume_readonly(world, &rs); + + return t; } -bool ecs_query_next_instanced( - ecs_iter_t *it) +ecs_entity_t ecs_quantity_init( + ecs_world_t *world, + const ecs_entity_desc_t *desc) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - - ecs_query_iter_t *iter = &it->priv.iter.query; - ecs_query_t *query = iter->query; - ecs_world_t *world = query->world; - ecs_flags32_t flags = query->flags; - (void)world; + ecs_suspend_readonly_state_t rs; + world = flecs_suspend_readonly(world, &rs); - ECS_BIT_SET(it->flags, EcsIterIsValid); + ecs_entity_t t = ecs_entity_init(world, desc); + if (!t) { + return 0; + } - ecs_poly_assert(world, ecs_world_t); + ecs_add_id(world, t, EcsQuantity); - if (!query->constraints_satisfied) { - goto done; - } + flecs_resume_readonly(world, &rs); - query_iter_cursor_t cur; - ecs_query_table_node_t *node, *next, *prev; - if ((prev = iter->prev)) { - /* Match has been iterated, update monitor for change tracking */ - if (flags & EcsQueryHasMonitor) { - sync_match_monitor(query, prev->match); - } - if (flags & EcsQueryHasOutColumns) { - mark_columns_dirty(query, prev->match); - } - } + return t; +} - iter->skip_count = 0; +void flecs_meta_units_init( + ecs_world_t *world) +{ + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsUnit), + .name = "unit", .symbol = "EcsUnit", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsInherit)) + }), + .type.size = sizeof(EcsUnit), + .type.alignment = ECS_ALIGNOF(EcsUnit) + }); - flecs_iter_validate(it); + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsUnitPrefix), + .name = "unit_prefix", .symbol = "EcsUnitPrefix", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsInherit)) + }), + .type.size = sizeof(EcsUnitPrefix), + .type.alignment = ECS_ALIGNOF(EcsUnitPrefix) + }); - for (node = iter->node; node != NULL; node = next) { - ecs_query_table_match_t *match = node->match; - ecs_table_t *table = match->table; + ecs_component(world, { + .entity = ecs_entity(world, { .id = EcsQuantity, + .name = "quantity", .symbol = "EcsQuantity", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsInherit)) + }) + }); - next = node->next; + ecs_set_hooks(world, EcsUnit, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsUnit), + .copy = ecs_copy(EcsUnit), + .dtor = ecs_dtor(EcsUnit) + }); - if (table) { - cur.first = node->offset; - cur.count = node->count; - if (!cur.count) { - cur.count = ecs_table_count(table); + ecs_set_hooks(world, EcsUnitPrefix, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsUnitPrefix), + .copy = ecs_copy(EcsUnitPrefix), + .dtor = ecs_dtor(EcsUnitPrefix) + }); - /* List should never contain empty tables */ - ecs_assert(cur.count != 0, ECS_INTERNAL_ERROR, NULL); - } + ecs_add_id(world, EcsQuantity, EcsExclusive); + ecs_add_id(world, EcsQuantity, EcsPairIsTag); - ecs_vector_t *bitset_columns = match->bitset_columns; - ecs_vector_t *sparse_columns = match->sparse_columns; + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsUnit) }, + .events = {EcsOnSet}, + .callback = flecs_set_unit, + .global_observer = true + }); - if (bitset_columns || sparse_columns) { - bool found = false; + ecs_observer(world, { + .query.terms = { + { .id = ecs_id(EcsUnit) }, + { .id = EcsQuantity } + }, + .events = { EcsMonitor }, + .callback = flecs_unit_quantity_monitor, + .global_observer = true + }); - do { - found = false; - if (bitset_columns) { - if (bitset_column_next(table, bitset_columns, iter, - &cur) == -1) - { - /* No more enabled components for table */ - iter->bitset_first = 0; - break; - } else { - found = true; - next = node; - } - } +} - if (sparse_columns) { - if (sparse_column_next(table, match, - sparse_columns, iter, &cur, found) == -1) - { - /* No more elements in sparse column */ - if (found) { - /* Try again */ - next = node->next; - found = false; - } else { - /* Nothing found */ - iter->bitset_first = 0; - break; - } - } else { - found = true; - next = node; - iter->bitset_first = cur.first + cur.count; - } - } - } while (!found); +#endif - if (!found) { - continue; - } - } - } else { - cur.count = 0; - cur.first = 0; - } +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ - it->ids = match->ids; - it->columns = match->columns; - it->subjects = match->subjects; - it->sizes = match->sizes; - it->references = match->references; - it->instance_count = 0; - flecs_iter_populate_data(world, it, match->table, cur.first, cur.count, - it->ptrs, NULL); +#ifdef FLECS_SCRIPT - iter->node = next; - iter->prev = node; - goto yield; - } +#define flecs_expr_ast_new(parser, T, kind)\ + (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) -done: -error: - ecs_iter_fini(it); - return false; - -yield: - return true; +static +void* flecs_expr_ast_new_( + ecs_parser_t *parser, + ecs_size_t size, + ecs_expr_node_kind_t kind) +{ + ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &parser->script->allocator; + ecs_expr_node_t *result = flecs_calloc_w_dbg_info(a, size, + "ecs_expr_node_t"); + result->kind = kind; + result->pos = parser->pos; + return result; } -bool ecs_query_changed( - ecs_query_t *query, - const ecs_iter_t *it) +ecs_expr_value_node_t* flecs_expr_value_from( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type) { - if (it) { - ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INVALID_PARAMETER, NULL); - - ecs_query_table_match_t *qt = - (ecs_query_table_match_t*)it->priv.iter.query.prev; - ecs_assert(qt != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_value_node_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_value_node_t); + result->ptr = &result->storage.u64; + result->node.kind = EcsExprValue; + result->node.pos = node ? node->pos : NULL; + result->node.type = type; + result->node.type_info = ecs_get_type_info(script->world, type); + return result; +} - if (!query) { - query = it->priv.iter.query.query; - } else { - ecs_check(query == it->priv.iter.query.query, - ECS_INVALID_PARAMETER, NULL); - } +ecs_expr_variable_t* flecs_expr_variable_from( + ecs_script_t *script, + ecs_expr_node_t *node, + const char *name) +{ + ecs_expr_variable_t *result = flecs_calloc_t( + &((ecs_script_impl_t*)script)->allocator, ecs_expr_variable_t); + result->name = name; + result->sp = -1; + result->node.kind = EcsExprVariable; + result->node.pos = node ? node->pos : NULL; + return result; +} - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_poly_assert(query, ecs_query_t); +ecs_expr_member_t* flecs_expr_member_from( + ecs_script_t *script, + ecs_expr_node_t *node, + const char *name) +{ + ecs_expr_member_t *result = flecs_calloc_t( + &flecs_script_impl(script)->allocator, ecs_expr_member_t); + result->node.kind = EcsExprMember; + result->node.pos = node->pos; + result->left = node; + result->member_name =name; + return result; +} - flecs_process_pending_tables(it->real_world); +ecs_expr_value_node_t* flecs_expr_bool( + ecs_parser_t *parser, + bool value) +{ + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); + result->storage.bool_ = value; + result->ptr = &result->storage.bool_; + result->node.type = ecs_id(ecs_bool_t); + return result; +} - return check_match_monitor(query, qt); +ecs_expr_value_node_t* flecs_expr_char( + ecs_parser_t *parser, + const char *value) +{ + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); + if(value[0] == '\\' && value[1] == '\'') { + result->storage.char_ = '\''; + } else { + char ch = 0; + const char *ptr = flecs_chrparse(value, &ch); + if(!ptr) { + flecs_free_t( + &parser->script->allocator, ecs_expr_value_node_t, result); + return NULL; + } + result->storage.char_ = ch; } + result->ptr = &result->storage.char_; + result->node.type = ecs_id(ecs_char_t); + return result; +} - ecs_poly_assert(query, ecs_query_t); - ecs_check(!(query->flags & EcsQueryIsOrphaned), - ECS_INVALID_PARAMETER, NULL); +ecs_expr_value_node_t* flecs_expr_int( + ecs_parser_t *parser, + int64_t value) +{ + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); + result->storage.i64 = value; + result->ptr = &result->storage.i64; + result->node.type = ecs_id(ecs_i64_t); + return result; +} - flecs_process_pending_tables(query->world); +ecs_expr_value_node_t* flecs_expr_uint( + ecs_parser_t *parser, + uint64_t value) +{ + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); + result->storage.u64 = value; + result->ptr = &result->storage.u64; + result->node.type = ecs_id(ecs_i64_t); + return result; +} - if (!(query->flags & EcsQueryHasMonitor)) { - query->flags |= EcsQueryHasMonitor; - init_query_monitors(query); - return true; /* Monitors didn't exist yet */ - } +ecs_expr_value_node_t* flecs_expr_float( + ecs_parser_t *parser, + double value) +{ + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); + result->storage.f64 = value; + result->ptr = &result->storage.f64; + result->node.type = ecs_id(ecs_f64_t); + return result; +} - if (query->match_count != query->prev_match_count) { - return true; +ecs_expr_value_node_t* flecs_expr_string( + ecs_parser_t *parser, + const char *value) +{ + char *str = ECS_CONST_CAST(char*, value); + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); + result->storage.string = str; + result->ptr = &result->storage.string; + result->node.type = ecs_id(ecs_string_t); + + if (!flecs_string_escape(str)) { + flecs_free_t(&parser->script->allocator, ecs_expr_value_node_t, result); + return NULL; } - return check_query_monitor(query); -error: - return false; + return result; } -void ecs_query_skip( - ecs_iter_t *it) +ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( + ecs_parser_t *parser, + const char *value) { - ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); - ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INVALID_PARAMETER, NULL); + ecs_expr_interpolated_string_t *result = flecs_expr_ast_new( + parser, ecs_expr_interpolated_string_t, EcsExprInterpolatedString); + result->value = ECS_CONST_CAST(char*, value); + result->buffer = flecs_strdup(&parser->script->allocator, value); + result->buffer_size = ecs_os_strlen(result->buffer) + 1; + result->node.type = ecs_id(ecs_string_t); + ecs_vec_init_t(&parser->script->allocator, &result->fragments, char*, 0); + ecs_vec_init_t(&parser->script->allocator, &result->expressions, + ecs_expr_node_t*, 0); - if (it->instance_count > it->count) { - it->priv.iter.query.skip_count ++; - if (it->priv.iter.query.skip_count == it->instance_count) { - /* For non-instanced queries, make sure all entities are skipped */ - it->priv.iter.query.prev = NULL; - } - } else { - it->priv.iter.query.prev = NULL; - } + return result; } -bool ecs_query_orphaned( - ecs_query_t *query) +ecs_expr_value_node_t* flecs_expr_entity( + ecs_parser_t *parser, + ecs_entity_t value) { - ecs_poly_assert(query, ecs_query_t); - return query->flags & EcsQueryIsOrphaned; + ecs_expr_value_node_t *result = flecs_expr_ast_new( + parser, ecs_expr_value_node_t, EcsExprValue); + result->storage.entity = value; + result->ptr = &result->storage.entity; + result->node.type = ecs_id(ecs_entity_t); + return result; } -#include +ecs_expr_initializer_t* flecs_expr_initializer( + ecs_parser_t *parser) +{ + ecs_expr_initializer_t *result = flecs_expr_ast_new( + parser, ecs_expr_initializer_t, EcsExprInitializer); + ecs_vec_init_t(&parser->script->allocator, &result->elements, + ecs_expr_initializer_element_t, 0); + return result; +} -/* Marker object used to differentiate a component vs. a tag edge */ -static ecs_table_diff_t ecs_table_edge_is_component; +ecs_expr_identifier_t* flecs_expr_identifier( + ecs_parser_t *parser, + const char *value) +{ + ecs_expr_identifier_t *result = flecs_expr_ast_new( + parser, ecs_expr_identifier_t, EcsExprIdentifier); + result->value = value; + return result; +} -static -uint64_t ids_hash(const void *ptr) { - const ecs_ids_t *type = ptr; - ecs_id_t *ids = type->array; - int32_t count = type->count; - uint64_t hash = flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); - return hash; +ecs_expr_variable_t* flecs_expr_variable( + ecs_parser_t *parser, + const char *value) +{ + ecs_expr_variable_t *result = flecs_expr_ast_new( + parser, ecs_expr_variable_t, EcsExprVariable); + result->name = value; + result->sp = -1; + return result; } -static -int ids_compare(const void *ptr_1, const void *ptr_2) { - const ecs_ids_t *type_1 = ptr_1; - const ecs_ids_t *type_2 = ptr_2; +ecs_expr_unary_t* flecs_expr_unary( + ecs_parser_t *parser) +{ + ecs_expr_unary_t *result = flecs_expr_ast_new( + parser, ecs_expr_unary_t, EcsExprUnary); + return result; +} - int32_t count_1 = type_1->count; - int32_t count_2 = type_2->count; +ecs_expr_binary_t* flecs_expr_binary( + ecs_parser_t *parser) +{ + ecs_expr_binary_t *result = flecs_expr_ast_new( + parser, ecs_expr_binary_t, EcsExprBinary); + return result; +} - if (count_1 != count_2) { - return (count_1 > count_2) - (count_1 < count_2); - } +ecs_expr_member_t* flecs_expr_member( + ecs_parser_t *parser) +{ + ecs_expr_member_t *result = flecs_expr_ast_new( + parser, ecs_expr_member_t, EcsExprMember); + return result; +} - const ecs_id_t *ids_1 = type_1->array; - const ecs_id_t *ids_2 = type_2->array; - - int32_t i; - for (i = 0; i < count_1; i ++) { - ecs_id_t id_1 = ids_1[i]; - ecs_id_t id_2 = ids_2[i]; +ecs_expr_function_t* flecs_expr_function( + ecs_parser_t *parser) +{ + ecs_expr_function_t *result = flecs_expr_ast_new( + parser, ecs_expr_function_t, EcsExprFunction); + return result; +} - if (id_1 != id_2) { - return (id_1 > id_2) - (id_1 < id_2); - } - } +ecs_expr_element_t* flecs_expr_element( + ecs_parser_t *parser) +{ + ecs_expr_element_t *result = flecs_expr_ast_new( + parser, ecs_expr_element_t, EcsExprElement); + return result; +} - return 0; +ecs_expr_match_t* flecs_expr_match( + ecs_parser_t *parser) +{ + ecs_expr_match_t *result = flecs_expr_ast_new( + parser, ecs_expr_match_t, EcsExprMatch); + return result; } -void flecs_table_hashmap_init(ecs_hashmap_t *hm) { - flecs_hashmap_init(hm, ecs_ids_t, ecs_table_t*, ids_hash, ids_compare); +ecs_expr_new_t* flecs_expr_new( + ecs_parser_t *parser) +{ + ecs_expr_new_t *result = flecs_expr_ast_new( + parser, ecs_expr_new_t, EcsExprNew); + return result; } -const EcsComponent* flecs_component_from_id( - const ecs_world_t *world, - ecs_entity_t e) +static +bool flecs_expr_explicit_cast_allowed( + ecs_world_t *world, + ecs_entity_t from, + ecs_entity_t to) { - ecs_entity_t pair = 0; + if (from == to) { + return true; + } - /* If this is a pair, get the pair component from the identifier */ - if (ECS_HAS_ROLE(e, PAIR)) { - pair = e; - e = ecs_get_alive(world, ECS_PAIR_FIRST(e)); + const EcsType *from_type = ecs_get(world, from, EcsType); + const EcsType *to_type = ecs_get(world, to, EcsType); + ecs_assert(from_type != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(to_type != NULL, ECS_INTERNAL_ERROR, NULL); - if (ecs_has_id(world, e, EcsTag)) { - return NULL; - } + /* Treat opaque types asthe types that they're pretending to be*/ + if (from_type->kind == EcsOpaqueType) { + const EcsOpaque *o = ecs_get(world, from, EcsOpaque); + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + from_type = ecs_get(world, o->as_type, EcsType); + ecs_assert(from_type != NULL, ECS_INTERNAL_ERROR, NULL); } - - if (e & ECS_ROLE_MASK) { - return NULL; + if (to_type->kind == EcsOpaqueType) { + const EcsOpaque *o = ecs_get(world, to, EcsOpaque); + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + to_type = ecs_get(world, o->as_type, EcsType); + ecs_assert(to_type != NULL, ECS_INTERNAL_ERROR, NULL); } - const EcsComponent *component = ecs_get(world, e, EcsComponent); - if ((!component || !component->size) && pair) { - /* If this is a pair column and the pair is not a component, use - * the component type of the component the pair is applied to. */ - e = ECS_PAIR_SECOND(pair); + if (from_type->kind != EcsPrimitiveType || + to_type->kind != EcsPrimitiveType) + { + if (from_type->kind == EcsEnumType || + from_type->kind == EcsBitmaskType) + { + if (flecs_expr_is_type_integer(to)) { + /* Can cast enums/bitmasks to integers */ + return true; + } + } + + if (flecs_expr_is_type_integer(from)) { + if (to_type->kind == EcsEnumType || + to_type->kind == EcsBitmaskType) + { + /* Can cast integers to enums/bitmasks */ + return true; + } + } - /* Because generations are not stored in the pair, get the currently - * alive id */ - e = ecs_get_alive(world, e); + /* Cannot cast complex types that are not the same */ + return false; + } - /* If a pair is used with a not alive id, the pair is not valid */ - ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); + /* Anything can be casted to a number */ + if (flecs_expr_is_type_number(to)) { + return true; + } - component = ecs_get(world, e, EcsComponent); + /* Anything can be casted to a number */ + if (to == ecs_id(ecs_string_t)) { + return true; } - return component; + return true; } -/* Ensure the ids used in the columns exist */ -static -int32_t ensure_columns( - ecs_world_t *world, - ecs_table_t *table) +ecs_expr_cast_t* flecs_expr_cast( + ecs_script_t *script, + ecs_expr_node_t *expr, + ecs_entity_t type) { - int32_t i, count = ecs_vector_count(table->type); - ecs_id_t* ids = ecs_vector_first(table->type, ecs_id_t); + if (!flecs_expr_explicit_cast_allowed(script->world, expr->type, type)) { + char *from = ecs_id_str(script->world, expr->type); + char *to = ecs_id_str(script->world, type); + flecs_expr_visit_error(script, expr, "invalid cast from %s to %s", + from, to); + ecs_os_free(from); + ecs_os_free(to); + return NULL; + } - for (i = 0; i < count; i++) { - ecs_ensure_id(world, ids[i]); + ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; + ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); + result->node.kind = EcsExprCast; + if (flecs_expr_is_type_number(expr->type) && + flecs_expr_is_type_number(type)) + { + result->node.kind = EcsExprCastNumber; } - return count; + result->node.pos = expr->pos; + result->node.type = type; + result->node.type_info = ecs_get_type_info(script->world, type); + ecs_assert(result->node.type_info != NULL, ECS_INTERNAL_ERROR, NULL); + result->expr = expr; + return result; } +#endif + +/** + * @file addons/script/expr/parser.c + * @brief Script expression parser. + */ + + +#ifdef FLECS_SCRIPT + +/* From https://en.cppreference.com/w/c/language/operator_precedence */ + +static int flecs_expr_precedence[] = { + [EcsTokParenOpen] = 1, + [EcsTokMember] = 1, + [EcsTokBracketOpen] = 1, + [EcsTokNot] = 2, + [EcsTokMul] = 3, + [EcsTokDiv] = 3, + [EcsTokMod] = 3, + [EcsTokAdd] = 4, + [EcsTokSub] = 4, + [EcsTokShiftLeft] = 5, + [EcsTokShiftRight] = 5, + [EcsTokGt] = 6, + [EcsTokGtEq] = 6, + [EcsTokLt] = 6, + [EcsTokLtEq] = 6, + [EcsTokEq] = 7, + [EcsTokNeq] = 7, + [EcsTokBitwiseAnd] = 8, + [EcsTokBitwiseOr] = 10, + [EcsTokAnd] = 11, + [EcsTokOr] = 12, +}; + static -ecs_vector_t* ids_to_vector( - const ecs_ids_t *entities) -{ - if (entities->count) { - ecs_vector_t *result = NULL; - ecs_vector_set_count(&result, ecs_entity_t, entities->count); - ecs_entity_t *array = ecs_vector_first(result, ecs_entity_t); - ecs_os_memcpy_n(array, entities->array, ecs_entity_t, entities->count); - return result; - } else { - return NULL; - } -} +const char* flecs_script_parse_lhs( + ecs_parser_t *parser, + const char *pos, + ecs_tokenizer_t *tokenizer, + ecs_token_kind_t left_oper, + ecs_expr_node_t **out); static -void table_diff_free( - ecs_table_diff_t *diff) +void flecs_script_parser_expr_free( + ecs_parser_t *parser, + ecs_expr_node_t *node) { - ecs_os_free(diff->added.array); - ecs_os_free(diff->removed.array); - ecs_os_free(diff->on_set.array); - ecs_os_free(diff->un_set.array); - ecs_os_free(diff); + flecs_expr_visit_free(&parser->script->pub, node); } static -ecs_graph_edge_t* graph_edge_new( - ecs_world_t *world) +bool flecs_has_precedence( + ecs_token_kind_t first, + ecs_token_kind_t second) { - ecs_graph_edge_t *result = (ecs_graph_edge_t*)world->store.first_free; - if (result) { - world->store.first_free = result->hdr.next; - ecs_os_zeromem(result); - } else { - result = ecs_os_calloc_t(ecs_graph_edge_t); + if (!flecs_expr_precedence[first]) { + return false; } - return result; + return flecs_expr_precedence[first] <= flecs_expr_precedence[second]; } static -void graph_edge_free( - ecs_world_t *world, - ecs_graph_edge_t *edge) +ecs_entity_t flecs_script_default_lookup( + const ecs_world_t *world, + const char *name, + void *ctx) { - if (world->is_fini) { - ecs_os_free(edge); - } else { - edge->hdr.next = world->store.first_free; - world->store.first_free = &edge->hdr; - } + (void)ctx; + return ecs_lookup(world, name); } static -ecs_graph_edge_t* ensure_hi_edge( - ecs_world_t *world, - ecs_graph_edges_t *edges, - ecs_id_t id) +const char* flecs_script_parse_match_elems( + ecs_parser_t *parser, + const char *pos, + ecs_expr_match_t *node) { - if (!ecs_map_is_initialized(&edges->hi)) { - ecs_map_init(&edges->hi, ecs_graph_edge_t*, 1); - } + ecs_allocator_t *a = &parser->script->allocator; + bool old_significant_newline = parser->significant_newline; + parser->significant_newline = true; - ecs_graph_edge_t **ep = ecs_map_ensure(&edges->hi, ecs_graph_edge_t*, id); - ecs_graph_edge_t *edge = ep[0]; - if (edge) { - return edge; - } + ecs_vec_init_t(NULL, &node->elements, ecs_expr_match_element_t, 0); - if (id < ECS_HI_COMPONENT_ID) { - edge = &edges->lo[id]; - } else { - edge = graph_edge_new(world); - } + do { + ParserBegin; - ep[0] = edge; - return edge; + LookAhead( + case '\n': { + pos = lookahead; + continue; + } + + case '}': { + /* Return last character of initializer */ + pos = lookahead - 1; + parser->significant_newline = old_significant_newline; + EndOfRule; + } + ) + + ecs_expr_match_element_t *elem = ecs_vec_append_t( + a, &node->elements, ecs_expr_match_element_t); + ecs_os_zeromem(elem); + + pos = flecs_script_parse_expr(parser, pos, 0, &elem->compare); + if (!pos) { + goto error; + } + + Parse_1(':', { + pos = flecs_script_parse_expr(parser, pos, 0, &elem->expr); + if (!pos) { + goto error; + } + + Parse( + case ';': + case '\n': { + break; + } + ) + + break; + }) + + } while (true); + + ParserEnd; } -static -ecs_graph_edge_t* ensure_edge( - ecs_world_t *world, - ecs_graph_edges_t *edges, - ecs_id_t id) +const char* flecs_script_parse_initializer( + ecs_parser_t *parser, + const char *pos, + char until, + ecs_expr_initializer_t **node_out) { - ecs_graph_edge_t *edge; - - if (id < ECS_HI_COMPONENT_ID) { - if (!edges->lo) { - edges->lo = ecs_os_calloc_n(ecs_graph_edge_t, ECS_HI_COMPONENT_ID); + bool first = true; + + ecs_expr_initializer_t *node = *node_out = flecs_expr_initializer(parser); + ecs_allocator_t *a = &parser->script->allocator; + + do { + ParserBegin; + + /* End of initializer */ + LookAhead( + case ')': + case '}': { + if ((char)lookahead_token.kind != until) { + Error("expected '%c'", until); + } + if (first) { + node->node.kind = EcsExprEmptyInitializer; + } + pos = lookahead - 1; + EndOfRule; + }) + + first = false; + + ecs_expr_initializer_element_t *elem = ecs_vec_append_t( + a, &node->elements, ecs_expr_initializer_element_t); + ecs_os_zeromem(elem); + + /* Parse member name */ + { + LookAhead_2(EcsTokIdentifier, ':', { + elem->member = Token(0); + LookAhead_Keep(); + pos = lookahead; + break; + }) } - edge = &edges->lo[id]; - } else { - if (!ecs_map_is_initialized(&edges->hi)) { - ecs_map_init(&edges->hi, ecs_graph_edge_t*, 1); + { + LookAhead_2(EcsTokIdentifier, EcsTokAddAssign, { + elem->member = Token(0); + elem->operator = EcsTokAddAssign; + LookAhead_Keep(); + pos = lookahead; + break; + }) + } + { + LookAhead_2(EcsTokIdentifier, EcsTokMulAssign, { + elem->member = Token(0); + elem->operator = EcsTokMulAssign; + LookAhead_Keep(); + pos = lookahead; + break; + }) } - edge = ensure_hi_edge(world, edges, id); - } - return edge; -} + pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); + if (!pos) { + goto error; + } -static -void disconnect_edge( - ecs_world_t *world, - ecs_id_t id, - ecs_graph_edge_t *edge) -{ - ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); - (void)id; + { + /* Parse next element or end of initializer*/ + LookAhead( + case ',': { + pos = lookahead; + break; + } - /* Remove backref from destination table */ - ecs_graph_edge_hdr_t *next = edge->hdr.next; - ecs_graph_edge_hdr_t *prev = edge->hdr.prev; + case ')': + case '}': + /* Return last character of initializer */ + pos = lookahead - 1; - if (next) { - next->prev = prev; - } - if (prev) { - prev->next = next; - } + case '\n': { + if ((char)lookahead_token.kind != until) { + Error("expected '%c'", until); + } + EndOfRule; + } - /* Remove data associated with edge */ - ecs_table_diff_t *diff = edge->diff; - if (diff && diff != &ecs_table_edge_is_component) { - table_diff_free(diff); - } + case '\0': + pos = lookahead; + EndOfRule; + ) + } + } while (true); - /* If edge id is low, clear it from fast lookup array */ - if (id < ECS_HI_COMPONENT_ID) { - ecs_os_memset_t(edge, 0, ecs_graph_edge_t); - } else { - graph_edge_free(world, edge); - } + ParserEnd; } static -void remove_edge( - ecs_world_t *world, - ecs_graph_edges_t *edges, - ecs_id_t id, - ecs_graph_edge_t *edge) +const char* flecs_script_parse_collection_initializer( + ecs_parser_t *parser, + const char *pos, + ecs_expr_initializer_t **node_out) { - ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_map_is_initialized(&edges->hi), ECS_INTERNAL_ERROR, NULL); - disconnect_edge(world, id, edge); - ecs_map_remove(&edges->hi, id); -} + bool first = true; + + ecs_expr_initializer_t *node = *node_out = flecs_expr_initializer(parser); + ecs_allocator_t *a = &parser->script->allocator; -static -void init_edges( - ecs_graph_edges_t *edges) -{ - edges->lo = NULL; - ecs_os_zeromem(&edges->hi); -} + do { + ParserBegin; -static -void init_node( - ecs_graph_node_t *node) -{ - init_edges(&node->add); - init_edges(&node->remove); -} + /* End of initializer */ + LookAhead_1(']', { + if (first) { + node->node.kind = EcsExprEmptyInitializer; + } + pos = lookahead - 1; + EndOfRule; + }) -typedef struct { - int32_t first; - int32_t count; -} id_first_count_t; + first = false; + + ecs_expr_initializer_element_t *elem = ecs_vec_append_t( + a, &node->elements, ecs_expr_initializer_element_t); + ecs_os_zeromem(elem); + + pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); + if (!pos) { + goto error; + } + + { + /* Parse next element or end of initializer */ + LookAhead( + case ',': { + pos = lookahead; + break; + } + case ']': { + EndOfRule; + } + ) + } + } while (true); + + ParserEnd; +} static -void set_trigger_flags_for_id( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id) +const char* flecs_script_parse_function( + ecs_parser_t *parser, + const char *pos, + ecs_expr_node_t **out) { - /* Set flags if triggers are registered for table */ - if (flecs_check_triggers_for_event(world, id, EcsOnAdd)) { - table->flags |= EcsTableHasOnAdd; - } - if (flecs_check_triggers_for_event(world, id, EcsOnRemove)) { - table->flags |= EcsTableHasOnRemove; + ecs_expr_function_t *result = flecs_expr_function(parser); + result->left = *out; + + pos = flecs_script_parse_initializer( + parser, pos, ')', &result->args); + if (!pos) { + goto error; } - if (flecs_check_triggers_for_event(world, id, EcsOnSet)) { - table->flags |= EcsTableHasOnSet; + + *out = (ecs_expr_node_t*)result; + + if (pos[0] != ')') { + Error("expected end of argument list"); } - if (flecs_check_triggers_for_event(world, id, EcsUnSet)) { - table->flags |= EcsTableHasUnSet; + + pos ++; + + return pos; +error: + if (result->args) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)result->args); } + flecs_free_t(&parser->script->allocator, ecs_expr_function_t, result); + return NULL; } static -void register_table_for_id( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - int32_t column, - int32_t count, - ecs_table_record_t *tr) +const char* flecs_script_parse_rhs( + ecs_parser_t *parser, + const char *pos, + ecs_tokenizer_t *tokenizer, + ecs_token_kind_t left_oper, + ecs_expr_node_t **out) { - flecs_register_for_id_record(world, id, table, tr); - tr->column = column; - tr->count = count; - tr->id = id; - set_trigger_flags_for_id(world, table, id); - ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); -} + const char *last_pos = pos; -static -void flecs_table_records_register( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - int32_t count = ecs_vector_count(table->type); + do { + TokenFramePush(); - if (!count) { - return; - } + last_pos = pos; - /* Count number of unique ids, pairs, relations and objects so we can figure - * out how many table records are needed for this table. */ - int32_t id_count = 0, pair_count = 0, type_flag_count = 0; - int32_t first_id = -1, first_pair = -1; - ecs_map_t relations = ECS_MAP_INIT(0), objects = ECS_MAP_INIT(0); - bool has_childof = false; + LookAhead( + case EcsTokNumber: + if (pos[0] == '-') { + lookahead = &pos[1]; + lookahead_token.kind = EcsTokSub; + } else { + Error("unexpected number"); + } + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBracketOpen: + case EcsTokMember: + { + ecs_token_kind_t oper = lookahead_token.kind; + + /* Only consume more tokens if operator has precedence */ + if (flecs_has_precedence(left_oper, oper)) { + break; + } + + /* Consume lookahead token */ + pos = lookahead; + + switch(oper) { + case EcsTokBracketOpen: { + ecs_expr_element_t *result = flecs_expr_element(parser); + result->left = *out; + + *out = (ecs_expr_node_t*)result; + + pos = flecs_script_parse_lhs( + parser, pos, tokenizer, 0, &result->index); + if (!pos) { + goto error; + } + + Parse_1(']', { + break; + }); + + break; + } + + case EcsTokMember: { + Parse( + case EcsTokFunction: + case EcsTokIdentifier: { + ecs_expr_member_t *result = flecs_expr_member(parser); + result->left = *out; + result->member_name = Token(1); + *out = (ecs_expr_node_t*)result; + + if(t->kind == EcsTokFunction) { + pos = flecs_script_parse_function(parser, pos, out); + if (!pos) { + goto error; + } + } + + break; + } + ); + + break; + } + + default: { + ecs_expr_binary_t *result = flecs_expr_binary(parser); + result->left = *out; + result->operator = oper; + + *out = (ecs_expr_node_t*)result; + + pos = flecs_script_parse_lhs(parser, pos, tokenizer, + result->operator, &result->right); + if (!pos) { + goto error; + } + + break; + } + }; + + /* Ensures lookahead tokens in token buffer don't get overwritten */ + parser->token_keep = parser->token_cur; + break; + } + ) - int32_t i; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - ecs_entity_t rel = 0, obj = 0; + TokenFramePop(); + } while (pos != last_pos); - if (ECS_HAS_ROLE(id, PAIR)) { - id_first_count_t *r; + return pos; +error: + return NULL; +} - rel = ECS_PAIR_FIRST(id); - obj = ECS_PAIR_SECOND(id); +static +const char* flecs_script_parse_lhs( + ecs_parser_t *parser, + const char *pos, + ecs_tokenizer_t *tokenizer, + ecs_token_kind_t left_oper, + ecs_expr_node_t **out) +{ + TokenFramePush(); - if (0 == pair_count ++) { - first_pair = i; - } + bool can_have_rhs = true; - if (rel == EcsChildOf) { - has_childof = true; + Parse( + case EcsTokNumber: { + const char *expr = Token(0); + int base = 10; + bool is_negative = false; + const char *base_expr = expr; + if (base_expr[0] == '-') { + is_negative = true; + base_expr ++; } - if (!ecs_map_is_initialized(&relations)) { - ecs_map_init(&relations, id_first_count_t, count); - ecs_map_init(&objects, id_first_count_t, count); + if (base_expr[0] == '0' && (base_expr[1] == 'x' || base_expr[1] == 'X')) { + base = 16; + } else if (base_expr[0] == '0' && (base_expr[1] == 'b' || base_expr[1] == 'B')) { + base = 2; } - r = ecs_map_ensure(&relations, id_first_count_t, rel); - if ((++r->count) == 1) { - r->first = i; + if (base == 10 && + (strchr(expr, '.') || strchr(expr, 'e') || strchr(expr, 'E'))) + { + *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); + break; } - r = ecs_map_ensure(&objects, id_first_count_t, obj); - if ((++r->count) == 1) { - r->first = i; - } - } else { - rel = id & ECS_COMPONENT_MASK; - if (rel != id) { - type_flag_count ++; - } + if (base == 2) { + const char *bin_expr = base_expr + 2; + char *end; + uint64_t v = strtoull(bin_expr, &end, base); - if (0 == id_count ++) { - first_id = i; + if (is_negative) { + *out = (ecs_expr_node_t*)flecs_expr_int(parser, -(int64_t)v); + } else { + *out = (ecs_expr_node_t*)flecs_expr_uint(parser, v); + } + } else if (is_negative) { + char *end; + *out = (ecs_expr_node_t*)flecs_expr_int(parser, + strtoll(expr, &end, base)); + } else { + char *end; + *out = (ecs_expr_node_t*)flecs_expr_uint(parser, + strtoull(expr, &end, base)); } + break; } - } - - int32_t record_count = count + type_flag_count + (id_count != 0) + - (pair_count != 0) + ecs_map_count(&relations) + ecs_map_count(&objects) - + 1 /* for any */; - - if (!has_childof) { - record_count ++; - } - table->records = ecs_os_calloc_n(ecs_table_record_t, record_count); - table->record_count = flecs_ito(uint16_t, record_count); + case EcsTokChar: { + *out = (ecs_expr_node_t*)flecs_expr_char(parser, Token(0)); + break; + } - /* First initialize records for regular (non-wildcard) ids. Make sure that - * these table records line up with ids in table type. */ - int32_t first_role_id = -1; - for (i = 0; i < count; i ++) { - register_table_for_id(world, table, ids[i], i, 1, &table->records[i]); - if (first_role_id == -1) { - ecs_entity_t role = ids[i] & ECS_ROLE_MASK; - if (role && role != ECS_PAIR) { - first_role_id = i; + case EcsTokString: { + if (flecs_string_is_interpolated(Token(0))) { + *out = (ecs_expr_node_t*)flecs_expr_interpolated_string( + parser, Token(0)); + } else { + *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); } + break; } - } - /* Initialize records for ids with roles */ - int32_t r = i; - if (first_role_id != -1) { - for (i = first_role_id; i < count; i ++) { - ecs_id_t id = ids[i]; - ecs_entity_t role = id & ECS_ROLE_MASK; - if (role && role != ECS_PAIR) { - id &= ECS_COMPONENT_MASK; - id = ecs_pair(id, EcsWildcard); - register_table_for_id(world, table, id, i, 1, &table->records[r]); - r ++; - - /* Keep track of how many switch/bitset columns there are */ - if (role == ECS_SWITCH) { - if (!table->sw_column_count) { - table->sw_column_offset = flecs_ito(int16_t, i); - } - table->sw_column_count ++; - } else if (role == ECS_DISABLED) { - if (!table->bs_column_count) { - table->bs_column_offset = flecs_ito(int16_t, i); - } - table->bs_column_count ++; + case EcsTokIdentifier: + case EcsTokFunction: { + const char *expr = Token(0); + if (expr[0] == '$') { + *out = (ecs_expr_node_t*)flecs_expr_variable(parser, &expr[1]); + } else if (!ecs_os_strcmp(expr, "true")) { + *out = (ecs_expr_node_t*)flecs_expr_bool(parser, true); + } else if (!ecs_os_strcmp(expr, "false")) { + *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); + } else { + char *last_elem = strrchr(expr, '.'); + if (last_elem && last_elem[1] == '$') { + /* Scoped global variable */ + ecs_expr_variable_t *v = flecs_expr_variable(parser, expr); + ecs_os_memmove(&last_elem[1], &last_elem[2], + ecs_os_strlen(&last_elem[2]) + 1); + v->node.kind = EcsExprGlobalVariable; + *out = (ecs_expr_node_t*)v; + } else { + /* Entity identifier */ + *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); + } + } + + if(t->kind == EcsTokFunction) { + pos = flecs_script_parse_function(parser, pos, out); + if (!pos) { + goto error; } } + + break; } - } - /* Initialize records for relation wildcards */ - ecs_map_iter_t mit = ecs_map_iter(&relations); - id_first_count_t *elem; - uint64_t key; - while ((elem = ecs_map_next(&mit, id_first_count_t, &key))) { - ecs_id_t id = ecs_pair(key, EcsWildcard); - register_table_for_id(world, table, id, elem->first, elem->count, - &table->records[r]); - r ++; - } + case EcsTokNot: { + ecs_expr_unary_t *node = flecs_expr_unary(parser); + pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->expr); + if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); + goto error; + } - /* Initialize records for object wildcards */ - mit = ecs_map_iter(&objects); - while ((elem = ecs_map_next(&mit, id_first_count_t, &key))) { - ecs_id_t id = ecs_pair(EcsWildcard, key); - register_table_for_id(world, table, id, elem->first, elem->count, - &table->records[r]); - r ++; - } + node->operator = EcsTokNot; + *out = (ecs_expr_node_t*)node; + break; + } - /* Initialize records for all wildcards ids */ - if (id_count) { - register_table_for_id(world, table, EcsWildcard, - first_id, id_count, &table->records[r]); - r ++; - } - if (pair_count) { - register_table_for_id(world, table, ecs_pair(EcsWildcard, EcsWildcard), - first_pair, pair_count, &table->records[r]); - r ++; - } - if (count) { - register_table_for_id(world, table, EcsAny, 0, 1, &table->records[r]); - r ++; - } + case EcsTokSub: { + ecs_expr_binary_t *node = flecs_expr_binary(parser); + + /* Use EcsTokNot as it has the same precedence as a unary - */ + pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->right); + if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); + goto error; + } - /* Insert into (ChildOf, 0) (root) if table doesn't have childof */ - if (!has_childof && count) { - register_table_for_id(world, table, ecs_pair(EcsChildOf, 0), - 0, 1, &table->records[r]); - } + node->left = (ecs_expr_node_t*)flecs_expr_int(parser, -1); + node->operator = EcsTokMul; + *out = (ecs_expr_node_t*)node; + break; + } - ecs_map_fini(&relations); - ecs_map_fini(&objects); -} + case EcsTokKeywordMatch: { + ecs_expr_match_t *node = flecs_expr_match(parser); + pos = flecs_script_parse_expr(parser, pos, 0, &node->expr); + if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); + goto error; + } -void flecs_table_records_unregister( - ecs_world_t *world, - ecs_table_t *table) -{ - int32_t i, count = table->record_count; - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &table->records[i]; - ecs_table_cache_t *cache = tr->hdr.cache; - ecs_id_t id = tr->id; + Parse_1('{', { + pos = flecs_script_parse_match_elems(parser, pos, node); + if (!pos) { + flecs_script_parser_expr_free( + parser, (ecs_expr_node_t*)node); + goto error; + } - ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_get_id_record(world, id) == (ecs_id_record_t*)cache, - ECS_INTERNAL_ERROR, NULL); + Parse_1('}', { + *out = (ecs_expr_node_t*)node; + break; + }) - ecs_table_cache_remove(cache, table, &tr->hdr); + break; + }) + + can_have_rhs = false; - if (ecs_table_cache_is_empty(cache)) { - ecs_id_record_t *idr = (ecs_id_record_t*)cache; - flecs_remove_id_record(world, id, idr); + break; } - } - - ecs_os_free(table->records); -} -bool flecs_table_records_update_empty( - ecs_table_t *table) -{ - bool result = false; - bool is_empty = ecs_table_count(table) == 0; + case EcsTokKeywordNew: { + ecs_expr_new_t *node = flecs_expr_new(parser); + *out = (ecs_expr_node_t*)node; - int32_t i, count = table->record_count; - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &table->records[i]; - ecs_table_cache_t *cache = tr->hdr.cache; - result |= ecs_table_cache_set_empty(cache, table, is_empty); - } + int32_t stmt_count = ecs_vec_count(&parser->scope->stmts); - return result; -} + bool old_function_token = parser->function_token; + parser->function_token = false; + pos = flecs_script_stmt(parser, pos); + if (!pos) { + goto error; + } + parser->function_token = old_function_token; -static -void init_flags( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_id_t *ids = ecs_vector_first(table->type, ecs_id_t); - int32_t count = ecs_vector_count(table->type); + pos = flecs_parse_ws_eol(pos); - /* Iterate components to initialize table flags */ - int32_t i; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; + if ((ecs_vec_count(&parser->scope->stmts) - stmt_count) != 1) { + Error("expected entity statement after new"); + } - /* As we're iterating over the table components, also set the table - * flags. These allow us to quickly determine if the table contains - * data that needs to be handled in a special way, like prefabs or - * containers */ - if (id <= EcsLastInternalComponentId) { - table->flags |= EcsTableHasBuiltins; - } + ecs_script_node_t **script_node_ptr = ecs_vec_last_t( + &parser->scope->stmts, ecs_script_node_t*); + if (script_node_ptr == NULL) { + Error("expected entity statement after new"); + } - if (id == EcsModule) { - table->flags |= EcsTableHasBuiltins; - table->flags |= EcsTableHasModule; - } + ecs_script_node_t *script_node = *script_node_ptr; + ecs_assert(script_node != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_remove_last(&parser->scope->stmts); - if (id == EcsPrefab) { - table->flags |= EcsTableIsPrefab; - } + if (script_node->kind != EcsAstEntity) { + Error("expected entity statement after new"); + } - /* If table contains disabled entities, mark it as disabled */ - if (id == EcsDisabled) { - table->flags |= EcsTableIsDisabled; - } + node->entity = (ecs_script_entity_t*)script_node; - /* Does the table have pairs */ - if (ECS_HAS_ROLE(id, PAIR)) { - table->flags |= EcsTableHasPairs; - } + can_have_rhs = false; - /* Does table have IsA relations */ - if (ECS_HAS_RELATION(id, EcsIsA)) { - table->flags |= EcsTableHasIsA; + break; } - /* Does table have ChildOf relations */ - if (ECS_HAS_RELATION(id, EcsChildOf)) { - table->flags |= EcsTableHasChildOf; - } + case '(': { + pos = flecs_script_parse_expr(parser, pos, 0, out); + if (!pos) { + goto error; + } - /* Does table have switch columns */ - if (ECS_HAS_ROLE(id, SWITCH)) { - table->flags |= EcsTableHasSwitch; - } + Parse_1(')', { + break; + }) - /* Does table support component disabling */ - if (ECS_HAS_ROLE(id, DISABLED)) { - table->flags |= EcsTableHasDisabled; + break; } - if (ECS_HAS_RELATION(id, EcsChildOf)) { - ecs_poly_assert(world, ecs_world_t); - ecs_entity_t obj = ecs_pair_second(world, id); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - - if (obj == EcsFlecs || obj == EcsFlecsCore || - ecs_has_id(world, obj, EcsModule)) - { - /* If table contains entities that are inside one of the builtin - * modules, it contains builtin entities */ - table->flags |= EcsTableHasBuiltins; - table->flags |= EcsTableHasModule; + case '{': { + ecs_expr_initializer_t *node = NULL; + pos = flecs_script_parse_initializer(parser, pos, '}', &node); + if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); + goto error; } - } - } -} -static -void init_table( - ecs_world_t *world, - ecs_table_t *table) -{ - table->type_info = NULL; - table->flags = 0; - table->dirty_state = NULL; - table->alloc_count = 0; - table->lock = 0; - table->refcount = 1; - table->generation = 0; + *out = (ecs_expr_node_t*)node; + + Parse_1('}', { + break; + }) - /* Ensure the component ids for the table exist */ - ensure_columns(world, table); + can_have_rhs = false; + break; + } - init_node(&table->node); - init_flags(world, table); - flecs_table_records_register(world, table); - flecs_table_init_data(world, table); -} + case '[': { + ecs_expr_initializer_t *node = NULL; + pos = flecs_script_parse_collection_initializer(parser, pos, &node); + if (!pos) { + flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); + goto error; + } -static -ecs_table_t *create_table( - ecs_world_t *world, - ecs_vector_t *type, - flecs_hashmap_result_t table_elem) -{ - ecs_table_t *result = flecs_sparse_add(&world->store.tables, ecs_table_t); - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); + node->is_collection = true; + *out = (ecs_expr_node_t*)node; - ecs_vector_reclaim(&type, ecs_id_t); + Parse_1(']', { + break; + }) - result->id = flecs_sparse_last_id(&world->store.tables); - result->type = type; + can_have_rhs = false; + break; + } + ) - init_table(world, result); + TokenFramePop(); - if (ecs_should_log_2()) { - char *expr = ecs_type_str(world, result->type); - ecs_dbg_2( - "#[green]table#[normal] [%s] #[green]created#[normal] with id %d", - expr, result->id); - ecs_os_free(expr); + /* Return if this was end of expression, or if the parsed expression cannot + * have a right hand side. */ + if (!pos[0] || !can_have_rhs) { + return pos; } - ecs_log_push_2(); + /* Parse right-hand side of expression if there is one */ + return flecs_script_parse_rhs(parser, pos, tokenizer, left_oper, out); +error: + return NULL; +} - /* Store table in table hashmap */ - *(ecs_table_t**)table_elem.value = result; +const char* flecs_script_parse_expr( + ecs_parser_t *parser, + const char *pos, + ecs_token_kind_t left_oper, + ecs_expr_node_t **out) +{ + if (parser->expr_depth >= ECS_PARSER_MAX_RECURSION_DEPTH) { + ecs_parser_error(parser->name, parser->code, pos - parser->code, + "maximum expression nesting depth exceeded"); + return NULL; + } - /* Set keyvalue to one that has the same lifecycle as the table */ - ecs_ids_t key = { - .array = ecs_vector_first(result->type, ecs_id_t), - .count = ecs_vector_count(result->type) - }; - *(ecs_ids_t*)table_elem.key = key; + parser->expr_depth ++; - flecs_notify_queries(world, &(ecs_query_event_t) { - .kind = EcsQueryTableMatch, - .table = result - }); + ecs_tokenizer_t _tokenizer; + ecs_os_zeromem(&_tokenizer); + _tokenizer.tokens = _tokenizer.stack.tokens; + ecs_tokenizer_t *tokenizer = &_tokenizer; - /* Update counters */ - world->info.table_count ++; - world->info.table_record_count += result->record_count; - world->info.table_storage_count += result->storage_count; - world->info.empty_table_count ++; - world->info.table_create_total ++; - - if (!result->storage_count) { - world->info.tag_table_count ++; - } else { - world->info.trivial_table_count += !(result->flags & EcsTableIsComplex); + bool old_function_token = parser->function_token; + parser->function_token = true; + pos = flecs_script_parse_lhs(parser, pos, tokenizer, left_oper, out); + parser->function_token = old_function_token; + if (!pos) { + parser->expr_depth --; + if (out && *out) { + flecs_script_parser_expr_free(parser, *out); + *out = NULL; + } + return NULL; } - ecs_log_pop_2(); - - return result; + parser->expr_depth --; + return pos; } -static -ecs_table_t* find_or_create( +ecs_script_t* ecs_expr_parse( ecs_world_t *world, - const ecs_ids_t *ids, - ecs_vector_t *type) -{ - ecs_poly_assert(world, ecs_world_t); - - /* Make sure array is ordered and does not contain duplicates */ - int32_t id_count = ids->count; - - if (!id_count) { - return &world->store.root; - } - - ecs_table_t *table; - flecs_hashmap_result_t elem = flecs_hashmap_ensure( - &world->store.table_map, ids, ecs_table_t*); - if ((table = *(ecs_table_t**)elem.value)) { - if (type) { - ecs_vector_free(type); - } - return table; + const char *expr, + const ecs_expr_eval_desc_t *desc) +{ + ecs_expr_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; } - if (!type) { - type = ids_to_vector(ids); + if (!priv_desc.lookup_action) { + priv_desc.lookup_action = flecs_script_default_lookup; } - /* If we get here, table needs to be created which is only allowed when the - * application is not currently in progress */ - ecs_assert(!world->is_readonly, ECS_INTERNAL_ERROR, NULL); - - /* If we get here, the table has not been found, so create it. */ - return create_table(world, type, elem); -} + ecs_script_t *script = flecs_script_new(world); + ecs_script_impl_t *impl = flecs_script_impl(script); -static -void add_id_to_ids( - ecs_vector_t **idv, - ecs_entity_t add, - ecs_entity_t r_exclusive) -{ - int32_t i, count = ecs_vector_count(idv[0]); - ecs_id_t *array = ecs_vector_first(idv[0], ecs_id_t); + ecs_parser_t parser = { + .name = script->name, + .code = script->code, + .script = impl, + .scope = impl->root, + .significant_newline = false, + .function_token = true + }; - for (i = 0; i < count; i ++) { - ecs_id_t e = array[i]; + impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; + impl->token_buffer = flecs_alloc_w_dbg_info( + &impl->allocator, impl->token_buffer_size, "token buffer"); + parser.token_cur = impl->token_buffer; - if (e == add) { - return; - } + const char *ptr = flecs_script_parse_expr(&parser, expr, 0, &impl->expr); + if (!ptr) { + goto error; + } - if (r_exclusive && ECS_HAS_ROLE(e, PAIR)) { - if (ECS_PAIR_FIRST(e) == r_exclusive) { - array[i] = add; /* Replace */ - return; - } - } + impl->next_token = ptr; + impl->token_remaining = parser.token_cur; - if (e >= add) { - if (e != add) { - ecs_id_t *ptr = ecs_vector_insert_at(idv, ecs_id_t, i); - ptr[0] = add; - return; - } - } + if (flecs_expr_visit_type(script, impl->expr, &priv_desc)) { + goto error; } - ecs_id_t *ptr = ecs_vector_add(idv, ecs_id_t); - ptr[0] = add; -} - -static -void remove_id_from_ids( - ecs_type_t type, - ecs_id_t remove, - ecs_ids_t *out) -{ - int32_t count = ecs_vector_count(type); - ecs_id_t *array = ecs_vector_first(type, ecs_id_t); - int32_t i, el = 0; + // printf("%s\n", ecs_script_ast_to_str(script, true)); - if (ecs_id_is_wildcard(remove)) { - for (i = 0; i < count; i ++) { - ecs_id_t id = array[i]; - if (!ecs_id_match(id, remove)) { - out->array[el ++] = id; - ecs_assert(el <= count, ECS_INTERNAL_ERROR, NULL); - } - } - } else { - for (i = 0; i < count; i ++) { - ecs_id_t id = array[i]; - if (id != remove) { - out->array[el ++] = id; - ecs_assert(el <= count, ECS_INTERNAL_ERROR, NULL); - } + if (!desc || !desc->disable_folding) { + if (flecs_expr_visit_fold(script, &impl->expr, &priv_desc)) { + goto error; } } - out->count = el; + // printf("%s\n", ecs_script_ast_to_str(script, true)); + + return script; +error: + ecs_script_free(script); + return NULL; } -int32_t flecs_table_switch_from_case( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_entity_t add) +int ecs_expr_eval( + const ecs_script_t *script, + ecs_value_t *value, + const ecs_expr_eval_desc_t *desc) { - ecs_type_t type = table->type; - ecs_entity_t *array = ecs_vector_first(type, ecs_entity_t); + ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_impl_t *impl = flecs_script_impl( + /* Safe, won't be writing to script */ + ECS_CONST_CAST(ecs_script_t*, script)); + ecs_assert(impl->expr != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t i, count = table->sw_column_count; - if (!count) { - return -1; + ecs_expr_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; } - add = add & ECS_COMPONENT_MASK; - - ecs_sw_column_t *sw_columns = NULL; - - if ((sw_columns = table->storage.sw_columns)) { - /* Fast path, we can get the switch type from the column data */ - for (i = 0; i < count; i ++) { - ecs_table_t *sw_type = sw_columns[i].type; - if (ecs_search(world, sw_type, add, 0) != -1) { - return i; - } - } - } else { - /* Slow path, table is empty, so we'll have to get the switch types by - * actually inspecting the switch type entities. */ - for (i = 0; i < count; i ++) { - ecs_entity_t e = array[i + table->sw_column_offset]; - ecs_assert(ECS_HAS_ROLE(e, SWITCH), ECS_INTERNAL_ERROR, NULL); - e = e & ECS_COMPONENT_MASK; - - const EcsType *type_ptr = ecs_get(world, e, EcsType); - ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - - if (ecs_search(world, type_ptr->normalized, add, 0) != -1) { - return i; - } - } + if (!priv_desc.lookup_action) { + priv_desc.lookup_action = flecs_script_default_lookup; } - /* If a table was not found, this is an invalid switch case */ - ecs_abort(ECS_TYPE_INVALID_CASE, NULL); + if (flecs_expr_visit_eval(script, impl->expr, &priv_desc, value)) { + goto error; + } + return 0; +error: return -1; } -static -void ids_append( - ecs_ids_t *ids, - ecs_id_t id) -{ - ids->array = ecs_os_realloc_n(ids->array, ecs_id_t, ids->count + 1); - ids->array[ids->count ++] = id; -} - -static -void diff_insert_isa( +const char* ecs_expr_run( ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *base_diff, - ecs_ids_t *append_to, - ecs_ids_t *append_from, - ecs_id_t add) + const char *expr, + ecs_value_t *value, + const ecs_expr_eval_desc_t *desc) { - ecs_entity_t base = ecs_pair_second(world, add); - ecs_table_t *base_table = ecs_get_table(world, base); - if (!base_table) { - return; + ecs_expr_eval_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.type) { + priv_desc.type = value->type; + } else if (desc && (value->type != desc->type)) { + ecs_throw(ECS_INVALID_PARAMETER, + "type of value parameter does not match desc->type"); } - ecs_type_t base_type = base_table->type, type = table->type; - ecs_table_t *table_wo_base = base_table; + ecs_script_t *s = ecs_expr_parse(world, expr, &priv_desc); + if (!s) { + goto error; + } - /* If the table does not have a component from the base, it should - * trigger an OnSet */ - ecs_id_t *ids = ecs_vector_first(base_type, ecs_id_t); - int32_t j, i, count = ecs_vector_count(base_type); - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; + if (ecs_expr_eval(s, value, &priv_desc)) { + ecs_script_free(s); + goto error; + } - if (ECS_HAS_RELATION(id, EcsIsA)) { - /* The base has an IsA relation. Find table without the base, which - * gives us the list of ids the current base inherits and doesn't - * override. This saves us from having to recursively check for each - * base in the hierarchy whether the component is overridden. */ - table_wo_base = flecs_table_traverse_remove( - world, table_wo_base, &id, base_diff); + const char *result = flecs_script_impl(s)->next_token; - /* Because we removed, the ids are stored in un_set vs. on_set */ - for (j = 0; j < append_from->count; j ++) { - ecs_id_t base_id = append_from->array[j]; - /* We still have to make sure the id isn't overridden by the - * current table */ - if (!type || ecs_search(world, table, base_id, NULL) == -1) { - ids_append(append_to, base_id); - } - } + ecs_script_free(s); - continue; - } + return result; +error: + return NULL; +} - /* Identifiers are not inherited */ - if (ECS_HAS_RELATION(id, ecs_id(EcsIdentifier))) { - continue; +char* ecs_script_string_interpolate( + ecs_world_t *world, + const char *str, + const ecs_script_vars_t *vars) +{ + if (!flecs_string_is_interpolated(str)) { + char *result = ecs_os_strdup(str); + if (!flecs_string_escape(result)) { + ecs_os_free(result); + return NULL; } + return result; + } - if (!ecs_get_typeid(world, id)) { - continue; - } + char *expr = flecs_asprintf("\"%s\"", str); - if (!type || ecs_search(world, table, id, NULL) == -1) { - ids_append(append_to, id); - } + ecs_expr_eval_desc_t desc = { .vars = vars }; + char *r = NULL; + if (!ecs_expr_run(world, expr, &ecs_value_ptr(ecs_string_t, &r), &desc)) { + ecs_os_free(expr); + return NULL; } -} -static -void diff_insert_added_isa( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) -{ - ecs_table_diff_t base_diff; - diff_insert_isa(world, table, &base_diff, &diff->on_set, - &base_diff.un_set, id); -} + ecs_os_free(expr); -static -void diff_insert_removed_isa( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) -{ - ecs_table_diff_t base_diff; - diff_insert_isa(world, table, &base_diff, &diff->un_set, - &base_diff.un_set, id); + return r; } +#endif + +/** + * @file addons/script/expr/stack.c + * @brief Script expression stack implementation. + */ + + +#ifdef FLECS_SCRIPT + static -void diff_insert_added( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) +void flecs_expr_value_alloc( + ecs_expr_stack_t *stack, + ecs_expr_value_t *v, + const ecs_type_info_t *ti) { - diff->added.array[diff->added.count ++] = id; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v->type_info == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + v->type_info = ti; + v->value.type = ti->component; + v->value.ptr = flecs_stack_alloc(&stack->stack, ti->size, ti->alignment); - if (ECS_HAS_RELATION(id, EcsIsA)) { - diff_insert_added_isa(world, table, diff, id); + if (ti->hooks.ctor) { + ti->hooks.ctor(v->value.ptr, 1, ti); } } static -void diff_insert_removed( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *diff, - ecs_id_t id) +void flecs_expr_value_free( + ecs_expr_value_t *v) { - diff->removed.array[diff->removed.count ++] = id; + const ecs_type_info_t *ti = v->type_info; + // printf("stack free %p (%s) owned = %d\n", v->value.ptr, + // v->type_info->name, v->owned); + v->type_info = NULL; - if (ECS_HAS_RELATION(id, EcsIsA)) { - /* Removing an IsA relation also "removes" all components from the - * instance. Any id from base that's not overridden should be UnSet. */ - diff_insert_removed_isa(world, table, diff, id); - return; + if (!v->owned) { + return; /* Runtime doesn't own value, don't destruct */ } - if (table->flags & EcsTableHasIsA) { - if (!ecs_get_typeid(world, id)) { - /* Do nothing if id is not a component */ - return; - } - - /* If next table has a base and component is removed, check if - * the removed component was an override. Removed overrides reexpose the - * base component, thus "changing" the value which requires an OnSet. */ - if (ecs_search_relation(world, table, 0, id, EcsIsA, - 1, -1, 0, 0, 0, 0) != -1) - { - ids_append(&diff->on_set, id); - return; - } + if (ti && ti->hooks.dtor) { + ecs_assert(v->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ti->hooks.dtor(v->value.ptr, 1, ti); + flecs_stack_free(v->value.ptr, ti->size); } - if (ecs_get_typeid(world, id) != 0) { - ids_append(&diff->un_set, id); - } + v->value.ptr = NULL; } -static -void compute_table_diff( - ecs_world_t *world, - ecs_table_t *node, - ecs_table_t *next, - ecs_graph_edge_t *edge, - ecs_id_t id) +void flecs_expr_stack_init( + ecs_expr_stack_t *stack) { - if (node == next) { - return; - } - - ecs_type_t node_type = node->type; - ecs_type_t next_type = next->type; - - ecs_id_t *ids_node = ecs_vector_first(node_type, ecs_id_t); - ecs_id_t *ids_next = ecs_vector_first(next_type, ecs_id_t); - int32_t i_node = 0, node_count = ecs_vector_count(node_type); - int32_t i_next = 0, next_count = ecs_vector_count(next_type); - int32_t added_count = 0; - int32_t removed_count = 0; - bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA) && - !(node->flags & EcsTableHasIsA) && !(next->flags & EcsTableHasIsA); + ecs_os_zeromem(stack); + flecs_stack_init(&stack->stack); +} - /* First do a scan to see how big the diff is, so we don't have to realloc - * or alloc more memory than required. */ - for (; i_node < node_count && i_next < next_count; ) { - ecs_id_t id_node = ids_node[i_node]; - ecs_id_t id_next = ids_next[i_next]; +void flecs_expr_stack_fini( + ecs_expr_stack_t *stack) +{ + ecs_assert(stack->frame == 0, ECS_INTERNAL_ERROR, NULL); + flecs_stack_fini(&stack->stack); +} - bool added = id_next < id_node; - bool removed = id_node < id_next; +ecs_expr_value_t* flecs_expr_stack_result( + ecs_expr_stack_t *stack, + ecs_expr_node_t *node) +{ + ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_expr_stack_alloc(stack, node->type_info); +} - trivial_edge &= !added || id_next == id; - trivial_edge &= !removed || id_node == id; +ecs_expr_value_t* flecs_expr_stack_alloc( + ecs_expr_stack_t *stack, + const ecs_type_info_t *ti) +{ + ecs_assert(stack->frame > 0, ECS_INTERNAL_ERROR, NULL); - added_count += added; - removed_count += removed; + int32_t sp = stack->frames[stack->frame - 1].sp ++; + ecs_assert(sp < FLECS_EXPR_STACK_MAX, ECS_OUT_OF_RANGE, + "expression nesting is too deep"); + ecs_expr_value_t *v = &stack->values[sp]; - i_node += id_node <= id_next; - i_next += id_next <= id_node; + if (ti) { + flecs_expr_value_alloc(stack, v, ti); } - added_count += next_count - i_next; - removed_count += node_count - i_node; - - trivial_edge &= (added_count + removed_count) <= 1 && - !ecs_id_is_wildcard(id); + return v; +} - if (trivial_edge) { - /* If edge is trivial there's no need to create a diff element for it. - * Store whether the id is a tag or not, so that we can still tell - * whether an UnSet handler should be called or not. */ - if (node->storage_table != next->storage_table) { - edge->diff = &ecs_table_edge_is_component; - } - return; +void flecs_expr_stack_push( + ecs_expr_stack_t *stack) +{ + int32_t frame = stack->frame ++; + ecs_assert(frame < FLECS_EXPR_STACK_MAX, ECS_OUT_OF_RANGE, + "expression nesting is too deep"); + stack->frames[frame].cur = flecs_stack_get_cursor(&stack->stack); + if (frame) { + stack->frames[frame].sp = stack->frames[frame - 1].sp; + } else { + stack->frames[frame].sp = 0; } +} - ecs_table_diff_t *diff = ecs_os_calloc_t(ecs_table_diff_t); - edge->diff = diff; - if (added_count) { - diff->added.array = ecs_os_malloc_n(ecs_id_t, added_count); - diff->added.count = 0; - diff->added.size = added_count; +void flecs_expr_stack_pop( + ecs_expr_stack_t *stack) +{ + int32_t frame = -- stack->frame; + ecs_assert(frame >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t sp, start = 0, end = stack->frames[frame].sp; + if (frame) { + start = stack->frames[frame - 1].sp; } - if (removed_count) { - diff->removed.array = ecs_os_malloc_n(ecs_id_t, removed_count); - diff->removed.count = 0; - diff->removed.size = removed_count; + + for (sp = end - 1; sp >= start; sp --) { + flecs_expr_value_free(&stack->values[sp]); } - for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { - ecs_id_t id_node = ids_node[i_node]; - ecs_id_t id_next = ids_next[i_next]; + flecs_stack_restore_cursor(&stack->stack, stack->frames[frame].cur); +} - if (id_next < id_node) { - diff_insert_added(world, node, diff, id_next); - } else if (id_node < id_next) { - diff_insert_removed(world, next, diff, id_node); - } +#endif - i_node += id_node <= id_next; - i_next += id_next <= id_node; - } +/** + * @file addons/script/expr/parser.c * brief Scriptexpoutsion parser. + */ - for (; i_next < next_count; i_next ++) { - diff_insert_added(world, node, diff, ids_next[i_next]); - } - for (; i_node < node_count; i_node ++) { - diff_insert_removed(world, next, diff, ids_node[i_node]); - } - ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); -} +#ifdef FLECS_SCRIPT -static -void add_with_ids_to_ids( +int flecs_value_copy_to( ecs_world_t *world, - ecs_vector_t **idv, - ecs_entity_t r, - ecs_entity_t o) + ecs_value_t *dst, + const ecs_expr_value_t *src) { - /* Check if component/relation has With pairs, which contain ids - * that need to be added to the table. */ - ecs_table_t *id_table = ecs_get_table(world, r); - if (!id_table) { - return; - } - - ecs_table_record_t *tr = flecs_get_table_record(world, id_table, - ecs_pair(EcsWith, EcsWildcard)); - if (tr) { - int32_t i, with_count = tr->count; - int32_t start = tr->column; - int32_t end = start + with_count; - ecs_id_t *id_ids = ecs_vector_first(id_table->type, ecs_id_t); - - for (i = start; i < end; i ++) { - ecs_assert(ECS_PAIR_FIRST(id_ids[i]) == EcsWith, - ECS_INTERNAL_ERROR, NULL); - ecs_id_t id_r = ECS_PAIR_SECOND(id_ids[i]); - ecs_id_t id = id_r; - if (o) { - id = ecs_pair(id_r, o); - } - - /* Always make sure vector has room for one more */ - add_id_to_ids(idv, id, 0); + ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->value.type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->value.ptr != 0, ECS_INTERNAL_ERROR, NULL); - /* Add recursively in case id also has With pairs */ - add_with_ids_to_ids(world, idv, id_r, o); + if (src->value.type == dst->type) { + ecs_assert(src->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_value_copy_w_type_info( + world, src->type_info, dst->ptr, src->value.ptr); + } else { + /* Cast value to desired output type */ + ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); + if (ecs_meta_set_value(&cur, &src->value)) { + goto error; } } + + return 0; +error: + return -1; } -static -ecs_table_t* find_or_create_table_with_id( +int flecs_value_move_to( ecs_world_t *world, - ecs_table_t *node, - ecs_entity_t id) + ecs_value_t *dst, + ecs_value_t *src) { - /* If table has one or more switches and this is a case, return self */ - if (ECS_HAS_ROLE(id, CASE)) { - ecs_assert((node->flags & EcsTableHasSwitch) != 0, - ECS_TYPE_INVALID_CASE, NULL); - return node; - } else { - ecs_type_t type = node->type; - ecs_entity_t r_exclusive = 0; - ecs_entity_t r = 0, o = 0, re = 0; - - if (ECS_HAS_ROLE(id, PAIR)) { - r = ECS_PAIR_FIRST(id); - o = ECS_PAIR_SECOND(id); - re = ecs_get_alive(world, r); - if (re && ecs_has_id(world, re, EcsExclusive)) { - r_exclusive = (uint32_t)re; - } - } else { - r = id & ECS_COMPONENT_MASK; - re = ecs_get_alive(world, r); - } + ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(src->ptr != 0, ECS_INTERNAL_ERROR, NULL); - ecs_vector_t *idv = ecs_vector_copy(type, ecs_id_t); - add_id_to_ids(&idv, id, r_exclusive); - if (re) { - add_with_ids_to_ids(world, &idv, re, o); + if (src->type == dst->type) { + ecs_value_move(world, src->type, dst->ptr, src->ptr); + } else { + ecs_value_t tmp; + tmp.type = src->type; + tmp.ptr = ecs_value_new(world, src->type); + ecs_value_move(world, src->type, tmp.ptr, src->ptr); + + /* Cast value to desired output type */ + ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); + if (ecs_meta_set_value(&cur, &tmp)) { + goto error; } - ecs_ids_t ids = { - .array = ecs_vector_first(idv, ecs_id_t), - .count = ecs_vector_count(idv) - }; - - return find_or_create(world, &ids, idv); + ecs_value_free(world, src->type, tmp.ptr); } -} -static -ecs_table_t* find_or_create_table_without_id( - ecs_world_t *world, - ecs_table_t *node, - ecs_entity_t id) -{ - /* If table has one or more switches and this is a case, return self */ - if (ECS_HAS_ROLE(id, CASE)) { - ecs_assert((node->flags & EcsTableHasSwitch) != 0, - ECS_TYPE_INVALID_CASE, NULL); - return node; - } else { - ecs_type_t type = node->type; - int32_t count = ecs_vector_count(type); + return 0; +error: + return -1; +} - ecs_ids_t ids = { - .array = ecs_os_alloca_n(ecs_id_t, count), - .count = count - }; - remove_id_from_ids(type, id, &ids); +int flecs_value_unary( + const ecs_script_t *script, + const ecs_value_t *expr, + ecs_value_t *out, + ecs_token_kind_t operator) +{ + (void)script; - return flecs_table_find_or_create(world, &ids);; + if (operator == EcsTokNot) { + ecs_assert(expr->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); + ecs_assert(out->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); + *(bool*)out->ptr = !*(bool*)expr->ptr; + } else { + ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); } -} - -static -ecs_table_t* find_or_create_table_with_isa( - ecs_world_t *world, - ecs_table_t *node, - ecs_entity_t base) -{ - ecs_type_t base_type = ecs_get_type(world, base); - ecs_id_t *ids = ecs_vector_first(base_type, ecs_id_t); - int32_t i, count = ecs_vector_count(base_type); - /* Start from back, as roles have high ids */ - for (i = count - 1; i >= 0; i --) { - ecs_id_t id = ids[i]; - if (!(id & ECS_ROLE_MASK)) { /* early out if we found everything */ - break; - } + return 0; +} - if (ECS_HAS_RELATION(id, EcsIsA)) { - ecs_entity_t base_of_base = ecs_pair_second(world, id); - node = find_or_create_table_with_isa(world, node, base_of_base); - } +#define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) + +#define ECS_BOP_DO(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = (R)(ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T)) + +#define ECS_BOP(left, right, result, op, R, T)\ + if ((result)->type == ecs_id(ecs_u64_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_u64_t, T);\ + } else if ((result)->type == ecs_id(ecs_u32_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_u32_t, T);\ + } else if ((result)->type == ecs_id(ecs_u16_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_u16_t, T);\ + } else if ((result)->type == ecs_id(ecs_u8_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_u8_t, T);\ + } else if ((result)->type == ecs_id(ecs_i64_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_i64_t, T);\ + } else if ((result)->type == ecs_id(ecs_i32_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_i32_t, T);\ + } else if ((result)->type == ecs_id(ecs_i16_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_i16_t, T);\ + } else if ((result)->type == ecs_id(ecs_i8_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_i8_t, T);\ + } else if ((result)->type == ecs_id(ecs_f64_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_f64_t, T);\ + } else if ((result)->type == ecs_id(ecs_f32_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_f32_t, T);\ + } else if ((result)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_DO(left, right, result, op, ecs_char_t, T);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BOP_COND(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +#define ECS_BOP_ASSIGN(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) op (R)(ECS_VALUE_GET(right, T)) + +/* Unsigned operations */ +#define ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ + if ((right)->type == ecs_id(ecs_u64_t)) { \ + OP(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if ((right)->type == ecs_id(ecs_u32_t)) { \ + OP(left, right, result, op, ecs_u32_t, ecs_u32_t);\ + } else if ((right)->type == ecs_id(ecs_u16_t)) { \ + OP(left, right, result, op, ecs_u16_t, ecs_u16_t);\ + } else if ((right)->type == ecs_id(ecs_u8_t)) { \ + OP(left, right, result, op, ecs_u8_t, ecs_u8_t);\ + } + +/* Unsigned + signed operations */ +#define ECS_BINARY_INT_OPS(left, right, result, op, OP)\ + ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ + else if ((right)->type == ecs_id(ecs_i64_t)) { \ + OP(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if ((right)->type == ecs_id(ecs_i32_t)) { \ + OP(left, right, result, op, ecs_i32_t, ecs_i32_t);\ + } else if ((right)->type == ecs_id(ecs_i16_t)) { \ + OP(left, right, result, op, ecs_i16_t, ecs_i16_t);\ + } else if ((right)->type == ecs_id(ecs_i8_t)) { \ + OP(left, right, result, op, ecs_i8_t, ecs_i8_t);\ + } + +/* Unsigned + signed + floating point operations */ +#define ECS_BINARY_NUMBER_OPS(left, right, result, op, OP)\ + ECS_BINARY_INT_OPS(left, right, result, op, OP)\ + else if ((right)->type == ecs_id(ecs_f64_t)) { \ + OP(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else if ((right)->type == ecs_id(ecs_f32_t)) { \ + OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ + } + + +/* Combinations + error checking */ + +#define ECS_BINARY_INT_OP(left, right, result, op)\ + ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + ECS_BINARY_UINT_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_OP(left, right, result, op)\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP) else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP_COND)\ + else if ((right)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if ((right)->type == ecs_id(ecs_u8_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if ((right)->type == ecs_id(ecs_bool_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else if ((right)->type == ecs_id(ecs_entity_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_entity_t, ecs_entity_t);\ + } else if ((right)->type == ecs_id(ecs_string_t)) { \ + char *lstr = *(char**)(left)->ptr;\ + char *rstr = *(char**)(right)->ptr;\ + if (lstr && rstr) {\ + *(bool*)(result)->ptr = ecs_os_strcmp(lstr, rstr) op 0;\ + } else {\ + *(bool*)(result)->ptr = lstr == rstr;\ + }\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } - if (ECS_HAS_ROLE(id, OVERRIDE)) { - /* Override found, add it to table */ - id &= ECS_COMPONENT_MASK; - node = flecs_table_traverse_add(world, node, &id, NULL); - } +#define ECS_BINARY_COND_OP(left, right, result, op)\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_COND)\ + else if ((right)->type == ecs_id(ecs_char_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if ((right)->type == ecs_id(ecs_u8_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if ((right)->type == ecs_id(ecs_bool_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } - return node; -} +#define ECS_BINARY_ASSIGN_OP(left, right, result, op)\ + ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_ASSIGN)\ -static -void init_edge( - ecs_table_t *table, - ecs_graph_edge_t *edge, - ecs_id_t id, - ecs_table_t *to) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->id == 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->hdr.next == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->hdr.prev == NULL, ECS_INTERNAL_ERROR, NULL); - - edge->from = table; - edge->to = to; - edge->id = id; -} +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if ((right)->type == ecs_id(ecs_bool_t)) { \ + ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } -static -void init_add_edge( - ecs_world_t *world, - ecs_table_t *table, - ecs_graph_edge_t *edge, - ecs_id_t id, - ecs_table_t *to) +int flecs_value_binary( + const ecs_script_t *script, + const ecs_value_t *left, + const ecs_value_t *right, + ecs_value_t *out, + ecs_token_kind_t operator) { - init_edge(table, edge, id, to); - - ensure_hi_edge(world, &table->node.add, id); - - if (table != to) { - /* Add edges are appended to refs.next */ - ecs_graph_edge_hdr_t *to_refs = &to->node.refs; - ecs_graph_edge_hdr_t *next = to_refs->next; - - to_refs->next = &edge->hdr; - edge->hdr.prev = to_refs; + (void)script; - edge->hdr.next = next; - if (next) { - next->prev = &edge->hdr; + if (operator == EcsTokDiv || operator == EcsTokMod) { + if (flecs_value_is_0(right)) { + ecs_err("%s: division by zero", + script->name ? script->name : "anonymous script"); + return -1; } + } - compute_table_diff(world, table, to, edge, id); + switch(operator) { + case EcsTokAdd: + ECS_BINARY_OP(left, right, out, +); + break; + case EcsTokSub: + ECS_BINARY_OP(left, right, out, -); + break; + case EcsTokMul: + ECS_BINARY_OP(left, right, out, *); + break; + case EcsTokDiv: + ECS_BINARY_OP(left, right, out, /); + break; + case EcsTokMod: + ECS_BINARY_INT_OP(left, right, out, %); + break; + case EcsTokEq: + ECS_BINARY_COND_EQ_OP(left, right, out, ==); + break; + case EcsTokNeq: + ECS_BINARY_COND_EQ_OP(left, right, out, !=); + break; + case EcsTokGt: + ECS_BINARY_COND_OP(left, right, out, >); + break; + case EcsTokGtEq: + ECS_BINARY_COND_OP(left, right, out, >=); + break; + case EcsTokLt: + ECS_BINARY_COND_OP(left, right, out, <); + break; + case EcsTokLtEq: + ECS_BINARY_COND_OP(left, right, out, <=); + break; + case EcsTokAnd: + ECS_BINARY_BOOL_OP(left, right, out, &&); + break; + case EcsTokOr: + ECS_BINARY_BOOL_OP(left, right, out, ||); + break; + case EcsTokBitwiseAnd: + ECS_BINARY_INT_OP(left, right, out, &); + break; + case EcsTokBitwiseOr: + ECS_BINARY_INT_OP(left, right, out, |); + break; + case EcsTokShiftLeft: + ECS_BINARY_INT_OP(left, right, out, <<); + break; + case EcsTokShiftRight: + ECS_BINARY_INT_OP(left, right, out, >>); + break; + case EcsTokAddAssign: + ECS_BINARY_ASSIGN_OP(left, right, out, +=); + break; + case EcsTokMulAssign: + ECS_BINARY_ASSIGN_OP(left, right, out, *=); + break; + case EcsTokEnd: + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokRange: + case EcsTokIdentifier: + case EcsTokFunction: + case EcsTokString: + case EcsTokChar: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: + case EcsTokKeywordTemplate: + case EcsTokKeywordMatch: + case EcsTokKeywordNew: + case EcsTokKeywordExport: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + default: + ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); } + + return 0; } -static -void init_remove_edge( - ecs_world_t *world, - ecs_table_t *table, - ecs_graph_edge_t *edge, - ecs_id_t id, - ecs_table_t *to) +bool flecs_string_is_interpolated( + const char *value) { - init_edge(table, edge, id, to); - - ensure_hi_edge(world, &table->node.remove, id); - - if (table != to) { - /* Remove edges are appended to refs.prev */ - ecs_graph_edge_hdr_t *to_refs = &to->node.refs; - ecs_graph_edge_hdr_t *prev = to_refs->prev; + const char *ptr = value; - to_refs->prev = &edge->hdr; - edge->hdr.next = to_refs; + for (ptr = strchr(ptr, '$'); ptr; ptr = strchr(ptr + 1, '$')) { + if (ptr != value) { + if (ptr[-1] == '\\') { + continue; /* Escaped */ + } + } - edge->hdr.prev = prev; - if (prev) { - prev->next = &edge->hdr; + if (isspace(ptr[1]) || !ptr[1]) { + continue; /* $ by itself */ } - compute_table_diff(world, table, to, edge, id); + return true; } -} - -static -ecs_table_t* find_or_create_table_without( - ecs_world_t *world, - ecs_table_t *node, - ecs_graph_edge_t *edge, - ecs_id_t id) -{ - ecs_table_t *to = find_or_create_table_without_id(world, node, id); - - init_remove_edge(world, node, edge, id, to); - return to; -} + ptr = value; -static -ecs_table_t* find_or_create_table_with( - ecs_world_t *world, - ecs_table_t *node, - ecs_graph_edge_t *edge, - ecs_id_t id) -{ - ecs_table_t *to = find_or_create_table_with_id(world, node, id); + for (ptr = strchr(ptr, '{'); ptr; ptr = strchr(ptr + 1, '{')) { + if (ptr != value) { + if (ptr[-1] == '\\') { + continue; /* Escaped */ + } + } - if (ECS_HAS_ROLE(id, PAIR) && ECS_PAIR_FIRST(id) == EcsIsA) { - ecs_entity_t base = ecs_pair_second(world, id); - to = find_or_create_table_with_isa(world, to, base); + return true; } - init_add_edge(world, node, edge, id, to); - - return to; + return false; } -static -void populate_diff( - ecs_graph_edge_t *edge, - ecs_id_t *add_ptr, - ecs_id_t *remove_ptr, - ecs_table_diff_t *out) +char* flecs_string_escape( + char *str) { - if (out) { - ecs_table_diff_t *diff = edge->diff; - - if (diff && diff != &ecs_table_edge_is_component) { - ecs_assert(!add_ptr || !ECS_HAS_ROLE(add_ptr[0], CASE), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!remove_ptr || !ECS_HAS_ROLE(remove_ptr[0], CASE), - ECS_INTERNAL_ERROR, NULL); - *out = *diff; - } else { - out->on_set.count = 0; + const char *ptr; + char *out = str, ch; - if (add_ptr) { - out->added.array = add_ptr; - out->added.count = 1; + for (ptr = str; ptr[0]; ) { + if (ptr[0] == '\\') { + if (ptr[1] == '{') { /* Escape string interpolation delimiter */ + ch = '{'; + ptr += 2; + } else if (ptr[1] == '$') { /* Escape string interpolation var */ + ch = '$'; + ptr += 2; } else { - out->added.count = 0; - } - - if (remove_ptr) { - out->removed.array = remove_ptr; - out->removed.count = 1; - if (diff == &ecs_table_edge_is_component) { - out->un_set.array = remove_ptr; - out->un_set.count = 1; - } else { - out->un_set.count = 0; + ptr = flecs_chrparse(ptr, &ch); + if (!ptr) { + ecs_err("invalid escape sequence in string '%s'", str); + return NULL; } - } else { - out->removed.count = 0; - out->un_set.count = 0; } + } else { + ch = ptr[0]; + ptr ++; } + + out[0] = ch; + out ++; + } + + out[0] = '\0'; + + return out + 1; +} + +bool flecs_value_is_0( + const ecs_value_t *value) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + if (type == ecs_id(ecs_i8_t)) { + return *(ecs_i8_t*)ptr == 0; + } else if (type == ecs_id(ecs_i16_t)) { + return *(ecs_i16_t*)ptr == 0; + } else if (type == ecs_id(ecs_i32_t)) { + return *(ecs_i32_t*)ptr == 0; + } else if (type == ecs_id(ecs_i64_t)) { + return *(ecs_i64_t*)ptr == 0; + } else if (type == ecs_id(ecs_iptr_t)) { + return *(ecs_iptr_t*)ptr == 0; + } else if (type == ecs_id(ecs_u8_t)) { + return *(ecs_u8_t*)ptr == 0; + } else if (type == ecs_id(ecs_u16_t)) { + return *(ecs_u16_t*)ptr == 0; + } else if (type == ecs_id(ecs_u32_t)) { + return *(ecs_u32_t*)ptr == 0; + } else if (type == ecs_id(ecs_u64_t)) { + return *(ecs_u64_t*)ptr == 0; + } else if (type == ecs_id(ecs_uptr_t)) { + return *(ecs_uptr_t*)ptr == 0; + } else if (type == ecs_id(ecs_f32_t)) { + return ECS_EQZERO(*(ecs_f32_t*)ptr); + } else if (type == ecs_id(ecs_f64_t)) { + return ECS_EQZERO(*(ecs_f64_t*)ptr); + } else { + return true; } } -ecs_table_t* flecs_table_traverse_remove( - ecs_world_t *world, - ecs_table_t *node, - ecs_id_t *id_ptr, - ecs_table_diff_t *diff) -{ - ecs_poly_assert(world, ecs_world_t); +#endif - node = node ? node : &world->store.root; +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ - /* Removing 0 from an entity is not valid */ - ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); - ecs_id_t id = id_ptr[0]; - ecs_graph_edge_t *edge = ensure_edge(world, &node->node.remove, id); - ecs_table_t *to = edge->to; +#ifdef FLECS_SCRIPT - if (!to) { - to = find_or_create_table_without(world, node, edge, id); - ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); - } +typedef struct ecs_script_eval_ctx_t { + const ecs_script_t *script; + ecs_world_t *world; + const ecs_expr_eval_desc_t *desc; + ecs_expr_stack_t *stack; +} ecs_script_eval_ctx_t; - populate_diff(edge, NULL, id_ptr, diff); +static +int flecs_expr_visit_eval_priv( + ecs_script_eval_ctx_t *ctx, + ecs_expr_node_t *node, + ecs_expr_value_t *out); - return to; -error: - return NULL; +static +int flecs_expr_value_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_value_node_t *node, + ecs_expr_value_t *out) +{ + (void)ctx; + out->value.type = node->node.type; + out->value.ptr = node->ptr; + out->owned = false; + return 0; } -ecs_table_t* flecs_table_traverse_add( - ecs_world_t *world, - ecs_table_t *node, - ecs_id_t *id_ptr, - ecs_table_diff_t *diff) +static +int flecs_expr_interpolated_string_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_interpolated_string_t *node, + ecs_expr_value_t *out) { - ecs_poly_assert(world, ecs_world_t); + ecs_assert(node->node.type == ecs_id(ecs_string_t), + ECS_INTERNAL_ERROR, NULL); - node = node ? node : &world->store.root; + ecs_strbuf_t buf = ECS_STRBUF_INIT; - /* Adding 0 to an entity is not valid */ - ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); + flecs_expr_stack_push(ctx->stack); - ecs_id_t id = id_ptr[0]; - ecs_graph_edge_t *edge = ensure_edge(world, &node->node.add, id); - ecs_table_t *to = edge->to; + int32_t i, e = 0, count = ecs_vec_count(&node->fragments); + char **fragments = ecs_vec_first(&node->fragments); + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + if (fragment) { + ecs_strbuf_appendstr(&buf, fragment); + } else { + ecs_expr_node_t *expr = ecs_vec_get_t( + &node->expressions, ecs_expr_node_t*, e ++)[0]; + + ecs_expr_value_t *val = flecs_expr_stack_result(ctx->stack, + (ecs_expr_node_t*)node); + val->owned = true; + if (flecs_expr_visit_eval_priv(ctx, expr, val)) { + goto error; + } - if (!to) { - to = find_or_create_table_with(world, node, edge, id); - ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(val->value.type == ecs_id(ecs_string_t), + ECS_INTERNAL_ERROR, NULL); + + ecs_strbuf_appendstr(&buf, *(char**)val->value.ptr); + } } - populate_diff(edge, id_ptr, NULL, diff); + *(char**)out->value.ptr = ecs_strbuf_get(&buf); + out->owned = true; - return to; + flecs_expr_stack_pop(ctx->stack); + return 0; error: - return NULL; + flecs_expr_stack_pop(ctx->stack); + return -1; } -ecs_table_t* flecs_table_find_or_create( - ecs_world_t *world, - const ecs_ids_t *ids) -{ - ecs_poly_assert(world, ecs_world_t); - return find_or_create(world, ids, NULL); -} +static +int flecs_expr_initializer_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_initializer_t *node, + void *value, + ecs_meta_cursor_t *cur, + ecs_size_t value_size); -void flecs_init_root_table( - ecs_world_t *world) +int flecs_expr_initializer_validate_assign( + const ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_expr_initializer_element_t *elem, + ecs_entity_t type, + ecs_size_t value_size) { - ecs_poly_assert(world, ecs_world_t); - - ecs_ids_t entities = { - .array = NULL, - .count = 0 - }; + const ecs_type_info_t *ti = ecs_get_type_info(script->world, type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - world->store.root.type = ids_to_vector(&entities); - init_table(world, &world->store.root); + uintptr_t offset = elem->offset; + uintptr_t type_size = flecs_uto(uintptr_t, ti->size); + uintptr_t dst_size = flecs_uto(uintptr_t, value_size); + if (offset > dst_size || type_size > (dst_size - offset)) { + flecs_expr_visit_error(script, node, + "initializer of type '%s' writes past end of target value", + flecs_errstr(ecs_get_path(script->world, type))); + goto error; + } - /* Ensure table indices start at 1, as 0 is reserved for the root */ - uint64_t new_id = flecs_sparse_new_id(&world->store.tables); - ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); - (void)new_id; + return 0; +error: + return -1; } -void flecs_table_clear_edges( - ecs_world_t *world, - ecs_table_t *table) +static +int flecs_expr_initializer_eval_static( + ecs_script_eval_ctx_t *ctx, + ecs_expr_initializer_t *node, + void *value, + ecs_size_t value_size) { - (void)world; - ecs_poly_assert(world, ecs_world_t); + flecs_expr_stack_push(ctx->stack); - ecs_log_push_1(); + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + ecs_assert(elem->value != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_iter_t it; - ecs_graph_node_t *table_node = &table->node; - ecs_graph_edges_t *node_add = &table_node->add; - ecs_graph_edges_t *node_remove = &table_node->remove; - ecs_map_t *add_hi = &node_add->hi; - ecs_map_t *remove_hi = &node_remove->hi; - ecs_graph_edge_hdr_t *node_refs = &table_node->refs; - ecs_graph_edge_t *edge; - uint64_t key; + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_eval(ctx, + (ecs_expr_initializer_t*)elem->value, value, NULL, value_size)) + { + goto error; + } + continue; + } - /* Cleanup outgoing edges */ - it = ecs_map_iter(add_hi); - while ((edge = ecs_map_next_ptr(&it, ecs_graph_edge_t*, &key))) { - disconnect_edge(world, key, edge); - } + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); + if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { + goto error; + } - it = ecs_map_iter(remove_hi); - while ((edge = ecs_map_next_ptr(&it, ecs_graph_edge_t*, &key))) { - disconnect_edge(world, key, edge); - } + /* Type is guaranteed to be correct, since type visitor will insert + * a cast to the type of the initializer element. */ + ecs_entity_t type = elem->value->type; + if (flecs_expr_initializer_validate_assign( + ctx->script, node, elem, type, value_size)) + { + goto error; + } - /* Cleanup incoming add edges */ - ecs_graph_edge_hdr_t *next, *cur = node_refs->next; - if (cur) { - do { - edge = (ecs_graph_edge_t*)cur; - ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); - next = cur->next; - remove_edge(world, &edge->from->node.add, edge->id, edge); - } while ((cur = next)); - } + if (!elem->operator) { + if (expr->owned) { + if (ecs_value_move(ctx->world, type, + ECS_OFFSET(value, elem->offset), expr->value.ptr)) + { + goto error; + } + } else { + if (ecs_value_copy(ctx->world, type, + ECS_OFFSET(value, elem->offset), expr->value.ptr)) + { + goto error; + } + } + } else { + ecs_value_t dst = { + .type = type, + .ptr = ECS_OFFSET(value, elem->offset) + }; - /* Cleanup incoming remove edges */ - cur = node_refs->prev; - if (cur) { - do { - edge = (ecs_graph_edge_t*)cur; - ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); - next = cur->prev; - remove_edge(world, &edge->from->node.remove, edge->id, edge); - } while ((cur = next)); + if (flecs_value_binary( + ctx->script, NULL, &expr->value, &dst, elem->operator)) + { + goto error; + } + } } - ecs_os_free(node_add->lo); - ecs_os_free(node_remove->lo); - ecs_map_fini(add_hi); - ecs_map_fini(remove_hi); - table_node->add.lo = NULL; - table_node->remove.lo = NULL; - - ecs_log_pop_1(); + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; } -/* Public convenience functions for traversing table graph */ -ecs_table_t* ecs_table_add_id( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id) +static +int flecs_expr_initializer_eval_dynamic( + ecs_script_eval_ctx_t *ctx, + ecs_expr_initializer_t *node, + void *value, + ecs_meta_cursor_t *cur, + ecs_size_t value_size) { - return flecs_table_traverse_add(world, table, &id, NULL); -} + (void)value_size; -ecs_table_t* ecs_table_remove_id( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id) -{ - return flecs_table_traverse_remove(world, table, &id, NULL); -} + flecs_expr_stack_push(ctx->stack); -#include + ecs_meta_cursor_t local_cur; + if (!cur) { + local_cur = ecs_meta_cursor( + ctx->world, node->node.type, value); + cur = &local_cur; + } + + if (ecs_meta_push(cur)) { + goto error; + } -/* Utility macro's to enforce consistency when initializing iterator fields */ + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + ecs_assert(elem->value != NULL, ECS_INTERNAL_ERROR, NULL); -/* If term count is smaller than cache size, initialize with inline array, - * otherwise allocate. */ -#define INIT_CACHE(it, fields, f, count, cache_size)\ - if (!it->f && (fields & flecs_iter_cache_##f) && count) {\ - if (count <= cache_size) {\ - it->f = it->priv.cache.f;\ - it->priv.cache.used |= flecs_iter_cache_##f;\ - } else {\ - it->f = ecs_os_calloc(ECS_SIZEOF(*(it->f)) * count);\ - it->priv.cache.allocated |= flecs_iter_cache_##f;\ - }\ - } + if (i) { + ecs_meta_next(cur); + } -/* If array is using the cache, make sure that its address is correct in case - * the iterator got moved (typically happens when returned by a function) */ -#define VALIDATE_CACHE(it, f)\ - if (it->f) {\ - if (it->priv.cache.used & flecs_iter_cache_##f) {\ - it->f = it->priv.cache.f;\ - }\ - } + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_eval(ctx, + (ecs_expr_initializer_t*)elem->value, value, cur, value_size)) + { + goto error; + } + continue; + } -/* If array is allocated, free it when finalizing the iterator */ -#define FINI_CACHE(it, f)\ - if (it->f) {\ - if (it->priv.cache.allocated & flecs_iter_cache_##f) {\ - ecs_os_free((void*)it->f);\ - }\ - } + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); + if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { + goto error; + } -void flecs_iter_init( - ecs_iter_t *it, - ecs_flags8_t fields) -{ - ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INTERNAL_ERROR, NULL); + if (elem->member) { + ecs_meta_member(cur, elem->member); + } - it->priv.cache.used = 0; - it->priv.cache.allocated = 0; + ecs_value_t v_elem_value = { + .ptr = expr->value.ptr, + .type = expr->value.type + }; - INIT_CACHE(it, fields, ids, it->term_count, ECS_TERM_CACHE_SIZE); - INIT_CACHE(it, fields, subjects, it->term_count, ECS_TERM_CACHE_SIZE); - INIT_CACHE(it, fields, match_indices, it->term_count, ECS_TERM_CACHE_SIZE); - INIT_CACHE(it, fields, columns, it->term_count, ECS_TERM_CACHE_SIZE); - INIT_CACHE(it, fields, variables, it->variable_count, - ECS_VARIABLE_CACHE_SIZE); - INIT_CACHE(it, fields, sizes, it->term_count, ECS_TERM_CACHE_SIZE); + if (ecs_meta_set_value(cur, &v_elem_value)) { + goto error; + } + } - if (!ECS_BIT_IS_SET(it->flags, EcsIterIsFilter)) { - INIT_CACHE(it, fields, ptrs, it->term_count, ECS_TERM_CACHE_SIZE); - } else { - it->ptrs = NULL; + if (ecs_meta_pop(cur)) { + goto error; } + + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; } static -void iter_validate_cache( - ecs_iter_t *it) +int flecs_expr_initializer_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_initializer_t *node, + void *value, + ecs_meta_cursor_t *cur, + ecs_size_t value_size) { - /* Make sure pointers to cache are up to date in case iter has moved */ - VALIDATE_CACHE(it, ids); - VALIDATE_CACHE(it, subjects); - VALIDATE_CACHE(it, match_indices); - VALIDATE_CACHE(it, columns); - VALIDATE_CACHE(it, variables); - VALIDATE_CACHE(it, sizes); - VALIDATE_CACHE(it, ptrs); + if (node->is_dynamic) { + return flecs_expr_initializer_eval_dynamic( + ctx, node, value, cur, value_size); + } else { + return flecs_expr_initializer_eval_static(ctx, node, value, value_size); + } } -void flecs_iter_validate( - ecs_iter_t *it) +static +int flecs_expr_initializer_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_initializer_t *node, + ecs_expr_value_t *out) { - iter_validate_cache(it); - ECS_BIT_SET(it->flags, EcsIterIsValid); + out->owned = true; + + const ecs_type_info_t *ti = ecs_get_type_info( + ctx->world, node->node.type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + return flecs_expr_initializer_eval( + ctx, node, out->value.ptr, NULL, ti->size); } -void ecs_iter_fini( - ecs_iter_t *it) +static +int flecs_expr_empty_initializer_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_initializer_t *node, + ecs_expr_value_t *out) { - ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + if (node->is_dynamic) { + ecs_meta_cursor_t cur = ecs_meta_cursor( + ctx->world, node->node.type, out->value.ptr); + + if (ecs_meta_push(&cur)) { + goto error; + } - if (it->fini) { - it->fini(it); + if (ecs_meta_pop(&cur)) { + goto error; + } } - FINI_CACHE(it, ids); - FINI_CACHE(it, columns); - FINI_CACHE(it, subjects); - FINI_CACHE(it, sizes); - FINI_CACHE(it, ptrs); - FINI_CACHE(it, match_indices); - FINI_CACHE(it, variables); + return 0; +error: + return -1; } static -ecs_size_t iter_get_size_for_id( - ecs_world_t *world, - ecs_id_t id) +int flecs_expr_unary_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_unary_t *node, + ecs_expr_value_t *out) { - if (ECS_HAS_ROLE(id, SWITCH)) { - return ECS_SIZEOF(ecs_entity_t); - } + flecs_expr_stack_push(ctx->stack); - ecs_entity_t type_id = ecs_get_typeid(world, id); - if (!type_id) { - return 0; + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { + goto error; } - const ecs_type_info_t *ti = flecs_get_type_info(world, type_id); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + if (flecs_value_unary( + ctx->script, &expr->value, &out->value, node->operator)) + { + goto error; + } - return ti->size; + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; } static -bool flecs_iter_populate_term_data( - ecs_world_t *world, - ecs_iter_t *it, - int32_t t, - int32_t column, - void **ptr_out, - ecs_size_t *size_out) +int flecs_expr_binary_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_binary_t *node, + ecs_expr_value_t *out) { - bool is_shared = false; - ecs_table_t *table; - ecs_vector_t *vec; - ecs_size_t size = 0; - ecs_size_t align; - int32_t row; + flecs_expr_stack_push(ctx->stack); - if (!column) { - /* Term has no data. This includes terms that have Not operators. */ - goto no_data; + /* Evaluate left & right expressions */ + ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_expr_visit_eval_priv(ctx, node->left, left)) { + goto error; } - if (!it->terms) { - goto no_data; + ecs_expr_value_t *right = flecs_expr_stack_result(ctx->stack, node->right); + if (flecs_expr_visit_eval_priv(ctx, node->right, right)) { + goto error; } - /* Filter terms may match with data but don't return it */ - if (it->terms[t].inout == EcsInOutFilter) { - if (size_out) { - size = iter_get_size_for_id(world, it->ids[t]); + int32_t i, vector_count = node->vector_count; + if (!vector_count) { + if (flecs_value_binary( + ctx->script, &left->value, &right->value, &out->value, node->operator)) + { + goto error; + } + } else { + ecs_entity_t vector_type = node->vector_type; + ecs_value_t left_value = { .ptr = left->value.ptr, .type = vector_type }; + ecs_value_t right_value = { .ptr = right->value.ptr, .type = vector_type }; + ecs_value_t out_value = { .ptr = out->value.ptr, .type = vector_type }; + ecs_size_t size = flecs_expr_storage_size(vector_type); + ecs_size_t right_offset = 0; + + if (left->value.type == right->value.type) { + right_offset = size; + } + + for (i = 0; i < vector_count; i ++) { + if (flecs_value_binary(ctx->script, &left_value, &right_value, + &out_value, node->operator)) + { + goto error; + } + + left_value.ptr = ECS_OFFSET(left_value.ptr, size); + right_value.ptr = ECS_OFFSET(right_value.ptr, right_offset); + out_value.ptr = ECS_OFFSET(out_value.ptr, size); } - goto no_data; } - if (column < 0) { - is_shared = true; + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; +} - /* Data is not from This */ - if (it->references) { - /* Iterator provides cached references for non-This terms */ - ecs_ref_t *ref = &it->references[-column - 1]; - if (ptr_out) { - ptr_out[0] = (void*)ecs_get_ref_id( - world, ref, ref->entity, ref->component); - } +static +int flecs_expr_identifier_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_identifier_t *node, + ecs_expr_value_t *out) +{ + if (node->expr) { + return flecs_expr_visit_eval_priv(ctx, node->expr, out); + } else { + ecs_assert(ctx->desc != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx->desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t e = ctx->desc->lookup_action( + ctx->world, node->value, ctx->desc->lookup_ctx); + if (!e) { + flecs_expr_visit_error(ctx->script, node, + "unresolved identifier '%s'", node->value); + goto error; + } - if (!ref->component) { - is_shared = false; - } + ecs_assert(out->value.type == ecs_id(ecs_entity_t), + ECS_INTERNAL_ERROR, NULL); + *(ecs_entity_t*)out->value.ptr = e; + } - /* If cached references were provided, the code that populated - * the iterator also had a chance to cache sizes, so size array - * should already have been assigned. This saves us from having - * to do additional lookups to find the component size. */ - ecs_assert(size_out == NULL, ECS_INTERNAL_ERROR, NULL); - return is_shared; - } else { - ecs_entity_t subj = it->subjects[t]; - ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); + return 0; +error: + return -1; +} - /* Don't use ecs_get_id directly. Instead, go directly to the - * storage so that we can get both the pointer and size */ - ecs_record_t *r = ecs_eis_get(world, subj); - ecs_assert(r != NULL && r->table != NULL, ECS_INTERNAL_ERROR, NULL); +static +int flecs_expr_variable_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_variable_t *node, + ecs_expr_value_t *out) +{ + ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, + "variables available at parse time are not provided"); + ecs_assert(ctx->desc->vars != NULL, ECS_INVALID_OPERATION, + "variables available at parse time are not provided"); + + const ecs_script_var_t *var = flecs_script_find_var( + ctx->desc->vars, node->name, + ctx->desc->disable_dynamic_variable_binding ? &node->sp : NULL); + if (!var) { + flecs_expr_visit_error(ctx->script, node, "unresolved variable '%s'", + node->name); + goto error; + } - row = ECS_RECORD_TO_ROW(r->row); - table = r->table; + /* Should've been populated by type visitor */ + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); + out->value = var->value; + out->owned = false; + return 0; +error: + return -1; +} - ecs_id_t id = it->ids[t]; - ecs_table_t *s_table = table->storage_table; - ecs_table_record_t *tr; +static +int flecs_expr_global_variable_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_variable_t *node, + ecs_expr_value_t *out) +{ + (void)ctx; - if (!s_table || !(tr = flecs_get_table_record(world, s_table, id))){ - /* The entity has no components or the id is not a component */ - ecs_id_t term_id = it->terms[t].id; - if (ECS_HAS_ROLE(term_id, SWITCH) || ECS_HAS_ROLE(term_id, CASE)) { - /* Edge case: if this is a switch. find switch column in - * actual table, as its not in the storage table */ - tr = flecs_get_table_record(world, table, id); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - column = tr->column + 1; - goto has_switch; - } else { - goto no_data; - } - } + ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, + "variables available at parse time are not provided"); - /* We now have row and column, so we can get the storage for the id - * which gives us the pointer and size */ - column = tr->column; - ecs_type_info_t *ti = &table->type_info[column]; - ecs_column_t *s = &table->storage.columns[column]; - size = ti->size; - align = ti->alignment; - vec = s->data; - /* Fallthrough to has_data */ - } - } else { - /* Data is from This, use table from iterator */ - table = it->table; - if (!table) { - goto no_data; - } + ecs_assert(node->global_value.type == node->node.type, + ECS_INTERNAL_ERROR, NULL); + out->value = node->global_value; + out->owned = false; - row = it->offset; - - int32_t storage_column = ecs_table_type_to_storage_index( - table, column - 1); - if (storage_column == -1) { - ecs_id_t id = it->terms[t].id; - if (ECS_HAS_ROLE(id, SWITCH) || ECS_HAS_ROLE(id, CASE)) { - goto has_switch; - } - goto no_data; - } + return 0; +} - ecs_type_info_t *ti = &table->type_info[storage_column]; - ecs_column_t *s = &table->storage.columns[storage_column]; - size = ti->size; - align = ti->alignment; - vec = s->data; +static +int flecs_expr_cast_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_cast_t *node, + ecs_expr_value_t *out) +{ + flecs_expr_stack_push(ctx->stack); - if (!table || !ecs_table_count(table)) { - goto no_data; - } + /* Evaluate expression to cast */ + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { + goto error; + } - /* Fallthrough to has_data */ + /* Copy expression result to storage of casted-to type */ + if (flecs_value_copy_to(ctx->world, &out->value, expr)) { + flecs_expr_visit_error(ctx->script, node, "failed to cast value"); + goto error; } -has_data: - if (ptr_out) ptr_out[0] = ecs_vector_get_t(vec, size, align, row); - if (size_out) size_out[0] = size; - return is_shared; + out->owned = true; + + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; +} -has_switch: { - /* Edge case: if column is a switch we should return the vector with case - * identifiers. Will be replaced in the future with pluggable storage */ - ecs_switch_t *sw = table->storage.sw_columns[ - (column - 1) - table->sw_column_offset].data; - vec = flecs_switch_values(sw); - size = ECS_SIZEOF(ecs_entity_t); - align = ECS_ALIGNOF(ecs_entity_t); - goto has_data; +static +bool flecs_expr_get_signed( + const ecs_value_t *value, + int64_t *out) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + + if (type == ecs_id(ecs_i8_t)) { + *out = *(int8_t*)ptr; + return true; + } else if (type == ecs_id(ecs_i16_t)) { + *out = *(int16_t*)ptr; + return true; + } else if (type == ecs_id(ecs_i32_t)) { + *out = *(int32_t*)ptr; + return true; + } else if (type == ecs_id(ecs_i64_t)) { + *out = *(int64_t*)ptr; + return true; } -no_data: - if (ptr_out) ptr_out[0] = NULL; - if (size_out) size_out[0] = size; return false; } -void flecs_iter_populate_data( - ecs_world_t *world, - ecs_iter_t *it, - ecs_table_t *table, - int32_t offset, - int32_t count, - void **ptrs, - ecs_size_t *sizes) +static +bool flecs_expr_get_unsigned( + const ecs_value_t *value, + uint64_t *out) { - if (it->table) { - it->frame_offset += ecs_table_count(it->table); + ecs_entity_t type = value->type; + void *ptr = value->ptr; + + if (type == ecs_id(ecs_u8_t)) { + *out = *(uint8_t*)ptr; + return true; + } else if (type == ecs_id(ecs_u16_t)) { + *out = *(uint16_t*)ptr; + return true; + } else if (type == ecs_id(ecs_u32_t)) { + *out = *(uint32_t*)ptr; + return true; + } else if (type == ecs_id(ecs_u64_t)) { + *out = *(uint64_t*)ptr; + return true; } - it->table = table; - it->offset = offset; - it->count = count; + return false; +} - if (table) { - it->type = it->table->type; - if (!count) { - count = it->count = ecs_table_count(table); - } - if (count) { - it->entities = ecs_vector_get( - table->storage.entities, ecs_entity_t, offset); - } else { - it->entities = NULL; - } +static +bool flecs_expr_get_float( + const ecs_value_t *value, + double *out) +{ + ecs_entity_t type = value->type; + void *ptr = value->ptr; + + if (type == ecs_id(ecs_f32_t)) { + *out = (double)*(float*)ptr; + return true; + } else if (type == ecs_id(ecs_f64_t)) { + *out = *(double*)ptr; + return true; } - int t, term_count = it->term_count; + return false; +} - if (ECS_BIT_IS_SET(it->flags, EcsIterIsFilter)) { - ECS_BIT_CLEAR(it->flags, EcsIterHasShared); +#define FLECS_EXPR_NUMBER_CAST\ + if (type == ecs_id(ecs_i8_t)) *(ecs_i8_t*)ptr = (ecs_i8_t)value;\ + else if (type == ecs_id(ecs_i16_t)) *(ecs_i16_t*)ptr = (ecs_i16_t)value;\ + else if (type == ecs_id(ecs_i32_t)) *(ecs_i32_t*)ptr = (ecs_i32_t)value;\ + else if (type == ecs_id(ecs_i64_t)) *(ecs_i64_t*)ptr = (ecs_i64_t)value;\ + else if (type == ecs_id(ecs_iptr_t)) *(ecs_iptr_t*)ptr = (ecs_iptr_t)value;\ + else if (type == ecs_id(ecs_u8_t)) *(ecs_u8_t*)ptr = (ecs_u8_t)value;\ + else if (type == ecs_id(ecs_u16_t)) *(ecs_u16_t*)ptr = (ecs_u16_t)value;\ + else if (type == ecs_id(ecs_u32_t)) *(ecs_u32_t*)ptr = (ecs_u32_t)value;\ + else if (type == ecs_id(ecs_u64_t)) *(ecs_u64_t*)ptr = (ecs_u64_t)value;\ + else if (type == ecs_id(ecs_uptr_t)) *(ecs_uptr_t*)ptr = (ecs_uptr_t)value;\ + else if (type == ecs_id(ecs_f32_t)) *(ecs_f32_t*)ptr = (ecs_f32_t)value;\ + else if (type == ecs_id(ecs_f64_t)) *(ecs_f64_t*)ptr = (ecs_f64_t)value;\ - if (!sizes) { - return; - } +static +void flecs_expr_set_signed( + const ecs_value_t *out, + int64_t value) +{ + ecs_entity_t type = out->type; + void *ptr = out->ptr; + FLECS_EXPR_NUMBER_CAST +} - /* Fetch sizes, skip fetching data */ - for (t = 0; t < term_count; t ++) { - sizes[t] = iter_get_size_for_id(world, it->ids[t]); - } - return; - } +static +void flecs_expr_set_unsigned( + const ecs_value_t *out, + uint64_t value) +{ + ecs_entity_t type = out->type; + void *ptr = out->ptr; + FLECS_EXPR_NUMBER_CAST +} - bool has_shared = false; +static +void flecs_expr_set_float( + const ecs_value_t *out, + double value) +{ + ecs_entity_t type = out->type; + void *ptr = out->ptr; + FLECS_EXPR_NUMBER_CAST +} - if (ptrs && sizes) { - for (t = 0; t < term_count; t ++) { - int32_t column = it->columns[t]; - has_shared |= flecs_iter_populate_term_data(world, it, t, column, - &ptrs[t], - &sizes[t]); - } - } else { - for (t = 0; t < term_count; t ++) { - ecs_assert(it->columns != NULL, ECS_INTERNAL_ERROR, NULL); +static +int flecs_expr_cast_number_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_cast_t *node, + ecs_expr_value_t *out) +{ + flecs_expr_stack_push(ctx->stack); - int32_t column = it->columns[t]; - void **ptr = NULL; - if (ptrs) { - ptr = &ptrs[t]; - } - ecs_size_t *size = NULL; - if (sizes) { - size = &sizes[t]; - } + /* Evaluate expression to cast */ + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { + goto error; + } - has_shared |= flecs_iter_populate_term_data(world, it, t, column, - ptr, size); - } + int64_t signed_; + uint64_t unsigned_; + double float_; + if (flecs_expr_get_signed(&expr->value, &signed_)) { + flecs_expr_set_signed(&out->value, signed_); + } else if (flecs_expr_get_unsigned(&expr->value, &unsigned_)) { + flecs_expr_set_unsigned(&out->value, unsigned_); + } else if (flecs_expr_get_float(&expr->value, &float_)) { + flecs_expr_set_float(&out->value, float_); } - ECS_BIT_COND(it->flags, EcsIterHasShared, has_shared); + flecs_expr_stack_pop(ctx->stack); + return 0; +error: + flecs_expr_stack_pop(ctx->stack); + return -1; } -bool flecs_iter_next_row( - ecs_iter_t *it) +static +int flecs_expr_function_args_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_initializer_t *node, + ecs_value_t *args) { - ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); - - bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); - if (!is_instanced) { - int32_t instance_count = it->instance_count; - int32_t count = it->count; - int32_t offset = it->offset; - - if (instance_count > count && offset < (instance_count - 1)) { - ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); - int t, term_count = it->term_count; - - for (t = 0; t < term_count; t ++) { - int32_t column = it->columns[t]; - if (column >= 0) { - void *ptr = it->ptrs[t]; - if (ptr) { - it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); - } - } - } - - if (it->entities) { - it->entities ++; - } - it->offset ++; + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + ecs_expr_value_t *expr = flecs_expr_stack_result( + ctx->stack, elem->value); - return true; + if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { + goto error; } + + args[i] = expr->value; } - return false; + return 0; +error: + return -1; } -bool flecs_iter_next_instanced( - ecs_iter_t *it, - bool result) +static +int flecs_expr_function_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_function_t *node, + ecs_expr_value_t *out) { - it->instance_count = it->count; - bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); - bool has_shared = ECS_BIT_IS_SET(it->flags, EcsIterHasShared); - if (result && !is_instanced && it->count && has_shared) { - it->count = 1; - } - return result; -} + flecs_expr_stack_push(ctx->stack); -/* --- Public API --- */ + const ecs_function_calldata_t *calldata = &node->calldata; -void* ecs_term_w_size( - const ecs_iter_t *it, - size_t size, - int32_t term) -{ - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(!size || ecs_term_size(it, term) == size || - (!ecs_term_size(it, term) && (!it->ptrs || !it->ptrs[term - 1])), - ECS_INVALID_PARAMETER, NULL); + ecs_function_ctx_t call_ctx = { + .world = ctx->world, + .function = calldata->function, + .ctx = calldata->ctx + }; - (void)size; + ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (!term) { - return it->entities; + ecs_value_t *argv = NULL; + int32_t argc = ecs_vec_count(&node->args->elements); + if (argc) { + argv = ecs_os_alloca_n(ecs_value_t, argc); + if (flecs_expr_function_args_visit_eval(ctx, node->args, argv)) { + goto error; + } } - if (!it->ptrs) { - return NULL; + int32_t elem_count = calldata->vector_elem_count; + if (elem_count) { + node->calldata.is.vector_callback( + &call_ctx, argc, argv, &out->value, elem_count); + } else { + node->calldata.is.callback(&call_ctx, argc, argv, &out->value); } - return it->ptrs[term - 1]; + out->owned = true; + + flecs_expr_stack_pop(ctx->stack); + return 0; error: - return NULL; + flecs_expr_stack_pop(ctx->stack); + return -1; } -bool ecs_term_is_readonly( - const ecs_iter_t *it, - int32_t term_index) +static +int flecs_expr_method_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_function_t *node, + ecs_expr_value_t *out) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); + flecs_expr_stack_push(ctx->stack); - ecs_term_t *term = &it->terms[term_index - 1]; - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - - if (term->inout == EcsIn) { - return true; - } else { - ecs_term_id_t *subj = &term->subj; + if (node->left) { + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { + goto error; + } - if (term->inout == EcsInOutDefault) { - if (subj->entity != EcsThis) { - return true; - } + const ecs_function_calldata_t *calldata = &node->calldata; - if (!(subj->set.mask & EcsSelf)) { - return true; + ecs_function_ctx_t call_ctx = { + .world = ctx->world, + .function = calldata->function, + .ctx = calldata->ctx + }; + + ecs_assert(expr->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t argc = ecs_vec_count(&node->args->elements); + ecs_value_t *argv = ecs_os_alloca_n(ecs_value_t, argc + 1); + argv[0] = expr->value; + + if (argc) { + if (flecs_expr_function_args_visit_eval( + ctx, node->args, &argv[1])) + { + goto error; } } + + int32_t elem_count = calldata->vector_elem_count; + if (elem_count) { + node->calldata.is.vector_callback( + &call_ctx, argc, argv, &out->value, elem_count); + } else { + node->calldata.is.callback(&call_ctx, argc, argv, &out->value); + } + + out->owned = true; } + flecs_expr_stack_pop(ctx->stack); + return 0; error: - return false; + flecs_expr_stack_pop(ctx->stack); + return -1; } -bool ecs_term_is_writeonly( - const ecs_iter_t *it, - int32_t term_index) +static +int flecs_expr_member_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_member_t *node, + ecs_expr_value_t *out) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); + flecs_expr_stack_push(ctx->stack); - ecs_term_t *term = &it->terms[term_index - 1]; - ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); - - if (term->inout == EcsOut) { - return true; + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { + goto error; } -error: - return false; -} + out->value.ptr = ECS_OFFSET(expr->value.ptr, node->offset); + out->value.type = node->node.type; + out->owned = false; -int32_t ecs_iter_find_column( - const ecs_iter_t *it, - ecs_entity_t component) -{ - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); - return ecs_search(it->real_world, it->table, component, 0); + flecs_expr_stack_pop(ctx->stack); + return 0; error: + flecs_expr_stack_pop(ctx->stack); return -1; } -bool ecs_term_is_set( - const ecs_iter_t *it, - int32_t index) +static +int flecs_expr_element_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_element_t *node, + ecs_expr_value_t *out) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - - int32_t column = it->columns[index - 1]; - if (!column) { - return false; - } else if (column < 0) { - if (it->references) { - column = -column - 1; - ecs_ref_t *ref = &it->references[column]; - return ref->entity != 0; - } else { - return true; - } + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { + goto error; } - return true; -error: - return false; -} - -void* ecs_iter_column_w_size( - const ecs_iter_t *it, - size_t size, - int32_t index) -{ - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); - (void)size; - - ecs_table_t *table = it->table; - int32_t storage_index = ecs_table_type_to_storage_index(table, index); - if (storage_index == -1) { - return NULL; + ecs_expr_value_t *index = flecs_expr_stack_result(ctx->stack, node->index); + if (flecs_expr_visit_eval_priv(ctx, node->index, index)) { + goto error; } - ecs_type_info_t *ti = &table->type_info[storage_index]; - ecs_check(!size || (ecs_size_t)size == ti->size, - ECS_INVALID_PARAMETER, NULL); + int64_t index_value = *(int64_t*)index->value.ptr; + + out->value.ptr = ECS_OFFSET(expr->value.ptr, node->elem_size * index_value); + out->value.type = node->node.type; + out->owned = false; - ecs_column_t *column = &table->storage.columns[storage_index]; - int32_t alignment = ti->alignment; - return ecs_vector_get_t(column->data, flecs_uto(int32_t, size), alignment, - it->offset); + return 0; error: - return NULL; + return -1; } -size_t ecs_iter_column_size( - const ecs_iter_t *it, - int32_t index) +static +int flecs_expr_match_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_match_t *node, + ecs_expr_value_t *out) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_table_t *table = it->table; - int32_t storage_index = ecs_table_type_to_storage_index(table, index); - if (storage_index == -1) { - return 0; + flecs_expr_stack_push(ctx->stack); + + ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { + goto error; } - ecs_type_info_t *ti = &table->type_info[storage_index]; - return flecs_ito(size_t, ti->size); -error: - return 0; -} + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); -char* ecs_iter_str( - const ecs_iter_t *it) -{ - ecs_world_t *world = it->world; - ecs_strbuf_t buf = ECS_STRBUF_INIT; - int i; + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; - if (it->term_count) { - ecs_strbuf_list_push(&buf, "term: ", ","); - for (i = 0; i < it->term_count; i ++) { - ecs_id_t id = ecs_term_id(it, i + 1); - char *str = ecs_id_str(world, id); - ecs_strbuf_list_appendstr(&buf, str); - ecs_os_free(str); + flecs_expr_stack_push(ctx->stack); + ecs_expr_value_t *compare = flecs_expr_stack_result( + ctx->stack, node->expr); + if (flecs_expr_visit_eval_priv(ctx, elem->compare, compare)) { + goto error; } - ecs_strbuf_list_pop(&buf, "\n"); - ecs_strbuf_list_push(&buf, "subj: ", ","); - for (i = 0; i < it->term_count; i ++) { - ecs_entity_t subj = ecs_term_source(it, i + 1); - char *str = ecs_get_fullpath(world, subj); - ecs_strbuf_list_appendstr(&buf, str); - ecs_os_free(str); - } - ecs_strbuf_list_pop(&buf, "\n"); - } + bool value = false; + ecs_value_t result = { .type = ecs_id(ecs_bool_t), .ptr = &value }; - if (it->variable_count) { - int32_t actual_count = 0; - for (i = 0; i < it->variable_count; i ++) { - const char *var_name = it->variable_names[i]; - if (!var_name || var_name[0] == '_' || var_name[0] == '.') { - /* Skip anonymous variables */ - continue; - } + if (flecs_value_binary( + ctx->script, &expr->value, &compare->value, &result, EcsTokEq)) + { + goto error; + } - ecs_var_t var = it->variables[i]; - if (!var.entity) { - /* Skip table variables */ - continue; - } + flecs_expr_stack_pop(ctx->stack); - if (!actual_count) { - ecs_strbuf_list_push(&buf, "vars: ", ","); + if (value) { + if (flecs_expr_visit_eval_priv(ctx, elem->expr, out)) { + goto error; } - - char *str = ecs_get_fullpath(world, var.entity); - ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); - ecs_os_free(str); - - actual_count ++; - } - if (actual_count) { - ecs_strbuf_list_pop(&buf, "\n"); + break; } } - if (it->count) { - ecs_strbuf_appendstr(&buf, "this:\n"); - for (i = 0; i < it->count; i ++) { - ecs_entity_t e = it->entities[i]; - char *str = ecs_get_fullpath(world, e); - ecs_strbuf_appendstr(&buf, " - "); - ecs_strbuf_appendstr(&buf, str); - ecs_strbuf_appendstr(&buf, "\n"); + if (i == count) { + if (node->any.expr) { + if (flecs_expr_visit_eval_priv(ctx, node->any.expr, out)) { + goto error; + } + } else { + char *str = ecs_ptr_to_str( + ctx->world, expr->value.type, expr->value.ptr); + flecs_expr_visit_error(ctx->script, node, + "match value '%s' not handled by case", str); ecs_os_free(str); + goto error; } } - return ecs_strbuf_get(&buf); -} - -void ecs_iter_poly( - const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter_out, - ecs_term_t *filter) -{ - ecs_iterable_t *iterable = ecs_get_iterable(poly); - iterable->init(world, poly, iter_out, filter); -} - -bool ecs_iter_next( - ecs_iter_t *iter) -{ - ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); - return iter->next(iter); + flecs_expr_stack_pop(ctx->stack); + return 0; error: - return false; + flecs_expr_stack_pop(ctx->stack); + return -1; } -bool ecs_iter_count( - ecs_iter_t *it) +static +int flecs_expr_new_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_new_t *node, + ecs_expr_value_t *out) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - int32_t count = 0; - while (ecs_iter_next(it)) { - count += it->count; + ecs_script_eval_visitor_t *v = ctx->desc ? ctx->desc->script_visitor : NULL; + ecs_script_eval_visitor_t temp_v = {0}; + ecs_script_eval_desc_t desc = {0}; + + if (!v) { + /* Safe const cast, script won't modify variables since it only contains + * an entity statement. */ + desc.vars = ctx->desc ? + ECS_CONST_CAST(ecs_script_vars_t*, ctx->desc->vars) : NULL; + flecs_script_eval_visit_init( + (const ecs_script_impl_t*)ctx->script, &temp_v, &desc); + v = &temp_v; } - return count; -error: - return 0; -} -bool ecs_iter_is_true( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - bool result = ecs_iter_next(it); - if (result) { - ecs_iter_fini(it); + ecs_script_visit_push(v, (ecs_script_node_t*)node->entity); + + if (flecs_script_eval_node(&v->base, (ecs_script_node_t*)node->entity)) { + return -1; } - return result; -error: - return false; -} -ecs_entity_t ecs_iter_get_var( - ecs_iter_t *it, - int32_t var_id) -{ - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_visit_pop(v, (ecs_script_node_t*)node->entity); - ecs_var_t *var = &it->variables[var_id]; - ecs_entity_t e = var->entity; - if (!e) { - ecs_table_t *table = var->range.table; - if (table) { - if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { - ecs_assert(ecs_table_count(table) > var->range.offset, - ECS_INTERNAL_ERROR, NULL); - e = ecs_vector_get(table->storage.entities, ecs_entity_t, - var->range.offset)[0]; - } - } - } else { - ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); + *(ecs_entity_t*)out->value.ptr = node->entity->eval; + out->value.type = ecs_id(ecs_entity_t); + + if (v == &temp_v) { + flecs_script_eval_visit_fini(v, &desc); } - return e; -error: return 0; } -ecs_table_t* ecs_iter_get_var_as_table( - ecs_iter_t *it, - int32_t var_id) +static +int flecs_expr_component_visit_eval( + ecs_script_eval_ctx_t *ctx, + ecs_expr_element_t *node, + ecs_expr_value_t *out) { - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); + if (flecs_expr_visit_eval_priv(ctx, node->left, left)) { + goto error; + } - ecs_var_t *var = &it->variables[var_id]; - ecs_table_t *table = var->range.table; - if (!table) { - /* If table is not set, try to get table from entity */ - ecs_entity_t e = var->entity; - if (e) { - ecs_record_t *r = ecs_eis_get(it->real_world, e); - if (r) { - table = r->table; - if (ecs_table_count(table) != 1) { - /* If table contains more than the entity, make sure not to - * return a partial table. */ - return NULL; - } - } - } + /* Left side of expression must be of entity type */ + ecs_assert(left->value.type == ecs_id(ecs_entity_t), + ECS_INTERNAL_ERROR, NULL); + + /* Component must be resolvable at parse time */ + ecs_expr_node_t *index = node->index; + if (index->kind == EcsExprIdentifier) { + index = ((ecs_expr_identifier_t*)index)->expr; } - if (table) { - if (var->range.offset) { - /* Don't return whole table if only partial table is matched */ - return NULL; - } + ecs_assert(index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); - if (!var->range.count || ecs_table_count(table) == var->range.count) { - /* Return table if count matches */ - return table; - } + ecs_entity_t entity = *(ecs_entity_t*)left->value.ptr; + ecs_entity_t component = ((ecs_expr_value_node_t*)index)->storage.entity; + + ecs_assert(out->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); + out->value.ptr = ECS_CONST_CAST(void*, + ecs_get_id(ctx->world, entity, component)); + out->owned = false; + + if (!out->value.ptr) { + char *estr = ecs_get_path(ctx->world, entity); + char *cstr = ecs_get_path(ctx->world, component); + flecs_expr_visit_error(ctx->script, node, + "entity '%s' does not have component '%s'", estr, cstr); + ecs_os_free(estr); + ecs_os_free(cstr); + goto error; } + return 0; error: - return NULL; + return -1; } -ecs_table_range_t ecs_iter_get_var_as_range( - ecs_iter_t *it, - int32_t var_id) +static +int flecs_expr_visit_eval_priv( + ecs_script_eval_ctx_t *ctx, + ecs_expr_node_t *node, + ecs_expr_value_t *out) { - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_table_range_t result = { 0 }; + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_var_t *var = &it->variables[var_id]; - ecs_table_t *table = var->range.table; - if (!table) { - ecs_entity_t e = var->entity; - if (e) { - ecs_record_t *r = ecs_eis_get(it->real_world, e); - if (r) { - result.table = r->table; - result.offset = ECS_RECORD_TO_ROW(r->row); - result.count = 1; - } + switch(node->kind) { + case EcsExprValue: + if (flecs_expr_value_visit_eval( + ctx, (ecs_expr_value_node_t*)node, out)) + { + goto error; } - } else { - result.table = table; - result.offset = var->range.offset; - result.count = var->range.count; - if (!result.count) { - result.count = ecs_table_count(table); + break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_visit_eval( + ctx, (ecs_expr_interpolated_string_t*)node, out)) + { + goto error; + } + break; + case EcsExprEmptyInitializer: + if (flecs_expr_empty_initializer_visit_eval( + ctx, (ecs_expr_initializer_t*)node, out)) + { + goto error; + } + break; + case EcsExprInitializer: + if (flecs_expr_initializer_visit_eval( + ctx, (ecs_expr_initializer_t*)node, out)) + { + goto error; + } + break; + case EcsExprUnary: + if (flecs_expr_unary_visit_eval( + ctx, (ecs_expr_unary_t*)node, out)) + { + goto error; + } + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_eval( + ctx, (ecs_expr_binary_t*)node, out)) + { + goto error; + } + break; + case EcsExprIdentifier: + if (flecs_expr_identifier_visit_eval( + ctx, (ecs_expr_identifier_t*)node, out)) + { + goto error; + } + break; + case EcsExprVariable: + if (flecs_expr_variable_visit_eval( + ctx, (ecs_expr_variable_t*)node, out)) + { + goto error; + } + break; + case EcsExprGlobalVariable: + if (flecs_expr_global_variable_visit_eval( + ctx, (ecs_expr_variable_t*)node, out)) + { + goto error; + } + break; + case EcsExprFunction: + if (flecs_expr_function_visit_eval( + ctx, (ecs_expr_function_t*)node, out)) + { + goto error; + } + break; + case EcsExprMethod: + if (flecs_expr_method_visit_eval( + ctx, (ecs_expr_function_t*)node, out)) + { + goto error; + } + break; + case EcsExprMember: + if (flecs_expr_member_visit_eval( + ctx, (ecs_expr_member_t*)node, out)) + { + goto error; + } + break; + case EcsExprElement: + if (flecs_expr_element_visit_eval( + ctx, (ecs_expr_element_t*)node, out)) + { + goto error; + } + break; + case EcsExprMatch: + if (flecs_expr_match_visit_eval( + ctx, (ecs_expr_match_t*)node, out)) + { + goto error; + } + break; + case EcsExprNew: + if (flecs_expr_new_visit_eval( + ctx, (ecs_expr_new_t*)node, out)) + { + goto error; + } + break; + case EcsExprComponent: + if (flecs_expr_component_visit_eval( + ctx, (ecs_expr_element_t*)node, out)) + { + goto error; + } + break; + case EcsExprCast: + if (flecs_expr_cast_visit_eval( + ctx, (ecs_expr_cast_t*)node, out)) + { + goto error; + } + break; + case EcsExprCastNumber: + if (flecs_expr_cast_number_visit_eval( + ctx, (ecs_expr_cast_t*)node, out)) + { + goto error; } + break; } - return result; + return 0; error: - return (ecs_table_range_t){0}; + return -1; } -void ecs_iter_set_var( - ecs_iter_t *it, - int32_t var_id, - ecs_entity_t entity) +int flecs_expr_visit_eval( + const ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_expr_eval_desc_t *desc, + ecs_value_t *out) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < ECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - /* Can't set variable while iterating */ - ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); - ecs_check(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); - - iter_validate_cache(it); + ecs_expr_stack_t *stack = NULL, stack_local; + if (desc && desc->runtime) { + stack = &desc->runtime->expr_stack; + } + if (!stack) { + stack = &stack_local; + flecs_expr_stack_init(stack); + } - ecs_var_t *var = &it->variables[var_id]; - var->entity = entity; + flecs_expr_stack_push(stack); - ecs_record_t *r = ecs_eis_get(it->real_world, entity); - if (r) { - var->range.table = r->table; - var->range.offset = ECS_RECORD_TO_ROW(r->row); - var->range.count = 1; + ecs_expr_value_t val_tmp; + ecs_expr_value_t *val; + if (out->type && (out->type == node->type) && out->ptr) { + val_tmp = (ecs_expr_value_t){ + .value = *out, + .owned = false, + .type_info = ecs_get_type_info(script->world, out->type) + }; + val = &val_tmp; } else { - var->range.table = NULL; - var->range.offset = 0; - var->range.count = 0; + val = flecs_expr_stack_result(stack, node); } - it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); - -error: - return; -} - -void ecs_iter_set_var_as_table( - ecs_iter_t *it, - int32_t var_id, - const ecs_table_t *table) -{ - ecs_table_range_t range = { .table = (ecs_table_t*)table }; - ecs_iter_set_var_as_range(it, var_id, &range); -} + ecs_script_eval_ctx_t ctx = { + .script = script, + .world = script->world, + .stack = stack, + .desc = desc + }; -void ecs_iter_set_var_as_range( - ecs_iter_t *it, - int32_t var_id, - const ecs_table_range_t *range) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < ECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); - ecs_check(range != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(range->table != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!range->offset || range->offset < ecs_table_count(range->table), - ECS_INVALID_PARAMETER, NULL); - ecs_check((range->offset + range->count) <= ecs_table_count(range->table), - ECS_INVALID_PARAMETER, NULL); + // ecs_strbuf_t buf = ECS_STRBUF_INIT; + // flecs_expr_to_str_buf(script, node, &buf, true); + // char *str = ecs_strbuf_get(&buf); + // printf("%s\n", str); + // ecs_os_free(str); - /* Can't set variable while iterating */ - ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, NULL); + if (flecs_expr_visit_eval_priv(&ctx, node, val)) { + goto error; + } - iter_validate_cache(it); + if (desc && !out->type) { + out->type = desc->type; + } - ecs_var_t *var = &it->variables[var_id]; - var->range = *range; + if (!out->type) { + out->type = node->type; + } - if (range->count == 1) { - ecs_table_t *table = range->table; - var->entity = ecs_vector_get( - table->storage.entities, ecs_entity_t, range->offset)[0]; - } else { - var->entity = 0; + if (out->type && !out->ptr) { + out->ptr = ecs_value_new(ctx.world, out->type); } - it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); + if (val != &val_tmp || out->ptr != val->value.ptr) { + if (val->owned) { + /* Values owned by the runtime can be moved to output */ + if (flecs_value_move_to(ctx.world, out, &val->value)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } + } else { + /* Values not owned by runtime should be copied */ + if (flecs_value_copy_to(ctx.world, out, val)) { + flecs_expr_visit_error(script, node, "failed to write to output"); + goto error; + } + } + } + flecs_expr_stack_pop(stack); + if (stack == &stack_local) { + flecs_expr_stack_fini(stack); + } + return 0; error: - return; + flecs_expr_stack_pop(stack); + if (stack == &stack_local) { + flecs_expr_stack_fini(stack); + } + return -1; } -bool ecs_iter_var_is_constrained( - ecs_iter_t *it, - int32_t var_id) -{ - return (it->constrained_vars & (flecs_uto(uint64_t, 1 << var_id))) != 0; -} +#endif -ecs_iter_t ecs_page_iter( - const ecs_iter_t *it, - int32_t offset, - int32_t limit) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); +/** + * @file addons/script/expr_fold.c + * @brief Script expression constant folding. + */ - ecs_iter_t result = *it; - result.priv.iter.page = (ecs_page_iter_t){ - .offset = offset, - .limit = limit, - .remaining = limit - }; - result.next = ecs_page_next; - result.chain_it = (ecs_iter_t*)it; - return result; -error: - return (ecs_iter_t){ 0 }; +#ifdef FLECS_SCRIPT + +static +void flecs_visit_fold_replace( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + ecs_expr_node_t *with) +{ + ecs_assert(*node_ptr != with, ECS_INTERNAL_ERROR, NULL); + flecs_expr_visit_free(script, *node_ptr); + *node_ptr = with; } static -void offset_iter( - ecs_iter_t *it, - int32_t offset) +int flecs_expr_unary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) { - it->entities = &it->entities[offset]; + ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; - int32_t t, term_count = it->term_count; - for (t = 0; t < term_count; t ++) { - void *ptrs = it->ptrs[t]; - if (!ptrs) { - continue; - } + if (node->operator != EcsTokNot) { + flecs_expr_visit_error(script, node, + "operator invalid for unary expression"); + goto error; + } - if (it->subjects[t]) { - continue; - } + if (flecs_expr_visit_fold(script, &node->expr, desc)) { + goto error; + } + + if (node->expr->kind != EcsExprValue) { + /* Only folding literals */ + return 0; + } + + if (node->expr->type != ecs_id(ecs_bool_t)) { + char *type_str = ecs_get_path(script->world, node->node.type); + flecs_expr_visit_error(script, node, + "! operator cannot be applied to value of type '%s' (must be bool)"); + ecs_os_free(type_str); + goto error; + } + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, ecs_id(ecs_bool_t)); + result->ptr = &result->storage.bool_; - it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); + ecs_value_t dst = { .ptr = result->ptr, .type = ecs_id(ecs_bool_t) }; + ecs_value_t src = { + .ptr = ((ecs_expr_value_node_t*)node->expr)->ptr, .type = ecs_id(ecs_bool_t) }; + if (flecs_value_unary(script, &src, &dst, node->operator)) { + goto error; } + + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); + + return 0; +error: + return -1; } static -bool ecs_page_next_instanced( - ecs_iter_t *it) +int flecs_expr_binary_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); - - ecs_iter_t *chain_it = it->chain_it; - bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); - - do { - if (!ecs_iter_next(chain_it)) { - goto done; - } + ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; - ecs_page_iter_t *iter = &it->priv.iter.page; - - /* Copy everything up to the private iterator data */ - ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); + if (flecs_expr_visit_fold(script, &node->left, desc)) { + goto error; + } - /* Keep instancing setting from original iterator */ - ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); + if (flecs_expr_visit_fold(script, &node->right, desc)) { + goto error; + } - if (!chain_it->table) { - goto yield; /* Task query */ - } + if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { + /* Only folding literals */ + return 0; + } - int32_t offset = iter->offset; - int32_t limit = iter->limit; - if (!(offset || limit)) { - if (it->count) { - goto yield; - } else { - goto done; - } - } + ecs_expr_value_node_t *left = (ecs_expr_value_node_t*)node->left; + ecs_expr_value_node_t *right = (ecs_expr_value_node_t*)node->right; - int32_t count = it->count; - int32_t remaining = iter->remaining; + ecs_value_t lop = { .type = left->node.type, .ptr = left->ptr }; + ecs_value_t rop = { .type = right->node.type, .ptr = right->ptr }; - if (offset) { - if (offset > count) { - /* No entities to iterate in current table */ - iter->offset -= count; - it->count = 0; - continue; - } else { - it->offset += offset; - count = it->count -= offset; - iter->offset = 0; - offset_iter(it, offset); - } + /* flecs_value_binary will detect division by 0, but we have more + * information about where it happens here. */ + if (node->operator == EcsTokDiv || node->operator == EcsTokMod) { + if (flecs_value_is_0(&rop)) { + flecs_expr_visit_error(script, node, + "invalid division by zero"); + goto error; } + } - if (remaining) { - if (remaining > count) { - iter->remaining -= count; - } else { - it->count = remaining; - iter->remaining = 0; - } - } else if (limit) { - /* Limit hit: no more entities left to iterate */ - goto done; - } - } while (it->count == 0); + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); + ecs_value_t res = { .type = result->node.type, .ptr = result->ptr }; -yield: - if (!ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { - it->offset = 0; + if (flecs_value_binary(script, &lop, &rop, &res, node->operator)) { + goto error; } - return true; -done: -error: - return false; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); + return 0; +error: + return -1; } -bool ecs_page_next( - ecs_iter_t *it) +static +int flecs_expr_cast_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; - ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); + if (flecs_expr_visit_fold(script, &node->expr, desc)) { + goto error; + } - if (flecs_iter_next_row(it)) { - return true; + if (node->expr->kind != EcsExprValue) { + /* Only folding literals for now */ + return 0; } - return flecs_iter_next_instanced(it, ecs_page_next_instanced(it)); -error: - return false; -} + ecs_expr_value_node_t *expr = (ecs_expr_value_node_t*)node->expr; + ecs_entity_t dst_type = node->node.type; + ecs_entity_t src_type = expr->node.type; -ecs_iter_t ecs_worker_iter( - const ecs_iter_t *it, - int32_t index, - int32_t count) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(count > 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(index >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); + if (dst_type == src_type) { + /* No cast necessary if types are equal */ + return 0; + } + + void *dst_ptr = ecs_value_new(script->world, dst_type); + + ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, dst_ptr); + ecs_value_t value = { + .type = src_type, + .ptr = expr->ptr + }; + + if (ecs_meta_set_value(&cur, &value)) { + flecs_expr_visit_error(script, node, "failed to assign value"); + ecs_value_free(script->world, dst_type, dst_ptr); + goto error; + } + + if (expr->ptr != &expr->storage) { + ecs_value_free(script->world, expr->node.type, expr->ptr); + } - return (ecs_iter_t){ - .real_world = it->real_world, - .world = it->world, - .priv.iter.worker = { - .index = index, - .count = count - }, - .next = ecs_worker_next, - .chain_it = (ecs_iter_t*)it, - .flags = it->flags & EcsIterIsInstanced - }; + expr->node.type = dst_type; + expr->ptr = dst_ptr; -error: - return (ecs_iter_t){ 0 }; + node->expr = NULL; /* Prevent cleanup */ + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)expr); + + return 0; +error: + return -1; } static -bool ecs_worker_next_instanced( - ecs_iter_t *it) +int flecs_expr_interpolated_string_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); - - bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); - - ecs_iter_t *chain_it = it->chain_it; - ecs_worker_iter_t *iter = &it->priv.iter.worker; - int32_t res_count = iter->count, res_index = iter->index; - int32_t per_worker, instances_per_worker, first; - - do { - if (!ecs_iter_next(chain_it)) { - return false; - } + ecs_expr_interpolated_string_t *node = + (ecs_expr_interpolated_string_t*)*node_ptr; - /* Copy everything up to the private iterator data */ - ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); + bool can_fold = true; - /* Keep instancing setting from original iterator */ - ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); + int32_t i, e = 0, count = ecs_vec_count(&node->fragments); + char **fragments = ecs_vec_first(&node->fragments); + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + if (!fragment) { + ecs_expr_node_t **expr_ptr = ecs_vec_get_t( + &node->expressions, ecs_expr_node_t*, e ++); - int32_t count = it->count; - int32_t instance_count = it->instance_count; - per_worker = count / res_count; - instances_per_worker = instance_count / res_count; - first = per_worker * res_index; - count -= per_worker * res_count; + if (flecs_expr_visit_fold(script, expr_ptr, desc)) { + goto error; + } - if (count) { - if (res_index < count) { - per_worker ++; - first += res_index; - } else { - first += count; + if (expr_ptr[0]->kind != EcsExprValue) { + can_fold = false; } } + } - if (!per_worker && it->table == NULL) { - if (res_index == 0) { - return true; + if (can_fold) { + ecs_strbuf_t buf = ECS_STRBUF_INIT; + e = 0; + + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; + if (fragment) { + ecs_strbuf_appendstr(&buf, fragment); } else { - return false; + ecs_expr_node_t *expr = ecs_vec_get_t( + &node->expressions, ecs_expr_node_t*, e ++)[0]; + ecs_assert(expr->kind == EcsExprValue, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(expr->type == ecs_id(ecs_string_t), + ECS_INTERNAL_ERROR, NULL); + ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)expr; + ecs_strbuf_appendstr(&buf, *(char**)val->ptr); } } - } while (!per_worker); - it->instance_count = instances_per_worker; - it->frame_offset += first; + char **value = ecs_value_new(script->world, ecs_id(ecs_string_t)); + *value = ecs_strbuf_get(&buf); - offset_iter(it, it->offset + first); - it->count = per_worker; + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, ecs_id(ecs_string_t)); + result->ptr = value; - if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { - it->offset += first; - } else { - it->offset = 0; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); } - return true; + return 0; error: - return false; + return -1; } -bool ecs_worker_next( - ecs_iter_t *it) +static +int flecs_expr_initializer_pre_fold( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_expr_eval_desc_t *desc, + bool *can_fold) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + ecs_assert(elem->value != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If this is a nested initializer, don't fold it but instead fold its + * values. Because nested initializers are flattened, this ensures that + * we'll be using the correct member offsets later. */ + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_pre_fold( + script, (ecs_expr_initializer_t*)elem->value, desc, can_fold)) + { + goto error; + } + continue; + } + + if (flecs_expr_visit_fold(script, &elem->value, desc)) { + goto error; + } - ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); + if (elem->value->kind != EcsExprValue) { + *can_fold = false; + } - if (flecs_iter_next_row(it)) { - return true; + if (elem->operator) { + *can_fold = false; + } + } + + if (node->is_dynamic) { + *can_fold = false; + return 0; } - return flecs_iter_next_instanced(it, ecs_worker_next_instanced(it)); + return 0; error: - return false; + return -1; } -#include - static -int32_t count_events( - const ecs_entity_t *events) +int flecs_expr_initializer_post_fold( + ecs_script_t *script, + ecs_expr_initializer_t *node, + void *value, + ecs_size_t value_size) { - int32_t i; + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + ecs_assert(elem->value != NULL, ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < ECS_TRIGGER_DESC_EVENT_COUNT_MAX; i ++) { - if (!events[i]) { - break; + if (elem->value->kind == EcsExprInitializer) { + if (flecs_expr_initializer_post_fold( + script, (ecs_expr_initializer_t*)elem->value, value, value_size)) + { + goto error; + } + continue; + } + + if (elem->value->kind != EcsExprValue) { + flecs_expr_visit_error(script, node, + "expected value for initializer element"); + goto error; + } + + ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; + + /* Type is guaranteed to be correct, since type visitor will insert + * a cast to the type of the initializer element. */ + ecs_entity_t type = elem_value->node.type; + if (flecs_expr_initializer_validate_assign( + script, node, elem, type, value_size)) + { + goto error; + } + + if (ecs_value_copy(script->world, type, + ECS_OFFSET(value, elem->offset), elem_value->ptr)) + { + goto error; } } - return i; + return 0; +error: + return -1; } static -ecs_entity_t get_actual_event( - ecs_trigger_t *trigger, - ecs_entity_t event) +int flecs_expr_initializer_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) { - /* If operator is Not, reverse the event */ - if (trigger->term.oper == EcsNot) { - if (event == EcsOnAdd) { - event = EcsOnRemove; - } else if (event == EcsOnRemove) { - event = EcsOnAdd; + bool can_fold = true; + void *value = NULL; + + ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)*node_ptr; + + if (flecs_expr_initializer_pre_fold(script, node, desc, &can_fold)) { + goto error; + } + + /* If all elements of initializer fold to literals, initializer itself can + * be folded into a literal. */ + if (can_fold) { + value = ecs_value_new(script->world, node->node.type); + const ecs_type_info_t *type_info = ecs_get_type_info( + script->world, node->node.type); + ecs_assert(type_info != NULL, ECS_INTERNAL_ERROR, NULL); + + if (flecs_expr_initializer_post_fold( + script, node, value, type_info->size)) + { + goto error; } + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); + result->ptr = value; + value = NULL; + + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); } - return event; + return 0; +error: + if (value) { + ecs_value_free(script->world, node->node.type, value); + } + return -1; } static -void unregister_event_trigger( - ecs_event_record_t *evt, - ecs_id_t id) +int flecs_expr_identifier_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) { - if (ecs_map_remove(&evt->event_ids, id) == 0) { - ecs_map_fini(&evt->event_ids); + (void)desc; + + ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; + + ecs_expr_node_t *expr = node->expr; + if (expr) { + node->expr = NULL; + flecs_visit_fold_replace(script, node_ptr, expr); } + + return 0; } static -ecs_event_id_record_t* ensure_event_id_record( - ecs_map_t *map, - ecs_id_t id) +int flecs_expr_variable_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) { - ecs_event_id_record_t **idt = ecs_map_ensure( - map, ecs_event_id_record_t*, id); - if (!idt[0]) { - idt[0] = ecs_os_calloc_t(ecs_event_id_record_t); + (void)desc; + + ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; + + ecs_script_var_t *var = flecs_script_find_var( + desc->vars, node->name, &node->sp); + /* Should've been caught by type visitor */ + ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t type = node->node.type; + + if (var->is_const) { + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + void *value = ecs_value_new(script->world, type); + ecs_value_copy(script->world, type, value, var->value.ptr); + result->ptr = value; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); } - return idt[0]; + return 0; } static -void inc_trigger_count( - ecs_world_t *world, - ecs_entity_t event, - ecs_event_record_t *evt, - ecs_id_t id, - int32_t value) +int flecs_expr_global_variable_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) { - ecs_event_id_record_t *idt = ensure_event_id_record(&evt->event_ids, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t result = idt->trigger_count += value; - if (result == 1) { - /* Notify framework that there are triggers for the event/id. This - * allows parts of the code to skip event evaluation early */ - flecs_notify_tables(world, id, &(ecs_table_event_t){ - .kind = EcsTableTriggersForId, - .event = event - }); - } else if (result == 0) { - /* Ditto, but the reverse */ - flecs_notify_tables(world, id, &(ecs_table_event_t){ - .kind = EcsTableNoTriggersForId, - .event = event - }); + (void)desc; - /* Remove admin for id for event */ - if (!ecs_map_is_initialized(&idt->triggers) && - !ecs_map_is_initialized(&idt->set_triggers)) - { - unregister_event_trigger(evt, id); - ecs_os_free(idt); - } - } + ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; + ecs_entity_t type = node->node.type; + + /* Global const variables are always const, so we can always fold */ + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + void *value = ecs_value_new(script->world, type); + ecs_value_copy(script->world, type, value, node->global_value.ptr); + result->ptr = value; + flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); + + return 0; } static -void register_trigger_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_trigger_t *trigger, - ecs_id_t id, - size_t triggers_offset) +int flecs_expr_arguments_visit_fold( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_expr_eval_desc_t *desc) { - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t term_id = trigger->term.id; - - int i; - for (i = 0; i < trigger->event_count; i ++) { - ecs_entity_t event = get_actual_event(trigger, trigger->events[i]); - - /* Get triggers for event */ - ecs_event_record_t *evt = flecs_sparse_ensure( - events, ecs_event_record_t, event); - ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; - if (!ecs_map_is_initialized(&evt->event_ids)) { - ecs_map_init(&evt->event_ids, ecs_event_id_record_t*, 1); + if (flecs_expr_visit_fold(script, &elem->value, desc)) { + goto error; } + } - /* Get triggers for (component) id for event */ - ecs_event_id_record_t *idt = ensure_event_id_record( - &evt->event_ids, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + return 0; +error: + return -1; +} - ecs_map_t *triggers = ECS_OFFSET(idt, triggers_offset); - if (!ecs_map_is_initialized(triggers)) { - ecs_map_init(triggers, ecs_trigger_t*, 1); - } +static +int flecs_expr_function_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + ecs_expr_function_t *node = (ecs_expr_function_t*)*node_ptr; - ecs_map_ensure(triggers, ecs_trigger_t*, trigger->id)[0] = trigger; + if (flecs_expr_visit_fold(script, &node->left, desc)) { + goto error; + } - inc_trigger_count(world, event, evt, term_id, 1); - if (term_id != id) { - inc_trigger_count(world, event, evt, id, 1); + if (node->args) { + if (flecs_expr_arguments_visit_fold(script, node->args, desc)) { + goto error; } } + + return 0; +error: + return -1; } static -void register_trigger( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_trigger_t *trigger) +int flecs_expr_member_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) { - ecs_term_t *term = &trigger->term; + ecs_expr_member_t *node = (ecs_expr_member_t*)*node_ptr; - if (term->subj.set.mask & EcsSelf) { - if (term->subj.entity == EcsThis) { - register_trigger_for_id(world, observable, trigger, term->id, - offsetof(ecs_event_id_record_t, triggers)); - } else { - register_trigger_for_id(world, observable, trigger, term->id, - offsetof(ecs_event_id_record_t, entity_triggers)); - } + if (flecs_expr_visit_fold(script, &node->left, desc)) { + goto error; } - if (trigger->term.subj.set.mask & EcsSuperSet) { - ecs_id_t pair = ecs_pair(term->subj.set.relation, EcsWildcard); - register_trigger_for_id(world, observable, trigger, pair, - offsetof(ecs_event_id_record_t, set_triggers)); - } + return 0; +error: + return -1; +} - if (ECS_HAS_ROLE(term->id, SWITCH)) { - ecs_entity_t sw = term->id & ECS_COMPONENT_MASK; - ecs_id_t sw_case = ecs_case(sw, EcsWildcard); - register_trigger_for_id(world, observable, trigger, sw_case, - offsetof(ecs_event_id_record_t, triggers)); +static +int flecs_expr_element_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + ecs_expr_element_t *node = (ecs_expr_element_t*)*node_ptr; + + if (flecs_expr_visit_fold(script, &node->left, desc)) { + goto error; } - if (ECS_HAS_ROLE(term->id, CASE)) { - ecs_entity_t sw = ECS_PAIR_FIRST(term->id); - register_trigger_for_id(world, observable, trigger, ECS_SWITCH | sw, - offsetof(ecs_event_id_record_t, triggers)); + if (flecs_expr_visit_fold(script, &node->index, desc)) { + goto error; } + + return 0; +error: + return -1; } static -void unregister_trigger_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_trigger_t *trigger, - ecs_id_t id, - size_t triggers_offset) +int flecs_expr_match_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) { - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t term_id = trigger->term.id; + ecs_expr_match_t *node = (ecs_expr_match_t*)*node_ptr; - int i; - for (i = 0; i < trigger->event_count; i ++) { - ecs_entity_t event = get_actual_event(trigger, trigger->events[i]); - - /* Get triggers for event */ - ecs_event_record_t *evt = flecs_sparse_get( - events, ecs_event_record_t, event); - ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); + if (flecs_expr_visit_fold(script, &node->expr, desc)) { + goto error; + } - /* Get triggers for (component) id */ - ecs_event_id_record_t *idt = ecs_map_get_ptr( - &evt->event_ids, ecs_event_id_record_t*, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); - ecs_map_t *id_triggers = ECS_OFFSET(idt, triggers_offset); + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (flecs_expr_visit_fold(script, &elem->compare, desc)) { + goto error; + } - if (ecs_map_remove(id_triggers, trigger->id) == 0) { - ecs_map_fini(id_triggers); + if (flecs_expr_visit_fold(script, &elem->expr, desc)) { + goto error; } + } - inc_trigger_count(world, event, evt, term_id, -1); + return 0; +error: + return -1; +} - if (id != term_id) { - /* Id is different from term_id in case of a set trigger. If they're - * the same, inc_trigger_count could already have done cleanup */ - if (!ecs_map_is_initialized(&idt->triggers) && - !ecs_map_is_initialized(&idt->set_triggers) && - !idt->trigger_count) - { - unregister_event_trigger(evt, id); - } +int flecs_expr_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node_ptr, + const ecs_expr_eval_desc_t *desc) +{ + ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_expr_node_t *node = *node_ptr; - inc_trigger_count(world, event, evt, id, -1); + switch(node->kind) { + case EcsExprValue: + break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprInitializer: + case EcsExprEmptyInitializer: + if (flecs_expr_initializer_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprUnary: + if (flecs_expr_unary_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprIdentifier: + if (flecs_expr_identifier_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprVariable: + if (flecs_expr_variable_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprGlobalVariable: + if (flecs_expr_global_variable_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprFunction: + case EcsExprMethod: + if (flecs_expr_function_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprMember: + if (flecs_expr_member_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprElement: + case EcsExprComponent: + if (flecs_expr_element_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprMatch: + if (flecs_expr_match_visit_fold(script, node_ptr, desc)) { + goto error; + } + break; + case EcsExprNew: + break; + case EcsExprCast: + case EcsExprCastNumber: + if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { + goto error; } + break; } + + return 0; +error: + return -1; } -static -void unregister_trigger( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_trigger_t *trigger) -{ - ecs_term_t *term = &trigger->term; +#endif - if (term->subj.set.mask & EcsSelf) { - if (term->subj.entity == EcsThis) { - unregister_trigger_for_id(world, observable, trigger, term->id, - offsetof(ecs_event_id_record_t, triggers)); - } else { - unregister_trigger_for_id(world, observable, trigger, term->id, - offsetof(ecs_event_id_record_t, entity_triggers)); - } - } +/** + * @file addons/script/expr/visit_free.c + * @brief Visitor to free expression AST. + */ - if (term->subj.set.mask & EcsSuperSet) { - ecs_id_t pair = ecs_pair(term->subj.set.relation, EcsWildcard); - unregister_trigger_for_id(world, observable, trigger, pair, - offsetof(ecs_event_id_record_t, set_triggers)); - } - if (ECS_HAS_ROLE(term->id, SWITCH)) { - ecs_entity_t sw = term->id & ECS_COMPONENT_MASK; - ecs_id_t sw_case = ecs_case(sw, EcsWildcard); - unregister_trigger_for_id(world, observable, trigger, sw_case, - offsetof(ecs_event_id_record_t, triggers)); - } +#ifdef FLECS_SCRIPT - if (ECS_HAS_ROLE(term->id, CASE)) { - ecs_entity_t sw = ECS_PAIR_FIRST(term->id); - unregister_trigger_for_id(world, observable, trigger, ECS_SWITCH | sw, - offsetof(ecs_event_id_record_t, triggers)); +static +void flecs_expr_value_visit_free( + ecs_script_t *script, + ecs_expr_value_node_t *node) +{ + if (node->ptr != &node->storage) { + ecs_value_free(script->world, node->node.type, node->ptr); } } static -ecs_map_t* get_triggers_for_event( - const ecs_observable_t *observable, - ecs_entity_t event) +void flecs_expr_interpolated_string_visit_free( + ecs_script_t *script, + ecs_expr_interpolated_string_t *node) { - ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(event != 0, ECS_INTERNAL_ERROR, NULL); + int32_t i, count = ecs_vec_count(&node->expressions); + ecs_expr_node_t **expressions = ecs_vec_first(&node->expressions); + for (i = 0; i < count; i ++) { + flecs_expr_visit_free(script, expressions[i]); + } - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_fini_t(&flecs_script_impl(script)->allocator, + &node->fragments, char*); + ecs_vec_fini_t(&flecs_script_impl(script)->allocator, + &node->expressions, ecs_expr_node_t*); + flecs_free_n(&flecs_script_impl(script)->allocator, + char, node->buffer_size, node->buffer); +} - const ecs_event_record_t *evt = flecs_sparse_get( - events, ecs_event_record_t, event); - - if (evt) { - return (ecs_map_t*)&evt->event_ids; +static +void flecs_expr_initializer_visit_free( + ecs_script_t *script, + ecs_expr_initializer_t *node) +{ + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + flecs_expr_visit_free(script, elem->value); } -error: - return NULL; + ecs_allocator_t *a = &flecs_script_impl(script)->allocator; + ecs_vec_fini_t(a, &node->elements, ecs_expr_initializer_element_t); } static -ecs_event_id_record_t* get_triggers_for_id( - const ecs_map_t *evt, - ecs_id_t id) +void flecs_expr_unary_visit_free( + ecs_script_t *script, + ecs_expr_unary_t *node) { - return ecs_map_get_ptr(evt, ecs_event_id_record_t*, id); + flecs_expr_visit_free(script, node->expr); } -bool flecs_check_triggers_for_event( - const ecs_poly_t *object, - ecs_id_t id, - ecs_entity_t event) -{ - ecs_observable_t *observable = ecs_get_observable(object); - const ecs_map_t *evt = get_triggers_for_event(observable, event); - if (!evt) { - return false; - } - - ecs_event_id_record_t *edr = get_triggers_for_id(evt, id); - if (edr) { - return edr->trigger_count != 0; - } else { - return false; - } +static +void flecs_expr_binary_visit_free( + ecs_script_t *script, + ecs_expr_binary_t *node) +{ + flecs_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, node->right); } static -void init_iter( - ecs_iter_t *it, - bool *iter_set) +void flecs_expr_identifier_visit_free( + ecs_script_t *script, + ecs_expr_identifier_t *node) { - ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); - - if (*iter_set) { - return; - } + flecs_expr_visit_free(script, node->expr); +} - if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - it->ids = it->priv.cache.ids; - it->ids[0] = it->event_id; - return; - } +static +void flecs_expr_function_visit_free( + ecs_script_t *script, + ecs_expr_function_t *node) +{ + flecs_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, (ecs_expr_node_t*)node->args); +} - flecs_iter_init(it, flecs_iter_cache_all); - flecs_iter_validate(it); +static +void flecs_expr_member_visit_free( + ecs_script_t *script, + ecs_expr_member_t *node) +{ + flecs_expr_visit_free(script, node->left); +} - *iter_set = true; +static +void flecs_expr_element_visit_free( + ecs_script_t *script, + ecs_expr_element_t *node) +{ + flecs_expr_visit_free(script, node->left); + flecs_expr_visit_free(script, node->index); +} - it->ids[0] = it->event_id; +static +void flecs_expr_match_visit_free( + ecs_script_t *script, + ecs_expr_match_t *node) +{ + flecs_expr_visit_free(script, node->expr); - ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!it->count || it->offset < ecs_table_count(it->table), - ECS_INTERNAL_ERROR, NULL); - ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), - ECS_INTERNAL_ERROR, NULL); + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); - int32_t index = ecs_search_relation(it->real_world, it->table, 0, - it->event_id, EcsIsA, 0, 0, it->subjects, 0, 0, 0); - - if (index == -1) { - it->columns[0] = 0; - } else if (it->subjects[0]) { - it->columns[0] = -index - 1; - } else { - it->columns[0] = index + 1; + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + flecs_expr_visit_free(script, elem->compare); + flecs_expr_visit_free(script, elem->expr); } - ecs_term_t term = { - .id = it->event_id - }; - - it->term_count = 1; - it->terms = &term; - flecs_iter_populate_data(it->real_world, it, it->table, it->offset, - it->count, it->ptrs, it->sizes); + if (node->any.compare) { + flecs_expr_visit_free(script, node->any.compare); + } + if (node->any.expr) { + flecs_expr_visit_free(script, node->any.expr); + } + + ecs_vec_fini_t(&flecs_script_impl(script)->allocator, + &node->elements, ecs_expr_match_element_t); } static -bool ignore_trigger( - ecs_world_t *world, - ecs_trigger_t *t, - ecs_table_t *table) +void flecs_expr_new_visit_free( + ecs_script_t *script, + ecs_expr_new_t *node) { - int32_t *last_event_id = t->last_event_id; - if (last_event_id && last_event_id[0] == world->event_id) { - return true; + if (node->entity) { + flecs_script_visit_free_node(script, (ecs_script_node_t*)node->entity); } +} - if (!table) { - return false; - } +static +void flecs_expr_cast_visit_free( + ecs_script_t *script, + ecs_expr_cast_t *node) +{ + flecs_expr_visit_free(script, node->expr); +} - if (!t->match_prefab && (table->flags & EcsTableIsPrefab)) { - return true; +void flecs_expr_visit_free( + ecs_script_t *script, + ecs_expr_node_t *node) +{ + if (!node) { + return; } - if (!t->match_disabled && (table->flags & EcsTableIsDisabled)) { - return true; + + ecs_allocator_t *a = &flecs_script_impl(script)->allocator; + + switch(node->kind) { + case EcsExprValue: + flecs_expr_value_visit_free( + script, (ecs_expr_value_node_t*)node); + flecs_free_t(a, ecs_expr_value_node_t, node); + break; + case EcsExprInterpolatedString: + flecs_expr_interpolated_string_visit_free( + script, (ecs_expr_interpolated_string_t*)node); + flecs_free_t(a, ecs_expr_interpolated_string_t, node); + break; + case EcsExprInitializer: + case EcsExprEmptyInitializer: + flecs_expr_initializer_visit_free( + script, (ecs_expr_initializer_t*)node); + flecs_free_t(a, ecs_expr_initializer_t, node); + break; + case EcsExprUnary: + flecs_expr_unary_visit_free( + script, (ecs_expr_unary_t*)node); + flecs_free_t(a, ecs_expr_unary_t, node); + break; + case EcsExprBinary: + flecs_expr_binary_visit_free( + script, (ecs_expr_binary_t*)node); + flecs_free_t(a, ecs_expr_binary_t, node); + break; + case EcsExprIdentifier: + flecs_expr_identifier_visit_free( + script, (ecs_expr_identifier_t*)node); + flecs_free_t(a, ecs_expr_identifier_t, node); + break; + case EcsExprVariable: + case EcsExprGlobalVariable: + flecs_free_t(a, ecs_expr_variable_t, node); + break; + case EcsExprFunction: + case EcsExprMethod: + flecs_expr_function_visit_free( + script, (ecs_expr_function_t*)node); + flecs_free_t(a, ecs_expr_function_t, node); + break; + case EcsExprMember: + flecs_expr_member_visit_free( + script, (ecs_expr_member_t*)node); + flecs_free_t(a, ecs_expr_member_t, node); + break; + case EcsExprElement: + case EcsExprComponent: + flecs_expr_element_visit_free( + script, (ecs_expr_element_t*)node); + flecs_free_t(a, ecs_expr_element_t, node); + break; + case EcsExprMatch: + flecs_expr_match_visit_free( + script, (ecs_expr_match_t*)node); + flecs_free_t(a, ecs_expr_match_t, node); + break; + case EcsExprNew: + flecs_expr_new_visit_free( + script, (ecs_expr_new_t*)node); + flecs_free_t(a, ecs_expr_new_t, node); + break; + case EcsExprCast: + case EcsExprCastNumber: + flecs_expr_cast_visit_free( + script, (ecs_expr_cast_t*)node); + flecs_free_t(a, ecs_expr_cast_t, node); + break; } - - return false; } -static -void notify_self_triggers( - ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *triggers) -{ - ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); +#endif - ecs_map_iter_t mit = ecs_map_iter(triggers); - ecs_trigger_t *t; - while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { - if (ignore_trigger(world, t, it->table)) { - continue; - } +/** + * @file addons/script/expr_to_str.c + * @brief Script expression AST to string visitor. + */ + + +#ifdef FLECS_SCRIPT - ECS_BIT_COND(it->flags, EcsIterIsFilter, - t->term.inout == EcsInOutFilter); +typedef struct ecs_expr_str_visitor_t { + const ecs_script_t *script; + const ecs_world_t *world; + ecs_strbuf_t *buf; + int32_t depth; + bool newline; + bool colors; +} ecs_expr_str_visitor_t; + +static +int flecs_expr_node_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_node_t *node); + +static +void flecs_expr_color_to_str( + ecs_expr_str_visitor_t *v, + const char *color) +{ + if (v->colors) ecs_strbuf_appendstr(v->buf, color); +} - it->system = t->entity; - it->self = t->self; - it->ctx = t->ctx; - it->binding_ctx = t->binding_ctx; - it->term_index = t->term.index; - it->terms = &t->term; - t->callback(it); +static +int flecs_expr_value_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_value_node_t *node) +{ + flecs_expr_color_to_str(v, ECS_YELLOW); + + int ret = 0; + ecs_entity_t type = node->node.type; + if (ecs_has(v->world, type, EcsTypeSerializer)) { + ret = ecs_ptr_to_str_buf(v->world, type, node->ptr, v->buf); + } else { + ecs_strbuf_appendstr(v->buf, "{}"); } + + flecs_expr_color_to_str(v, ECS_NORMAL); + return ret; } static -void notify_entity_triggers( - ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *triggers) +int flecs_expr_interpolated_string_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_interpolated_string_t *node) { - ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i, e = 0, count = ecs_vec_count(&node->fragments); + char **fragments = ecs_vec_first(&node->fragments); + ecs_expr_node_t **expressions = ecs_vec_first(&node->expressions); - if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - return; - } + ecs_strbuf_appendlit(v->buf, "interpolated("); - ecs_map_iter_t mit = ecs_map_iter(triggers); - ecs_trigger_t *t; - int32_t offset = it->offset, count = it->count; - ecs_entity_t *entities = it->entities; - - ecs_entity_t dummy = 0; - it->entities = &dummy; + for (i = 0; i < count; i ++) { + char *fragment = fragments[i]; - while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { - if (ignore_trigger(world, t, it->table)) { - continue; + if (i) { + ecs_strbuf_appendlit(v->buf, ", "); } - int32_t i, entity_count = it->count; - for (i = 0; i < entity_count; i ++) { - if (entities[i] != t->term.subj.entity) { - continue; + if (fragment) { + flecs_expr_color_to_str(v, ECS_YELLOW); + ecs_strbuf_appendlit(v->buf, "\""); + ecs_strbuf_appendstr(v->buf, fragment); + ecs_strbuf_appendlit(v->buf, "\""); + flecs_expr_color_to_str(v, ECS_NORMAL); + } else { + ecs_expr_node_t *expr = expressions[e ++]; + if (flecs_expr_node_to_str(v, expr)) { + return -1; } - - ECS_BIT_COND(it->flags, EcsIterIsFilter, - t->term.inout == EcsInOutFilter); - it->system = t->entity; - it->self = t->self; - it->ctx = t->ctx; - it->binding_ctx = t->binding_ctx; - it->term_index = t->term.index; - it->terms = &t->term; - it->offset = i; - it->count = 1; - it->subjects[0] = entities[i]; - - t->callback(it); } } - it->offset = offset; - it->count = count; - it->entities = entities; - it->subjects[0] = 0; + ecs_strbuf_appendlit(v->buf, ")"); + + return 0; } static -void notify_set_base_triggers( - ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *triggers) +int flecs_expr_unary_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_unary_t *node) { - ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_strbuf_appendstr(v->buf, flecs_token_str(node->operator)); - ecs_entity_t event_id = it->event_id; - ecs_entity_t rel = ECS_PAIR_FIRST(event_id); - ecs_entity_t obj = ecs_pair_second(world, event_id); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); - ecs_record_t *obj_record = ecs_eis_get(world, obj); - if (!obj_record) { - return; + if (flecs_expr_node_to_str(v, node->expr)) { + goto error; } - ecs_table_t *obj_table = obj_record->table; - if (!obj_table) { - return; - } + return 0; +error: + return -1; +} - ecs_map_iter_t mit = ecs_map_iter(triggers); - ecs_trigger_t *t; - while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { - if (ignore_trigger(world, t, it->table)) { - continue; - } +static +int flecs_expr_initializer_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_initializer_t *node) +{ + ecs_strbuf_appendlit(v->buf, "{"); - ecs_term_t *term = &t->term; - ecs_id_t id = term->id; - int32_t column = ecs_search_relation(world, obj_table, 0, id, rel, - 0, 0, it->subjects, it->ids, 0, 0); - - bool result = column != -1; - if (term->oper == EcsNot) { - result = !result; - } - if (!result) { - continue; + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + if (i) { + ecs_strbuf_appendstr(v->buf, ", "); } - if (!term->subj.set.min_depth && flecs_get_table_record( - world, it->table, id) != NULL) - { - continue; + ecs_expr_initializer_element_t *elem = &elems[i]; + if (elem->member) { + ecs_strbuf_appendstr(v->buf, elem->member); + ecs_strbuf_appendlit(v->buf, ":"); } - if (!ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - if (!it->subjects[0]) { - it->subjects[0] = obj; - } - - /* Populate pointer from object */ - int32_t s_column = ecs_table_type_to_storage_index( - obj_table, column); - if (s_column != -1) { - ecs_column_t *c = &obj_table->storage.columns[s_column]; - int32_t row = ECS_RECORD_TO_ROW(obj_record->row); - ecs_type_info_t *ti = &obj_table->type_info[s_column]; - void *ptr = ecs_vector_get_t(c->data, ti->size, ti->alignment, row); - it->ptrs[0] = ptr; - it->sizes[0] = ti->size; - } + if (flecs_expr_node_to_str(v, elem->value)) { + goto error; } - - ECS_BIT_COND(it->flags, EcsIterIsFilter, - t->term.inout == EcsInOutFilter); - it->event_id = t->term.id; - it->system = t->entity; - it->self = t->self; - it->ctx = t->ctx; - it->binding_ctx = t->binding_ctx; - it->term_index = t->term.index; - it->terms = &t->term; - - t->callback(it); } + + ecs_strbuf_appendlit(v->buf, "}"); + + return 0; +error: + return -1; } static -void notify_set_triggers( - ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *triggers) +int flecs_expr_binary_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_binary_t *node) { - ecs_assert(triggers != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); + ecs_strbuf_appendlit(v->buf, "("); - if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - return; + if (flecs_expr_node_to_str(v, node->left)) { + goto error; } - ecs_map_iter_t mit = ecs_map_iter(triggers); - ecs_trigger_t *t; - while ((t = ecs_map_next_ptr(&mit, ecs_trigger_t*, NULL))) { - if (!ecs_id_match(it->event_id, t->term.id)) { - continue; - } - - if (ignore_trigger(world, t, it->table)) { - continue; - } + ecs_strbuf_appendlit(v->buf, " "); - ecs_entity_t subj = it->entities[0]; - int32_t i, count = it->count; - ecs_entity_t term_subj = t->term.subj.entity; + ecs_strbuf_appendstr(v->buf, flecs_token_str(node->operator)); - /* If trigger is for a specific entity, make sure it is in the table - * being triggered for */ - if (term_subj != EcsThis) { - for (i = 0; i < count; i ++) { - if (it->entities[i] == term_subj) { - break; - } - } + ecs_strbuf_appendlit(v->buf, " "); - if (i == count) { - continue; - } + if (flecs_expr_node_to_str(v, node->right)) { + goto error; + } - /* If the entity matches, trigger for no other entities */ - it->entities[0] = 0; - it->count = 1; - } + ecs_strbuf_appendlit(v->buf, ")"); - if (flecs_term_match_table(world, &t->term, it->table, it->type, - it->ids, it->columns, it->subjects, NULL, true, it->flags)) - { - if (!it->subjects[0]) { - /* Do not match owned components */ - continue; - } + return 0; +error: + return -1; +} - ECS_BIT_COND(it->flags, EcsIterIsFilter, - t->term.inout == EcsInOutFilter); - it->system = t->entity; - it->self = t->self; - it->ctx = t->ctx; - it->binding_ctx = t->binding_ctx; - it->term_index = t->term.index; - it->terms = &t->term; - - /* Triggers for supersets can be instanced */ - bool instanced = t->instanced; - bool is_filter = ECS_BIT_IS_SET(it->flags, EcsIterIsFilter); - if (it->count == 1 || instanced || is_filter || !it->sizes[0]) { - ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); - t->callback(it); - ECS_BIT_CLEAR(it->flags, EcsIterIsInstanced); - } else { - ecs_entity_t *entities = it->entities; - it->count = 1; - for (i = 0; i < count; i ++) { - it->entities = &entities[i]; - t->callback(it); - } - it->entities = entities; - } - } +static +int flecs_expr_identifier_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_identifier_t *node) +{ + ecs_strbuf_appendlit(v->buf, "@"); + ecs_strbuf_appendstr(v->buf, node->value); + return 0; +} - it->entities[0] = subj; - it->count = count; - } +static +int flecs_expr_variable_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_variable_t *node) +{ + flecs_expr_color_to_str(v, ECS_GREEN); + ecs_strbuf_appendlit(v->buf, "$"); + ecs_strbuf_appendstr(v->buf, node->name); + flecs_expr_color_to_str(v, ECS_NORMAL); + return 0; } static -void notify_triggers_for_id( - ecs_world_t *world, - const ecs_map_t *evt, - ecs_id_t event_id, - ecs_iter_t *it, - bool *iter_set) +int flecs_expr_member_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_member_t *node) { - const ecs_event_id_record_t *idt = get_triggers_for_id(evt, event_id); - if (!idt) { - return; + if (flecs_expr_node_to_str(v, node->left)) { + return -1; } - if (ecs_map_is_initialized(&idt->triggers)) { - init_iter(it, iter_set); - notify_self_triggers(world, it, &idt->triggers); - } - if (ecs_map_is_initialized(&idt->entity_triggers)) { - init_iter(it, iter_set); - notify_entity_triggers(world, it, &idt->entity_triggers); - } - if (ecs_map_is_initialized(&idt->set_triggers)) { - init_iter(it, iter_set); - notify_set_base_triggers(world, it, &idt->set_triggers); - } + ecs_strbuf_appendlit(v->buf, "."); + ecs_strbuf_appendstr(v->buf, node->member_name); + return 0; } static -void notify_set_triggers_for_id( - ecs_world_t *world, - const ecs_map_t *evt, - ecs_iter_t *it, - bool *iter_set, - ecs_id_t set_id) +int flecs_expr_function_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_function_t *node) { - const ecs_event_id_record_t *idt = get_triggers_for_id(evt, set_id); - if (idt && ecs_map_is_initialized(&idt->set_triggers)) { - init_iter(it, iter_set); - notify_set_triggers(world, it, &idt->set_triggers); + if (node->left) { + if (flecs_expr_node_to_str(v, node->left)) { + return -1; + } + ecs_strbuf_appendlit(v->buf, "."); + } + + ecs_strbuf_append(v->buf, "%s(", node->function_name); + + if (node->args) { + if (flecs_expr_node_to_str(v, (ecs_expr_node_t*)node->args)) { + return -1; + } } + + ecs_strbuf_append(v->buf, ")"); + return 0; } static -void trigger_yield_existing( - ecs_world_t *world, - ecs_trigger_t *trigger) +int flecs_expr_element_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_element_t *node) { - ecs_iter_action_t callback = trigger->callback; - - /* If yield existing is enabled, trigger for each thing that matches - * the event, if the event is iterable. */ - int i, count = trigger->event_count; - for (i = 0; i < count; i ++) { - ecs_entity_t evt = trigger->events[i]; - const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); - if (!iterable) { - continue; - } + if (flecs_expr_node_to_str(v, node->left)) { + return -1; + } - ecs_iter_t it; - iterable->init(world, world, &it, &trigger->term); - it.system = trigger->entity; - it.ctx = trigger->ctx; - it.binding_ctx = trigger->binding_ctx; - it.event = evt; - - ecs_iter_next_action_t next = it.next; - ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); - while (next(&it)) { - it.event_id = it.ids[0]; - callback(&it); - } + ecs_strbuf_appendlit(v->buf, "["); + if (flecs_expr_node_to_str(v, node->index)) { + return -1; } + ecs_strbuf_appendlit(v->buf, "]"); + return 0; } -void flecs_triggers_notify( - ecs_iter_t *it, - ecs_observable_t *observable, - ecs_ids_t *ids, - ecs_entity_t event) +static +int flecs_expr_match_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_match_t *node) { - ecs_assert(ids != NULL && ids->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ids->array != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t events[2] = {event, EcsWildcard}; - int32_t e, i, ids_count = ids->count; - ecs_id_t *ids_array = ids->array; - ecs_world_t *world = it->real_world; - - for (e = 0; e < 2; e ++) { - event = events[e]; - const ecs_map_t *evt = get_triggers_for_event(observable, event); - if (!evt) { - continue; + if (node->node.type) { + flecs_expr_color_to_str(v, ECS_BLUE); + const char *name = ecs_get_name(v->world, node->node.type); + if (name) { + ecs_strbuf_appendstr(v->buf, name); + } else { + char *path = ecs_get_path(v->world, node->node.type); + ecs_strbuf_appendstr(v->buf, path); + ecs_os_free(path); } + flecs_expr_color_to_str(v, ECS_NORMAL); + ecs_strbuf_appendlit(v->buf, "("); + } - it->event = event; - - for (i = 0; i < ids_count; i ++) { - ecs_id_t id = ids_array[i]; - ecs_entity_t role = id & ECS_ROLE_MASK; - bool iter_set = false; + flecs_expr_color_to_str(v, ECS_BLUE); + ecs_strbuf_appendlit(v->buf, "match "); + flecs_expr_color_to_str(v, ECS_GREEN); + if (flecs_expr_node_to_str(v, node->expr)) { + return -1; + } - it->event_id = id; + ecs_strbuf_appendlit(v->buf, " {\n"); - notify_triggers_for_id(world, evt, id, it, &iter_set); + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); - if (role == ECS_PAIR || role == ECS_CASE) { - ecs_entity_t pred = ECS_PAIR_FIRST(id); - ecs_entity_t obj = ECS_PAIR_SECOND(id); + for (i = 0; i < count; i ++) { + ecs_strbuf_appendlit(v->buf, " "); - ecs_id_t tid = role | ecs_entity_t_comb(EcsWildcard, pred); - notify_triggers_for_id(world, evt, tid, it, &iter_set); + ecs_expr_match_element_t *elem = &elems[i]; + if (flecs_expr_node_to_str(v, elem->compare)) { + return -1; + } - tid = role | ecs_entity_t_comb(obj, EcsWildcard); - notify_triggers_for_id(world, evt, tid, it, &iter_set); - - tid = role | ecs_entity_t_comb(EcsWildcard, EcsWildcard); - notify_triggers_for_id(world, evt, tid, it, &iter_set); - } else { - notify_triggers_for_id(world, evt, EcsWildcard, it, &iter_set); - } + ecs_strbuf_appendlit(v->buf, ": "); - if (iter_set) { - ecs_iter_fini(it); - } + if (flecs_expr_node_to_str(v, elem->expr)) { + return -1; } + + ecs_strbuf_appendlit(v->buf, "\n"); } -} -void flecs_set_triggers_notify( - ecs_iter_t *it, - ecs_observable_t *observable, - ecs_ids_t *ids, - ecs_entity_t event, - ecs_id_t set_id) -{ - ecs_assert(ids != NULL && ids->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ids->array != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t events[2] = {event, EcsWildcard}; - int32_t e, i, ids_count = ids->count; - ecs_id_t *ids_array = ids->array; - ecs_world_t *world = it->real_world; + ecs_strbuf_appendlit(v->buf, "}"); - for (e = 0; e < 2; e ++) { - event = events[e]; - const ecs_map_t *evt = get_triggers_for_event(observable, event); - if (!evt) { - continue; - } + if (node->node.type) { + ecs_strbuf_appendlit(v->buf, ")"); + } - it->event = event; + ecs_strbuf_appendlit(v->buf, "\n"); - for (i = 0; i < ids_count; i ++) { - ecs_id_t id = ids_array[i]; - bool iter_set = false; + return 0; +} - it->event_id = id; +static +int flecs_expr_new_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_new_t *node) +{ + flecs_expr_color_to_str(v, ECS_BLUE); + ecs_strbuf_appendlit(v->buf, "new "); + flecs_expr_color_to_str(v, ECS_NORMAL); - notify_set_triggers_for_id(world, evt, it, &iter_set, set_id); + ecs_assert(node->entity != NULL, ECS_INTERNAL_ERROR, NULL); - if (iter_set) { - ecs_iter_fini(it); - } - } - } + return ecs_script_ast_node_to_buf(v->script, + (ecs_script_node_t*)node->entity, v->buf, v->colors, v->depth + 1); } -ecs_entity_t ecs_trigger_init( - ecs_world_t *world, - const ecs_trigger_desc_t *desc) +static +int flecs_expr_cast_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_cast_t *node) { - char *name = NULL; + ecs_entity_t type = node->node.type; - ecs_poly_assert(world, ecs_world_t); - ecs_check(!world->is_readonly, ECS_INVALID_OPERATION, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(!world->is_fini, ECS_INVALID_OPERATION, NULL); - - const char *expr = desc->expr; - ecs_trigger_t *trigger = NULL; - - ecs_observable_t *observable = desc->observable; - if (!observable) { - observable = ecs_get_observable(world); + flecs_expr_color_to_str(v, ECS_BLUE); + const char *name = ecs_get_name(v->world, type); + if (name) { + ecs_strbuf_appendstr(v->buf, name); + } else { + char *path = ecs_get_path(v->world, type); + ecs_strbuf_appendstr(v->buf, path); + ecs_os_free(path); } + flecs_expr_color_to_str(v, ECS_NORMAL); + ecs_strbuf_appendlit(v->buf, "("); - /* If entity is provided, create it */ - ecs_entity_t existing = desc->entity.entity; - ecs_entity_t entity = ecs_entity_init(world, &desc->entity); - if (!existing && !desc->entity.name) { - ecs_add_pair(world, entity, EcsChildOf, EcsFlecsHidden); + if (flecs_expr_node_to_str(v, node->expr)) { + return -1; } - bool added = false; - EcsTrigger *comp = ecs_get_mut(world, entity, EcsTrigger, &added); - if (added) { - ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); - - /* Something went wrong with the construction of the entity */ - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - name = ecs_get_fullpath(world, entity); + ecs_strbuf_append(v->buf, ")"); - ecs_term_t term; - if (expr) { - #ifdef FLECS_PARSER - const char *ptr = ecs_parse_term(world, name, expr, expr, &term); - if (!ptr) { - goto error; - } + return 0; +} - if (!ecs_term_is_initialized(&term)) { - ecs_parser_error( - name, expr, 0, "invalid empty trigger expression"); - goto error; - } +static +int flecs_expr_node_to_str( + ecs_expr_str_visitor_t *v, + const ecs_expr_node_t *node) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - if (ptr[0]) { - ecs_parser_error(name, expr, 0, - "too many terms in trigger expression (expected 1)"); - goto error; - } - #else - ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); - #endif - } else { - term = ecs_term_copy(&desc->term); + switch(node->kind) { + case EcsExprValue: + if (flecs_expr_value_to_str(v, + (const ecs_expr_value_node_t*)node)) + { + goto error; } - - if (ecs_term_finalize(world, name, &term)) { - ecs_term_fini(&term); + break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_to_str(v, + (const ecs_expr_interpolated_string_t*)node)) + { goto error; } - - trigger = flecs_sparse_add(world->triggers, ecs_trigger_t); - trigger->id = flecs_sparse_last_id(world->triggers); - - trigger->term = ecs_term_move(&term); - trigger->callback = desc->callback; - trigger->ctx = desc->ctx; - trigger->binding_ctx = desc->binding_ctx; - trigger->ctx_free = desc->ctx_free; - trigger->binding_ctx_free = desc->binding_ctx_free; - trigger->event_count = count_events(desc->events); - ecs_os_memcpy(trigger->events, desc->events, - trigger->event_count * ECS_SIZEOF(ecs_entity_t)); - trigger->entity = entity; - trigger->self = desc->self; - trigger->observable = observable; - trigger->match_prefab = desc->match_prefab; - trigger->match_disabled = desc->match_disabled; - trigger->instanced = desc->instanced; - trigger->last_event_id = desc->last_event_id; - - if (trigger->term.id == EcsPrefab) { - trigger->match_prefab = true; + break; + case EcsExprInitializer: + case EcsExprEmptyInitializer: + if (flecs_expr_initializer_to_str(v, + (const ecs_expr_initializer_t*)node)) + { + goto error; } - if (trigger->term.id == EcsDisabled) { - trigger->match_disabled = true; + break; + case EcsExprUnary: + if (flecs_expr_unary_to_str(v, + (const ecs_expr_unary_t*)node)) + { + goto error; } - - comp->trigger = trigger; - - /* Trigger must have at least one event */ - ecs_check(trigger->event_count != 0, ECS_INVALID_PARAMETER, NULL); - - register_trigger(world, observable, trigger); - - ecs_term_fini(&term); - - if (desc->entity.name) { - ecs_trace("#[green]trigger#[reset] %s created", - ecs_get_name(world, entity)); + break; + case EcsExprBinary: + if (flecs_expr_binary_to_str(v, + (const ecs_expr_binary_t*)node)) + { + goto error; } - - if (desc->yield_existing) { - trigger_yield_existing(world, trigger); + break; + case EcsExprIdentifier: + if (flecs_expr_identifier_to_str(v, + (const ecs_expr_identifier_t*)node)) + { + goto error; } - } else { - ecs_assert(comp->trigger != NULL, ECS_INTERNAL_ERROR, NULL); - - /* If existing entity handle was provided, override existing params */ - if (existing) { - if (desc->callback) { - ((ecs_trigger_t*)comp->trigger)->callback = desc->callback; - } - if (desc->ctx) { - ((ecs_trigger_t*)comp->trigger)->ctx = desc->ctx; - } - if (desc->binding_ctx) { - ((ecs_trigger_t*)comp->trigger)->binding_ctx = desc->binding_ctx; - } + break; + case EcsExprVariable: + case EcsExprGlobalVariable: + if (flecs_expr_variable_to_str(v, + (const ecs_expr_variable_t*)node)) + { + goto error; + } + break; + case EcsExprFunction: + case EcsExprMethod: + if (flecs_expr_function_to_str(v, + (const ecs_expr_function_t*)node)) + { + goto error; + } + break; + case EcsExprMember: + if (flecs_expr_member_to_str(v, + (const ecs_expr_member_t*)node)) + { + goto error; + } + break; + case EcsExprElement: + case EcsExprComponent: + if (flecs_expr_element_to_str(v, + (const ecs_expr_element_t*)node)) + { + goto error; + } + break; + case EcsExprMatch: + if (flecs_expr_match_to_str(v, + (const ecs_expr_match_t*)node)) + { + goto error; + } + break; + case EcsExprNew: + if (flecs_expr_new_to_str(v, + (const ecs_expr_new_t*)node)) + { + goto error; + } + break; + case EcsExprCast: + case EcsExprCastNumber: + if (flecs_expr_cast_to_str(v, + (const ecs_expr_cast_t*)node)) + { + goto error; } + break; + default: + ecs_abort(ECS_INTERNAL_ERROR, "invalid node kind"); } - ecs_os_free(name); - return entity; -error: - ecs_os_free(name); - ecs_delete(world, entity); return 0; +error: + return -1; } -void* ecs_get_trigger_ctx( - const ecs_world_t *world, - ecs_entity_t trigger) -{ - const EcsTrigger *t = ecs_get(world, trigger, EcsTrigger); - if (t) { - return t->trigger->ctx; - } else { - return NULL; - } -} - -void* ecs_get_trigger_binding_ctx( - const ecs_world_t *world, - ecs_entity_t trigger) +void flecs_expr_to_str_buf( + const ecs_script_t *script, + const ecs_expr_node_t *expr, + ecs_strbuf_t *buf, + bool colors) { - const EcsTrigger *t = ecs_get(world, trigger, EcsTrigger); - if (t) { - return t->trigger->binding_ctx; - } else { - return NULL; - } -} + ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); -void flecs_trigger_fini( - ecs_world_t *world, - ecs_trigger_t *trigger) -{ - unregister_trigger(world, trigger->observable, trigger); - ecs_term_fini(&trigger->term); - - if (trigger->ctx_free) { - trigger->ctx_free(trigger->ctx); - } + ecs_world_t *world = script->world; + ecs_expr_str_visitor_t v = { + .script = script, + .world = world, + .buf = buf, + .colors = colors + }; - if (trigger->binding_ctx_free) { - trigger->binding_ctx_free(trigger->binding_ctx); + if (flecs_expr_node_to_str(&v, expr)) { + ecs_strbuf_reset(buf); } - - flecs_sparse_remove(world->triggers, trigger->id); } -#include - -#ifndef FLECS_NDEBUG -static int64_t s_min[] = { - [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; -static int64_t s_max[] = { - [1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; -static uint64_t u_max[] = { - [1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; +#endif -uint64_t _flecs_ito( - size_t size, - bool is_signed, - bool lt_zero, - uint64_t u, - const char *err) -{ - union { - uint64_t u; - int64_t s; - } v; +/** + * @file addons/script/expr_ast.c + * @brief Script expression AST implementation. + */ - v.u = u; - if (is_signed) { - ecs_assert(v.s >= s_min[size], ECS_INVALID_CONVERSION, err); - ecs_assert(v.s <= s_max[size], ECS_INVALID_CONVERSION, err); - } else { - ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); - ecs_assert(u <= u_max[size], ECS_INVALID_CONVERSION, err); - } +#ifdef FLECS_SCRIPT - return u; -} -#endif +static +int flecs_expr_visit_type_priv( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc); -int32_t flecs_next_pow_of_2( - int32_t n) +bool flecs_expr_is_type_integer( + ecs_entity_t type) { - n --; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - n |= n >> 16; - n ++; - - return n; + if (type == ecs_id(ecs_u8_t)) return true; + else if (type == ecs_id(ecs_u16_t)) return true; + else if (type == ecs_id(ecs_u32_t)) return true; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_uptr_t)) return true; + else if (type == ecs_id(ecs_i8_t)) return true; + else if (type == ecs_id(ecs_i16_t)) return true; + else if (type == ecs_id(ecs_i32_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_iptr_t)) return true; + else return false; } -/** Convert time to double */ -double ecs_time_to_double( - ecs_time_t t) +bool flecs_expr_is_type_number( + ecs_entity_t type) { - double result; - result = t.sec; - return result + (double)t.nanosec / (double)1000000000; + if (flecs_expr_is_type_integer(type)) return true; + else if (type == ecs_id(ecs_f32_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return true; + else return false; } -ecs_time_t ecs_time_sub( - ecs_time_t t1, - ecs_time_t t2) +/* Returns how expressive a type is. This is used to determine whether an + * implicit cast is allowed, where only casts from less to more expressive types + * are valid. */ +static +int32_t flecs_expr_expressiveness_score( + ecs_entity_t type) { - ecs_time_t result; + if (type == ecs_id(ecs_bool_t)) return 1; + else if (type == ecs_id(ecs_char_t)) return 2; - if (t1.nanosec >= t2.nanosec) { - result.nanosec = t1.nanosec - t2.nanosec; - result.sec = t1.sec - t2.sec; - } else { - result.nanosec = t1.nanosec - t2.nanosec + 1000000000; - result.sec = t1.sec - t2.sec - 1; - } + else if (type == ecs_id(ecs_u8_t)) return 2; + else if (type == ecs_id(ecs_u16_t)) return 3; + else if (type == ecs_id(ecs_u32_t)) return 4; + else if (type == ecs_id(ecs_uptr_t)) return 5; + else if (type == ecs_id(ecs_u64_t)) return 6; - return result; -} + else if (type == ecs_id(ecs_i8_t)) return 7; + else if (type == ecs_id(ecs_i16_t)) return 8; + else if (type == ecs_id(ecs_i32_t)) return 9; + else if (type == ecs_id(ecs_iptr_t)) return 10; + else if (type == ecs_id(ecs_i64_t)) return 11; -void ecs_sleepf( - double t) -{ - if (t > 0) { - int sec = (int)t; - int nsec = (int)((t - sec) * 1000000000); - ecs_os_sleep(sec, nsec); - } -} + else if (type == ecs_id(ecs_f32_t)) return 12; + else if (type == ecs_id(ecs_f64_t)) return 13; -double ecs_time_measure( - ecs_time_t *start) -{ - ecs_time_t stop, temp; - ecs_os_get_time(&stop); - temp = stop; - stop = ecs_time_sub(stop, *start); - *start = temp; - return ecs_time_to_double(stop); + else if (type == ecs_id(ecs_string_t)) return -1; + else if (type == ecs_id(ecs_entity_t)) return -1; + else return false; } -void* ecs_os_memdup( - const void *src, - ecs_size_t size) +/* Returns a score based on the storage size of a type. This is used in + * combination with expressiveness to determine whether a type can be implicitly + * casted. An implicit cast is only valid if the destination type is both more + * expressive and has a larger storage size. */ +static +ecs_size_t flecs_expr_storage_score( + ecs_entity_t type) { - if (!src) { - return NULL; - } - - void *dst = ecs_os_malloc(size); - ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_os_memcpy(dst, src, size); - return dst; -} + if (type == ecs_id(ecs_bool_t)) return 1; + else if (type == ecs_id(ecs_char_t)) return 1; -int flecs_entity_compare( - ecs_entity_t e1, - const void *ptr1, - ecs_entity_t e2, - const void *ptr2) -{ - (void)ptr1; - (void)ptr2; - return (e1 > e2) - (e1 < e2); -} + /* Unsigned integers have a larger storage size than signed integers, since + * the unsigned range of a signed integer is smaller. */ + else if (type == ecs_id(ecs_u8_t)) return 2; + else if (type == ecs_id(ecs_u16_t)) return 3; + else if (type == ecs_id(ecs_u32_t)) return 4; + else if (type == ecs_id(ecs_uptr_t)) return 6; + else if (type == ecs_id(ecs_u64_t)) return 7; -int flecs_entity_compare_qsort( - const void *e1, - const void *e2) -{ - ecs_entity_t v1 = *(ecs_entity_t*)e1; - ecs_entity_t v2 = *(ecs_entity_t*)e2; - return flecs_entity_compare(v1, NULL, v2, NULL); + else if (type == ecs_id(ecs_i8_t)) return 1; + else if (type == ecs_id(ecs_i16_t)) return 2; + else if (type == ecs_id(ecs_i32_t)) return 3; + else if (type == ecs_id(ecs_iptr_t)) return 5; + else if (type == ecs_id(ecs_i64_t)) return 6; + + /* Floating points have a smaller storage score, since the largest integer + * that can be represented exactly is lower than the actual storage size. */ + else if (type == ecs_id(ecs_f32_t)) return 3; + else if (type == ecs_id(ecs_f64_t)) return 4; + + else if (type == ecs_id(ecs_string_t)) return -1; + else if (type == ecs_id(ecs_entity_t)) return -1; + else return false; } -uint64_t flecs_string_hash( - const void *ptr) +/** Returns the storage size for primitive type */ +ecs_size_t flecs_expr_storage_size( + ecs_entity_t type) { - const ecs_hashed_string_t *str = ptr; - ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); - return str->hash; -} + if (type == ecs_id(ecs_bool_t)) return ECS_SIZEOF(ecs_bool_t); + else if (type == ecs_id(ecs_char_t)) return ECS_SIZEOF(ecs_char_t); -/* - This code was taken from sokol_time.h - - zlib/libpng license - Copyright (c) 2018 Andre Weissflog - This software is provided 'as-is', without any express or implied warranty. - In no event will the authors be held liable for any damages arising from the - use of this software. - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software in a - product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. - 3. This notice may not be removed or altered from any source - distribution. -*/ + /* Unsigned integers have a larger storage size than signed integers, since + * the unsigned range of a signed integer is smaller. */ + else if (type == ecs_id(ecs_u8_t)) return ECS_SIZEOF(ecs_u8_t); + else if (type == ecs_id(ecs_u16_t)) return ECS_SIZEOF(ecs_u16_t); + else if (type == ecs_id(ecs_u32_t)) return ECS_SIZEOF(ecs_u32_t); + else if (type == ecs_id(ecs_uptr_t)) return ECS_SIZEOF(ecs_uptr_t); + else if (type == ecs_id(ecs_u64_t)) return ECS_SIZEOF(ecs_u64_t); + else if (type == ecs_id(ecs_i8_t)) return ECS_SIZEOF(ecs_i8_t); + else if (type == ecs_id(ecs_i16_t)) return ECS_SIZEOF(ecs_i16_t); + else if (type == ecs_id(ecs_i32_t)) return ECS_SIZEOF(ecs_i32_t); + else if (type == ecs_id(ecs_iptr_t)) return ECS_SIZEOF(ecs_iptr_t); + else if (type == ecs_id(ecs_i64_t)) return ECS_SIZEOF(ecs_i64_t); -/* -- Component lifecycle -- */ + /* Floating points have a smaller storage score, since the largest integer + * that can be represented exactly is lower than the actual storage size. */ + else if (type == ecs_id(ecs_f32_t)) return ECS_SIZEOF(ecs_f32_t); + else if (type == ecs_id(ecs_f64_t)) return ECS_SIZEOF(ecs_f64_t); -/* Component lifecycle actions for EcsIdentifier */ -static ECS_DTOR(EcsIdentifier, ptr, { - ecs_os_strset(&ptr->value, NULL); -}) + else if (type == ecs_id(ecs_string_t)) return ECS_SIZEOF(ecs_string_t); + else if (type == ecs_id(ecs_entity_t)) return ECS_SIZEOF(ecs_entity_t); + else return false; +} -static ECS_COPY(EcsIdentifier, dst, src, { - ecs_os_strset(&dst->value, src->value); - dst->hash = src->hash; - dst->length = src->length; - dst->index_hash = src->index_hash; - dst->index = src->index; -}) +/* This function returns true if an type can be casted without changing the + * precision of the value. It is used to determine a type for operands in a + * binary expression in case they are of different types. */ +static +bool flecs_expr_implicit_cast_allowed( + ecs_entity_t from, + ecs_entity_t to) +{ + int32_t from_e = flecs_expr_expressiveness_score(from); + int32_t to_e = flecs_expr_expressiveness_score(to); + if (from_e == -1 || to_e == -1) { + return false; + } -static ECS_MOVE(EcsIdentifier, dst, src, { - ecs_os_strset(&dst->value, NULL); - dst->value = src->value; - dst->hash = src->hash; - dst->length = src->length; - dst->index_hash = src->index_hash; - dst->index = src->index; + if (to_e >= from_e) { + return flecs_expr_storage_score(to) >= flecs_expr_storage_score(from); + } - src->value = NULL; - src->hash = 0; - src->index_hash = 0; - src->index = 0; - src->length = 0; -}) + return false; +} static -void ecs_on_set(EcsIdentifier)(ecs_iter_t *it) { - EcsIdentifier *ptr = ecs_term(it, EcsIdentifier, 1); - - ecs_world_t *world = it->real_world; - ecs_entity_t evt = it->event; - ecs_id_t evt_id = it->event_id; - ecs_entity_t kind = ECS_PAIR_SECOND(evt_id); /* Name, Symbol, Alias */ +ecs_entity_t flecs_expr_cast_to_lvalue( + ecs_entity_t lvalue, + ecs_entity_t operand) +{ + if (flecs_expr_implicit_cast_allowed(operand, lvalue)) { + return lvalue; + } - ecs_id_t pair = ecs_childof(0); + return operand; +} - ecs_hashmap_t *name_index = NULL; - if (kind == EcsSymbol) { - name_index = &world->symbols; - } else if (kind == EcsAlias) { - name_index = &world->aliases; - } else if (kind == EcsName) { - ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair); - ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL); +static +ecs_entity_t flecs_expr_narrow_type( + ecs_entity_t lvalue, + ecs_expr_node_t *node) +{ + ecs_entity_t type = node->type; - if (evt == EcsOnSet) { - name_index = flecs_ensure_id_name_index(world, pair); - } else { - name_index = flecs_get_id_name_index(world, pair); - } + if (node->kind != EcsExprValue) { + return type; } - int i, count = it->count; - for (i = 0; i < count; i ++) { - EcsIdentifier *cur = &ptr[i]; - uint64_t hash; - ecs_size_t len; - const char *name = cur->value; + if (!flecs_expr_is_type_number(type)) { + return type; + } - if (cur->index && cur->index != name_index) { - /* If index doesn't match up, the value must have been copied from - * another entity, so reset index & cached index hash */ - cur->index = NULL; - cur->index_hash = 0; - } + void *ptr = ((ecs_expr_value_node_t*)node)->ptr; - if (cur->value && (evt == EcsOnSet)) { - len = cur->length = ecs_os_strlen(name); - hash = cur->hash = flecs_hash(name, len); + uint64_t uval; + + if (type == ecs_id(ecs_u8_t)) { + uval = *(ecs_u8_t*)ptr; + } else if (type == ecs_id(ecs_u16_t)) { + uval = *(ecs_u16_t*)ptr; + } else if (type == ecs_id(ecs_u32_t)) { + uval = *(ecs_u32_t*)ptr; + } else if (type == ecs_id(ecs_u64_t)) { + uval = *(ecs_u32_t*)ptr; + } else { + int64_t ival; + + if (type == ecs_id(ecs_i8_t)) { + ival = *(ecs_i8_t*)ptr; + } else if (type == ecs_id(ecs_i16_t)) { + ival = *(ecs_i16_t*)ptr; + } else if (type == ecs_id(ecs_i32_t)) { + ival = *(ecs_i32_t*)ptr; + } else if (type == ecs_id(ecs_i64_t)) { + ival = *(ecs_i64_t*)ptr; } else { - len = cur->length = 0; - hash = cur->hash = 0; - cur->index = NULL; + /* If the lvalue type is a floating point type we can narrow the + * literal to that since we'll lose double precision anyway. */ + if (lvalue == ecs_id(ecs_f32_t)) { + return ecs_id(ecs_f32_t); + } + return type; } - if (name_index) { - uint64_t index_hash = cur->index_hash; - ecs_entity_t e = it->entities[i]; - - if (hash != index_hash) { - if (index_hash) { - flecs_name_index_remove(name_index, e, index_hash); - } - if (hash) { - flecs_name_index_ensure(name_index, e, name, len, hash); - cur->index_hash = hash; - cur->index = name_index; - } - } else { - /* Name didn't change, but the string could have been - * reallocated. Make sure name index points to correct string */ - flecs_name_index_update_name(name_index, e, hash, name); - } + if (ival <= INT8_MAX && ival >= INT8_MIN) { + return ecs_id(ecs_i8_t); + } else if (ival <= INT16_MAX && ival >= INT16_MIN) { + return ecs_id(ecs_i16_t); + } else if (ival <= INT32_MAX && ival >= INT32_MIN) { + return ecs_id(ecs_i32_t); + } else { + return ecs_id(ecs_i64_t); } } -} -/* Component lifecycle actions for EcsTrigger */ -static void ecs_on_remove(EcsTrigger)(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsTrigger *ptr = ecs_term(it, EcsTrigger, 1); - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - if (ptr[i].trigger) { - flecs_trigger_fini(world, (ecs_trigger_t*)ptr[i].trigger); - } + if (uval <= UINT8_MAX) { + return ecs_id(ecs_u8_t); + } else if (uval <= UINT16_MAX) { + return ecs_id(ecs_u16_t); + } else if (uval <= UINT32_MAX) { + return ecs_id(ecs_u32_t); + } else { + return ecs_id(ecs_u64_t); } } -/* Component lifecycle actions for EcsObserver */ -static void ecs_on_remove(EcsObserver)(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsObserver *ptr = ecs_term(it, EcsObserver, 1); - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - if (ptr[i].observer) { - flecs_observer_fini(world, (ecs_observer_t*)ptr[i].observer); +static +bool flecs_expr_oper_valid_for_type( + ecs_world_t *world, + ecs_entity_t type, + ecs_token_kind_t op) +{ + switch(op) { + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokAddAssign: + case EcsTokMulAssign: + return flecs_expr_is_type_number(type); + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + if (ecs_get(world, type, EcsBitmask) != NULL) { + return true; } + + /* fall through */ + case EcsTokShiftLeft: + case EcsTokShiftRight: + return flecs_expr_is_type_integer(type); + case EcsTokEq: + case EcsTokNeq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + return flecs_expr_is_type_number(type) || + (type == ecs_id(ecs_bool_t)) || + (type == ecs_id(ecs_char_t)) || + (type == ecs_id(ecs_entity_t)); + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokRange: + case EcsTokIdentifier: + case EcsTokFunction: + case EcsTokChar: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: + case EcsTokKeywordMatch: + case EcsTokKeywordNew: + case EcsTokKeywordExport: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokEnd: + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); } } -/* -- Builtin triggers -- */ - static -void assert_relation_unused( - ecs_world_t *world, - ecs_entity_t rel, - ecs_entity_t property) +int32_t flecs_script_get_vector_type_data( + ecs_world_t *world, + ecs_entity_t type, + ecs_entity_t *vector_type_out) { - if (world->is_fini) { - return; + const EcsStruct *stype = ecs_get(world, type, EcsStruct); + if (!stype) { + return 0; } - if (flecs_get_id_record(world, ecs_pair(rel, EcsWildcard)) != NULL) { - char *r_str = ecs_get_fullpath(world, rel); - char *p_str = ecs_get_fullpath(world, property); - ecs_throw(ECS_ID_IN_USE, - "cannot change property '%s' to relation '%s': already in use", - p_str, r_str); - - ecs_os_free(r_str); - ecs_os_free(p_str); + ecs_entity_t vector_type = 0; + ecs_member_t *members = ecs_vec_first(&stype->members); + int32_t i, count = ecs_vec_count(&stype->members); + for (i = 0; i < count; i ++) { + ecs_member_t *member = &members[i]; + if (!i) { + if (!ecs_owns(world, member->type, EcsPrimitive)) { + /* Only primitive types can be used in vector ops */ + return 0; + } + + vector_type = member->type; + } else if (member->type != vector_type) { + /* Only structs with members of the same type can be used as vector */ + return 0; + } } -error: - return; + *vector_type_out = vector_type; + + return count; } static -void register_final(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - if (flecs_get_id_record(world, ecs_pair(EcsIsA, e)) != NULL) { - char *e_str = ecs_get_fullpath(world, e); - ecs_throw(ECS_ID_IN_USE, - "cannot change property 'Final' to '%s': already inherited from", - e_str); - ecs_os_free(e_str); - error: - continue; +int flecs_expr_type_for_operator( + ecs_script_t *script, + ecs_expr_node_t *node, /* Only used for error reporting */ + ecs_entity_t node_type, + ecs_expr_node_t *left, + ecs_expr_node_t *right, + ecs_token_kind_t operator, + ecs_entity_t *operand_type, + ecs_entity_t *result_type, + int32_t *vector_elem_count) +{ + ecs_world_t *world = script->world; + + if (operator == EcsTokDiv || operator == EcsTokMod) { + if (right->kind == EcsExprValue) { + ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)right; + ecs_value_t v = { .type = val->node.type, .ptr = val->ptr }; + if (flecs_value_is_0(&v)) { + flecs_expr_visit_error(script, node, + "invalid division by zero"); + return -1; + } } } -} - -static -void register_on_delete(ecs_iter_t *it) { - ecs_world_t *world = it->world; - ecs_id_t id = ecs_term_id(it, 1); - ecs_entity_t event = it->event; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - assert_relation_unused(world, e, EcsOnDelete); - if (event == EcsOnAdd) { - ecs_id_record_t *r = flecs_ensure_id_record(world, e); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - r->flags |= ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)); + switch(operator) { + case EcsTokDiv: + /* Result type of a division is always a float */ + if (left->type != ecs_id(ecs_f32_t) && left->type != ecs_id(ecs_f64_t)){ + *operand_type = ecs_id(ecs_f64_t); + *result_type = ecs_id(ecs_f64_t); } else { - ecs_id_record_t *r = flecs_get_id_record(world, e); - if (r) { - r->flags &= ~ECS_ID_ON_DELETE_MASK; - } + *operand_type = left->type; + *result_type = left->type; } - flecs_add_flag(world, e, ECS_FLAG_OBSERVED_ID); + return 0; + case EcsTokMod: + /* Mod only accepts integer operands, and results in an integer. We + * could disallow doing mod on floating point types, but in practice + * that would likely just result in code having to do a manual + * conversion to an integer. */ + *operand_type = ecs_id(ecs_i64_t); + *result_type = ecs_id(ecs_i64_t); + return 0; + case EcsTokAnd: + case EcsTokOr: + /* Result type of a condition operator is always a bool */ + *operand_type = ecs_id(ecs_bool_t); + *result_type = ecs_id(ecs_bool_t); + return 0; + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + /* Result type of equality operator is always bool, but operand types + * should not be casted to bool */ + *result_type = ecs_id(ecs_bool_t); + break; + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokBitwiseAnd: + case EcsTokBitwiseOr: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + break; + case EcsTokAddAssign: + case EcsTokMulAssign: + case EcsTokUnknown: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokMember: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokColon: + case EcsTokAssign: + case EcsTokNot: + case EcsTokOptional: + case EcsTokAnnotation: + case EcsTokNewline: + case EcsTokMatch: + case EcsTokRange: + case EcsTokIdentifier: + case EcsTokFunction: + case EcsTokChar: + case EcsTokString: + case EcsTokNumber: + case EcsTokKeywordModule: + case EcsTokKeywordUsing: + case EcsTokKeywordWith: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: + case EcsTokKeywordMatch: + case EcsTokKeywordNew: + case EcsTokKeywordExport: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokEnd: + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } -} -static -void register_on_delete_object(ecs_iter_t *it) { - ecs_world_t *world = it->world; - ecs_id_t id = ecs_term_id(it, 1); - ecs_entity_t event = it->event; + /* If one of the types is an entity or id, the other one should be also */ + if (left->type == ecs_id(ecs_entity_t) || + right->type == ecs_id(ecs_entity_t)) + { + *operand_type = ecs_id(ecs_entity_t); + goto done; + } - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - assert_relation_unused(world, e, EcsOnDeleteObject); + ecs_entity_t left_type = left->type; + const EcsPrimitive *ltype_ptr = ecs_get(world, left_type, EcsPrimitive); + const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); + if (!ltype_ptr || !rtype_ptr) { + if (ecs_get(world, left_type, EcsBitmask) != NULL) { + *operand_type = ecs_id(ecs_u32_t); + goto done; + } - if (event == EcsOnAdd) { - ecs_id_record_t *r = flecs_ensure_id_record(world, e); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - r->flags |= ECS_ID_ON_DELETE_OBJECT_FLAG(ECS_PAIR_SECOND(id)); - } else { - ecs_id_record_t *r = flecs_get_id_record(world, e); - if (r) { - r->flags &= ~ECS_ID_ON_DELETE_OBJECT_MASK; + if (ecs_get(world, right->type, EcsBitmask) != NULL) { + *operand_type = ecs_id(ecs_u32_t); + goto done; + } + + { + const EcsEnum *ptr = ecs_get(script->world, left_type, EcsEnum); + if (ptr) { + *operand_type = ptr->underlying_type; + goto done; } } - flecs_add_flag(world, e, ECS_FLAG_OBSERVED_ID); - } -} + { + const EcsEnum *ptr = ecs_get(script->world, right->type, EcsEnum); + if (ptr) { + *operand_type = ptr->underlying_type; + goto done; + } + } -static -void register_exclusive(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - assert_relation_unused(world, e, EcsExclusive); + if (!ltype_ptr) { + ecs_entity_t vector_type = 0; + int32_t elem_count = flecs_script_get_vector_type_data( + world, left_type, &vector_type); + if (elem_count) { + /* For vector operations the right operand type must be either a + * primitive type or the same type as the left operand. */ + if (!rtype_ptr && (left_type != right->type)) { + flecs_expr_visit_error(script, node, + "invalid types for binary expression (%s, %s)", + flecs_errstr(ecs_get_path(world, left->type)), + flecs_errstr_1(ecs_get_path(world, right->type))); + goto error; + } - ecs_id_record_t *r = flecs_ensure_id_record(world, e); - r->flags |= ECS_ID_EXCLUSIVE; - } -} + /* This is a binary vector operation, check if it's allowed */ + if (!vector_elem_count) { + flecs_expr_visit_error(script, node, + "binary vector operation is not allowed here (%s, %s)", + flecs_errstr(ecs_get_path(world, left->type)), + flecs_errstr_1(ecs_get_path(world, right->type))); + goto error; + } -static -void register_dont_inherit(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - assert_relation_unused(world, e, EcsDontInherit); + *result_type = left_type; + *operand_type = vector_type; + *vector_elem_count = elem_count; + goto done; + } + } - ecs_id_record_t *r = flecs_ensure_id_record(world, e); - r->flags |= ECS_ID_DONT_INHERIT; - } -} + flecs_expr_visit_error(script, node, + "invalid types for binary expression (%s, %s)", + flecs_errstr(ecs_get_path(world, left->type)), + flecs_errstr_1(ecs_get_path(world, right->type))) + goto error; + } + + /* If left and right type are the same, do nothing */ + if (left_type == right->type) { + *operand_type = left->type; + goto done; + } + + /* If types are not the same, find the smallest type for literals that can + * represent the value without losing precision. */ + ecs_entity_t ltype; + if (left_type == left->type) { /* If this is not a vector type */ + ltype = flecs_expr_narrow_type(node_type, left); + } else { + ltype = left_type; + } + + ecs_entity_t rtype = flecs_expr_narrow_type(node_type, right); + + /* If types are not the same, try to implicitly cast to expression type */ + ltype = flecs_expr_cast_to_lvalue(node_type, ltype); + rtype = flecs_expr_cast_to_lvalue(node_type, rtype); + + if (ltype == rtype) { + *operand_type = ltype; + goto done; + } + + if (operator == EcsTokEq || operator == EcsTokNeq) { + /* If this is an equality comparison and one of the operands is a bool, + * cast the other operand to a bool as well. This ensures that + * expressions such as true == 2 evaluate to true. */ + if (ltype == ecs_id(ecs_bool_t) || rtype == ecs_id(ecs_bool_t)) { + *operand_type = ecs_id(ecs_bool_t); + goto done; + } + + /* Equality comparisons between floating point types are invalid */ + if (ltype == ecs_id(ecs_f32_t) || ltype == ecs_id(ecs_f64_t)) { + flecs_expr_visit_error(script, node, + "floating point value is invalid in equality comparison"); + goto error; + } + + if (rtype == ecs_id(ecs_f32_t) || rtype == ecs_id(ecs_f64_t)) { + flecs_expr_visit_error(script, node, + "floating point value is invalid in equality comparison"); + goto error; + } + } + + /* If after the implicit cast types are not the same, try to implicitly cast + * to the most expressive type. */ + if (flecs_expr_expressiveness_score(ltype) >= + flecs_expr_expressiveness_score(rtype)) + { + if (flecs_expr_implicit_cast_allowed(rtype, ltype)) { + *operand_type = ltype; + goto done; + } + } else { + if (flecs_expr_implicit_cast_allowed(ltype, rtype)) { + *operand_type = rtype; + goto done; + } + } + + /* If we get here one or both operands cannot be coerced to the same type + * while guaranteeing no loss of precision. Pick the type that's least + * likely to cause trouble. */ -static -void on_symmetric_add_remove(ecs_iter_t *it) { - ecs_entity_t pair = ecs_term_id(it, 1); + if (flecs_expr_is_type_number(ltype) && flecs_expr_is_type_number(rtype)) { - if (!ECS_HAS_ROLE(pair, PAIR)) { - /* If relationship was not added as a pair, there's nothing to do */ - return; - } + /* If one of the types is a floating point, use f64 */ + if (ltype == ecs_id(ecs_f32_t) || ltype == ecs_id(ecs_f64_t) || + rtype == ecs_id(ecs_f32_t) || rtype == ecs_id(ecs_f64_t)) + { + *operand_type = ecs_id(ecs_f64_t); + goto done; + } - ecs_entity_t rel = ECS_PAIR_FIRST(pair); - ecs_entity_t obj = ECS_PAIR_SECOND(pair); - ecs_entity_t event = it->event; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t subj = it->entities[i]; - if (event == EcsOnAdd) { - if (!ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { - ecs_add_pair(it->world, obj, rel, subj); - } - } else { - if (ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { - ecs_remove_pair(it->world, obj, rel, subj); - } + /* If one of the types is an integer, use i64 */ + if (ltype == ecs_id(ecs_i8_t) || ltype == ecs_id(ecs_i16_t) || + ltype == ecs_id(ecs_i32_t) || ltype == ecs_id(ecs_i64_t)) + { + *operand_type = ecs_id(ecs_i64_t); + goto done; + } + if (rtype == ecs_id(ecs_i8_t) || rtype == ecs_id(ecs_i16_t) || + rtype == ecs_id(ecs_i32_t) || rtype == ecs_id(ecs_i64_t)) + { + *operand_type = ecs_id(ecs_i64_t); + goto done; } } -} -static -void register_symmetric(ecs_iter_t *it) { - ecs_world_t *world = it->real_world; + /* If all of that didn't work, give up */ - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t r = it->entities[i]; - assert_relation_unused(world, r, EcsSymmetric); + char *ltype_str = ecs_id_str(world, ltype); + char *rtype_str = ecs_id_str(world, rtype); + flecs_expr_visit_error(script, node, + "incompatible types in expression (%s vs %s)", + ltype_str, rtype_str); + ecs_os_free(ltype_str); + ecs_os_free(rtype_str); +error: + return -1; - /* Create trigger that adds the reverse relationship when R(X, Y) is - * added, or remove the reverse relationship when R(X, Y) is removed. */ - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term.id = ecs_pair(r, EcsWildcard), - .callback = on_symmetric_add_remove, - .events = {EcsOnAdd, EcsOnRemove} - }); - } -} +done: + if (operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { + /* Result of subtracting two unsigned ints can be negative */ + *operand_type = ecs_id(ecs_i64_t); + } -static -void on_set_component(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsComponent *c = ecs_term(it, EcsComponent, 1); + if (!*result_type) { + *result_type = *operand_type; + } - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_type_info_t *ti = flecs_ensure_type_info(world, e); - ti->size = c[i].size; - ti->alignment = c[i].alignment; + if (ecs_get(script->world, *result_type, EcsBitmask) != NULL) { + *operand_type = ecs_id(ecs_u64_t); } + + return 0; } static -void on_set_component_lifecycle(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsComponentLifecycle *cl = ecs_term(it, EcsComponentLifecycle, 1); - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_set_component_actions_w_id(world, e, &cl[i]); - } +int flecs_expr_type_for_binary_expr( + ecs_script_t *script, + ecs_expr_binary_t *node, + ecs_entity_t *operand_type, + ecs_entity_t *result_type, + int32_t *vector_elem_count) +{ + return flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, + node->node.type, node->left, node->right, node->operator, + operand_type, result_type, vector_elem_count); } static -void ensure_module_tag(ecs_iter_t *it) { - ecs_world_t *world = it->world; +int flecs_expr_interpolated_string_visit_type( + ecs_script_t *script, + ecs_expr_interpolated_string_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + char *ptr, *frag = NULL; + char ch; - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t parent = ecs_get_object(world, e, EcsChildOf, 0); - if (parent) { - ecs_add_id(world, parent, EcsModule); + for (ptr = node->value; (ch = ptr[0]); ptr ++) { + if (ch == '\\') { + ptr ++; + /* Next character is escaped, ignore */ + continue; } - } -} - -/* -- Triggers for keeping hashed ids in sync -- */ -static -void on_parent_change(ecs_iter_t *it) { - ecs_world_t *world = it->world; - ecs_table_t *other_table = it->other_table, *table = it->table; + if ((ch == '$') && (isspace(ptr[1]) || !ptr[1])) { + /* $ by itself */ + continue; + } - int32_t col = ecs_search(it->real_world, table, - ecs_pair(ecs_id(EcsIdentifier), EcsName), 0); - bool has_name = col != -1; - bool other_has_name = ecs_search(it->real_world, other_table, - ecs_pair(ecs_id(EcsIdentifier), EcsName), 0) != -1; + if (ch == '$' || ch == '{') { + if (!frag) { + frag = node->value; + } - if (!has_name && !other_has_name) { - /* If tables don't have names, index does not need to be updated */ - return; - } + char *frag_end = ptr; - ecs_id_t to_pair = it->event_id; - ecs_id_t from_pair = ecs_childof(0); + ecs_expr_node_t *result = NULL; - /* Find the other ChildOf relationship */ - ecs_search(it->real_world, other_table, - ecs_pair(EcsChildOf, EcsWildcard), &from_pair); + if (ch == '$') { + char *var_name = ++ ptr; + ptr = ECS_CONST_CAST(char*, flecs_tokenizer_identifier( + NULL, ptr, NULL)); + if (!ptr) { + goto error; + } - bool to_has_name = has_name, from_has_name = other_has_name; - if (it->event == EcsOnRemove) { - if (from_pair != ecs_childof(0)) { - /* Because ChildOf is an exclusive relationship, events always come - * in OnAdd/OnRemove pairs (add for the new, remove for the old - * parent). We only need one of those events, so filter out the - * OnRemove events except for the case where a parent is removed and - * not replaced with another parent. */ - return; - } + /* Fiddly, but reduces need for allocations */ + ecs_size_t var_name_pos = flecs_ito(int32_t, var_name - node->value); + var_name = &node->buffer[var_name_pos]; + ecs_size_t var_name_end = flecs_ito(int32_t, ptr - node->value); + node->buffer[var_name_end] = '\0'; - ecs_id_t temp = from_pair; - from_pair = to_pair; - to_pair = temp; + ecs_expr_variable_t *var = flecs_expr_variable_from( + script, (ecs_expr_node_t*)node, var_name); + if (!var) { + goto error; + } - to_has_name = other_has_name; - from_has_name = has_name; - } + result = (ecs_expr_node_t*)var; + } else { + ecs_script_impl_t *impl = flecs_script_impl(script); - /* Get the table column with names */ - const EcsIdentifier *names = ecs_iter_column(it, EcsIdentifier, col); + ecs_parser_t parser = { + .script = impl, + .scope = impl->root, + .significant_newline = false, + .token_cur = impl->token_remaining + }; - ecs_hashmap_t *from_index = 0; - if (from_has_name) { - from_index = flecs_get_id_name_index(world, from_pair); - } - ecs_hashmap_t *to_index = NULL; - if (to_has_name) { - to_index = flecs_ensure_id_name_index(world, to_pair); - } + ptr = ECS_CONST_CAST(char*, flecs_script_parse_expr( + &parser, ptr + 1, 0, &result)); + if (!ptr) { + goto error; + } - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - const EcsIdentifier *name = &names[i]; + impl->token_remaining = parser.token_cur; - uint64_t index_hash = name->index_hash; - if (from_index && index_hash) { - flecs_name_index_remove(from_index, e, index_hash); - } - const char *name_str = name->value; - if (to_index && name_str) { - ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); - flecs_name_index_ensure( - to_index, e, name_str, name->length, name->hash); - } - } -} + if (ptr[0] != '}') { + flecs_expr_visit_error(script, node, + "expected '}' at end of interpolated expression"); + goto error; + } + ptr ++; + } -/* -- Iterable mixins -- */ + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); -static -void on_event_iterable_init( - const ecs_world_t *world, - const ecs_poly_t *poly, /* Observable */ - ecs_iter_t *it, - ecs_term_t *filter) -{ - ecs_iter_poly(world, poly, it, filter); - it->event_id = filter->id; -} + ecs_expr_eval_desc_t priv_desc = *desc; + priv_desc.type = ecs_id(ecs_string_t); /* String output */ -/* -- Bootstrapping -- */ + if (flecs_expr_visit_type_priv(script, result, cur, &priv_desc)) { + flecs_expr_visit_free(script, result); + goto error; + } -#define bootstrap_component(world, table, name)\ - _bootstrap_component(world, table, ecs_id(name), #name, sizeof(name),\ - ECS_ALIGNOF(name)) + if (result->type != ecs_id(ecs_string_t)) { + result = (ecs_expr_node_t*)flecs_expr_cast(script, + (ecs_expr_node_t*)result, ecs_id(ecs_string_t)); + if (!result) { + /* Cast failed */ + goto error; + } + } -static -void _bootstrap_component( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t entity, - const char *symbol, - ecs_size_t size, - ecs_size_t alignment) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->expressions, ecs_expr_node_t*)[0] = result; - ecs_column_t *columns = table->storage.columns; - ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); + frag_end[0] = '\0'; - ecs_record_t *record = ecs_eis_ensure(world, entity); - record->table = table; + if (frag != frag_end) { + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->fragments, char*)[0] = frag; + } - int32_t index = flecs_table_append(world, table, &table->storage, - entity, record, false); - record->row = ECS_ROW_TO_RECORD(index, 0); + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->fragments, char*)[0] = NULL; - EcsComponent *component = ecs_vector_first(columns[0].data, EcsComponent); - component[index].size = size; - component[index].alignment = alignment; + frag = ptr; /* Point to next fragment */ + if (!ptr[0]) { + break; /* We already parsed the end of the string */ + } + } + } - const char *name = &symbol[3]; /* Strip 'Ecs' */ - ecs_size_t symbol_length = ecs_os_strlen(symbol); - ecs_size_t name_length = symbol_length - 3; + /* This would mean it's not an interpolated string, which means the parser + * messed up when creating the node. */ + ecs_assert(frag != NULL, ECS_INTERNAL_ERROR, NULL); - EcsIdentifier *name_col = ecs_vector_first(columns[1].data, EcsIdentifier); - name_col[index].value = ecs_os_strdup(name); - name_col[index].length = name_length; - name_col[index].hash = flecs_hash(name, name_length); - name_col[index].index_hash = 0; - name_col[index].index = NULL; + /* Add remaining fragment */ + if (frag != ptr) { + ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, + &node->fragments, char*)[0] = frag; + } - EcsIdentifier *symbol_col = ecs_vector_first(columns[2].data, EcsIdentifier); - symbol_col[index].value = ecs_os_strdup(symbol); - symbol_col[index].length = symbol_length; - symbol_col[index].hash = flecs_hash(symbol, symbol_length); - symbol_col[index].index_hash = 0; - symbol_col[index].index = NULL; + return 0; +error: + return -1; } -/** Initialize component table. This table is manually constructed to bootstrap - * flecs. After this function has been called, the builtin components can be - * created. - * The reason this table is constructed manually is because it requires the size - * and alignment of the EcsComponent and EcsIdentifier components, which haven't - * been created yet */ static -ecs_table_t* bootstrap_component_table( - ecs_world_t *world) +int flecs_expr_initializer_collection_check( + ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_meta_cursor_t *cur) { - /* Before creating the table, ensure component ids are alive */ - ecs_ensure(world, ecs_id(EcsComponent)); - ecs_ensure(world, EcsFinal); - ecs_ensure(world, ecs_id(EcsIdentifier)); - ecs_ensure(world, EcsName); - ecs_ensure(world, EcsSymbol); - ecs_ensure(world, EcsAlias); - ecs_ensure(world, EcsChildOf); - ecs_ensure(world, EcsFlecsCore); - ecs_ensure(world, EcsOnDelete); - ecs_ensure(world, EcsThrow); - ecs_ensure(world, EcsWildcard); - ecs_ensure(world, EcsAny); - - /* Before creating table, manually set flags for ChildOf/Identifier, as this - * can no longer be done after they are in use. */ - ecs_id_record_t *childof_idr = flecs_ensure_id_record(world, EcsChildOf); - childof_idr->flags |= ECS_ID_ON_DELETE_OBJECT_DELETE; - childof_idr->flags |= ECS_ID_DONT_INHERIT; - - ecs_id_record_t *ident_idr = flecs_ensure_id_record( - world, ecs_id(EcsIdentifier)); - ident_idr->flags |= ECS_ID_DONT_INHERIT; + if (cur) { + if (ecs_meta_is_collection(cur) != node->is_collection) { + char *type_str = ecs_get_path(script->world, node->node.type); + if (node->is_collection) { + flecs_expr_visit_error(script, node, + "invalid collection literal for non-collection type '%s'", + type_str); + } else { + flecs_expr_visit_error(script, node, + "invalid object literal for collection type '%s'", + type_str); + } - ecs_id_t entities[] = { - ecs_id(EcsComponent), - EcsFinal, - ecs_pair(ecs_id(EcsIdentifier), EcsName), - ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), - ecs_pair(EcsChildOf, EcsFlecsCore), - ecs_pair(EcsOnDelete, EcsThrow) - }; - - ecs_ids_t array = { - .array = entities, - .count = 6 - }; + ecs_os_free(type_str); + goto error; + } + } - ecs_table_t *result = flecs_table_find_or_create(world, &array); - ecs_data_t *data = &result->storage; + ecs_entity_t type = node->node.type; + if (type) { + const EcsOpaque *op = ecs_get(script->world, type, EcsOpaque); + if (op) { + type = op->as_type; + } - /* Preallocate enough memory for initial components */ - data->entities = ecs_vector_new(ecs_entity_t, EcsFirstUserComponentId); - data->record_ptrs = ecs_vector_new(ecs_record_t*, EcsFirstUserComponentId); + const EcsType *ptr = ecs_get(script->world, type, EcsType); + if (ptr) { + ecs_type_kind_t kind = ptr->kind; + if (node->is_collection) { + /* Only do this check if no cursor is provided. Cursors also + * handle inline arrays. */ + if (!cur) { + if (kind != EcsArrayType && kind != EcsVectorType) { + char *type_str = ecs_get_path( + script->world, node->node.type); + flecs_expr_visit_error(script, node, + "invalid collection literal for type '%s'", + type_str); + ecs_os_free(type_str); + goto error; + } + } + } else { + if (kind != EcsStructType) { + char *type_str = ecs_get_path( + script->world, node->node.type); + flecs_expr_visit_error(script, node, + "invalid object literal for type '%s'", type_str); + ecs_os_free(type_str); + goto error; + } + } + } + } - data->columns[0].data = ecs_vector_new(EcsComponent, EcsFirstUserComponentId); - data->columns[1].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); - data->columns[2].data = ecs_vector_new(EcsIdentifier, EcsFirstUserComponentId); - - return result; + return 0; +error: + return -1; } +/* Dynamic initializers use the cursor API to assign values, and are used for + * any type where a simple list of offsets into fields doesn't work. */ static -void bootstrap_entity( +bool flecs_expr_initializer_is_dynamic( ecs_world_t *world, - ecs_entity_t id, - const char *name, - ecs_entity_t parent) + ecs_entity_t type) { - char symbol[256]; - ecs_os_strcpy(symbol, "flecs.core."); - ecs_os_strcat(symbol, name); - - ecs_add_pair(world, id, EcsChildOf, parent); - ecs_set_name(world, id, name); - ecs_set_symbol(world, id, symbol); - - ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); - - if (!parent || parent == EcsFlecsCore) { - ecs_assert(ecs_lookup_fullpath(world, name) == id, - ECS_INTERNAL_ERROR, NULL); + const EcsType *t = ecs_get(world, type, EcsType); + if (t) { + return t->kind == EcsOpaqueType || t->kind == EcsVectorType; } + return false; } -void flecs_bootstrap( - ecs_world_t *world) +static +int flecs_expr_empty_initializer_visit_type( + ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) { - ecs_log_push(); + (void)desc; - ecs_set_name_prefix(world, "Ecs"); + node->node.type = ecs_meta_get_type(cur); + if (!node->node.type) { + flecs_expr_visit_error(script, node, + "unknown type for initializer"); + goto error; + } - /* Bootstrap type info (otherwise initialized by setting EcsComponent) */ - flecs_init_type_info_t(world, EcsComponent); - flecs_init_type_info_t(world, EcsIdentifier); - flecs_init_type_info_t(world, EcsComponentLifecycle); - flecs_init_type_info_t(world, EcsType); - flecs_init_type_info_t(world, EcsQuery); - flecs_init_type_info_t(world, EcsTrigger); - flecs_init_type_info_t(world, EcsObserver); - flecs_init_type_info_t(world, EcsIterable); - - /* Setup component lifecycle actions */ - ecs_set_component_actions(world, EcsComponent, { - .ctor = ecs_default_ctor - }); + node->is_dynamic = flecs_expr_initializer_is_dynamic( + script->world, node->node.type); - ecs_set_component_actions(world, EcsIdentifier, { - .ctor = ecs_default_ctor, - .dtor = ecs_dtor(EcsIdentifier), - .copy = ecs_copy(EcsIdentifier), - .move = ecs_move(EcsIdentifier), - .on_set = ecs_on_set(EcsIdentifier), - .on_remove = ecs_on_set(EcsIdentifier) - }); + if (ecs_meta_push(cur)) { + goto error; + } - ecs_set_component_actions(world, EcsTrigger, { - .ctor = ecs_default_ctor, - .on_remove = ecs_on_remove(EcsTrigger) - }); + if (flecs_expr_initializer_collection_check(script, node, cur)) { + goto error; + } - ecs_set_component_actions(world, EcsObserver, { - .ctor = ecs_default_ctor, - .on_remove = ecs_on_remove(EcsObserver) - }); + if (ecs_meta_pop(cur)) { + goto error; + } - /* Create table for initial components */ - ecs_table_t *table = bootstrap_component_table(world); - assert(table != NULL); + return 0; +error: + return -1; +} - bootstrap_component(world, table, EcsIdentifier); - bootstrap_component(world, table, EcsComponent); - bootstrap_component(world, table, EcsComponentLifecycle); +static +int flecs_expr_initializer_visit_type( + ecs_script_t *script, + ecs_expr_initializer_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + if (!cur || !cur->valid) { + flecs_expr_visit_error(script, node, "missing type for initializer"); + goto error; + } - bootstrap_component(world, table, EcsType); - bootstrap_component(world, table, EcsQuery); - bootstrap_component(world, table, EcsTrigger); - bootstrap_component(world, table, EcsObserver); - bootstrap_component(world, table, EcsIterable); + ecs_entity_t type = ecs_meta_get_type(cur); + ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); - world->info.last_component_id = EcsFirstUserComponentId; - world->info.last_id = EcsFirstUserEntityId; - world->info.min_id = 0; - world->info.max_id = 0; + /* Opaque types do not have deterministic offsets */ + bool is_dynamic = flecs_expr_initializer_is_dynamic(script->world, type); + node->is_dynamic = is_dynamic; - /* Populate core module */ - ecs_set_scope(world, EcsFlecsCore); + if (ecs_meta_push(cur)) { + goto error; + } - flecs_bootstrap_tag(world, EcsName); - flecs_bootstrap_tag(world, EcsSymbol); - flecs_bootstrap_tag(world, EcsAlias); + if (flecs_expr_initializer_collection_check(script, node, cur)) { + goto error; + } - flecs_bootstrap_tag(world, EcsModule); - flecs_bootstrap_tag(world, EcsPrivate); - flecs_bootstrap_tag(world, EcsPrefab); - flecs_bootstrap_tag(world, EcsDisabled); + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + for (i = 0; i < count; i ++) { + if (i) { + if (ecs_meta_next(cur)) { /* , */ + goto error; + } + } - /* Initialize builtin modules */ - ecs_set_name(world, EcsFlecs, "flecs"); - ecs_add_id(world, EcsFlecs, EcsModule); + ecs_expr_initializer_element_t *elem = &elems[i]; + if (!elem->value) { + flecs_expr_visit_error(script, node, + "missing value for initializer element"); + goto error; + } - ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); - ecs_set_name(world, EcsFlecsCore, "core"); - ecs_add_id(world, EcsFlecsCore, EcsModule); + if (elem->member) { + if (ecs_meta_dotmember(cur, elem->member)) { /* x: */ + flecs_expr_visit_error(script, node, "cannot resolve member"); + goto error; + } + } - ecs_add_pair(world, EcsFlecsHidden, EcsChildOf, EcsFlecs); - ecs_set_name(world, EcsFlecsHidden, "hidden"); - ecs_add_id(world, EcsFlecsHidden, EcsModule); + /* Check for "member: $" syntax */ + if (elem->value->kind == EcsExprVariable) { + ecs_expr_variable_t *var = (ecs_expr_variable_t*)elem->value; + if (var->name && !var->name[0]) { + var->name = ecs_meta_get_member(cur); + if (!var->name) { + flecs_expr_visit_error(script, node, + "cannot deduce variable name: not a member"); + goto error; + } + } + } - /* Initialize builtin entities */ - bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); - bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); - bootstrap_entity(world, EcsAny, "_", EcsFlecsCore); - bootstrap_entity(world, EcsThis, "This", EcsFlecsCore); - bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore); + ecs_entity_t elem_type = ecs_meta_get_type(cur); + ecs_meta_cursor_t elem_cur = *cur; + if (flecs_expr_visit_type_priv( + script, elem->value, &elem_cur, desc)) + { + goto error; + } - /* Component/relationship properties */ - flecs_bootstrap_tag(world, EcsTransitive); - flecs_bootstrap_tag(world, EcsReflexive); - flecs_bootstrap_tag(world, EcsSymmetric); - flecs_bootstrap_tag(world, EcsFinal); - flecs_bootstrap_tag(world, EcsDontInherit); - flecs_bootstrap_tag(world, EcsTag); - flecs_bootstrap_tag(world, EcsExclusive); - flecs_bootstrap_tag(world, EcsAcyclic); - flecs_bootstrap_tag(world, EcsWith); - flecs_bootstrap_tag(world, EcsOneOf); - - flecs_bootstrap_tag(world, EcsOnDelete); - flecs_bootstrap_tag(world, EcsOnDeleteObject); - flecs_bootstrap_tag(world, EcsRemove); - flecs_bootstrap_tag(world, EcsDelete); - flecs_bootstrap_tag(world, EcsThrow); + if (elem->value->type != elem_type) { + ecs_expr_node_t *cast = (ecs_expr_node_t*)flecs_expr_cast( + script, elem->value, elem_type); + if (!cast) { + goto error; + } + elem->value = cast; + } - flecs_bootstrap_tag(world, EcsDefaultChildComponent); + if (elem->operator) { + if (!flecs_expr_oper_valid_for_type( + script->world, elem_type, elem->operator)) + { + char *type_str = ecs_get_path(script->world, elem_type); + flecs_expr_visit_error(script, node, + "invalid operator for type '%s'", type_str); + ecs_os_free(type_str); + goto error; + } + } - /* Builtin relations */ - flecs_bootstrap_tag(world, EcsIsA); - flecs_bootstrap_tag(world, EcsChildOf); + if (!is_dynamic) { + elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); + } else { + if (elem->value->kind == EcsExprInitializer) { + /* If initializer is dynamic, make sure nested initializer is + * marked as dynamic too. This is necessary because a push for + * a nested initializer may have to allocate elements in the + * parent collection value. */ + ((ecs_expr_initializer_t*)elem->value)->is_dynamic = true; + } + } + } - /* Builtin events */ - bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); - bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); - bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); - bootstrap_entity(world, EcsUnSet, "UnSet", EcsFlecsCore); - bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); - bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); + node->node.type = type; - /* Transitive relations are always Acyclic */ - ecs_add_pair(world, EcsTransitive, EcsWith, EcsAcyclic); + if (ecs_meta_pop(cur)) { + goto error; + } - /* Transitive relations */ - ecs_add_id(world, EcsIsA, EcsTransitive); - ecs_add_id(world, EcsIsA, EcsReflexive); + return 0; +error: + return -1; +} - /* Tag relations (relations that should never have data) */ - ecs_add_id(world, EcsIsA, EcsTag); - ecs_add_id(world, EcsChildOf, EcsTag); - ecs_add_id(world, EcsDefaultChildComponent, EcsTag); +static +int flecs_expr_unary_visit_type( + ecs_script_t *script, + ecs_expr_unary_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + if (flecs_expr_visit_type_priv(script, node->expr, cur, desc)) { + goto error; + } - /* Acyclic relations */ - ecs_add_id(world, EcsIsA, EcsAcyclic); - ecs_add_id(world, EcsChildOf, EcsAcyclic); - ecs_add_id(world, EcsWith, EcsAcyclic); + /* The only supported unary expression is not (!) which returns a bool */ + node->node.type = ecs_id(ecs_bool_t); - /* Exclusive properties */ - ecs_add_id(world, EcsChildOf, EcsExclusive); - ecs_add_id(world, EcsOnDelete, EcsExclusive); - ecs_add_id(world, EcsOnDeleteObject, EcsExclusive); - ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive); - ecs_add_id(world, EcsOneOf, EcsExclusive); + if (node->expr->type != ecs_id(ecs_bool_t)) { + node->expr = (ecs_expr_node_t*)flecs_expr_cast( + script, node->expr, ecs_id(ecs_bool_t)); + if (!node->expr) { + goto error; + } + } - /* Make EcsOnAdd, EcsOnSet events iterable to enable .yield_existing */ - ecs_set(world, EcsOnAdd, EcsIterable, { .init = on_event_iterable_init }); - ecs_set(world, EcsOnSet, EcsIterable, { .init = on_event_iterable_init }); + return 0; +error: + return -1; +} - /* Removal of ChildOf objects (parents) deletes the subject (child) */ - ecs_add_pair(world, EcsChildOf, EcsOnDeleteObject, EcsDelete); +static +int flecs_expr_binary_visit_type( + ecs_script_t *script, + ecs_expr_binary_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + /* Operands must be of this type or casted to it */ + ecs_entity_t operand_type = 0; - /* ChildOf, Identifier, Disabled and Prefab should never be inherited */ - ecs_add_id(world, EcsChildOf, EcsDontInherit); - ecs_add_id(world, ecs_id(EcsIdentifier), EcsDontInherit); + /* Resulting type of binary expression */ + ecs_entity_t result_type = 0; - /* The (IsA, *) id record is used often in searches, so cache it */ - world->idr_isa_wildcard = flecs_ensure_id_record(world, - ecs_pair(EcsIsA, EcsWildcard)); + /* Number of elements in vector, if vector operation */ + int32_t vector_elem_count = 0; - ecs_trigger_init(world, &(ecs_trigger_desc_t) { - .term = { - .id = ecs_pair(EcsChildOf, EcsWildcard), - .subj.set.mask = EcsSelf - }, - .events = { EcsOnAdd, EcsOnRemove }, - .yield_existing = true, - .callback = on_parent_change - }); + if (cur->valid) { + /* Provides a hint to the type visitor. The lvalue type will be used to + * reduce the number of casts where possible. */ + node->node.type = ecs_meta_get_type(cur); - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = EcsFinal, .subj.set.mask = EcsSelf }, - .events = {EcsOnAdd}, - .callback = register_final - }); + /* If the result of the binary expression is a boolean it's likely a + * conditional expression. We don't want to hint that the operands + * of conditional expressions should be casted to booleans. */ + if (node->node.type == ecs_id(ecs_bool_t)) { + ecs_os_zeromem(cur); + } + } - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = ecs_pair(EcsOnDelete, EcsWildcard), .subj.set.mask = EcsSelf }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = register_on_delete - }); + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { + goto error; + } - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = ecs_pair(EcsOnDeleteObject, EcsWildcard), .subj.set.mask = EcsSelf }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = register_on_delete_object - }); + ecs_meta_cursor_t right_cur = *cur; + if (node->right->kind == EcsExprIdentifier) { + if (ecs_get(script->world, node->left->type, EcsEnum) != NULL) { + /* If the left hand side is an enum, interpret untyped identifiers + * on the right hand side as enum constants of the same type. */ + right_cur = ecs_meta_cursor(script->world, node->left->type, NULL); + } + } - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = EcsExclusive, .subj.set.mask = EcsSelf }, - .events = {EcsOnAdd}, - .callback = register_exclusive - }); + if (flecs_expr_visit_type_priv(script, node->right, &right_cur, desc)) { + goto error; + } - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = EcsSymmetric, .subj.set.mask = EcsSelf }, - .events = {EcsOnAdd}, - .callback = register_symmetric - }); + if (flecs_expr_type_for_binary_expr( + script, node, &operand_type, &result_type, &vector_elem_count)) + { + goto error; + } - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = EcsDontInherit, .subj.set.mask = EcsSelf }, - .events = {EcsOnAdd}, - .callback = register_dont_inherit - }); + if (!vector_elem_count) { + if (!flecs_expr_oper_valid_for_type( + script->world, result_type, node->operator)) + { + char *type_str = ecs_get_path(script->world, result_type); + flecs_expr_visit_error(script, node, "invalid operator %s for type '%s'", + flecs_token_str(node->operator), type_str); + ecs_os_free(type_str); + goto error; + } + + if (operand_type != node->left->type) { + node->left = (ecs_expr_node_t*)flecs_expr_cast( + script, node->left, operand_type); + if (!node->left) { + goto error; + } + } - /* Define trigger to make sure that adding a module to a child entity also - * adds it to the parent. */ - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = EcsModule, .subj.set.mask = EcsSelf }, - .events = {EcsOnAdd}, - .callback = ensure_module_tag - }); + node->vector_type = 0; + node->vector_count = 0; + } else { + node->vector_type = operand_type; + node->vector_count = vector_elem_count; + } - /* Define trigger for when component lifecycle is set for component */ - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = ecs_id(EcsComponentLifecycle), .subj.set.mask = EcsSelf }, - .events = {EcsOnSet}, - .callback = on_set_component_lifecycle - }); + if (operand_type != node->right->type) { + if (!vector_elem_count || (node->right->type != node->left->type)) { + /* If this is a vector operation between the same types, don't try + * to cast the right hand to the vector type. */ + node->right = (ecs_expr_node_t*)flecs_expr_cast( + script, node->right, operand_type); + if (!node->right) { + goto error; + } + } + } - /* Define trigger for updating component size when it changes */ - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = ecs_id(EcsComponent), .subj.set.mask = EcsSelf }, - .events = {EcsOnSet}, - .callback = on_set_component - }); + node->node.type = result_type; - ecs_add_id(world, EcsDisabled, EcsDontInherit); - ecs_add_id(world, EcsPrefab, EcsDontInherit); + return 0; +error: + return -1; +} - /* Run bootstrap functions for other parts of the code */ - flecs_bootstrap_hierarchy(world); +static +int flecs_expr_constant_identifier_visit_type( + ecs_script_t *script, + ecs_expr_identifier_t *node) +{ + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, node->node.type); - ecs_set_scope(world, 0); + ecs_meta_cursor_t expr_cur = ecs_meta_cursor( + script->world, node->node.type, &result->storage.u64); + if (ecs_meta_set_string(&expr_cur, node->value)) { + flecs_expr_visit_free(script, (ecs_expr_node_t*)result); + goto error; + } - ecs_log_pop(); + result->ptr = &result->storage.u64; + node->expr = (ecs_expr_node_t*)result; + + return 0; +error: + return -1; } -#include -#include +static +bool flecs_expr_is_entity_type( + ecs_world_t *world, + ecs_entity_t type, + bool *is_opaque) +{ + *is_opaque = false; + if (type == ecs_id(ecs_entity_t) || type == ecs_id(ecs_id_t)) { + return true; + } -#define ECS_NAME_BUFFER_LENGTH (64) + const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); + if (p) { + return p->kind == EcsEntity || p->kind == EcsId; + } + + const EcsOpaque *o = ecs_get(world, type, EcsOpaque); + if (o) { + *is_opaque = true; + bool dummy; + return flecs_expr_is_entity_type(world, o->as_type, &dummy); + } + + return false; +} static -bool path_append( - const ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t child, - const char *sep, - const char *prefix, - ecs_strbuf_t *buf) +int flecs_expr_identifier_variable_member_visit_type( + ecs_script_t *script, + ecs_expr_identifier_t *node, + const ecs_expr_eval_desc_t *desc) { - ecs_poly_assert(world, ecs_world_t); + char *member_sep = strchr(node->value, '.'); + while (member_sep) { + if (member_sep != node->value && member_sep[-1] == '\\') { + member_sep = strchr(member_sep + 1, '.'); + continue; + } + break; + } - ecs_entity_t cur = 0; - char buff[22]; - const char *name; + if (!member_sep) { + return 1; + } - if (ecs_is_valid(world, child)) { - cur = ecs_get_object(world, child, EcsChildOf, 0); - if (cur) { - if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) { - path_append(world, parent, cur, sep, prefix, buf); - ecs_strbuf_appendstr(buf, sep); - } - } else if (prefix) { - ecs_strbuf_appendstr(buf, prefix); - } + member_sep[0] = '\0'; - name = ecs_get_name(world, child); - if (!name || !ecs_os_strlen(name)) { - ecs_os_sprintf(buff, "%u", (uint32_t)child); - name = buff; - } - } else { - ecs_os_sprintf(buff, "%u", (uint32_t)child); - name = buff; + if (!flecs_script_find_var(desc->vars, node->value, NULL)) { + member_sep[0] = '.'; + return 1; } - ecs_strbuf_appendstr(buf, name); + ecs_expr_variable_t *var_node = flecs_expr_variable_from( + script, (ecs_expr_node_t*)node, node->value); + ecs_expr_member_t *member_node = flecs_expr_member_from( + script, (ecs_expr_node_t*)var_node, &member_sep[1]); - return cur != 0; + node->expr = (ecs_expr_node_t*)member_node; + + ecs_meta_cursor_t tmp_cur; ecs_os_zeromem(&tmp_cur); + if (flecs_expr_visit_type_priv(script, node->expr, &tmp_cur, desc)) { + goto error; + } + + node->node.type = node->expr->type; + return 0; +error: + flecs_expr_visit_free(script, node->expr); + node->expr = NULL; + return -1; } static -bool is_number( - const char *name) +int flecs_expr_identifier_visit_type( + ecs_script_t *script, + ecs_expr_identifier_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) { - ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); - - if (!isdigit(name[0])) { - return false; + (void)desc; + + ecs_entity_t type = node->node.type; + if (cur->valid) { + type = ecs_meta_get_type(cur); } - ecs_size_t i, length = ecs_os_strlen(name); - for (i = 1; i < length; i ++) { - char ch = name[i]; + const EcsType *type_ptr = NULL; + if (type) { + type_ptr = ecs_get(script->world, type, EcsType); + ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + } - if (!isdigit(ch)) { - break; + if (type_ptr && + (type_ptr->kind == EcsEnumType || type_ptr->kind == EcsBitmaskType)) + { + /* If the requested type is an enum or bitmask, use cursor to resolve + * identifier to correct type constant. This lets us type 'Red' in places + * where we expect a value of type Color, instead of Color.Red. */ + node->node.type = type; + if (flecs_expr_constant_identifier_visit_type(script, node)) { + goto error; + } + + return 0; + } else { + /* If not, try to resolve the identifier as entity */ + ecs_entity_t e = desc->lookup_action( + script->world, node->value, desc->lookup_ctx); + if (e || !ecs_os_strcmp(node->value, "#0")) { + const EcsScriptConstVar *global = NULL; + if (e) { + global = ecs_get(script->world, e, EcsScriptConstVar); + } + if (!global) { + bool is_opaque = false; + if (!type) { + type = ecs_id(ecs_entity_t); + } else if (!flecs_expr_is_entity_type(script->world, type, &is_opaque)) { + char *type_str = ecs_get_path(script->world, type); + flecs_expr_visit_error(script, node, + "cannot cast identifier '%s' to %s", + node->value, type_str); + ecs_os_free(type_str); + goto error; + } + + ecs_expr_value_node_t *result = flecs_expr_value_from( + script, (ecs_expr_node_t*)node, type); + + if (!is_opaque) { + result->storage.entity = e; + result->ptr = &result->storage.entity; + } else { + ecs_size_t size = flecs_type_size(script->world, type); + ecs_assert(size > 0, ECS_INTERNAL_ERROR, NULL); + result->ptr = flecs_walloc(script->world, size); + + ecs_meta_cursor_t expr_cur = ecs_meta_cursor( + script->world, type, result->ptr); + if (ecs_meta_set_entity(&expr_cur, e)) { + flecs_expr_visit_free(script, (ecs_expr_node_t*)result); + goto error; + } + } + + node->expr = (ecs_expr_node_t*)result; + node->node.type = type; + } else { + ecs_expr_variable_t *var_node = flecs_expr_variable_from( + script, (ecs_expr_node_t*)node, node->value); + node->expr = (ecs_expr_node_t*)var_node; + node->node.type = global->value.type; + + ecs_meta_cursor_t tmp_cur; ecs_os_zeromem(&tmp_cur); + if (flecs_expr_visit_type_priv( + script, (ecs_expr_node_t*)var_node, &tmp_cur, desc)) + { + goto error; + } + } + + return 0; + } + + /* If identifier could not be resolved as entity, try as variable */ + int32_t var_sp = -1; + ecs_script_var_t *var = flecs_script_find_var( + desc->vars, node->value, &var_sp); + if (var) { + ecs_expr_variable_t *var_node = flecs_expr_variable_from( + script, (ecs_expr_node_t*)node, node->value); + node->expr = (ecs_expr_node_t*)var_node; + node->node.type = var->value.type; + + ecs_meta_cursor_t tmp_cur; ecs_os_zeromem(&tmp_cur); + if (flecs_expr_visit_type_priv( + script, (ecs_expr_node_t*)var_node, &tmp_cur, desc)) + { + goto error; + } + + return 0; + } + + int var_member_result = flecs_expr_identifier_variable_member_visit_type( + script, node, desc); + if (var_member_result == 0) { + return 0; + } else if (var_member_result == -1) { + goto error; + } + + /* If unresolved identifiers aren't allowed here, throw error */ + if (!desc->allow_unresolved_identifiers) { + flecs_expr_visit_error(script, node, + "unresolved identifier '%s'", node->value); + goto error; } + + /* Identifier will be resolved at eval time, default to entity */ + node->node.type = ecs_id(ecs_entity_t); } - return i >= length; + return 0; +error: + return -1; } -static -ecs_entity_t name_to_id( - const ecs_world_t *world, - const char *name) +static +int flecs_expr_global_variable_resolve( + ecs_script_t *script, + ecs_expr_variable_t *node, + const ecs_expr_eval_desc_t *desc) { - long int result = atol(name); - ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t alive = ecs_get_alive(world, (ecs_entity_t)result); - if (alive) { - return alive; - } else { - return (ecs_entity_t)result; + ecs_world_t *world = script->world; + ecs_entity_t global = desc->lookup_action( + world, node->name, desc->lookup_ctx); + if (!global) { + flecs_expr_visit_error(script, node, "unresolved variable '%s'", + node->name); + goto error; + } + + const EcsScriptConstVar *v = ecs_get(world, global, EcsScriptConstVar); + if (!v) { + char *str = ecs_get_path(world, global); + flecs_expr_visit_error(script, node, + "entity '%s' is not a variable", node->name); + ecs_os_free(str); + goto error; } + + node->node.kind = EcsExprGlobalVariable; + node->node.type = v->value.type; + node->global_value = v->value; + + return 0; +error: + return -1; } static -ecs_entity_t get_builtin( - const char *name) +int flecs_expr_variable_visit_type( + ecs_script_t *script, + ecs_expr_variable_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) { - if (name[0] == '.' && name[1] == '\0') { - return EcsThis; - } else if (name[0] == '*' && name[1] == '\0') { - return EcsWildcard; - } else if (name[0] == '_' && name[1] == '\0') { - return EcsAny; + ecs_script_var_t *var = flecs_script_find_var( + desc->vars, node->name, &node->sp); + if (var) { + node->node.type = var->value.type; + if (!node->node.type) { + flecs_expr_visit_error(script, node, + "variable '%s' is not initialized", node->name); + goto error; + } + } else { + if (flecs_expr_global_variable_resolve(script, node, desc)) { + goto error; + } } + *cur = ecs_meta_cursor(script->world, node->node.type, NULL); + return 0; +error: + return -1; } static -bool is_sep( - const char **ptr, - const char *sep) +int flecs_expr_global_variable_visit_type( + ecs_script_t *script, + ecs_expr_variable_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) { - ecs_size_t len = ecs_os_strlen(sep); + (void)cur; - if (!ecs_os_strncmp(*ptr, sep, len)) { - *ptr += len; - return true; - } else { - return false; + if (flecs_expr_global_variable_resolve(script, node, desc)) { + goto error; } + + return 0; +error: + return -1; } static -const char* path_elem( - const char *path, - const char *sep, - int32_t *len) +int flecs_expr_arguments_visit_type( + ecs_script_t *script, + ecs_expr_initializer_t *node, + const ecs_expr_eval_desc_t *desc, + const struct ecs_script_function_t *func_data, + ecs_entity_t *vector_type_out) { - const char *ptr; - char ch; - int32_t template_nesting = 0; - int32_t count = 0; + ecs_script_parameter_t *params = ecs_vec_first(&func_data->params); + ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); + int32_t i, count = ecs_vec_count(&node->elements); + ecs_entity_t vector_type = 0; - for (ptr = path; (ch = *ptr); ptr ++) { - if (ch == '<') { - template_nesting ++; - } else if (ch == '>') { - template_nesting --; + for (i = 0; i < count; i ++) { + ecs_expr_initializer_element_t *elem = &elems[i]; + if (!elem->value) { + flecs_expr_visit_error(script, node, + "missing value for function argument"); + goto error; } - ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); + ecs_entity_t argtype = params[i].type; - if (!template_nesting && is_sep(&ptr, sep)) { - break; + if (argtype != EcsScriptVectorType) { + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, argtype, NULL); + + if (flecs_expr_visit_type_priv(script, elem->value, &cur, desc)){ + goto error; + } + } else { + ecs_meta_cursor_t cur; + ecs_os_zeromem(&cur); + + if (flecs_expr_visit_type_priv(script, elem->value, &cur, desc)){ + goto error; + } + + if (!vector_type) { + vector_type = elem->value->type; + } + + argtype = vector_type; + + /* Check if function provides implementation for type if argument + * is of a primitive type. If it doesn't, try to cast. */ + const EcsPrimitive *p = ecs_get( + script->world, argtype, EcsPrimitive); + if (p) { + if (!func_data->vector_callbacks[p->kind]) { + /* Fallback to types with max expressiveness */ + if (func_data->vector_callbacks[EcsF64]) { + vector_type = argtype = ecs_id(ecs_f64_t); + } else if (func_data->vector_callbacks[EcsI64]) { + vector_type = argtype = ecs_id(ecs_i64_t); + } else if (func_data->vector_callbacks[EcsU64]) { + vector_type = argtype = ecs_id(ecs_u64_t); + } else { + /* No matching implementation. Error will be caught + * later in the code. */ + } + } + } } - count ++; + if (elem->value->type != argtype) { + elem->value = (ecs_expr_node_t*)flecs_expr_cast( + script, elem->value, argtype); + if (!elem->value) { + goto error; + } + } } - if (len) { - *len = count; - } + *vector_type_out = vector_type; - if (count) { - return ptr; - } else { - return NULL; - } + return 0; error: - return NULL; + return -1; } static -ecs_entity_t get_parent_from_path( - const ecs_world_t *world, - ecs_entity_t parent, - const char **path_ptr, - const char *prefix, - bool new_entity) +int flecs_expr_populate_primitive_vector_calldata( + ecs_script_t *script, + ecs_expr_function_t *node, + const struct ecs_script_function_t *func_data, + ecs_entity_t vector_type, + ecs_entity_t elem_type, + int32_t count) { - bool start_from_root = false; - const char *path = *path_ptr; - - if (prefix) { - ecs_size_t len = ecs_os_strlen(prefix); - if (!ecs_os_strncmp(path, prefix, len)) { - path += len; - parent = 0; - start_from_root = true; - } + ecs_world_t *world = script->world; + const EcsPrimitive *ptype = ecs_get(world, elem_type, EcsPrimitive); + if (!ptype) { + flecs_expr_visit_error(script, node, + "type '%s' cannot be passed to vector argument of " + "function '%s': (member) type '%s' is not a primitive type", + flecs_errstr(ecs_get_path(world, vector_type)), + node->function_name, + flecs_errstr(ecs_get_path(world, elem_type))); + goto error; } - if (!start_from_root && !parent && new_entity) { - parent = ecs_get_scope(world); + ecs_vector_function_callback_t cb = func_data->vector_callbacks[ptype->kind]; + if (!cb) { + flecs_expr_visit_error(script, node, "function '%s' does not implement " + "matching vector operation for type '%s'", + node->function_name, + flecs_errstr(ecs_get_path(script->world, elem_type))); + goto error; } - *path_ptr = path; + node->calldata.is.vector_callback = cb; + node->calldata.vector_elem_count = count; - return parent; + return 0; +error: + return -1; } static -void on_set_symbol(ecs_iter_t *it) { - EcsIdentifier *n = ecs_term(it, EcsIdentifier, 1); - ecs_world_t *world = it->world; +int flecs_expr_populate_struct_vector_calldata( + ecs_script_t *script, + ecs_expr_function_t *node, + const struct ecs_script_function_t *func_data, + ecs_entity_t vector_type) +{ + ecs_world_t *world = script->world; + const EcsStruct *type = ecs_get(world, vector_type, EcsStruct); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); - int i; - for (i = 0; i < it->count; i ++) { - ecs_entity_t e = it->entities[i]; - flecs_name_index_ensure( - &world->symbols, e, n[i].value, n[i].length, n[i].hash); + ecs_entity_t elem_type = 0; + ecs_member_t *members = ecs_vec_first(&type->members); + int32_t i, count = ecs_vec_count(&type->members); + for (i = 0; i < count; i ++) { + ecs_member_t *m = &members[i]; + if (!i) { + elem_type = m->type; + } else { + if (m->type != elem_type) { + flecs_expr_visit_error(script, node, + "type '%s' cannot be passed to vector argument of " + "function '%s': all its members must be of the same type", + flecs_errstr(ecs_get_path(world, vector_type)), + node->function_name); + goto error; + } + } } -} -void flecs_bootstrap_hierarchy(ecs_world_t *world) { - ecs_trigger_init(world, &(ecs_trigger_desc_t){ - .term = {.id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), .subj.set.mask = EcsSelf }, - .callback = on_set_symbol, - .events = {EcsOnSet}, - .yield_existing = true - }); + return flecs_expr_populate_primitive_vector_calldata( + script, node, func_data, vector_type, elem_type, count); +error: + return -1; } +static +int flecs_expr_populate_array_vector_calldata( + ecs_script_t *script, + ecs_expr_function_t *node, + const struct ecs_script_function_t *func_data, + ecs_entity_t vector_type) +{ + const EcsArray *type = ecs_get(script->world, vector_type, EcsArray); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); -/* Public functions */ + return flecs_expr_populate_primitive_vector_calldata( + script, node, func_data, vector_type, type->type, type->count); +} -void ecs_get_path_w_sep_buf( - const ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t child, - const char *sep, - const char *prefix, - ecs_strbuf_t *buf) +static +int flecs_expr_populate_vector_vector_calldata( + ecs_script_t *script, + ecs_expr_function_t *node, + const struct ecs_script_function_t *func_data, + ecs_entity_t vector_type) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); + const EcsVector *type = ecs_get(script->world, vector_type, EcsVector); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + return flecs_expr_populate_primitive_vector_calldata( + script, node, func_data, vector_type, type->type, -1); +} - if (child == EcsThis) { - ecs_strbuf_appendstr(buf, "."); - return; - } - if (child == EcsWildcard) { - ecs_strbuf_appendstr(buf, "*"); - return; - } - if (child == EcsAny) { - ecs_strbuf_appendstr(buf, "_"); - return; - } +static +int flecs_expr_populate_vector_calldata( + ecs_script_t *script, + ecs_expr_function_t *node, + const struct ecs_script_function_t *func_data, + ecs_entity_t vector_type) +{ + ecs_world_t *world = script->world; + const EcsType *type = ecs_get(world, vector_type, EcsType); + ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); - if (!sep) { - sep = "."; + if (type->kind == EcsPrimitiveType) { + return flecs_expr_populate_primitive_vector_calldata( + script, node, func_data, vector_type, vector_type, 1); + } else if (type->kind == EcsStructType) { + return flecs_expr_populate_struct_vector_calldata( + script, node, func_data, vector_type); + } else if (type->kind == EcsArrayType) { + return flecs_expr_populate_array_vector_calldata( + script, node, func_data, vector_type); + } else if (type->kind == EcsVectorType) { + return flecs_expr_populate_vector_vector_calldata( + script, node, func_data, vector_type); } - if (!child || parent != child) { - path_append(world, parent, child, sep, prefix, buf); - } else { - ecs_strbuf_appendstr(buf, ""); - } + flecs_expr_visit_error(script, node, + "type '%s' cannot be passed to vector argument of function '%s'", + flecs_errstr(ecs_get_path(world, vector_type)), + node->function_name); -error: - return; + return -1; } -char* ecs_get_path_w_sep( - const ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t child, - const char *sep, - const char *prefix) +static +int flecs_expr_function_visit_type( + ecs_script_t *script, + ecs_expr_function_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf); - return ecs_strbuf_get(&buf); -} + bool is_method = false; + char *last_elem = NULL; + const char *func_identifier = NULL; -ecs_entity_t ecs_lookup_child( - const ecs_world_t *world, - ecs_entity_t parent, - const char *name) -{ - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + if (node->left->kind == EcsExprIdentifier) { + /* If identifier contains '.' separator(s), this is a method call, + * otherwise it's a regular function. */ + ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->left; + func_identifier = ident->value; - if (is_number(name)) { - return name_to_id(world, name); - } + last_elem = strrchr(func_identifier, '.'); + if (last_elem && last_elem != ident->value && last_elem[-1] != '\\') { + node->function_name = last_elem + 1; + last_elem[0] = '\0'; + is_method = true; + } else { + node->function_name = ident->value; + } - ecs_id_t pair = ecs_childof(parent); - ecs_hashmap_t *index = flecs_get_id_name_index(world, pair); - if (index) { - return flecs_name_index_find(index, name, 0, 0); - } else { - return 0; - } -error: - return 0; -} + } else if (node->left->kind == EcsExprMember) { + /* This is a method. Just like identifiers, method strings can contain + * separators. Split off last separator to get the method. */ + ecs_expr_member_t *member = (ecs_expr_member_t*)node->left; + last_elem = strrchr(member->member_name, '.'); + if (!last_elem) { + node->left = member->left; + node->function_name = member->member_name; -ecs_entity_t ecs_lookup( - const ecs_world_t *world, - const char *name) -{ - if (!name) { - return 0; + member->left = NULL; /* Prevent cleanup */ + flecs_expr_visit_free(script, (ecs_expr_node_t*)member); + } else { + node->function_name = last_elem + 1; + last_elem[0] = '\0'; + } + is_method = true; } - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + /* Left of function expression should not inherit lvalue type, since the + * function return type is what's going to be assigned. */ + ecs_os_zeromem(cur); - ecs_entity_t e = get_builtin(name); - if (e) { - return e; + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { + goto error; } - if (is_number(name)) { - return name_to_id(world, name); + ecs_world_t *world = script->world; + const ecs_vec_t *params = NULL; + const struct ecs_script_function_t *func_data = NULL; + + /* If this is a method, lookup function entity in scope of type */ + if (is_method) { + ecs_entity_t func = ecs_lookup_from( + world, node->left->type, node->function_name); + if (!func) { + /* If identifier could be a function (not a method) try that */ + if (func_identifier) { + is_method = false; + last_elem[0] = '.'; + node->function_name = func_identifier; + goto try_function; + } + + char *type_str = ecs_get_path(world, node->left->type); + flecs_expr_visit_error(script, node, + "unresolved method identifier '%s' for type '%s'", + node->function_name, type_str); + ecs_os_free(type_str); + goto error; + } + + func_data = ecs_get(world, func, EcsScriptMethod); + if (!func_data) { + char *path = ecs_get_path(world, func); + flecs_expr_visit_error(script, node, + "entity '%s' is not a valid method", path); + ecs_os_free(path); + goto error; + } + + node->node.kind = EcsExprMethod; + node->node.type = func_data->return_type; + node->calldata.function = func; + node->calldata.is.callback = func_data->callback; + node->calldata.ctx = func_data->ctx; + params = &func_data->params; } - e = flecs_name_index_find(&world->aliases, name, 0, 0); - if (e) { - return e; - } - - return ecs_lookup_child(world, 0, name); -error: - return 0; -} +try_function: + if (!is_method) { + ecs_entity_t func = desc->lookup_action( + world, node->function_name, desc->lookup_ctx); + if (!func) { + flecs_expr_visit_error(script, node, + "unresolved function identifier '%s'", + node->function_name); + goto error; + } -ecs_entity_t ecs_lookup_symbol( - const ecs_world_t *world, - const char *name, - bool lookup_as_path) -{ - if (!name) { - return 0; + func_data = ecs_get(world, func, EcsScriptFunction); + if (!func_data) { + char *path = ecs_get_path(world, func); + flecs_expr_visit_error(script, node, + "entity '%s' is not a valid method", path); + ecs_os_free(path); + goto error; + } + + node->node.type = func_data->return_type; + node->calldata.function = func; + node->calldata.is.callback = func_data->callback; + node->calldata.ctx = func_data->ctx; + node->calldata.vector_elem_count = 0; + params = &func_data->params; } - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + int32_t count = ecs_vec_count(&node->args->elements); + if (count != ecs_vec_count(params)) { + flecs_expr_visit_error(script, node, "expected %d arguments, got %d", + ecs_vec_count(params), count); + goto error; + } - ecs_entity_t e = flecs_name_index_find(&world->symbols, name, 0, 0); - if (e) { - return e; + ecs_entity_t vector_type = 0; + if (flecs_expr_arguments_visit_type( + script, node->args, desc, func_data, &vector_type)) + { + goto error; } - if (lookup_as_path) { - return ecs_lookup_fullpath(world, name); + if (vector_type) { + if (node->node.type == EcsScriptVectorType) { + node->node.type = vector_type; + } + + if (flecs_expr_populate_vector_calldata( + script, node, func_data, vector_type)) + { + goto error; + } + } else { + ecs_assert(node->node.type != EcsScriptVectorType, + ECS_INTERNAL_ERROR, NULL); } -error: return 0; +error: + return -1; } -ecs_entity_t ecs_lookup_path_w_sep( - const ecs_world_t *world, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix, - bool recursive) +static +int flecs_expr_member_visit_type( + ecs_script_t *script, + ecs_expr_member_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) { - if (!path) { - return 0; - } - - if (!sep) { - sep = "."; + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { + goto error; } - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_world_t *stage = world; - world = ecs_get_world(world); + ecs_world_t *world = script->world; + ecs_entity_t left_type = node->left->type; - ecs_entity_t e = get_builtin(path); - if (e) { - return e; + const EcsType *type = ecs_get(world, left_type, EcsType); + if (!type) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot resolve member on value of type '%s' " + "(missing reflection data)", type_str); + ecs_os_free(type_str); + goto error; } - e = flecs_name_index_find(&world->aliases, path, 0, 0); - if (e) { - return e; + if (type->kind != EcsStructType) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot resolve member on non-struct type '%s'", type_str); + ecs_os_free(type_str); + goto error; } - char buff[ECS_NAME_BUFFER_LENGTH]; - const char *ptr, *ptr_start; - char *elem = buff; - int32_t len, size = ECS_NAME_BUFFER_LENGTH; - ecs_entity_t cur; - bool lookup_path_search = false; - - ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); - ecs_entity_t *lookup_path_cur = lookup_path; - while (lookup_path_cur && *lookup_path_cur) { - lookup_path_cur ++; + if (ecs_meta_push(cur)) { + goto error; } - if (!sep) { - sep = "."; + int prev_log = ecs_log_set_level(-4); + if (ecs_meta_dotmember(cur, node->member_name)) { + ecs_log_set_level(prev_log); + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "unresolved member '%s' for type '%s'", + node->member_name, type_str); + ecs_os_free(type_str); + goto error; } + ecs_log_set_level(prev_log); - parent = get_parent_from_path(stage, parent, &path, prefix, true); + node->node.type = ecs_meta_get_type(cur); + node->offset = (uintptr_t)ecs_meta_get_ptr(cur); -retry: - cur = parent; - ptr_start = ptr = path; + return 0; +error: + return -1; +} - while ((ptr = path_elem(ptr, sep, &len))) { - if (len < size) { - ecs_os_memcpy(elem, ptr_start, len); - } else { - if (size == ECS_NAME_BUFFER_LENGTH) { - elem = NULL; - } +static +int flecs_expr_element_visit_type( + ecs_script_t *script, + ecs_expr_element_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + ecs_world_t *world = script->world; + + ecs_meta_cursor_t index_cur = {0}; + if (flecs_expr_visit_type_priv( + script, node->index, &index_cur, desc)) + { + goto error; + } - elem = ecs_os_realloc(elem, len + 1); - ecs_os_memcpy(elem, ptr_start, len); - size = len + 1; + /* Check if this is a component expression */ + if (node->index->kind == EcsExprIdentifier) { + /* Fetch type of left side of expression to check if it's of an entity + * type. Pass in an empty cursor object so we don't fail type checks in + * case it's a regular element expression. */ + ecs_meta_cursor_t tmp_cur = {0}; + if (flecs_expr_visit_type_priv(script, node->left, &tmp_cur, desc)) { + goto error; } - elem[len] = '\0'; - ptr_start = ptr; + ecs_entity_t left_type = node->left->type; - cur = ecs_lookup_child(world, cur, elem); - if (!cur) { - goto tail; + const EcsType *type = ecs_get(world, left_type, EcsType); + if (!type) { + char *type_str = ecs_get_path(world, left_type); + flecs_expr_visit_error(script, node, + "cannot use [] on value of type '%s' (missing reflection data)", + type_str); + ecs_os_free(type_str); + goto error; } - } -tail: - if (!cur && recursive) { - if (!lookup_path_search) { - if (parent) { - parent = ecs_get_object(world, parent, EcsChildOf, 0); - goto retry; - } else { - lookup_path_search = true; + bool is_entity_type = false; + + if (type->kind == EcsPrimitiveType) { + const EcsPrimitive *ptype = ecs_get(world, left_type, EcsPrimitive); + if (ptype->kind == EcsEntity) { + is_entity_type = true; } } - if (lookup_path_search) { - if (lookup_path_cur != lookup_path) { - lookup_path_cur --; - parent = lookup_path_cur[0]; - goto retry; + if (is_entity_type) { + ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->index; + node->node.type = desc->lookup_action( + script->world, ident->value, desc->lookup_ctx); + if (!node->node.type) { + flecs_expr_visit_error(script, node, + "unresolved component identifier '%s'", + ident->value); + goto error; } + + node->node.kind = EcsExprComponent; + + *cur = ecs_meta_cursor(script->world, node->node.type, NULL); + + return 0; } } - if (elem != buff) { - ecs_os_free(elem); + if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { + goto error; } - return cur; -error: - return 0; -} + if (ecs_meta_push(cur)) { + goto not_a_collection; + } -ecs_entity_t ecs_set_scope( - ecs_world_t *world, - ecs_entity_t scope) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); + if (!ecs_meta_is_collection(cur)) { + goto not_a_collection; + } - ecs_entity_t cur = stage->scope; - stage->scope = scope; + node->node.type = ecs_meta_get_type(cur); - return cur; -error: - return 0; -} + const ecs_type_info_t *elem_ti = ecs_get_type_info( + script->world, node->node.type); + node->elem_size = elem_ti->size; -ecs_entity_t ecs_get_scope( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - return stage->scope; -error: return 0; -} - -ecs_entity_t* ecs_set_lookup_path( - ecs_world_t *world, - const ecs_entity_t *lookup_path) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - - ecs_entity_t *cur = stage->lookup_path; - stage->lookup_path = (ecs_entity_t*)lookup_path; - return cur; -error: - return NULL; +not_a_collection: { + char *type_str = ecs_get_path(script->world, node->left->type); + flecs_expr_visit_error(script, node, + "invalid usage of [] on non collection/entity type '%s'", type_str); + ecs_os_free(type_str); } - -ecs_entity_t* ecs_get_lookup_path( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - return stage->lookup_path; error: - return NULL; + return -1; } -const char* ecs_set_name_prefix( - ecs_world_t *world, - const char *prefix) +static +bool flecs_expr_identifier_is_any( + ecs_expr_node_t *node) { - ecs_poly_assert(world, ecs_world_t); - const char *old_prefix = world->name_prefix; - world->name_prefix = prefix; - return old_prefix; + if (node->kind == EcsExprIdentifier) { + ecs_expr_identifier_t *id = (ecs_expr_identifier_t*)node; + if (id->value && !ecs_os_strcmp(id->value, "_")) { + return true; + } + } + return false; } -ecs_entity_t ecs_add_path_w_sep( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix) +static +int flecs_expr_match_visit_type( + ecs_script_t *script, + ecs_expr_match_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!sep) { - sep = "."; - } + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - if (!path) { - if (!entity) { - entity = ecs_new_id(world); - } + ecs_meta_cursor_t expr_cur; + ecs_os_zeromem(&expr_cur); + if (flecs_expr_visit_type_priv(script, node->expr, &expr_cur, desc)) { + goto error; + } - if (parent) { - ecs_add_pair(world, entity, EcsChildOf, entity); - } + int32_t i, count = ecs_vec_count(&node->elements); + ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); - return entity; + if (!count) { + flecs_expr_visit_error(script, node, + "match statement must have at least one case"); + goto error; } - parent = get_parent_from_path(world, parent, &path, prefix, entity == 0); + /* Determine most expressive type of all elements */ + node->node.type = ecs_meta_get_type(cur); - char buff[ECS_NAME_BUFFER_LENGTH]; - const char *ptr = path; - const char *ptr_start = path; - char *elem = buff; - int32_t len, size = ECS_NAME_BUFFER_LENGTH; + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; - ecs_entity_t cur = parent; + if (node->node.type) { + expr_cur = ecs_meta_cursor(script->world, node->node.type, NULL); + } else { + ecs_os_zeromem(&expr_cur); + } - char *name = NULL; + if (flecs_expr_visit_type_priv(script, elem->expr, &expr_cur, desc)) { + goto error; + } + + if (!node->node.type) { + node->node.type = elem->expr->type; + continue; + } + + if (flecs_expr_is_type_number(node->node.type)) { + ecs_entity_t result_type = 0, operand_type = 0; + if (flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, 0, + (ecs_expr_node_t*)node, elem->expr, + EcsTokAdd, /* Use operator that doesn't change types */ + &operand_type, &result_type, NULL)) + { + goto error; + } - while ((ptr = path_elem(ptr, sep, &len))) { - if (len < size) { - ecs_os_memcpy(elem, ptr_start, len); + /* "Accumulate" most expressive type in result node */ + node->node.type = result_type; } else { - if (size == ECS_NAME_BUFFER_LENGTH) { - elem = NULL; + /* If type is not a number it must match exactly */ + if (elem->expr->type != node->node.type) { + char *got = ecs_get_path(script->world, elem->expr->type); + char *expect = ecs_get_path(script->world, node->node.type); + flecs_expr_visit_error(script, node, + "invalid type for case %d in match (got %s, expected %s)", + i + 1, got, expect); + ecs_os_free(got); + ecs_os_free(expect); + goto error; } + } + } - elem = ecs_os_realloc(elem, len + 1); - ecs_os_memcpy(elem, ptr_start, len); - size = len + 1; + /* Loop over elements again, cast values to result type */ + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; + if (elem->expr->type != node->node.type) { + elem->expr = (ecs_expr_node_t*) + flecs_expr_cast(script, elem->expr, node->node.type); + if (!elem->expr) { + goto error; + } } + } - elem[len] = '\0'; - ptr_start = ptr; + /* If this is an enum type, cast to the underlying type. This is necessary + * because the compare operation executed by the match evaluation code isn't + * implemented for enums. */ + ecs_entity_t expr_type = node->expr->type; + { + const EcsEnum *ptr = ecs_get(script->world, expr_type, EcsEnum); + if (ptr) { + node->expr = (ecs_expr_node_t*) + flecs_expr_cast(script, node->expr, ptr->underlying_type); + } + } - ecs_entity_t e = ecs_lookup_child(world, cur, elem); - if (!e) { - if (name) { - ecs_os_free(name); - } + /* Do the same for bitmasks */ + { + const EcsBitmask *ptr = ecs_get(script->world, expr_type, EcsBitmask); + if (ptr) { + /* For now bitmasks are always u32s */ + node->expr = (ecs_expr_node_t*) + flecs_expr_cast(script, node->expr, ecs_id(ecs_u32_t)); + } + } - name = ecs_os_strdup(elem); + /* Make sure that case values match the input type */ + for (i = 0; i < count; i ++) { + ecs_expr_match_element_t *elem = &elems[i]; - /* If this is the last entity in the path, use the provided id */ - bool last_elem = false; - if (!path_elem(ptr, sep, NULL)) { - e = entity; - last_elem = true; + if (flecs_expr_identifier_is_any(elem->compare)) { + if (i != count - 1) { + flecs_expr_visit_error(script, node, + "any (_) must be the last case in match"); + goto error; } - if (!e) { - if (last_elem) { - ecs_entity_t prev = ecs_set_scope(world, 0); - e = ecs_new(world, 0); - ecs_set_scope(world, prev); - } else { - e = ecs_new_id(world); - } + node->any.compare = elem->compare; + node->any.expr = elem->expr; + ecs_vec_remove_last(&node->elements); + } else { + expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); + if (flecs_expr_visit_type_priv( + script, elem->compare, &expr_cur, desc)) + { + goto error; } - if (cur) { - ecs_add_pair(world, e, EcsChildOf, cur); + ecs_expr_node_t *compare = elem->compare; + if (compare->type != node->expr->type) { + elem->compare = (ecs_expr_node_t*) + flecs_expr_cast(script, compare, node->expr->type); + if (!elem->compare) { + goto error; + } } - - ecs_set_name(world, e, name); } - - cur = e; } - if (entity && (cur != entity)) { - ecs_throw(ECS_ALREADY_DEFINED, name); - } + return 0; +error: + return -1; +} - if (name) { - ecs_os_free(name); - } +static +int flecs_expr_new_visit_type( + ecs_script_t *script, + ecs_expr_new_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + (void)script; + (void)cur; + (void)desc; + node->node.type = ecs_id(ecs_entity_t); + return 0; +} - if (elem != buff) { - ecs_os_free(elem); +static +int flecs_expr_visit_type_priv( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_meta_cursor_t *cur, + const ecs_expr_eval_desc_t *desc) +{ + ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); + + switch(node->kind) { + case EcsExprValue: + break; + case EcsExprInterpolatedString: + if (flecs_expr_interpolated_string_visit_type( + script, (ecs_expr_interpolated_string_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprEmptyInitializer: + if (flecs_expr_empty_initializer_visit_type( + script, (ecs_expr_initializer_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprInitializer: + if (flecs_expr_initializer_visit_type( + script, (ecs_expr_initializer_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprUnary: + if (flecs_expr_unary_visit_type( + script, (ecs_expr_unary_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprBinary: + if (flecs_expr_binary_visit_type( + script, (ecs_expr_binary_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprIdentifier: + if (flecs_expr_identifier_visit_type( + script, (ecs_expr_identifier_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprVariable: + if (flecs_expr_variable_visit_type( + script, (ecs_expr_variable_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprGlobalVariable: + if (flecs_expr_global_variable_visit_type( + script, (ecs_expr_variable_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprFunction: + if (flecs_expr_function_visit_type( + script, (ecs_expr_function_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprMember: + if (flecs_expr_member_visit_type( + script, (ecs_expr_member_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprElement: + if (flecs_expr_element_visit_type( + script, (ecs_expr_element_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprMatch: + if (flecs_expr_match_visit_type( + script, (ecs_expr_match_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprNew: + if (flecs_expr_new_visit_type( + script, (ecs_expr_new_t*)node, cur, desc)) + { + goto error; + } + break; + case EcsExprCast: + case EcsExprCastNumber: + break; + case EcsExprMethod: + case EcsExprComponent: + /* Expressions are derived by type visitor */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); } - return cur; -error: + ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); + node->type_info = ecs_get_type_info(script->world, node->type); + ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + return 0; +error: + return -1; } -ecs_entity_t ecs_new_from_path_w_sep( - ecs_world_t *world, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix) +int flecs_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_expr_eval_desc_t *desc) { - if (!sep) { - sep = "."; + // ecs_strbuf_t buf = ECS_STRBUF_INIT; + // flecs_expr_to_str_buf(script, node, &buf, true); + // char *str = ecs_strbuf_get(&buf); + // printf("%s\n", str); + // ecs_os_free(str); + + if (node->kind == EcsExprEmptyInitializer) { + node->type = desc->type; + + if (node->type) { + ecs_expr_initializer_t* init_node = (ecs_expr_initializer_t*)node; + init_node->is_dynamic = flecs_expr_initializer_is_dynamic( + script->world, node->type); + + if (flecs_expr_initializer_collection_check( + script, init_node, NULL)) + { + return -1; + } + + node->type_info = ecs_get_type_info(script->world, node->type); + return 0; + } } - return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); + if (desc->type) { + ecs_meta_cursor_t cur = ecs_meta_cursor( + script->world, desc->type, NULL); + return flecs_expr_visit_type_priv(script, node, &cur, desc); + } else { + ecs_meta_cursor_t cur; + ecs_os_zeromem(&cur); + return flecs_expr_visit_type_priv(script, node, &cur, desc); + } } +#endif + diff --git a/deps/flecs.h b/deps/flecs.h index 26541a4f..77e4ca5d 100644 --- a/deps/flecs.h +++ b/deps/flecs.h @@ -11,33 +11,84 @@ #define FLECS_H /** - * @defgroup options API toggles & constants + * @defgroup c C API + * + * @{ + * @} + */ + +/** + * @defgroup core Core + * @ingroup c + * Core ECS functionality (entities, storage, queries). + * + * @{ + */ + +/** + * @defgroup options API defines + * Defines for customizing compile time features. + * * @{ */ -/* Customizable precision for floating point operations (such as time ops) */ -#ifndef FLECS_FLOAT -#define FLECS_FLOAT float +/* Flecs version macros */ +#define FLECS_VERSION_MAJOR 4 /**< Flecs major version. */ +#define FLECS_VERSION_MINOR 1 /**< Flecs minor version. */ +#define FLECS_VERSION_PATCH 4 /**< Flecs patch version. */ + +/** Flecs version. */ +#define FLECS_VERSION FLECS_VERSION_IMPL(\ + FLECS_VERSION_MAJOR, FLECS_VERSION_MINOR, FLECS_VERSION_PATCH) + +/** @def FLECS_CONFIG_HEADER + * Allows for including a user-customizable header that specifies compile-time + * features. */ +#ifdef FLECS_CONFIG_HEADER +#include "flecs_config.h" +#endif + +/** @def ecs_float_t + * Customizable precision for floating point operations */ +#ifndef ecs_float_t +#define ecs_float_t float +#endif + +/** @def ecs_ftime_t + * Customizable precision for scalar time values. Change to double precision for + * processes that can run for a long time (e.g. longer than a day). */ +#ifndef ecs_ftime_t +#define ecs_ftime_t ecs_float_t #endif -/* FLECS_LEGACY should be defined when building for C89 */ -// #define FLECS_LEGACY -/* FLECS_NO_DEPRECATED_WARNINGS disables deprecated warnings */ -#define FLECS_NO_DEPRECATED_WARNINGS +/** @def FLECS_ACCURATE_COUNTERS + * Define to ensure that global counters used for statistics (such as the + * allocation counters in the OS API) are accurate in multithreaded + * applications, at the cost of increased overhead. + */ +// #define FLECS_ACCURATE_COUNTERS + +/** @def FLECS_DISABLE_COUNTERS + * Disables counters used for statistics. Improves performance, but + * will prevent some features that rely on statistics from working, + * like the statistics pages in the explorer. + */ +// #define FLECS_DISABLE_COUNTERS /* Make sure provided configuration is valid */ #if defined(FLECS_DEBUG) && defined(FLECS_NDEBUG) -#error "invalid configuration: cannot both define FLECS_DEBUG and FLECS_NDEBUG" +#warning "invalid configuration: cannot both define FLECS_DEBUG and FLECS_NDEBUG" #endif #if defined(FLECS_DEBUG) && defined(NDEBUG) -#error "invalid configuration: cannot both define FLECS_DEBUG and NDEBUG" +#warning "invalid configuration: cannot both define FLECS_DEBUG and NDEBUG" #endif -/* Flecs debugging enables asserts, which are used for input parameter checking - * and cheap (constant time) sanity checks. There are lots of asserts in every - * part of the code, so this will slow down code. */ -#if !defined(FLECS_DEBUG) && !defined(FLECS_NDEBUG) +/** @def FLECS_DEBUG + * Used for input parameter checking and cheap sanity checks. There are lots of + * asserts in every part of the code, so this will slow down applications. + */ +#if !defined(FLECS_DEBUG) && !defined(FLECS_NDEBUG) #if defined(NDEBUG) #define FLECS_NDEBUG #else @@ -45,82 +96,277 @@ #endif #endif -/* FLECS_SANITIZE enables expensive checks that can detect issues early. This - * will severely slow down code. */ -// #define FLECS_SANITIZE +/** @def FLECS_SANITIZE + * Enables expensive checks that can detect issues early. Recommended for + * running tests or when debugging issues. This will severely slow down code. + */ #ifdef FLECS_SANITIZE +#ifndef FLECS_DEBUG #define FLECS_DEBUG /* If sanitized mode is enabled, so is debug mode */ #endif +#endif + +/** @def FLECS_DEBUG_INFO + * Adds additional debug information to internal data structures. Necessary when + * using natvis. + */ +#ifdef FLECS_DEBUG +#ifndef FLECS_DEBUG_INFO +#define FLECS_DEBUG_INFO +#endif +#endif /* Tip: if you see weird behavior that you think might be a bug, make sure to * test with the FLECS_DEBUG or FLECS_SANITIZE flags enabled. There's a good * chance that this gives you more information about the issue! */ -/* FLECS_SOFT_ASSERT disables aborting for recoverable errors */ +/** @def FLECS_SOFT_ASSERT + * Define to not abort for recoverable errors, like invalid parameters. An error + * is still thrown to the console. This is recommended for when running inside a + * third party runtime, such as the Unreal editor. + * + * Note that internal sanity checks (ECS_INTERNAL_ERROR) will still abort a + * process, as this gives more information than a (likely) subsequent crash. + * + * When a soft assert occurs, the code will attempt to minimize the number of + * side effects of the failed operation, but this may not always be possible. + * Even though an application may still be able to continue running after a soft + * assert, it should be treated as if in an undefined state. + */ // #define FLECS_SOFT_ASSERT -/* FLECS_KEEP_ASSERT keeps asserts in release mode. */ +/** @def FLECS_KEEP_ASSERT + * By default asserts are disabled in release mode, when either FLECS_NDEBUG or + * NDEBUG is defined. Defining FLECS_KEEP_ASSERT ensures that asserts are not + * disabled. This define can be combined with FLECS_SOFT_ASSERT. + */ // #define FLECS_KEEP_ASSERT -/* The following macro's let you customize with which addons Flecs is built. - * Without any addons Flecs is just a minimal ECS storage, but addons add +/** @def FLECS_DEFAULT_TO_UNCACHED_QUERIES + * When set, this will cause queries with the EcsQueryCacheDefault policy + * to default to EcsQueryCacheNone. This can reduce the memory footprint of + * applications at the cost of performance. Queries that use features which + * require caching such as group_by and order_by will still use caching. + */ +// #define FLECS_DEFAULT_TO_UNCACHED_QUERIES + +/** @def FLECS_CREATE_MEMBER_ENTITIES + * By default, Flecs does not create entities for component members when they + * are registered with reflection. Define this addon if an application relies + * on members being defined as entities. Features that require members to be + * defined as entities are: + * - Member queries + * - Metrics + * - Alerts + * + * Member entities can also be created on a per-type basis by setting + * ecs_struct_desc_t::create_member_entities to true. + */ +// #define FLECS_CREATE_MEMBER_ENTITIES + +/** @def FLECS_CPP_NO_AUTO_REGISTRATION + * When set, the C++ API will require that components are registered before they + * are used. This is useful in multithreaded applications, where components need + * to be registered beforehand, and to catch issues in projects where component + * registration is mandatory. Disabling automatic component registration also + * slightly improves performance. + * The C API is not affected by this feature. + */ +// #define FLECS_CPP_NO_AUTO_REGISTRATION + +/** @def FLECS_CPP_NO_ENUM_REFLECTION + * When set, the C++ API will not attempt to discover and register enum + * constants for registered enum components. This will cause C++ APIs that + * accept enum constants to not work. + * Disabling this feature can significantly improve compile times and reduces + * the RAM footprint of an application. + */ +// #define FLECS_CPP_NO_ENUM_REFLECTION + +/** @def FLECS_NO_ALWAYS_INLINE + * When set, this will prevent functions from being annotated with always_inline + * which can improve performance at the cost of increased binary footprint. + */ +// #define FLECS_NO_ALWAYS_INLINE + +/** @def FLECS_CUSTOM_BUILD + * This macro lets you customize which addons to build flecs with. + * Without any addons Flecs is just a minimal ECS storage, but addons add * features such as systems, scheduling and reflection. If an addon is disabled, * it is excluded from the build, so that it consumes no resources. By default * all addons are enabled. - * + * * You can customize a build by either whitelisting or blacklisting addons. To * whitelist addons, first define the FLECS_CUSTOM_BUILD macro, which disables * all addons. You can then manually select the addons you need by defining * their macro, like "FLECS_SYSTEM". - * + * * To blacklist an addon, make sure to *not* define FLECS_CUSTOM_BUILD, and * instead define the addons you don't need by defining FLECS_NO_, for - * example "FLECS_NO_SYSTEM". If there are any addons that depend on the + * example "FLECS_NO_SYSTEM". If there are any addons that depend on the * blacklisted addon, an error will be thrown during the build. - * - * Note that addons can have dependencies on each other. Addons will + * + * Note that addons can have dependencies on each other. Addons will * automatically enable their dependencies. To see the list of addons that was * compiled in a build, enable tracing before creating the world by doing: - * ecs_log_set_level(0); + * + * @code + * ecs_log_set_level(0); + * @endcode + * * which outputs the full list of addons Flecs was compiled with. */ - -/* Define if you want to create a custom build by whitelisting addons */ // #define FLECS_CUSTOM_BUILD #ifndef FLECS_CUSTOM_BUILD -// #define FLECS_C /* C API convenience macro's, always enabled */ -#define FLECS_CPP /* C++ API */ -#define FLECS_MODULE /* Module support */ -#define FLECS_PARSER /* String parser for queries */ -#define FLECS_PLECS /* ECS data definition format */ -#define FLECS_RULES /* Constraint solver for advanced queries */ -#define FLECS_SNAPSHOT /* Snapshot & restore ECS data */ -#define FLECS_STATS /* Keep track of runtime statistics */ -#define FLECS_SYSTEM /* System support */ -#define FLECS_PIPELINE /* Pipeline support */ -#define FLECS_TIMER /* Timer support */ -#define FLECS_META /* Reflection support */ -#define FLECS_META_C /* Utilities for populating reflection data */ -#define FLECS_UNITS /* Builtin standard units */ -#define FLECS_EXPR /* Parsing strings to/from component values */ -#define FLECS_JSON /* Parsing JSON to/from component values */ -#define FLECS_DOC /* Document entities & components */ -#define FLECS_COREDOC /* Documentation for core entities & components */ -#define FLECS_LOG /* When enabled ECS provides more detailed logs */ -#define FLECS_APP /* Application addon */ -#define FLECS_OS_API_IMPL /* Default implementation for OS API */ -#define FLECS_HTTP /* Tiny HTTP server for connecting to remote UI */ -#define FLECS_REST /* REST API for querying application data */ +#define FLECS_ALERTS /**< Monitor conditions for errors */ +#define FLECS_APP /**< Application addon */ +// #define FLECS_C /**< C API convenience macros, always enabled */ +#define FLECS_CPP /**< C++ API */ +#define FLECS_DOC /**< Document entities & components */ +// #define FLECS_JOURNAL /**< Journaling addon */ +#define FLECS_JSON /**< Parsing JSON to/from component values */ +#define FLECS_HTTP /**< Tiny HTTP server for connecting to remote UI */ +#define FLECS_LOG /**< When enabled ECS provides more detailed logs */ +#define FLECS_META /**< Reflection support */ +#define FLECS_METRICS /**< Expose component data as statistics */ +#define FLECS_MODULE /**< Module support */ +#define FLECS_OS_API_IMPL /**< Default implementation for OS API */ +// #define FLECS_PERF_TRACE /**< Enable performance tracing */ +#define FLECS_PIPELINE /**< Pipeline support */ +#define FLECS_REST /**< REST API for querying application data */ +#define FLECS_PARSER /**< Utilities for script and query DSL parsers */ +#define FLECS_QUERY_DSL /**< Flecs query DSL parser */ +#define FLECS_SCRIPT /**< Flecs entity notation language */ +// #define FLECS_SCRIPT_MATH /**< Math functions for flecs script (may require linking with libm) */ +#define FLECS_SYSTEM /**< System support */ +#define FLECS_STATS /**< Track runtime statistics */ +#define FLECS_TIMER /**< Timer support */ +#define FLECS_UNITS /**< Builtin standard units */ #endif // ifndef FLECS_CUSTOM_BUILD +/** @def FLECS_LOW_FOOTPRINT + * Set a number of constants to values that decrease memory footprint, at the + * cost of decreased performance. */ +// #define FLECS_LOW_FOOTPRINT +#ifdef FLECS_LOW_FOOTPRINT +#define FLECS_HI_COMPONENT_ID 16 +#define FLECS_HI_ID_RECORD_ID 16 +#define FLECS_ENTITY_PAGE_BITS 6 +#define FLECS_USE_OS_ALLOC +#define FLECS_DEFAULT_TO_UNCACHED_QUERIES +#endif + +/** @def FLECS_HI_COMPONENT_ID + * This constant can be used to balance between performance and memory + * utilization. The constant is used in two ways: + * - Entity ids 0..FLECS_HI_COMPONENT_ID are reserved for component ids. + * - Used as lookup array size in table edges. + * + * Increasing this value increases the size of the lookup array, which allows + * fast table traversal, which improves performance of ECS add/remove + * operations. Component ids that fall outside of this range use a regular map + * lookup, which is slower but more memory efficient. + * + * This value must be set to a value that is a power of 2. Setting it to a value + * that is not a power of two will degrade performance. + */ +#ifndef FLECS_HI_COMPONENT_ID +#define FLECS_HI_COMPONENT_ID 256 +#endif + +/** @def FLECS_HI_ID_RECORD_ID + * This constant can be used to balance between performance and memory + * utilization. The constant is used to determine the size of the component record + * lookup array. Id values that fall outside of this range use a regular map + * lookup, which is slower but more memory efficient. + */ +#ifndef FLECS_HI_ID_RECORD_ID +#define FLECS_HI_ID_RECORD_ID 1024 +#endif + +/** @def FLECS_SPARSE_PAGE_BITS + * This constant is used to determine the number of bits of an id that is used + * to determine the page index when used with a sparse set. The number of bits + * determines the page size, which is (1 << bits). + * Lower values decrease memory utilization, at the cost of more allocations. */ +#ifndef FLECS_SPARSE_PAGE_BITS +#define FLECS_SPARSE_PAGE_BITS 6 +#endif + +/** @def FLECS_ENTITY_PAGE_BITS + * Same as FLECS_SPARSE_PAGE_BITS, but for the entity index. */ +#ifndef FLECS_ENTITY_PAGE_BITS +#define FLECS_ENTITY_PAGE_BITS 10 +#endif + +/** @def FLECS_USE_OS_ALLOC + * When enabled, Flecs will use the OS allocator provided in the OS API directly + * instead of the builtin block allocator. This can decrease memory utilization + * as memory will be freed more often, at the cost of decreased performance. */ +// #define FLECS_USE_OS_ALLOC + +/** @def FLECS_ID_DESC_MAX + * Maximum number of ids to add ecs_entity_desc_t / ecs_bulk_desc_t */ +#ifndef FLECS_ID_DESC_MAX +#define FLECS_ID_DESC_MAX 32 +#endif + +/** @def FLECS_EVENT_DESC_MAX + * Maximum number of events in ecs_observer_desc_t */ +#ifndef FLECS_EVENT_DESC_MAX +#define FLECS_EVENT_DESC_MAX 8 +#endif + +/** @def FLECS_VARIABLE_COUNT_MAX + * Maximum number of query variables per query */ +#define FLECS_VARIABLE_COUNT_MAX 64 + +/** @def FLECS_TERM_COUNT_MAX + * Maximum number of terms in queries. Should not exceed 64. */ +#ifndef FLECS_TERM_COUNT_MAX +#define FLECS_TERM_COUNT_MAX 32 +#endif + +/** @def FLECS_TERM_ARG_COUNT_MAX + * Maximum number of arguments for a term. */ +#ifndef FLECS_TERM_ARG_COUNT_MAX +#define FLECS_TERM_ARG_COUNT_MAX 16 +#endif + +/** @def FLECS_QUERY_VARIABLE_COUNT_MAX + * Maximum number of query variables per query. Should not exceed 128. */ +#ifndef FLECS_QUERY_VARIABLE_COUNT_MAX +#define FLECS_QUERY_VARIABLE_COUNT_MAX 64 +#endif + +/** @def FLECS_QUERY_SCOPE_NESTING_MAX + * Maximum nesting depth of query scopes */ +#ifndef FLECS_QUERY_SCOPE_NESTING_MAX +#define FLECS_QUERY_SCOPE_NESTING_MAX 8 +#endif + +/** @def FLECS_DAG_DEPTH_MAX + * Maximum of levels in a DAG (acyclic relationship graph). If a graph with a + * depth larger than this is encountered, a CYCLE_DETECTED panic is thrown. + */ +#ifndef FLECS_DAG_DEPTH_MAX +#define FLECS_DAG_DEPTH_MAX 128 +#endif + +/** @def FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE + * Size of depth cache in tree spawner component. Higher values speed up prefab + * instantiation for deeper hierarchies, at the cost of slightly more memory. + */ +#define FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE (6) + /** @} */ /** * @file api_defines.h * @brief Supporting defines for the public API. * - * This file contains constants / macro's that are typically not used by an + * This file contains constants / macros that are typically not used by an * application but support the public API, and therefore must be exposed. This * header should not be included by itself. */ @@ -128,8 +374,287 @@ #ifndef FLECS_API_DEFINES_H #define FLECS_API_DEFINES_H -#if defined(_WIN32) || defined(_MSC_VER) || defined(__MING32__) +/** + * @file api_flags.h + * @brief Bitset flags used by internals. + */ + +#ifndef FLECS_API_FLAGS_H +#define FLECS_API_FLAGS_H + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////////////////////////////////////////////////////////////////////////// +//// World flags +//////////////////////////////////////////////////////////////////////////////// + +#define EcsWorldQuitWorkers (1u << 0) +#define EcsWorldReadonly (1u << 1) +#define EcsWorldInit (1u << 2) +#define EcsWorldQuit (1u << 3) +#define EcsWorldFini (1u << 4) +#define EcsWorldMeasureFrameTime (1u << 5) +#define EcsWorldMeasureSystemTime (1u << 6) +#define EcsWorldMultiThreaded (1u << 7) +#define EcsWorldFrameInProgress (1u << 8) + +//////////////////////////////////////////////////////////////////////////////// +//// OS API flags +//////////////////////////////////////////////////////////////////////////////// + +#define EcsOsApiHighResolutionTimer (1u << 0) +#define EcsOsApiLogWithColors (1u << 1) +#define EcsOsApiLogWithTimeStamp (1u << 2) +#define EcsOsApiLogWithTimeDelta (1u << 3) + + +//////////////////////////////////////////////////////////////////////////////// +//// Entity flags (set in upper bits of ecs_record_t::row) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsEntityIsId (1u << 31) +#define EcsEntityIsTarget (1u << 30) +#define EcsEntityIsTraversable (1u << 29) +#define EcsEntityHasDontFragment (1u << 28) + + +//////////////////////////////////////////////////////////////////////////////// +//// Id flags (used by ecs_component_record_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsIdOnDeleteRemove (1u << 0) +#define EcsIdOnDeleteDelete (1u << 1) +#define EcsIdOnDeletePanic (1u << 2) +#define EcsIdOnDeleteMask\ + (EcsIdOnDeletePanic|EcsIdOnDeleteRemove|EcsIdOnDeleteDelete) + +#define EcsIdOnDeleteTargetRemove (1u << 3) +#define EcsIdOnDeleteTargetDelete (1u << 4) +#define EcsIdOnDeleteTargetPanic (1u << 5) +#define EcsIdOnDeleteTargetMask\ + (EcsIdOnDeleteTargetPanic|EcsIdOnDeleteTargetRemove|\ + EcsIdOnDeleteTargetDelete) + +#define EcsIdOnInstantiateOverride (1u << 6) +#define EcsIdOnInstantiateInherit (1u << 7) +#define EcsIdOnInstantiateDontInherit (1u << 8) +#define EcsIdOnInstantiateMask\ + (EcsIdOnInstantiateOverride|EcsIdOnInstantiateInherit|\ + EcsIdOnInstantiateDontInherit) + +#define EcsIdExclusive (1u << 9) +#define EcsIdTraversable (1u << 10) +#define EcsIdPairIsTag (1u << 11) +#define EcsIdWith (1u << 12) +#define EcsIdCanToggle (1u << 13) +#define EcsIdIsTransitive (1u << 14) +#define EcsIdInheritable (1u << 15) + +#define EcsIdHasOnAdd (1u << 16) /* Same values as table flags */ +#define EcsIdHasOnRemove (1u << 17) +#define EcsIdHasOnSet (1u << 18) +#define EcsIdHasOnTableCreate (1u << 19) +#define EcsIdHasOnTableDelete (1u << 20) +#define EcsIdSparse (1u << 21) +#define EcsIdDontFragment (1u << 22) +#define EcsIdMatchDontFragment (1u << 23) /* For (*, T) wildcards */ +#define EcsIdOrderedChildren (1u << 24) +#define EcsIdSingleton (1u << 25) +#define EcsIdEventMask\ + (EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet|\ + EcsIdHasOnTableCreate|EcsIdHasOnTableDelete|EcsIdSparse|\ + EcsIdOrderedChildren) +#define EcsIdPrefabChildren (1u << 26) + +#define EcsIdMarkedForDelete (1u << 30) + +/* Utilities for converting from flags to delete policies and vice versa */ +#define ECS_ID_ON_DELETE(flags) \ + ((ecs_entity_t[]){0, EcsRemove, EcsDelete, 0, EcsPanic}\ + [((flags) & EcsIdOnDeleteMask)]) +#define ECS_ID_ON_DELETE_TARGET(flags) ECS_ID_ON_DELETE(flags >> 3) +#define ECS_ID_ON_DELETE_FLAG(id) (1u << ((id) - EcsRemove)) +#define ECS_ID_ON_DELETE_TARGET_FLAG(id) (1u << (3 + ((id) - EcsRemove))) + +/* Utilities for converting from flags to instantiate policies and vice versa */ +#define ECS_ID_ON_INSTANTIATE(flags) \ + ((ecs_entity_t[]){EcsOverride, EcsOverride, EcsInherit, 0, EcsDontInherit}\ + [(((flags) & EcsIdOnInstantiateMask) >> 6)]) +#define ECS_ID_ON_INSTANTIATE_FLAG(id) (1u << (6 + ((id) - EcsOverride))) + + +//////////////////////////////////////////////////////////////////////////////// +//// Bits set in world->non_trivial array +//////////////////////////////////////////////////////////////////////////////// + +#define EcsNonTrivialIdSparse (1u << 0) +#define EcsNonTrivialIdNonFragmenting (1u << 1) +#define EcsNonTrivialIdInherit (1u << 2) + + +//////////////////////////////////////////////////////////////////////////////// +//// Iterator flags (used by ecs_iter_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsIterIsValid (1u << 0u) /* Does iterator contain valid result */ +#define EcsIterNoData (1u << 1u) /* Does iterator provide (component) data */ +#define EcsIterNoResults (1u << 2u) /* Iterator has no results */ +#define EcsIterMatchEmptyTables (1u << 3u) /* Match empty tables */ +#define EcsIterIgnoreThis (1u << 4u) /* Only evaluate non-this terms */ +#define EcsIterTrivialChangeDetection (1u << 5u) +#define EcsIterHasCondSet (1u << 6u) /* Does iterator have conditionally set fields */ +#define EcsIterProfile (1u << 7u) /* Profile iterator performance */ +#define EcsIterTrivialSearch (1u << 8u) /* Trivial iterator mode */ +#define EcsIterTrivialTest (1u << 11u) /* Trivial test mode (constrained $this) */ +#define EcsIterTrivialCached (1u << 14u) /* Trivial search for cached query */ +#define EcsIterCached (1u << 15u) /* Cached query */ +#define EcsIterFixedInChangeComputed (1u << 16u) /* Change detection for fixed in terms is done */ +#define EcsIterFixedInChanged (1u << 17u) /* Fixed in terms changed */ +#define EcsIterSkip (1u << 18u) /* Result was skipped for change detection */ +#define EcsIterCppEach (1u << 19u) /* Uses C++ 'each' iterator */ +#define EcsIterImmutableCacheData (1u << 21u) /* Internally used by engine to indicate immutable arrays from cache */ + + +/* Same as event flags */ +#define EcsIterTableOnly (1u << 20u) /* Result only populates table */ + + +//////////////////////////////////////////////////////////////////////////////// +//// Event flags (used by ecs_event_decs_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsEventTableOnly (1u << 20u) /* Table event (no data, same as iter flags) */ +#define EcsEventNoOnSet (1u << 16u) /* Don't emit OnSet for inherited ids */ + + +//////////////////////////////////////////////////////////////////////////////// +//// Query flags (used by ecs_query_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +/* Flags that can only be set by the query implementation */ +#define EcsQueryMatchThis (1u << 11u) /* Query has terms with $this source */ +#define EcsQueryMatchOnlyThis (1u << 12u) /* Query only has terms with $this source */ +#define EcsQueryMatchOnlySelf (1u << 13u) /* Query has no terms with up traversal */ +#define EcsQueryMatchWildcards (1u << 14u) /* Query matches wildcards */ +#define EcsQueryMatchNothing (1u << 15u) /* Query matches nothing */ +#define EcsQueryHasCondSet (1u << 16u) /* Query has conditionally set fields */ +#define EcsQueryHasPred (1u << 17u) /* Query has equality predicates */ +#define EcsQueryHasScopes (1u << 18u) /* Query has query scopes */ +#define EcsQueryHasRefs (1u << 19u) /* Query has terms with static source */ +#define EcsQueryHasOutTerms (1u << 20u) /* Query has [out] terms */ +#define EcsQueryHasNonThisOutTerms (1u << 21u) /* Query has [out] terms with no $this source */ +#define EcsQueryHasChangeDetection (1u << 22u) /* Query has monitor for change detection */ +#define EcsQueryIsTrivial (1u << 23u) /* Query can use trivial evaluation function */ +#define EcsQueryHasCacheable (1u << 24u) /* Query has cacheable terms */ +#define EcsQueryIsCacheable (1u << 25u) /* All terms of query are cacheable */ +#define EcsQueryHasTableThisVar (1u << 26u) /* Does query have $this table var */ +#define EcsQueryCacheYieldEmptyTables (1u << 27u) /* Does query cache empty tables */ +#define EcsQueryTrivialCache (1u << 28u) /* Trivial cache (no wildcards, traversal, order_by, group_by, change detection) */ +#define EcsQueryNested (1u << 29u) /* Query created by a query (for observer, cache) */ +#define EcsQueryCacheWithFilter (1u << 30u) +#define EcsQueryValid (1u << 31u) + +//////////////////////////////////////////////////////////////////////////////// +//// Term flags (used by ecs_term_t::flags_) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsTermMatchAny (1u << 0) +#define EcsTermMatchAnySrc (1u << 1) +#define EcsTermTransitive (1u << 2) +#define EcsTermReflexive (1u << 3) +#define EcsTermIdInherited (1u << 4) +#define EcsTermIsTrivial (1u << 5) +#define EcsTermIsCacheable (1u << 6) +#define EcsTermIsScope (1u << 7) +#define EcsTermIsMember (1u << 8) +#define EcsTermIsToggle (1u << 9) +#define EcsTermIsSparse (1u << 10) +#define EcsTermIsOr (1u << 11) +#define EcsTermDontFragment (1u << 12) +#define EcsTermNonFragmentingChildOf (1u << 13) + + +//////////////////////////////////////////////////////////////////////////////// +//// Observer flags (used by ecs_observer_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsObserverMatchPrefab (1u << 1u) /* Same as query*/ +#define EcsObserverMatchDisabled (1u << 2u) /* Same as query*/ +#define EcsObserverIsMulti (1u << 3u) /* Does observer have multiple terms */ +#define EcsObserverIsMonitor (1u << 4u) /* Is observer a monitor */ +#define EcsObserverIsDisabled (1u << 5u) /* Is observer entity disabled */ +#define EcsObserverIsParentDisabled (1u << 6u) /* Is module parent of observer disabled */ +#define EcsObserverBypassQuery (1u << 7u) /* Don't evaluate query for multi-component observer*/ +#define EcsObserverYieldOnCreate (1u << 8u) /* Yield matching entities when creating observer */ +#define EcsObserverYieldOnDelete (1u << 9u) /* Yield matching entities when deleting observer */ +#define EcsObserverKeepAlive (1u << 11u) /* Observer keeps component alive (same value as EcsTermKeepAlive) */ + +//////////////////////////////////////////////////////////////////////////////// +//// Table flags (used by ecs_table_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsTableHasBuiltins (1u << 0u) /* Does table have builtin components */ +#define EcsTableIsPrefab (1u << 1u) /* Does the table store prefabs */ +#define EcsTableHasIsA (1u << 2u) /* Does the table have IsA relationship */ +#define EcsTableHasMultiIsA (1u << 3u) /* Does table have multiple IsA pairs */ +#define EcsTableHasChildOf (1u << 4u) /* Does the table type ChildOf relationship */ +#define EcsTableHasParent (1u << 5u) /* Does the table type Parent component */ +#define EcsTableHasName (1u << 6u) /* Does the table type have (Identifier, Name) */ +#define EcsTableHasPairs (1u << 7u) /* Does the table type have pairs */ +#define EcsTableHasModule (1u << 8u) /* Does the table have module data */ +#define EcsTableIsDisabled (1u << 9u) /* Does the table type has EcsDisabled */ +#define EcsTableNotQueryable (1u << 10u) /* Table should never be returned by queries */ +#define EcsTableHasCtors (1u << 11u) +#define EcsTableHasDtors (1u << 12u) +#define EcsTableHasCopy (1u << 13u) +#define EcsTableHasMove (1u << 14u) +#define EcsTableHasToggle (1u << 15u) + +#define EcsTableHasOnAdd (1u << 16u) /* Same values as id flags */ +#define EcsTableHasOnRemove (1u << 17u) +#define EcsTableHasOnSet (1u << 18u) +#define EcsTableHasOnTableCreate (1u << 19u) +#define EcsTableHasOnTableDelete (1u << 20u) +#define EcsTableHasSparse (1u << 21u) +#define EcsTableHasDontFragment (1u << 22u) +#define EcsTableOverrideDontFragment (1u << 23u) +#define EcsTableHasOrderedChildren (1u << 24u) +#define EcsTableHasOverrides (1u << 25u) + +#define EcsTableHasTraversable (1u << 27u) +#define EcsTableEdgeReparent (1u << 28u) +#define EcsTableMarkedForDelete (1u << 29u) + +/* Composite table flags */ +#define EcsTableHasLifecycle (EcsTableHasCtors | EcsTableHasDtors) +#define EcsTableIsComplex (EcsTableHasLifecycle | EcsTableHasToggle | EcsTableHasSparse) +#define EcsTableHasAddActions (EcsTableHasIsA | EcsTableHasCtors | EcsTableHasOnAdd | EcsTableHasOnSet) +#define EcsTableHasRemoveActions (EcsTableHasIsA | EcsTableHasDtors | EcsTableHasOnRemove) +#define EcsTableEdgeFlags (EcsTableHasOnAdd | EcsTableHasOnRemove | EcsTableHasSparse) +#define EcsTableAddEdgeFlags (EcsTableHasOnAdd | EcsTableHasSparse) +#define EcsTableRemoveEdgeFlags (EcsTableHasOnRemove | EcsTableHasSparse | EcsTableHasOrderedChildren) + +//////////////////////////////////////////////////////////////////////////////// +//// Aperiodic action flags (used by ecs_run_aperiodic) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsAperiodicComponentMonitors (1u << 2u) /* Process component monitors */ +#define EcsAperiodicEmptyQueries (1u << 4u) /* Process empty queries */ + +#ifdef __cplusplus +} +#endif + +#endif + + +#if defined(_WIN32) || defined(_MSC_VER) #define ECS_TARGET_WINDOWS +#elif defined(__COSMOCC__) +#define ECS_TARGET_POSIX #elif defined(__ANDROID__) #define ECS_TARGET_ANDROID #define ECS_TARGET_POSIX @@ -147,27 +672,150 @@ #define ECS_TARGET_POSIX #endif -#if defined(__MING32__) -#define ECS_TARGET_POSIX +#if defined(__MINGW32__) || defined(__MINGW64__) +#define ECS_TARGET_MINGW #endif #if defined(_MSC_VER) +#ifndef __clang__ #define ECS_TARGET_MSVC #endif +#endif + +#if defined(__clang__) +#define ECS_TARGET_CLANG +#endif #if defined(__GNUC__) #define ECS_TARGET_GNU #endif +/* Map between clang and apple clang versions, as version 13 has a difference in + * the format of __PRETTY_FUNCTION__ which enum reflection depends on. */ +#if defined(__clang__) + #if defined(__APPLE__) + #if __clang_major__ == 13 + #if __clang_minor__ < 1 + #define ECS_CLANG_VERSION 12 + #else + #define ECS_CLANG_VERSION 13 + #endif + #else + #define ECS_CLANG_VERSION __clang_major__ + #endif + #else + #define ECS_CLANG_VERSION __clang_major__ + #endif +#endif + +/* Define noreturn attribute only for GCC or Clang. */ +#if defined(ECS_TARGET_GNU) || defined(ECS_TARGET_CLANG) + #define ECS_NORETURN __attribute__((noreturn)) +#else + #define ECS_NORETURN +#endif + +/* Ignored warnings */ +#if defined(ECS_TARGET_CLANG) +/* Ignore unknown options so we don't have to care about the compiler version */ +#pragma clang diagnostic ignored "-Wunknown-warning-option" +/* Warns for double or redundant semicolons. There are legitimate cases where a + * semicolon after an empty statement is useful, for example after a macro that + * is replaced with a code block. With this warning enabled, semicolons would + * only have to be added after macro's that are not code blocks, which in some + * cases isn't possible as the implementation of a macro can be different in + * debug/release mode. */ +#pragma clang diagnostic ignored "-Wextra-semi-stmt" +/* This is valid in C99, and Flecs must be compiled as C99. */ +#pragma clang diagnostic ignored "-Wdeclaration-after-statement" +/* Clang attribute to detect fallthrough isn't supported on older versions. + * Implicit fallthrough is still detected by gcc and ignored with "fall through" + * comments */ +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +/* This warning prevents adding a default case when all enum constants are part + * of the switch. In C however an enum type can assume any value in the range of + * the type, and this warning makes it harder to catch invalid enum values. */ +#pragma clang diagnostic ignored "-Wcovered-switch-default" +/* This warning prevents some casts of function results to a different kind of + * type, e.g. casting an int result to double. Not very useful in practice, as + * it just forces the code to assign to a variable first, then cast. */ +#pragma clang diagnostic ignored "-Wbad-function-cast" +/* Format strings can be passed down from other functions. */ +#pragma clang diagnostic ignored "-Wformat-nonliteral" +/* Useful, but not reliable enough. It can incorrectly flag macro's as unused + * in standalone builds. */ +#pragma clang diagnostic ignored "-Wunused-macros" +/* This warning gets thrown by clang even when a code is handling all case + * values but not all cases (for example, when the switch contains a LastValue + * case). Adding a "default" case fixes the warning, but silences future + * warnings about unhandled cases, which is worse. */ +#pragma clang diagnostic ignored "-Wswitch-default" +#if __clang_major__ == 13 +/* clang 13 can throw this warning for a define in ctype.h */ +#pragma clang diagnostic ignored "-Wreserved-identifier" +#endif +/* Filenames aren't consistent across targets as they can use different casing + * (e.g. WinSock2 vs winsock2). */ +#pragma clang diagnostic ignored "-Wnonportable-system-include-path" +/* Very difficult to workaround this warning in C, especially for an ECS. */ +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +/* This warning gets thrown when trying to cast pointer returned from dlproc */ +#pragma clang diagnostic ignored "-Wcast-function-type-strict" +/* This warning can get thrown for expressions that evaluate to constants + * in debug/release mode. */ +#pragma clang diagnostic ignored "-Wconstant-logical-operand" +/* With soft asserts enabled the code won't abort, which in some cases means + * code paths are reached where values are uninitialized. */ +#ifdef FLECS_SOFT_ASSERT +#pragma clang diagnostic ignored "-Wsometimes-uninitialized" +#endif + +/* Allows for enum reflection support on legacy compilers */ +#if __clang_major__ < 16 +#pragma clang diagnostic ignored "-Wenum-constexpr-conversion" +#endif + +#elif defined(ECS_TARGET_GNU) +#ifndef __cplusplus +#pragma GCC diagnostic ignored "-Wdeclaration-after-statement" +#pragma GCC diagnostic ignored "-Wbad-function-cast" +#endif +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#pragma GCC diagnostic ignored "-Wunused-macros" +/* This warning gets thrown *sometimes* when not all members for a struct are + * provided in an initializer. Flecs heavily relies on descriptor structs that + * only require partly initialization, so this warning isn't useful. + * It doesn't introduce any safety issues (fields are guaranteed to be 0 + * initialized), and later versions of gcc (>=11) seem to no longer throw this + * warning. */ +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +/* Produces false positives in addons/cpp/delegate.hpp. */ +#pragma GCC diagnostic ignored "-Warray-bounds" +/* Produces false positives in queries/src/cache.c */ +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#pragma GCC diagnostic ignored "-Wrestrict" + +#elif defined(ECS_TARGET_MSVC) +/* recursive on all control paths, function will cause runtime stack overflow + * This warning is incorrectly thrown on enum reflection code. */ +#pragma warning(disable: 4717) +#endif + +/* Allows for enum reflection support on legacy compilers */ +#if defined(__GNUC__) && __GNUC__ <= 10 +#pragma GCC diagnostic ignored "-Wconversion" +#endif + /* Standard library dependencies */ #include #include #include +#include /* Non-standard but required. If not provided by platform, add manually. */ #include -/* Contains macro's for importing / exporting symbols */ +/* Contains macros for importing / exporting symbols */ /* ) (.) @@ -192,11 +840,11 @@ /* Convenience macro for exporting symbols */ #ifndef flecs_STATIC -#if flecs_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__)) +#if defined(flecs_EXPORTS) && (defined(_MSC_VER) || defined(__MINGW32__)) #define FLECS_API __declspec(dllexport) -#elif flecs_EXPORTS +#elif defined(flecs_EXPORTS) #define FLECS_API __attribute__((__visibility__("default"))) -#elif defined _MSC_VER +#elif defined(_MSC_VER) #define FLECS_API __declspec(dllimport) #else #define FLECS_API @@ -218,7 +866,7 @@ extern "C" { #endif /* Some symbols are only exported when building in debug build, to enable - * whitebox testing of internal datastructures */ + * white-box testing of internal data structures */ #ifndef FLECS_NDEBUG #define FLECS_DBG_API FLECS_API #else @@ -254,9 +902,26 @@ typedef uint16_t ecs_flags16_t; typedef uint32_t ecs_flags32_t; typedef uint64_t ecs_flags64_t; +/* Bitmask type with compile-time defined size */ +#define ecs_flagsn_t_(bits) ecs_flags##bits##_t +#define ecs_flagsn_t(bits) ecs_flagsn_t_(bits) + +/* Bitset type that can store exactly as many bits as there are terms */ +#define ecs_termset_t ecs_flagsn_t(FLECS_TERM_COUNT_MAX) + +/* Utility macro's for setting/clearing termset bits */ +#define ECS_TERMSET_SET(set, flag) ((set) |= (ecs_termset_t)(flag)) +#define ECS_TERMSET_CLEAR(set, flag) ((set) &= (ecs_termset_t)~(flag)) +#define ECS_TERMSET_COND(set, flag, cond) ((cond) \ + ? (ECS_TERMSET_SET(set, flag)) \ + : (ECS_TERMSET_CLEAR(set, flag))) + /* Keep unsigned integers out of the codebase as they do more harm than good */ typedef int32_t ecs_size_t; +/* Allocator type */ +typedef struct ecs_allocator_t ecs_allocator_t; + #define ECS_SIZEOF(T) ECS_CAST(ecs_size_t, sizeof(T)) /* Use alignof in C++, or a trick in C. */ @@ -266,14 +931,22 @@ typedef int32_t ecs_size_t; #define ECS_ALIGNOF(T) (int64_t)__alignof(T) #elif defined(ECS_TARGET_GNU) #define ECS_ALIGNOF(T) (int64_t)__alignof__(T) +#elif defined(ECS_TARGET_CLANG) +#define ECS_ALIGNOF(T) (int64_t)__alignof__(T) #else #define ECS_ALIGNOF(T) ((int64_t)&((struct { char c; T d; } *)0)->d) #endif -#if defined(ECS_TARGET_GNU) -#define ECS_UNUSED __attribute__((unused)) +#ifndef FLECS_NO_ALWAYS_INLINE + #if defined(ECS_TARGET_CLANG) || defined(ECS_TARGET_GCC) + #define FLECS_ALWAYS_INLINE __attribute__((always_inline)) + #elif defined(ECS_TARGET_MSVC) + #define FLECS_ALWAYS_INLINE + #else + #define FLECS_ALWAYS_INLINE + #endif #else -#define ECS_UNUSED + #define FLECS_ALWAYS_INLINE #endif #ifndef FLECS_NO_DEPRECATED_WARNINGS @@ -302,81 +975,76 @@ typedef int32_t ecs_size_t; #define ECS_CAST(T, V) (static_cast(V)) #endif +/* Utility macro for doing const casts without warnings */ +#ifndef __cplusplus +#define ECS_CONST_CAST(type, value) ((type)(uintptr_t)(value)) +#else +#define ECS_CONST_CAST(type, value) (const_cast(value)) +#endif + +/* Utility macro for doing pointer casts without warnings */ +#ifndef __cplusplus +#define ECS_PTR_CAST(type, value) ((type)(uintptr_t)(value)) +#else +#define ECS_PTR_CAST(type, value) (reinterpret_cast(value)) +#endif + +/* Utility macro's to do bitwise comparisons between floats without warnings */ +#define ECS_EQ(a, b) (ecs_os_memcmp(&(a), &(b), sizeof(a)) == 0) +#define ECS_NEQ(a, b) (!ECS_EQ(a, b)) +#define ECS_EQZERO(a) ECS_EQ(a, (uint64_t){0}) +#define ECS_NEQZERO(a) ECS_NEQ(a, (uint64_t){0}) + +/* Utilities to convert flecs version to string */ +#define FLECS_VERSION_IMPLSTR(major, minor, patch) #major "." #minor "." #patch +#define FLECS_VERSION_IMPL(major, minor, patch) \ + FLECS_VERSION_IMPLSTR(major, minor, patch) + +#define ECS_CONCAT(a, b) a ## b + +//////////////////////////////////////////////////////////////////////////////// +//// Magic numbers for sanity checking +//////////////////////////////////////////////////////////////////////////////// + +/* Magic number to identify the type of the object */ +#define ecs_world_t_magic (0x65637377) +#define ecs_stage_t_magic (0x65637373) +#define ecs_query_t_magic (0x65637375) +#define ecs_observer_t_magic (0x65637362) + //////////////////////////////////////////////////////////////////////////////// -//// Entity id macro's +//// Entity id macros //////////////////////////////////////////////////////////////////////////////// #define ECS_ROW_MASK (0x0FFFFFFFu) #define ECS_ROW_FLAGS_MASK (~ECS_ROW_MASK) -#define ECS_FLAG_OBSERVED (1u << 31) -#define ECS_FLAG_OBSERVED_ID (1u << 30) -#define ECS_FLAG_OBSERVED_OBJECT (1u << 29) -#define ECS_FLAG_OBSERVED_ACYCLIC (1u << 28) #define ECS_RECORD_TO_ROW(v) (ECS_CAST(int32_t, (ECS_CAST(uint32_t, v) & ECS_ROW_MASK))) #define ECS_RECORD_TO_ROW_FLAGS(v) (ECS_CAST(uint32_t, v) & ECS_ROW_FLAGS_MASK) #define ECS_ROW_TO_RECORD(row, flags) (ECS_CAST(uint32_t, (ECS_CAST(uint32_t, row) | (flags)))) -#define ECS_ROLE_MASK (0xFFull << 56) +#define ECS_ID_FLAGS_MASK (0xFFull << 60) #define ECS_ENTITY_MASK (0xFFFFFFFFull) #define ECS_GENERATION_MASK (0xFFFFull << 32) #define ECS_GENERATION(e) ((e & ECS_GENERATION_MASK) >> 32) #define ECS_GENERATION_INC(e) ((e & ~ECS_GENERATION_MASK) | ((0xFFFF & (ECS_GENERATION(e) + 1)) << 32)) -#define ECS_COMPONENT_MASK (~ECS_ROLE_MASK) -#define ECS_HAS_ROLE(e, role) ((e & ECS_ROLE_MASK) == ECS_##role) +#define ECS_COMPONENT_MASK (~ECS_ID_FLAGS_MASK) +#define ECS_HAS_ID_FLAG(e, flag) ((e) & ECS_##flag) +#define ECS_IS_VALUE_PAIR(id) (((id) & ECS_ID_FLAGS_MASK) == ECS_VALUE_PAIR) +#define ECS_IS_PAIR(id) ((((id) & ECS_ID_FLAGS_MASK) == ECS_PAIR) || ECS_IS_VALUE_PAIR(id)) #define ECS_PAIR_FIRST(e) (ecs_entity_t_hi(e & ECS_COMPONENT_MASK)) #define ECS_PAIR_SECOND(e) (ecs_entity_t_lo(e)) -#define ECS_PAIR_RELATION ECS_PAIR_FIRST -#define ECS_PAIR_OBJECT ECS_PAIR_SECOND -#define ECS_HAS_RELATION(e, rel) (ECS_HAS_ROLE(e, PAIR) && (ECS_PAIR_FIRST(e) == rel)) - -#define ECS_HAS_PAIR_OBJECT(e, rel, obj)\ - (ECS_HAS_RELATION(e, rel) && ECS_PAIR_SECOND(e) == obj) - -#define ECS_HAS(id, has_id)(\ - (id == has_id) ||\ - (ECS_HAS_PAIR_OBJECT(id, ECS_PAIR_FIRST(has_id), ECS_PAIR_SECOND(has_id)))) - - -//////////////////////////////////////////////////////////////////////////////// -//// Flags for quickly querying properties of component/relation id -//////////////////////////////////////////////////////////////////////////////// - -#define ECS_ID_ON_DELETE_REMOVE (1u << 0) -#define ECS_ID_ON_DELETE_DELETE (1u << 1) -#define ECS_ID_ON_DELETE_THROW (1u << 2) -#define ECS_ID_ON_DELETE_MASK\ - (ECS_ID_ON_DELETE_THROW|ECS_ID_ON_DELETE_REMOVE|ECS_ID_ON_DELETE_DELETE) - -#define ECS_ID_ON_DELETE_OBJECT_REMOVE (1u << 3) -#define ECS_ID_ON_DELETE_OBJECT_DELETE (1u << 4) -#define ECS_ID_ON_DELETE_OBJECT_THROW (1u << 5) -#define ECS_ID_ON_DELETE_OBJECT_MASK\ - (ECS_ID_ON_DELETE_OBJECT_THROW|ECS_ID_ON_DELETE_OBJECT_REMOVE|\ - ECS_ID_ON_DELETE_OBJECT_DELETE) - -#define ECS_ID_EXCLUSIVE (1u << 6) -#define ECS_ID_DONT_INHERIT (1u << 7) -#define ECS_TYPE_INFO_INITIALIZED (1u << 8) - -/* Utilities for converting from flags to delete policies and vice versa */ -#define ECS_ID_ON_DELETE(flags) \ - ((ecs_entity_t[]){0, EcsRemove, EcsDelete, 0, EcsThrow}\ - [((flags) & ECS_ID_ON_DELETE_MASK)]) -#define ECS_ID_ON_DELETE_OBJECT(flags) ECS_ID_ON_DELETE(flags >> 3) -#define ECS_ID_ON_DELETE_FLAG(id) (1u << ((id) - EcsRemove)) -#define ECS_ID_ON_DELETE_OBJECT_FLAG(id) (1u << (3 + ((id) - EcsRemove))) +#define ECS_HAS_RELATION(e, rel) (ECS_HAS_ID_FLAG(e, PAIR) && (ECS_PAIR_FIRST(e) == rel)) +#define ECS_TERM_REF_FLAGS(ref) ((ref)->id & EcsTermRefFlags) +#define ECS_TERM_REF_ID(ref) ((ref)->id & ~EcsTermRefFlags) //////////////////////////////////////////////////////////////////////////////// //// Convert between C typenames and variables //////////////////////////////////////////////////////////////////////////////// /** Translate C type to id. */ -#define ecs_id(T) FLECS__E##T - -/** Translate C type to system function. */ -#define ecs_iter_action(T) FLECS__F##T +#define ecs_id(T) FLECS_ID##T##ID_ //////////////////////////////////////////////////////////////////////////////// @@ -387,17 +1055,20 @@ typedef int32_t ecs_size_t; #define ecs_entity_t_hi(value) ECS_CAST(uint32_t, (value) >> 32) #define ecs_entity_t_comb(lo, hi) ((ECS_CAST(uint64_t, hi) << 32) + ECS_CAST(uint32_t, lo)) -#define ecs_pair(pred, obj) (ECS_PAIR | ecs_entity_t_comb(obj, pred)) -#define ecs_case(pred, obj) (ECS_CASE | ecs_entity_t_comb(obj, pred)) - -/* Get object from pair with the correct (current) generation count */ +#define ecs_pair(rel, tgt) (ECS_PAIR | ecs_entity_t_comb(tgt, rel)) +#define ecs_pair_t(rel, tgt) (ECS_PAIR | ecs_entity_t_comb(tgt, ecs_id(rel))) #define ecs_pair_first(world, pair) ecs_get_alive(world, ECS_PAIR_FIRST(pair)) #define ecs_pair_second(world, pair) ecs_get_alive(world, ECS_PAIR_SECOND(pair)) #define ecs_pair_relation ecs_pair_first -#define ecs_pair_object ecs_pair_second +#define ecs_pair_target ecs_pair_second + +#define ecs_value_pair(rel, val) (ECS_VALUE_PAIR | ecs_entity_t_comb(val, rel)) + +#define flecs_poly_id(tag) ecs_pair(ecs_id(EcsPoly), tag) + //////////////////////////////////////////////////////////////////////////////// -//// Debug macro's +//// Debug macros //////////////////////////////////////////////////////////////////////////////// #ifndef FLECS_NDEBUG @@ -410,7 +1081,7 @@ typedef int32_t ecs_size_t; //////////////////////////////////////////////////////////////////////////////// -//// Convenience macro's for ctor, dtor, move and copy +//// Convenience macros for ctor, dtor, move and copy //////////////////////////////////////////////////////////////////////////////// #ifndef FLECS_LEGACY @@ -446,7 +1117,7 @@ typedef int32_t ecs_size_t; (void)type_info;\ for (int32_t i = 0; i < _count; i ++) {\ type *dst_var = &((type*)_dst_ptr)[i];\ - type *src_var = &((type*)_src_ptr)[i];\ + const type *src_var = &((const type*)_src_ptr)[i];\ (void)dst_var;\ (void)src_var;\ __VA_ARGS__\ @@ -474,13 +1145,13 @@ typedef int32_t ecs_size_t; }\ } - -#define ECS_ON_SET_IMPL(type, var, ...)\ - void type##_##on_set(ecs_iter_t *_it)\ +#define ECS_HOOK_IMPL(type, func, var, ...)\ + void func(ecs_iter_t *_it)\ {\ + type *field_data = ecs_field(_it, type, 0);\ for (int32_t i = 0; i < _it->count; i ++) {\ ecs_entity_t entity = _it->entities[i];\ - type *var = &((type*)_it->ptrs[0])[i];\ + type *var = &field_data[i];\ (void)entity;\ (void)var;\ __VA_ARGS__\ @@ -489,18 +1160,6 @@ typedef int32_t ecs_size_t; #endif - -//////////////////////////////////////////////////////////////////////////////// -//// Deprecated constants -//////////////////////////////////////////////////////////////////////////////// - -/* These constants should no longer be used, but are required by the core to - * guarantee backwards compatibility */ -#define ECS_AND (ECS_ROLE | (0x79ull << 56)) -#define ECS_OR (ECS_ROLE | (0x78ull << 56)) -#define ECS_XOR (ECS_ROLE | (0x77ull << 56)) -#define ECS_NOT (ECS_ROLE | (0x76ull << 56)) - #ifdef __cplusplus } #endif @@ -509,693 +1168,986 @@ typedef int32_t ecs_size_t; /** - * @file vector.h - * @brief Vector datastructure. - * - * This is an implementation of a simple vector type. The vector is allocated in - * a single block of memory, with the element count, and allocated number of - * elements encoded in the block. As this vector is used for user-types it has - * been designed to support alignments higher than 8 bytes. This makes the size - * of the vector header variable in size. To reduce the overhead associated with - * retrieving or computing this size, the functions are wrapped in macro calls - * that compute the header size at compile time. - * - * The API provides a number of _t macro's, which accept a size and alignment. - * These macro's are used when no compile-time type is available. - * - * The vector guarantees contiguous access to its elements. When an element is - * removed from the vector, the last element is copied to the removed element. - * - * The API requires passing in the type of the vector. This type is used to test - * whether the size of the provided type equals the size of the type with which - * the vector was created. In release mode this check is not performed. + * @defgroup core_types Core API Types + * Types for core API objects. * - * When elements are added to the vector, it will automatically resize to the - * next power of two. This can change the pointer of the vector, which is why - * operations that can increase the vector size, accept a double pointer to the - * vector. + * @{ */ -#ifndef FLECS_VECTOR_H -#define FLECS_VECTOR_H +/** Ids are the things that can be added to an entity. + * An id can be an entity or pair, and can have optional id flags. */ +typedef uint64_t ecs_id_t; +/** An entity identifier. + * Entity ids consist out of a number unique to the entity in the lower 32 bits, + * and a counter used to track entity liveliness in the upper 32 bits. When an + * id is recycled, its generation count is increased. This causes recycled ids + * to be very large (>4 billion), which is normal. */ +typedef ecs_id_t ecs_entity_t; -#ifdef __cplusplus -extern "C" { -#endif +/** A type is a list of (component) ids. + * Types are used to communicate the "type" of an entity. In most type systems a + * typeof operation returns a single type. In ECS however, an entity can have + * multiple components, which is why an ECS type consists of a vector of ids. + * + * The component ids of a type are sorted, which ensures that it doesn't matter + * in which order components are added to an entity. For example, if adding + * Position then Velocity would result in type [Position, Velocity], first + * adding Velocity then Position would also result in type [Position, Velocity]. + * + * Entities are grouped together by type in the ECS storage in tables. The + * storage has exactly one table per unique type that is created by the + * application that stores all entities and components for that type. This is + * also referred to as an archetype. + */ +typedef struct { + ecs_id_t *array; /**< Array with ids. */ + int32_t count; /**< Number of elements in array. */ +} ecs_type_t; + +/** A world is the container for all ECS data and supporting features. + * Applications can have multiple worlds, though in most cases will only need + * one. Worlds are isolated from each other, and can have separate sets of + * systems, components, modules etc. + * + * If an application has multiple worlds with overlapping components, it is + * common (though not strictly required) to use the same component ids across + * worlds, which can be achieved by declaring a global component id variable. + * To do this in the C API, see the entities/fwd_component_decl example. The + * C++ API automatically synchronizes component ids between worlds. + * + * Component id conflicts between worlds can occur when a world has already used + * an id for something else. There are a few ways to avoid this: + * + * - Ensure to register the same components in each world, in the same order. + * - Create a dummy world in which all components are preregistered which + * initializes the global id variables. + * + * In some use cases, typically when writing tests, multiple worlds are created + * and deleted with different components, registered in different order. To + * ensure isolation between tests, the C++ API has a `flecs::reset` function + * that forces the API to ignore the old component ids. */ +typedef struct ecs_world_t ecs_world_t; -#ifdef FLECS_NDEBUG -#define ECS_VECTOR_T_SIZE\ - (ECS_SIZEOF(int32_t) + ECS_SIZEOF(int32_t)) -#else -#define ECS_VECTOR_T_SIZE\ - (ECS_SIZEOF(int32_t) + ECS_SIZEOF(int32_t) + ECS_SIZEOF(int64_t)) -#endif +/** A stage enables modification while iterating and from multiple threads */ +typedef struct ecs_stage_t ecs_stage_t; -/* Compute the header size of the vector from size & alignment */ -#define ECS_VECTOR_U(size, alignment) size, ECS_CAST(int16_t, ECS_MAX(ECS_VECTOR_T_SIZE, alignment)) +/** A table stores entities and components for a specific type. */ +typedef struct ecs_table_t ecs_table_t; -/* Compute the header size of the vector from a provided compile-time type */ -#define ECS_VECTOR_T(T) ECS_VECTOR_U(ECS_SIZEOF(T), ECS_ALIGNOF(T)) +/** A term is a single element in a query. */ +typedef struct ecs_term_t ecs_term_t; -/* Utility macro's for creating vector on stack */ -#ifndef FLECS_NDEBUG -#define ECS_VECTOR_VALUE(T, elem_count)\ -{\ - .elem_size = (int32_t)(ECS_SIZEOF(T)),\ - .count = elem_count,\ - .size = elem_count\ -} -#else -#define ECS_VECTOR_VALUE(T, elem_count)\ -{\ - .count = elem_count,\ - .size = elem_count\ -} -#endif +/** A query returns entities matching a list of constraints. */ +typedef struct ecs_query_t ecs_query_t; -#define ECS_VECTOR_DECL(name, T, elem_count)\ -struct {\ - union {\ - ecs_vector_t vector;\ - uint64_t align;\ - } header;\ - T array[elem_count];\ -} __##name##_value = {\ - .header.vector = ECS_VECTOR_VALUE(T, elem_count)\ -};\ -const ecs_vector_t *name = (ecs_vector_t*)&__##name##_value +/** An observer is a system that is invoked when an event matches its query. + * Observers allow applications to respond to specific events, such as adding or + * removing a component. Observers are created by both specifying a query and + * a list of event kinds that should be listened for. An example of an observer + * that triggers when a Position component is added to an entity (in C++): + * + * @code + * world.observer() + * .event(flecs::OnAdd) + * .each([](Position& p) { + * // called when Position is added to an entity + * }); + * @endcode + * + * Observers only trigger when the source of the event matches the full observer + * query. For example, an OnAdd observer for Position, Velocity will only + * trigger after both components have been added to the entity. */ +typedef struct ecs_observer_t ecs_observer_t; -#define ECS_VECTOR_IMPL(name, T, elems, elem_count)\ -ecs_os_memcpy(__##name##_value.array, elems, sizeof(T) * elem_count) +/** An observable produces events that can be listened for by an observer. + * Currently only the world is observable. In the future, queries will become + * observable objects as well. */ +typedef struct ecs_observable_t ecs_observable_t; -#define ECS_VECTOR_STACK(name, T, elems, elem_count)\ -ECS_VECTOR_DECL(name, T, elem_count);\ -ECS_VECTOR_IMPL(name, T, elems, elem_count) +/** Type used for iterating iterable objects. + * Iterators are objects that provide applications with information + * about the currently iterated result, and store any state required for the + * iteration. */ +typedef struct ecs_iter_t ecs_iter_t; -typedef struct ecs_vector_t ecs_vector_t; +/** A ref is a fast way to fetch a component for a specific entity. + * Refs are a faster alternative to repeatedly calling ecs_get() for the same + * entity/component combination. When comparing the performance of getting a ref + * to calling ecs_get(), a ref is typically 3-5x faster. + * + * Refs achieve this performance by caching internal data structures associated + * with the entity and component on the ecs_ref_t object that otherwise would + * have to be looked up. */ +typedef struct ecs_ref_t ecs_ref_t; -typedef int (*ecs_comparator_t)( - const void* p1, - const void *p2); +/** Type hooks are callbacks associated with component lifecycle events. + * Typical examples of lifecycle events are construction, destruction, copying + * and moving of components. */ +typedef struct ecs_type_hooks_t ecs_type_hooks_t; -/** Create new vector. */ -FLECS_API -ecs_vector_t* _ecs_vector_new( - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count); +/** Type information. + * Contains information about a (component) type, such as its size and + * alignment and type hooks. */ +typedef struct ecs_type_info_t ecs_type_info_t; -#define ecs_vector_new(T, elem_count) \ - _ecs_vector_new(ECS_VECTOR_T(T), elem_count) +/** Information about an entity, like its table and row. */ +typedef struct ecs_record_t ecs_record_t; -#define ecs_vector_new_t(size, alignment, elem_count) \ - _ecs_vector_new(ECS_VECTOR_U(size, alignment), elem_count) +/** Information about a (component) id, such as type info and tables with the id */ +typedef struct ecs_component_record_t ecs_component_record_t; -/* Create new vector, initialize it with provided array */ -FLECS_API -ecs_vector_t* _ecs_vector_from_array( - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count, - void *array); +/** A poly object. + * A poly (short for polymorph) object is an object that has a variable list of + * capabilities, determined by a mixin table. This is the current list of types + * in the flecs API that can be used as an ecs_poly_t: + * + * - ecs_world_t + * - ecs_stage_t + * - ecs_query_t + * + * Functions that accept an ecs_poly_t argument can accept objects of these + * types. If the object does not have the requested mixin the API will throw an + * assert. + * + * The poly/mixin framework enables partially overlapping features to be + * implemented once, and enables objects of different types to interact with + * each other depending on what mixins they have, rather than their type + * (in some ways it's like a mini-ECS). Additionally, each poly object has a + * header that enables the API to do sanity checking on the input arguments. + */ +typedef void ecs_poly_t; -#define ecs_vector_from_array(T, elem_count, array)\ - _ecs_vector_from_array(ECS_VECTOR_T(T), elem_count, array) +/** Type that stores poly mixins */ +typedef struct ecs_mixins_t ecs_mixins_t; -/* Initialize vector with zero's */ -FLECS_API -void _ecs_vector_zero( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset); +/** Header for ecs_poly_t objects. */ +typedef struct ecs_header_t { + int32_t type; /**< Magic number indicating which type of flecs object */ + int32_t refcount; /**< Refcount, to enable RAII handles */ + ecs_mixins_t *mixins; /**< Table with offsets to (optional) mixins */ +} ecs_header_t; -#define ecs_vector_zero(vector, T) \ - _ecs_vector_zero(vector, ECS_VECTOR_T(T)) +typedef struct ecs_table_record_t ecs_table_record_t; -/** Free vector */ -FLECS_API -void ecs_vector_free( - ecs_vector_t *vector); +/** @} */ -/** Clear values in vector */ -FLECS_API -void ecs_vector_clear( - ecs_vector_t *vector); +/** + * @file vec.h + * @brief Vector with allocator support. + */ -/** Assert when the provided size does not match the vector type. */ -FLECS_API -void ecs_vector_assert_size( - ecs_vector_t* vector_inout, - ecs_size_t elem_size); +#ifndef FLECS_VEC_H +#define FLECS_VEC_H -/** Add element to vector. */ -FLECS_API -void* _ecs_vector_add( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset); -#define ecs_vector_add(vector, T) \ - ((T*)_ecs_vector_add(vector, ECS_VECTOR_T(T))) +#ifdef __cplusplus +extern "C" { +#endif -#define ecs_vector_add_t(vector, size, alignment) \ - _ecs_vector_add(vector, ECS_VECTOR_U(size, alignment)) +/** A component column. */ +typedef struct ecs_vec_t { + void *array; + int32_t count; + int32_t size; +#ifdef FLECS_SANITIZE + ecs_size_t elem_size; + const char *type_name; +#endif +} ecs_vec_t; -/** Insert element to vector. */ FLECS_API -void* _ecs_vector_insert_at( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t index); +void ecs_vec_init( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); -#define ecs_vector_insert_at(vector, T, index) \ - ((T*)_ecs_vector_insert_at(vector, ECS_VECTOR_T(T), index)) +FLECS_API +void ecs_vec_init_w_dbg_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count, + const char *type_name); -#define ecs_vector_insert_at_t(vector, size, alignment, index) \ - _ecs_vector_insert_at(vector, ECS_VECTOR_U(size, alignment), index) +#define ecs_vec_init_t(allocator, vec, T, elem_count) \ + ecs_vec_init_w_dbg_info(allocator, vec, ECS_SIZEOF(T), elem_count, "vec<"#T">") -/** Add n elements to the vector. */ FLECS_API -void* _ecs_vector_addn( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count); - -#define ecs_vector_addn(vector, T, elem_count) \ - ((T*)_ecs_vector_addn(vector, ECS_VECTOR_T(T), elem_count)) +void ecs_vec_init_if( + ecs_vec_t *vec, + ecs_size_t size); -#define ecs_vector_addn_t(vector, size, alignment, elem_count) \ - _ecs_vector_addn(vector, ECS_VECTOR_U(size, alignment), elem_count) +#define ecs_vec_init_if_t(vec, T) \ + ecs_vec_init_if(vec, ECS_SIZEOF(T)) -/** Get element from vector. */ FLECS_API -void* _ecs_vector_get( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - int32_t index); - -#define ecs_vector_get(vector, T, index) \ - ((T*)_ecs_vector_get(vector, ECS_VECTOR_T(T), index)) +void ecs_vec_fini( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size); -#define ecs_vector_get_t(vector, size, alignment, index) \ - _ecs_vector_get(vector, ECS_VECTOR_U(size, alignment), index) +#define ecs_vec_fini_t(allocator, vec, T) \ + ecs_vec_fini(allocator, vec, ECS_SIZEOF(T)) -/** Get last element from vector. */ FLECS_API -void* _ecs_vector_last( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset); +ecs_vec_t* ecs_vec_reset( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size); -#define ecs_vector_last(vector, T) \ - (T*)_ecs_vector_last(vector, ECS_VECTOR_T(T)) +#define ecs_vec_reset_t(allocator, vec, T) \ + ecs_vec_reset(allocator, vec, ECS_SIZEOF(T)) -#define ecs_vector_last_t(vector, size, alignment) \ - _ecs_vector_last(vector, ECS_VECTOR_U(size, alignment)) +FLECS_API +void ecs_vec_clear( + ecs_vec_t *vec); -/** Set minimum size for vector. If the current size of the vector is larger, - * the function will have no side effects. */ FLECS_API -int32_t _ecs_vector_set_min_size( - ecs_vector_t **array_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count); +void* ecs_vec_append( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size); -#define ecs_vector_set_min_size(vector, T, size) \ - _ecs_vector_set_min_size(vector, ECS_VECTOR_T(T), size) +#define ecs_vec_append_t(allocator, vec, T) \ + ECS_CAST(T*, ecs_vec_append(allocator, vec, ECS_SIZEOF(T))) -/** Set minimum count for vector. If the current count of the vector is larger, - * the function will have no side effects. */ FLECS_API -int32_t _ecs_vector_set_min_count( - ecs_vector_t **vector_inout, - ecs_size_t elem_size, - int16_t offset, - int32_t elem_count); +void ecs_vec_remove( + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem); + +#define ecs_vec_remove_t(vec, T, elem) \ + ecs_vec_remove(vec, ECS_SIZEOF(T), elem) -#define ecs_vector_set_min_count(vector, T, elem_count) \ - _ecs_vector_set_min_count(vector, ECS_VECTOR_T(T), elem_count) +FLECS_API +void ecs_vec_remove_ordered( + ecs_vec_t *v, + ecs_size_t size, + int32_t index); -#define ecs_vector_set_min_count_t(vector, size, alignment, elem_count) \ - _ecs_vector_set_min_count(vector, ECS_VECTOR_U(size, alignment), elem_count) +#define ecs_vec_remove_ordered_t(vec, T, elem) \ + ecs_vec_remove_ordered(vec, ECS_SIZEOF(T), elem) -/** Remove last element. This operation requires no swapping of values. */ FLECS_API -void ecs_vector_remove_last( - ecs_vector_t *vector); +void ecs_vec_remove_last( + ecs_vec_t *vec); -/** Remove last value, store last element in provided value. */ FLECS_API -bool _ecs_vector_pop( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - void *value); +ecs_vec_t ecs_vec_copy( + struct ecs_allocator_t *allocator, + const ecs_vec_t *vec, + ecs_size_t size); -#define ecs_vector_pop(vector, T, value) \ - _ecs_vector_pop(vector, ECS_VECTOR_T(T), value) +#define ecs_vec_copy_t(allocator, vec, T) \ + ecs_vec_copy(allocator, vec, ECS_SIZEOF(T)) -/** Append element at specified index to another vector. */ FLECS_API -int32_t _ecs_vector_move_index( - ecs_vector_t **dst, - ecs_vector_t *src, - ecs_size_t elem_size, - int16_t offset, - int32_t index); - -#define ecs_vector_move_index(dst, src, T, index) \ - _ecs_vector_move_index(dst, src, ECS_VECTOR_T(T), index) +ecs_vec_t ecs_vec_copy_shrink( + struct ecs_allocator_t *allocator, + const ecs_vec_t *vec, + ecs_size_t size); -#define ecs_vector_move_index_t(dst, src, size, alignment, index) \ - _ecs_vector_move_index(dst, src, ECS_VECTOR_U(size, alignment), index) +#define ecs_vec_copy_shrink_t(allocator, vec, T) \ + ecs_vec_copy_shrink(allocator, vec, ECS_SIZEOF(T)) -/** Remove element at specified index. Moves the last value to the index. */ FLECS_API -int32_t _ecs_vector_remove( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - int32_t index); +void ecs_vec_reclaim( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size); -#define ecs_vector_remove(vector, T, index) \ - _ecs_vector_remove(vector, ECS_VECTOR_T(T), index) +#define ecs_vec_reclaim_t(allocator, vec, T) \ + ecs_vec_reclaim(allocator, vec, ECS_SIZEOF(T)) + +FLECS_API +void ecs_vec_set_size( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); -#define ecs_vector_remove_t(vector, size, alignment, index) \ - _ecs_vector_remove(vector, ECS_VECTOR_U(size, alignment), index) +#define ecs_vec_set_size_t(allocator, vec, T, elem_count) \ + ecs_vec_set_size(allocator, vec, ECS_SIZEOF(T), elem_count) -/** Shrink vector to make the size match the count. */ FLECS_API -void _ecs_vector_reclaim( - ecs_vector_t **vector, - ecs_size_t elem_size, - int16_t offset); +void ecs_vec_set_min_size( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); -#define ecs_vector_reclaim(vector, T)\ - _ecs_vector_reclaim(vector, ECS_VECTOR_T(T)) +#define ecs_vec_set_min_size_t(allocator, vec, T, elem_count) \ + ecs_vec_set_min_size(allocator, vec, ECS_SIZEOF(T), elem_count) -#define ecs_vector_reclaim_t(vector, size, alignment)\ - _ecs_vector_reclaim(vector, ECS_VECTOR_U(size, alignment)) +FLECS_API +void ecs_vec_set_min_size_w_type_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count, + const ecs_type_info_t *ti); -/** Grow size of vector with provided number of elements. */ FLECS_API -int32_t _ecs_vector_grow( - ecs_vector_t **vector, - ecs_size_t elem_size, - int16_t offset, +void ecs_vec_set_min_count( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, int32_t elem_count); -#define ecs_vector_grow(vector, T, size) \ - _ecs_vector_grow(vector, ECS_VECTOR_T(T), size) +#define ecs_vec_set_min_count_t(allocator, vec, T, elem_count) \ + ecs_vec_set_min_count(allocator, vec, ECS_SIZEOF(T), elem_count) -/** Set allocation size of vector. */ FLECS_API -int32_t _ecs_vector_set_size( - ecs_vector_t **vector, - ecs_size_t elem_size, - int16_t offset, +void ecs_vec_set_min_count_zeromem( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, int32_t elem_count); -#define ecs_vector_set_size(vector, T, elem_count) \ - _ecs_vector_set_size(vector, ECS_VECTOR_T(T), elem_count) +#define ecs_vec_set_min_count_zeromem_t(allocator, vec, T, elem_count) \ + ecs_vec_set_min_count_zeromem(allocator, vec, ECS_SIZEOF(T), elem_count) -#define ecs_vector_set_size_t(vector, size, alignment, elem_count) \ - _ecs_vector_set_size(vector, ECS_VECTOR_U(size, alignment), elem_count) - -/** Set count of vector. If the size of the vector is smaller than the provided - * count, the vector is resized. */ FLECS_API -int32_t _ecs_vector_set_count( - ecs_vector_t **vector, - ecs_size_t elem_size, - int16_t offset, +void ecs_vec_set_count( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, int32_t elem_count); -#define ecs_vector_set_count(vector, T, elem_count) \ - _ecs_vector_set_count(vector, ECS_VECTOR_T(T), elem_count) - -#define ecs_vector_set_count_t(vector, size, alignment, elem_count) \ - _ecs_vector_set_count(vector, ECS_VECTOR_U(size, alignment), elem_count) +#define ecs_vec_set_count_t(allocator, vec, T, elem_count) \ + ecs_vec_set_count(allocator, vec, ECS_SIZEOF(T), elem_count) -/** Return number of elements in vector. */ FLECS_API -int32_t ecs_vector_count( - const ecs_vector_t *vector); +void ecs_vec_set_count_w_type_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count, + const ecs_type_info_t *ti); -/** Return size of vector. */ FLECS_API -int32_t ecs_vector_size( - const ecs_vector_t *vector); +void ecs_vec_set_min_count_w_type_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count, + const ecs_type_info_t *ti); -/** Return first element of vector. */ FLECS_API -void* _ecs_vector_first( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset); - -#define ecs_vector_first(vector, T) \ - ((T*)_ecs_vector_first(vector, ECS_VECTOR_T(T))) +void* ecs_vec_grow( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count); -#define ecs_vector_first_t(vector, size, alignment) \ - _ecs_vector_first(vector, ECS_VECTOR_U(size, alignment)) +#define ecs_vec_grow_t(allocator, vec, T, elem_count) \ + ecs_vec_grow(allocator, vec, ECS_SIZEOF(T), elem_count) -/** Sort elements in vector. */ FLECS_API -void _ecs_vector_sort( - ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - ecs_comparator_t compare_action); - -#define ecs_vector_sort(vector, T, compare_action) \ - _ecs_vector_sort(vector, ECS_VECTOR_T(T), compare_action) +int32_t ecs_vec_count( + const ecs_vec_t *vec); -/** Return memory occupied by vector. */ FLECS_API -void _ecs_vector_memory( - const ecs_vector_t *vector, - ecs_size_t elem_size, - int16_t offset, - int32_t *allocd, - int32_t *used); +int32_t ecs_vec_size( + const ecs_vec_t *vec); -#define ecs_vector_memory(vector, T, allocd, used) \ - _ecs_vector_memory(vector, ECS_VECTOR_T(T), allocd, used) +FLECS_API +void* ecs_vec_get( + const ecs_vec_t *vec, + ecs_size_t size, + int32_t index); -#define ecs_vector_memory_t(vector, size, alignment, allocd, used) \ - _ecs_vector_memory(vector, ECS_VECTOR_U(size, alignment), allocd, used) +#define ecs_vec_get_t(vec, T, index) \ + ECS_CAST(T*, ecs_vec_get(vec, ECS_SIZEOF(T), index)) -/** Copy vectors */ FLECS_API -ecs_vector_t* _ecs_vector_copy( - const ecs_vector_t *src, - ecs_size_t elem_size, - int16_t offset); +void* ecs_vec_first( + const ecs_vec_t *vec); -#define ecs_vector_copy(src, T) \ - _ecs_vector_copy(src, ECS_VECTOR_T(T)) +#define ecs_vec_first_t(vec, T) \ + ECS_CAST(T*, ecs_vec_first(vec)) -#define ecs_vector_copy_t(src, size, alignment) \ - _ecs_vector_copy(src, ECS_VECTOR_U(size, alignment)) +FLECS_API +void* ecs_vec_last( + const ecs_vec_t *vec, + ecs_size_t size); + +#define ecs_vec_last_t(vec, T) \ + ECS_CAST(T*, ecs_vec_last(vec, ECS_SIZEOF(T))) -#ifndef FLECS_LEGACY -#define ecs_vector_each(vector, T, var, ...)\ - {\ - int var##_i, var##_count = ecs_vector_count(vector);\ - T* var##_array = ecs_vector_first(vector, T);\ - for (var##_i = 0; var##_i < var##_count; var##_i ++) {\ - T* var = &var##_array[var##_i];\ - __VA_ARGS__\ - }\ - } -#endif #ifdef __cplusplus } #endif +#endif -/** C++ wrapper for vector class. */ -#ifdef __cplusplus -#ifndef FLECS_NO_CPP +/** + * @file sparse.h + * @brief Sparse set data structure. + */ -namespace flecs { +#ifndef FLECS_SPARSE_H +#define FLECS_SPARSE_H -template -class vector_iterator -{ -public: - explicit vector_iterator(T* value, int index) { - m_value = value; - m_index = index; - } - bool operator!=(vector_iterator const& other) const - { - return m_index != other.m_index; - } +#ifdef __cplusplus +extern "C" { +#endif - T const& operator*() const - { - return m_value[m_index]; - } +/** The number of elements in a single page */ +#define FLECS_SPARSE_PAGE_SIZE (1 << FLECS_SPARSE_PAGE_BITS) - vector_iterator& operator++() - { - ++m_index; - return *this; - } +/** Compute the page index from an id by stripping the first 12 bits */ +#define FLECS_SPARSE_PAGE(index) ((int32_t)((uint32_t)index >> FLECS_SPARSE_PAGE_BITS)) -private: - T* m_value; - int m_index; -}; +/** This computes the offset of an index inside a page */ +#define FLECS_SPARSE_OFFSET(index) ((int32_t)index & (FLECS_SPARSE_PAGE_SIZE - 1)) -/* C++ class mainly used as wrapper around internal ecs_vector_t. Do not use - * this class as a replacement for STL datastructures! */ -template -class vector { -public: - explicit vector(ecs_vector_t *v) : m_vector( v ) { } +typedef struct ecs_sparse_page_t { + int32_t *sparse; /* Sparse array with indices to dense array */ + void *data; /* Store data in sparse array to reduce + * indirection and provide stable pointers. */ +} ecs_sparse_page_t; - vector(size_t count = 0) : m_vector( nullptr ) { - if (count) { - init(count); - } - } +typedef struct ecs_sparse_t { + ecs_vec_t dense; /* Dense array with indices to sparse array. The + * dense array stores both alive and not alive + * sparse indices. The 'count' member keeps + * track of which indices are alive. */ - T& operator[](size_t index) { - return *static_cast(_ecs_vector_get(m_vector, ECS_VECTOR_T(T), index)); - } + ecs_vec_t pages; /* Chunks with sparse arrays & data */ + ecs_size_t size; /* Element size */ + int32_t count; /* Number of alive entries */ + uint64_t max_id; /* Local max index (if no global is set) */ + struct ecs_allocator_t *allocator; + struct ecs_block_allocator_t *page_allocator; +} ecs_sparse_t; - vector_iterator begin() { - return vector_iterator( - static_cast(_ecs_vector_first(m_vector, ECS_VECTOR_T(T))), 0); - } +/** Initialize sparse set */ +FLECS_DBG_API +void flecs_sparse_init( + ecs_sparse_t *result, + struct ecs_allocator_t *allocator, + struct ecs_block_allocator_t *page_allocator, + ecs_size_t size); - vector_iterator end() { - return vector_iterator( - static_cast(_ecs_vector_last(m_vector, ECS_VECTOR_T(T))), - ecs_vector_count(m_vector)); - } +#define flecs_sparse_init_t(result, allocator, page_allocator, T)\ + flecs_sparse_init(result, allocator, page_allocator, ECS_SIZEOF(T)) - void clear() { - ecs_vector_clear(m_vector); - } +FLECS_DBG_API +void flecs_sparse_fini( + ecs_sparse_t *sparse); - void destruct() { - ecs_vector_free(m_vector); - } +/** Remove all elements from sparse set */ +FLECS_DBG_API +void flecs_sparse_clear( + ecs_sparse_t *sparse); - void add(T& value) { - T* elem = static_cast(_ecs_vector_add(&m_vector, ECS_VECTOR_T(T))); - *elem = value; - } +/** Add element to sparse set, this generates or recycles an id */ +FLECS_DBG_API +void* flecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size); - void add(T&& value) { - T* elem = static_cast(_ecs_vector_add(&m_vector, ECS_VECTOR_T(T))) - *elem = value; - } +#define flecs_sparse_add_t(sparse, T)\ + ECS_CAST(T*, flecs_sparse_add(sparse, ECS_SIZEOF(T))) - T& get(int32_t index) { - return *static_cast(_ecs_vector_get(m_vector, ECS_VECTOR_T(T), index)); - } +/** Get last issued id. */ +FLECS_DBG_API +uint64_t flecs_sparse_last_id( + const ecs_sparse_t *sparse); - T& first() { - return *static_cast(_ecs_vector_first(m_vector, ECS_VECTOR_T(T))); - } +/** Generate or recycle a new id. */ +FLECS_DBG_API +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse); - T& last() { - return *static_cast(_ecs_vector_last(m_vector, ECS_VECTOR_T(T))); - } +/** Remove an element */ +FLECS_DBG_API +bool flecs_sparse_remove( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t id); - int32_t count() { - return ecs_vector_count(m_vector); - } +#define flecs_sparse_remove_t(sparse, T, id)\ + flecs_sparse_remove(sparse, ECS_SIZEOF(T), id) - int32_t size() { - return ecs_vector_size(m_vector); - } +/** Remove an element, increase generation */ +bool flecs_sparse_remove_w_gen( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t id); - ecs_vector_t *ptr() { - return m_vector; - } +#define flecs_sparse_remove_w_gen_t(sparse, T, id)\ + flecs_sparse_remove_w_gen(sparse, ECS_SIZEOF(T), id) - void ptr(ecs_vector_t *ptr) { - m_vector = ptr; - } +/** Test if id is alive, which requires the generation count to match. */ +FLECS_DBG_API +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t id); -private: - void init(size_t count) { - m_vector = ecs_vector_new(T, static_cast(count)); - } +/** Get value from sparse set by dense id. This function is useful in + * combination with flecs_sparse_count for iterating all values in the set. */ +FLECS_DBG_API +void* flecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index); - ecs_vector_t *m_vector; -}; +#define flecs_sparse_get_dense_t(sparse, T, index)\ + ECS_CAST(T*, flecs_sparse_get_dense(sparse, ECS_SIZEOF(T), index)) -} +/** Get the number of alive elements in the sparse set. */ +FLECS_DBG_API +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse); -#endif -#endif +/** Check if sparse set has id */ +bool flecs_sparse_has( + const ecs_sparse_t *sparse, + uint64_t id); -#endif +/** Like get_sparse, but don't care whether element is alive or not. */ +FLECS_DBG_API +void* flecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); -/** - * @file map.h - * @brief Map datastructure. - * - * Key-value datastructure. The map allows for fast retrieval of a payload for - * a 64-bit key. While it is not as fast as the sparse set, it is better at - * handling randomly distributed values. - * - * Payload is stored in bucket arrays. A bucket is computed from an id by - * using the (bucket_count - 1) as an AND-mask. The number of buckets is always - * a power of 2. Multiple keys will be stored in the same bucket. As a result - * the worst case retrieval performance of the map is O(n), though this is rare. - * On average lookup performance should equal O(1). - * - * The datastructure will automatically grow the number of buckets when the - * ratio between elements and buckets exceeds a certain threshold (LOAD_FACTOR). - * - * Note that while the implementation is a hashmap, it can only compute hashes - * for the provided 64 bit keys. This means that the provided keys must always - * be unique. If the provided keys are hashes themselves, it is the - * responsibility of the user to ensure that collisions are handled. - * - * In debug mode the map verifies that the type provided to the map functions - * matches the one used at creation time. - */ +#define flecs_sparse_get_t(sparse, T, index)\ + ECS_CAST(T*, flecs_sparse_get(sparse, ECS_SIZEOF(T), index)) -#ifndef FLECS_MAP_H -#define FLECS_MAP_H +/** Create element by (sparse) id. */ +FLECS_DBG_API +void* flecs_sparse_insert( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); +#define flecs_sparse_insert_t(sparse, T, index)\ + ECS_CAST(T*, flecs_sparse_insert(sparse, ECS_SIZEOF(T), index)) -#ifdef __cplusplus -extern "C" { -#endif +/** Get or create element by (sparse) id. */ +FLECS_DBG_API +void* flecs_sparse_ensure( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id, + bool *is_new); + +#define flecs_sparse_ensure_t(sparse, T, index, is_new)\ + ECS_CAST(T*, flecs_sparse_ensure(sparse, ECS_SIZEOF(T), index, is_new)) + +/** Fast version of ensure, no liveliness checking */ +FLECS_DBG_API +void* flecs_sparse_ensure_fast( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_ensure_fast_t(sparse, T, index)\ + ECS_CAST(T*, flecs_sparse_ensure_fast(sparse, ECS_SIZEOF(T), index)) + +/** Get pointer to ids (alive and not alive). Use with count() or size(). */ +FLECS_DBG_API +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse); + +FLECS_DBG_API +void flecs_sparse_shrink( + ecs_sparse_t *sparse); + +/* Publicly exposed APIs + * These APIs are not part of the public API and as a result may change without + * notice (though they haven't changed in a long time). */ + +FLECS_API +void ecs_sparse_init( + ecs_sparse_t *sparse, + ecs_size_t elem_size); + +#define ecs_sparse_init_t(sparse, T)\ + ecs_sparse_init(sparse, ECS_SIZEOF(T)) + +FLECS_API +void* ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size); + +#define ecs_sparse_add_t(sparse, T)\ + ECS_CAST(T*, ecs_sparse_add(sparse, ECS_SIZEOF(T))) + +FLECS_API +uint64_t ecs_sparse_last_id( + const ecs_sparse_t *sparse); + +FLECS_API +int32_t ecs_sparse_count( + const ecs_sparse_t *sparse); + +FLECS_API +void* ecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index); + +#define ecs_sparse_get_dense_t(sparse, T, index)\ + ECS_CAST(T*, ecs_sparse_get_dense(sparse, ECS_SIZEOF(T), index)) + +FLECS_API +void* ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define ecs_sparse_get_t(sparse, T, index)\ + ECS_CAST(T*, ecs_sparse_get(sparse, ECS_SIZEOF(T), index)) + +#ifdef __cplusplus +} +#endif + +#endif + +/** + * @file block_allocator.h + * @brief Block allocator. + */ + +#ifndef FLECS_BLOCK_ALLOCATOR_H +#define FLECS_BLOCK_ALLOCATOR_H + + +typedef struct ecs_map_t ecs_map_t; + +typedef struct ecs_block_allocator_block_t { + void *memory; + struct ecs_block_allocator_block_t *next; +} ecs_block_allocator_block_t; + +typedef struct ecs_block_allocator_chunk_header_t { + struct ecs_block_allocator_chunk_header_t *next; +} ecs_block_allocator_chunk_header_t; + +typedef struct ecs_block_allocator_t { + int32_t data_size; +#ifndef FLECS_USE_OS_ALLOC + int32_t chunk_size; + int32_t chunks_per_block; + int32_t block_size; + ecs_block_allocator_chunk_header_t *head; + ecs_block_allocator_block_t *block_head; +#ifdef FLECS_SANITIZE + int32_t alloc_count; + ecs_map_t *outstanding; +#endif +#endif +} ecs_block_allocator_t; + +FLECS_API +void flecs_ballocator_init( + ecs_block_allocator_t *ba, + ecs_size_t size); + +#define flecs_ballocator_init_t(ba, T)\ + flecs_ballocator_init(ba, ECS_SIZEOF(T)) +#define flecs_ballocator_init_n(ba, T, count)\ + flecs_ballocator_init(ba, ECS_SIZEOF(T) * count) + +FLECS_API +ecs_block_allocator_t* flecs_ballocator_new( + ecs_size_t size); + +#define flecs_ballocator_new_t(T)\ + flecs_ballocator_new(ECS_SIZEOF(T)) +#define flecs_ballocator_new_n(T, count)\ + flecs_ballocator_new(ECS_SIZEOF(T) * count) + +FLECS_API +void flecs_ballocator_fini( + ecs_block_allocator_t *ba); + +FLECS_API +void flecs_ballocator_free( + ecs_block_allocator_t *ba); + +FLECS_API +void* flecs_balloc( + ecs_block_allocator_t *allocator); + +FLECS_API +void* flecs_balloc_w_dbg_info( + ecs_block_allocator_t *allocator, + const char *type_name); + +FLECS_API +void* flecs_bcalloc( + ecs_block_allocator_t *allocator); + +FLECS_API +void* flecs_bcalloc_w_dbg_info( + ecs_block_allocator_t *allocator, + const char *type_name); + +FLECS_API +void flecs_bfree( + ecs_block_allocator_t *allocator, + void *memory); + +FLECS_API +void flecs_bfree_w_dbg_info( + ecs_block_allocator_t *allocator, + void *memory, + const char *type_name); + +FLECS_API +void* flecs_brealloc( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory); + +FLECS_API +void* flecs_brealloc_w_dbg_info( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory, + const char *type_name); + +FLECS_API +void* flecs_bdup( + ecs_block_allocator_t *ba, + void *memory); + +#endif + +/** + * @file datastructures/stack_allocator.h + * @brief Stack allocator. + */ + +#ifndef FLECS_STACK_ALLOCATOR_H +#define FLECS_STACK_ALLOCATOR_H + +/** Stack allocator for quick allocation of small temporary values */ + +typedef struct ecs_stack_page_t { + void *data; + struct ecs_stack_page_t *next; + int16_t sp; + uint32_t id; +} ecs_stack_page_t; + +typedef struct ecs_stack_cursor_t { + struct ecs_stack_cursor_t *prev; + struct ecs_stack_page_t *page; + int16_t sp; + bool is_free; +#ifdef FLECS_DEBUG + struct ecs_stack_t *owner; +#endif +} ecs_stack_cursor_t; + +typedef struct ecs_stack_t { + ecs_stack_page_t *first; + ecs_stack_page_t *tail_page; + ecs_stack_cursor_t *tail_cursor; +#ifdef FLECS_DEBUG + int32_t cursor_count; +#endif +} ecs_stack_t; + +#define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) +#define FLECS_STACK_PAGE_SIZE (1024 - FLECS_STACK_PAGE_OFFSET) + +FLECS_DBG_API +void flecs_stack_init( + ecs_stack_t *stack); + +FLECS_DBG_API +void flecs_stack_fini( + ecs_stack_t *stack); + +FLECS_DBG_API +void* flecs_stack_alloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align); + +#define flecs_stack_alloc_t(stack, T)\ + flecs_stack_alloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T)) + +#define flecs_stack_alloc_n(stack, T, count)\ + flecs_stack_alloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) + +FLECS_DBG_API +void* flecs_stack_calloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align); + +#define flecs_stack_calloc_t(stack, T)\ + flecs_stack_calloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T)) + +#define flecs_stack_calloc_n(stack, T, count)\ + flecs_stack_calloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) + +FLECS_DBG_API +void flecs_stack_free( + void *ptr, + ecs_size_t size); + +#define flecs_stack_free_t(ptr, T)\ + flecs_stack_free(ptr, ECS_SIZEOF(T)) + +#define flecs_stack_free_n(ptr, T, count)\ + flecs_stack_free(ptr, ECS_SIZEOF(T) * count) + +void flecs_stack_reset( + ecs_stack_t *stack); + +FLECS_DBG_API +ecs_stack_cursor_t* flecs_stack_get_cursor( + ecs_stack_t *stack); + +FLECS_DBG_API +void flecs_stack_restore_cursor( + ecs_stack_t *stack, + ecs_stack_cursor_t *cursor); + +#endif -typedef uint64_t ecs_map_key_t; +/** + * @file map.h + * @brief Map data structure. + */ + +#ifndef FLECS_MAP_H +#define FLECS_MAP_H + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint64_t ecs_map_data_t; +typedef ecs_map_data_t ecs_map_key_t; +typedef ecs_map_data_t ecs_map_val_t; /* Map type */ +typedef struct ecs_bucket_entry_t { + ecs_map_key_t key; + ecs_map_val_t value; + struct ecs_bucket_entry_t *next; +} ecs_bucket_entry_t; + typedef struct ecs_bucket_t { - ecs_map_key_t *keys; /* Array with keys */ - void *payload; /* Payload array */ - int32_t count; /* Number of elements in bucket */ + ecs_bucket_entry_t *first; } ecs_bucket_t; -typedef struct ecs_map_t { +struct ecs_map_t { ecs_bucket_t *buckets; - int16_t elem_size; - uint8_t bucket_shift; int32_t bucket_count; - int32_t count; -} ecs_map_t; + unsigned count : 26; + unsigned bucket_shift : 6; + struct ecs_allocator_t *allocator; +#ifdef FLECS_DEBUG + int32_t change_count; /* Track modifications while iterating */ + ecs_map_key_t last_iterated; /* Currently iterated element */ +#endif +}; typedef struct ecs_map_iter_t { const ecs_map_t *map; - struct ecs_bucket_t *bucket; - int32_t bucket_index; - int32_t element_index; - void *payload; + ecs_bucket_t *bucket; + ecs_bucket_entry_t *entry; + ecs_map_data_t *res; +#ifdef FLECS_DEBUG + int32_t change_count; +#endif } ecs_map_iter_t; -#define ECS_MAP_INIT(T) { .elem_size = ECS_SIZEOF(T) } +/* Function/macro postfixes meaning: + * _ptr: access ecs_map_val_t as void* + * _ref: access ecs_map_val_t* as T** + * _deref: dereferences a _ref + * _alloc: if _ptr is NULL, alloc + * _free: if _ptr is not NULL, free + */ /** Initialize new map. */ FLECS_API -void _ecs_map_init( +void ecs_map_init( ecs_map_t *map, - ecs_size_t elem_size, - int32_t elem_count); + struct ecs_allocator_t *allocator); -#define ecs_map_init(map, T, elem_count)\ - _ecs_map_init(map, sizeof(T), elem_count) +/** Initialize new map if uninitialized, leave as is otherwise */ +FLECS_API +void ecs_map_init_if( + ecs_map_t *map, + struct ecs_allocator_t *allocator); -/** Deinitialize map. */ +/** Reclaim map memory. */ FLECS_API -void ecs_map_fini( +void ecs_map_reclaim( ecs_map_t *map); -/** Create new map. */ +/** Deinitialize map. */ FLECS_API -ecs_map_t* _ecs_map_new( - ecs_size_t elem_size, - int32_t elem_count); - -#define ecs_map_new(T, elem_count)\ - _ecs_map_new(sizeof(T), elem_count) - -/** Is map initialized */ -bool ecs_map_is_initialized( - const ecs_map_t *result); +void ecs_map_fini( + ecs_map_t *map); /** Get element for key, returns NULL if they key doesn't exist. */ FLECS_API -void* _ecs_map_get( +ecs_map_val_t* ecs_map_get( const ecs_map_t *map, - ecs_size_t elem_size, ecs_map_key_t key); -#define ecs_map_get(map, T, key)\ - (T*)_ecs_map_get(map, sizeof(T), (ecs_map_key_t)key) - -/** Get pointer element. This dereferences the map element as a pointer. This - * operation returns NULL when either the element does not exist or whether the - * pointer is NULL, and should therefore only be used when the application knows - * for sure that a pointer should never be NULL. */ +/* Get element as pointer (auto-dereferences _ptr) */ FLECS_API -void* _ecs_map_get_ptr( +void* ecs_map_get_deref_( const ecs_map_t *map, ecs_map_key_t key); -#define ecs_map_get_ptr(map, T, key)\ - (T)_ecs_map_get_ptr(map, key) - -/** Test if map has key */ +/** Get or insert element for key. */ FLECS_API -bool ecs_map_has( - const ecs_map_t *map, +ecs_map_val_t* ecs_map_ensure( + ecs_map_t *map, ecs_map_key_t key); -/** Get or create element for key. */ +/** Get or insert pointer element for key, allocate if the pointer is NULL */ FLECS_API -void* _ecs_map_ensure( +void* ecs_map_ensure_alloc( ecs_map_t *map, ecs_size_t elem_size, ecs_map_key_t key); -#define ecs_map_ensure(map, T, key)\ - ((T*)_ecs_map_ensure(map, sizeof(T), (ecs_map_key_t)key)) - -/** Set element. */ +/** Insert element for key. */ FLECS_API -void* _ecs_map_set( +void ecs_map_insert( ecs_map_t *map, - ecs_size_t elem_size, ecs_map_key_t key, - const void *payload); - -#define ecs_map_set(map, key, payload)\ - _ecs_map_set(map, sizeof(*payload), (ecs_map_key_t)key, payload) + ecs_map_val_t value); -#define ecs_map_set_ptr(map, key, payload)\ - _ecs_map_set(map, sizeof(payload), (ecs_map_key_t)key, &payload) +/** Insert pointer element for key, populate with new allocation. */ +FLECS_API +void* ecs_map_insert_alloc( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key); -/** Free map. */ +/** Remove key from map. */ FLECS_API -void ecs_map_free( - ecs_map_t *map); +ecs_map_val_t ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key); -/** Remove key from map. - * Returns number of remaining elements. - */ +/* Remove pointer element, free if not NULL */ FLECS_API -int32_t ecs_map_remove( +void ecs_map_remove_free( ecs_map_t *map, ecs_map_key_t key); @@ -1205,76 +2157,46 @@ void ecs_map_clear( ecs_map_t *map); /** Return number of elements in map. */ -FLECS_API -int32_t ecs_map_count( - const ecs_map_t *map); +#define ecs_map_count(map) ((map) ? (map)->count : 0) -/** Return number of buckets in map. */ -FLECS_API -int32_t ecs_map_bucket_count( - const ecs_map_t *map); +/** Is map initialized */ +#define ecs_map_is_init(map) ((map) ? (map)->bucket_shift != 0 : false) /** Return iterator to map contents. */ FLECS_API ecs_map_iter_t ecs_map_iter( const ecs_map_t *map); -/** Obtain next element in map from iterator. */ -FLECS_API -void* _ecs_map_next( - ecs_map_iter_t* iter, - ecs_size_t elem_size, - ecs_map_key_t *key); - -#define ecs_map_next(iter, T, key) \ - (T*)_ecs_map_next(iter, sizeof(T), key) - -/** Obtain next pointer element from iterator. See ecs_map_get_ptr. */ -FLECS_API -void* _ecs_map_next_ptr( - ecs_map_iter_t* iter, - ecs_map_key_t *key); - -#define ecs_map_next_ptr(iter, T, key) \ - (T)_ecs_map_next_ptr(iter, key) - -/** Grow number of buckets in the map for specified number of elements. */ +/** Return whether map iterator is valid. */ FLECS_API -void ecs_map_grow( - ecs_map_t *map, - int32_t elem_count); +bool ecs_map_iter_valid( + ecs_map_iter_t *iter); -/** Set number of buckets in the map for specified number of elements. */ +/** Obtain next element in map from iterator. */ FLECS_API -void ecs_map_set_size( - ecs_map_t *map, - int32_t elem_count); +bool ecs_map_next( + ecs_map_iter_t *iter); /** Copy map. */ FLECS_API -ecs_map_t* ecs_map_copy( - ecs_map_t *map); +void ecs_map_copy( + ecs_map_t *dst, + const ecs_map_t *src); -/** Return memory occupied by map. */ -FLECS_API -void ecs_map_memory( - ecs_map_t *map, - int32_t *allocd, - int32_t *used); +#define ecs_map_get_ref(m, T, k) ECS_CAST(T**, ecs_map_get(m, k)) +#define ecs_map_get_deref(m, T, k) ECS_CAST(T*, ecs_map_get_deref_(m, k)) +#define ecs_map_get_ptr(m, k) ECS_CAST(void*, ecs_map_get_deref_(m, k)) +#define ecs_map_ensure_ref(m, T, k) ECS_CAST(T**, ecs_map_ensure(m, k)) -#ifndef FLECS_LEGACY -#define ecs_map_each(map, T, key, var, ...)\ - {\ - ecs_map_iter_t it = ecs_map_iter(map);\ - ecs_map_key_t key;\ - T* var;\ - (void)key;\ - (void)var;\ - while ((var = ecs_map_next(&it, T, &key))) {\ - __VA_ARGS__\ - }\ - } -#endif +#define ecs_map_insert_ptr(m, k, v) ecs_map_insert(m, k, ECS_CAST(ecs_map_val_t, ECS_PTR_CAST(uintptr_t, v))) +#define ecs_map_insert_alloc_t(m, T, k) ECS_CAST(T*, ecs_map_insert_alloc(m, ECS_SIZEOF(T), k)) +#define ecs_map_ensure_alloc_t(m, T, k) ECS_PTR_CAST(T*, (uintptr_t)ecs_map_ensure_alloc(m, ECS_SIZEOF(T), k)) +#define ecs_map_remove_ptr(m, k) (ECS_PTR_CAST(void*, ECS_CAST(uintptr_t, (ecs_map_remove(m, k))))) + +#define ecs_map_key(it) ((it)->res[0]) +#define ecs_map_value(it) ((it)->res[1]) +#define ecs_map_ptr(it) ECS_PTR_CAST(void*, ECS_CAST(uintptr_t, ecs_map_value(it))) +#define ecs_map_ref(it, T) (ECS_CAST(T**, &((it)->res[1]))) #ifdef __cplusplus } @@ -1283,90 +2205,179 @@ void ecs_map_memory( #endif /** - * @file strbuf.h - * @brief Utility for constructing strings. - * - * A buffer builds up a list of elements which individually can be up to N bytes - * large. While appending, data is added to these elements. More elements are - * added on the fly when needed. When an application calls ecs_strbuf_get, all - * elements are combined in one string and the element administration is freed. - * - * This approach prevents reallocs of large blocks of memory, and therefore - * copying large blocks of memory when appending to a large buffer. A buffer - * preallocates some memory for the element overhead so that for small strings - * there is hardly any overhead, while for large strings the overhead is offset - * by the reduced time spent on copying memory. + * @file allocator.h + * @brief Allocator that returns memory objects of any size. */ -#ifndef FLECS_STRBUF_H_ -#define FLECS_STRBUF_H_ +#ifndef FLECS_ALLOCATOR_H +#define FLECS_ALLOCATOR_H -#ifdef __cplusplus -extern "C" { +FLECS_DBG_API extern int64_t ecs_block_allocator_alloc_count; +FLECS_DBG_API extern int64_t ecs_block_allocator_free_count; +FLECS_DBG_API extern int64_t ecs_stack_allocator_alloc_count; +FLECS_DBG_API extern int64_t ecs_stack_allocator_free_count; + +struct ecs_allocator_t { +#ifndef FLECS_USE_OS_ALLOC + ecs_block_allocator_t chunks; + struct ecs_sparse_t sizes; /* */ +#else + bool dummy; #endif +}; -#define ECS_STRBUF_INIT (ecs_strbuf_t){0} -#define ECS_STRBUF_ELEMENT_SIZE (511) -#define ECS_STRBUF_MAX_LIST_DEPTH (32) +FLECS_API +void flecs_allocator_init( + ecs_allocator_t *a); -typedef struct ecs_strbuf_element { - bool buffer_embedded; - int32_t pos; - char *buf; - struct ecs_strbuf_element *next; -} ecs_strbuf_element; +FLECS_API +void flecs_allocator_fini( + ecs_allocator_t *a); -typedef struct ecs_strbuf_element_embedded { - ecs_strbuf_element super; - char buf[ECS_STRBUF_ELEMENT_SIZE + 1]; -} ecs_strbuf_element_embedded; +FLECS_API +ecs_block_allocator_t* flecs_allocator_get( + ecs_allocator_t *a, + ecs_size_t size); -typedef struct ecs_strbuf_element_str { - ecs_strbuf_element super; - char *alloc_str; -} ecs_strbuf_element_str; +FLECS_API +char* flecs_strdup( + ecs_allocator_t *a, + const char* str); -typedef struct ecs_strbuf_list_elem { - int32_t count; - const char *separator; -} ecs_strbuf_list_elem; +FLECS_API +void flecs_strfree( + ecs_allocator_t *a, + char* str); -typedef struct ecs_strbuf_t { - /* When set by an application, append will write to this buffer */ - char *buf; +FLECS_API +void* flecs_dup( + ecs_allocator_t *a, + ecs_size_t size, + const void *src); + +#ifndef FLECS_USE_OS_ALLOC +#define flecs_allocator(obj) (&obj->allocators.dyn) + +#define flecs_alloc(a, size) flecs_balloc(flecs_allocator_get(a, size)) +#define flecs_alloc_w_dbg_info(a, size, type_name) flecs_balloc_w_dbg_info(flecs_allocator_get(a, size), type_name) +#define flecs_alloc_t(a, T) flecs_alloc_w_dbg_info(a, ECS_SIZEOF(T), #T) +#define flecs_alloc_n(a, T, count) flecs_alloc_w_dbg_info(a, ECS_SIZEOF(T) * (count), #T) + +#define flecs_calloc(a, size) flecs_bcalloc(flecs_allocator_get(a, size)) +#define flecs_calloc_w_dbg_info(a, size, type_name) flecs_bcalloc_w_dbg_info(flecs_allocator_get(a, size), type_name) +#define flecs_calloc_t(a, T) flecs_calloc_w_dbg_info(a, ECS_SIZEOF(T), #T) +#define flecs_calloc_n(a, T, count) flecs_calloc_w_dbg_info(a, ECS_SIZEOF(T) * (count), #T) + +#define flecs_free(a, size, ptr)\ + flecs_bfree((ptr) ? flecs_allocator_get(a, size) : NULL, ptr) +#define flecs_free_t(a, T, ptr)\ + flecs_bfree_w_dbg_info((ptr) ? flecs_allocator_get(a, ECS_SIZEOF(T)) : NULL, ptr, #T) +#define flecs_free_n(a, T, count, ptr)\ + flecs_bfree_w_dbg_info((ptr) ? flecs_allocator_get(a, ECS_SIZEOF(T) * (count)) : NULL\ + , ptr, #T) + +#define flecs_realloc(a, size_dst, size_src, ptr)\ + flecs_brealloc(flecs_allocator_get(a, size_dst),\ + flecs_allocator_get(a, size_src),\ + ptr) +#define flecs_realloc_w_dbg_info(a, size_dst, size_src, ptr, type_name)\ + flecs_brealloc_w_dbg_info(flecs_allocator_get(a, size_dst),\ + flecs_allocator_get(a, size_src),\ + ptr,\ + type_name) +#define flecs_realloc_n(a, T, count_dst, count_src, ptr)\ + flecs_realloc(a, ECS_SIZEOF(T) * (count_dst), ECS_SIZEOF(T) * (count_src), ptr) + +#define flecs_dup_n(a, T, count, ptr) flecs_dup(a, ECS_SIZEOF(T) * (count), ptr) - /* The maximum number of characters that may be printed */ - int32_t max; +#else - /* Size of elements minus current element */ - int32_t size; +void* flecs_alloc( + ecs_allocator_t *a, + ecs_size_t size); - /* The number of elements in use */ - int32_t elementCount; +void* flecs_calloc( + ecs_allocator_t *a, + ecs_size_t size); - /* Always allocate at least one element */ - ecs_strbuf_element_embedded firstElement; +void* flecs_realloc( + ecs_allocator_t *a, + ecs_size_t dst_size, + ecs_size_t src_size, + void *ptr); - /* The current element being appended to */ - ecs_strbuf_element *current; +void flecs_free( + ecs_allocator_t *a, + ecs_size_t size, + void *ptr); - /* Stack that keeps track of number of list elements, used for conditionally - * inserting a separator */ - ecs_strbuf_list_elem list_stack[ECS_STRBUF_MAX_LIST_DEPTH]; - int32_t list_sp; +#define flecs_alloc_w_dbg_info(a, size, type_name) flecs_alloc(a, size) +#define flecs_alloc_t(a, T) flecs_alloc(a, ECS_SIZEOF(T)) +#define flecs_alloc_n(a, T, count) flecs_alloc(a, ECS_SIZEOF(T) * (count)) - /* This is set to the output string after calling ecs_strbuf_get */ - char *content; +#define flecs_calloc_w_dbg_info(a, size, type_name) flecs_calloc(a, size) +#define flecs_calloc_t(a, T) flecs_calloc(a, ECS_SIZEOF(T)) +#define flecs_calloc_n(a, T, count) flecs_calloc(a, ECS_SIZEOF(T) * (count)) - /* This is set to the output string length after calling ecs_strbuf_get */ - int32_t length; +#define flecs_free_t(a, T, ptr) flecs_free(a, ECS_SIZEOF(T), ptr) +#define flecs_free_n(a, T, count, ptr) flecs_free(a, ECS_SIZEOF(T) * (count), ptr) + +#define flecs_realloc_w_dbg_info(a, size_dst, size_src, ptr, type_name)\ + flecs_realloc(a, size_dst, size_src, ptr) + +#define flecs_realloc_n(a, T, count_dst, count_src, ptr)\ + flecs_realloc(a, ECS_SIZEOF(T) * count_dst, ECS_SIZEOF(T) * count_src, ptr) + +#define flecs_dup_n(a, T, count, ptr) flecs_dup(a, ECS_SIZEOF(T) * (count), ptr) + +#endif + +#endif + +/** + * @file strbuf.h + * @brief Utility for constructing strings. + */ + +#ifndef FLECS_STRBUF_H_ +#define FLECS_STRBUF_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +/* Fixes missing field initializer warning on g++ */ +#define ECS_STRBUF_INIT (ecs_strbuf_t){} +#else +#define ECS_STRBUF_INIT (ecs_strbuf_t){0} +#endif + +#define ECS_STRBUF_SMALL_STRING_SIZE (512) +#define ECS_STRBUF_MAX_LIST_DEPTH (32) + +typedef struct ecs_strbuf_list_elem { + int32_t count; + const char *separator; +} ecs_strbuf_list_elem; + +typedef struct ecs_strbuf_t { + char *content; + ecs_size_t length; + ecs_size_t size; + + ecs_strbuf_list_elem list_stack[ECS_STRBUF_MAX_LIST_DEPTH]; + int32_t list_sp; + + char small_string[ECS_STRBUF_SMALL_STRING_SIZE]; } ecs_strbuf_t; /* Append format string to a buffer. * Returns false when max is reached, true when there is still space */ FLECS_API -bool ecs_strbuf_append( +void ecs_strbuf_append( ecs_strbuf_t *buffer, const char *fmt, ...); @@ -1374,7 +2385,7 @@ bool ecs_strbuf_append( /* Append format string with argument list to a buffer. * Returns false when max is reached, true when there is still space */ FLECS_API -bool ecs_strbuf_vappend( +void ecs_strbuf_vappend( ecs_strbuf_t *buffer, const char *fmt, va_list args); @@ -1382,62 +2393,62 @@ bool ecs_strbuf_vappend( /* Append string to buffer. * Returns false when max is reached, true when there is still space */ FLECS_API -bool ecs_strbuf_appendstr( +void ecs_strbuf_appendstr( ecs_strbuf_t *buffer, const char *str); /* Append character to buffer. * Returns false when max is reached, true when there is still space */ FLECS_API -bool ecs_strbuf_appendch( +void ecs_strbuf_appendch( ecs_strbuf_t *buffer, char ch); -/* Append float to buffer. +/* Append int to buffer. * Returns false when max is reached, true when there is still space */ FLECS_API -bool ecs_strbuf_appendflt( +void ecs_strbuf_appendint( ecs_strbuf_t *buffer, - double v, - char nan_delim); + int64_t v); -/* Append source buffer to destination buffer. +/* Append float to buffer. * Returns false when max is reached, true when there is still space */ FLECS_API -bool ecs_strbuf_mergebuff( - ecs_strbuf_t *dst_buffer, - ecs_strbuf_t *src_buffer); +void ecs_strbuf_appendflt( + ecs_strbuf_t *buffer, + double v, + char nan_delim); -/* Append string to buffer, transfer ownership to buffer. +/* Append boolean to buffer. * Returns false when max is reached, true when there is still space */ FLECS_API -bool ecs_strbuf_appendstr_zerocpy( +void ecs_strbuf_appendbool( ecs_strbuf_t *buffer, - char *str); + bool v); -/* Append string to buffer, do not free/modify string. +/* Append source buffer to destination buffer. * Returns false when max is reached, true when there is still space */ FLECS_API -bool ecs_strbuf_appendstr_zerocpy_const( - ecs_strbuf_t *buffer, - const char *str); +void ecs_strbuf_mergebuff( + ecs_strbuf_t *dst_buffer, + ecs_strbuf_t *src_buffer); /* Append n characters to buffer. * Returns false when max is reached, true when there is still space */ FLECS_API -bool ecs_strbuf_appendstrn( +void ecs_strbuf_appendstrn( ecs_strbuf_t *buffer, const char *str, int32_t n); /* Return result string */ FLECS_API -char *ecs_strbuf_get( +char* ecs_strbuf_get( ecs_strbuf_t *buffer); /* Return small string from first element (appends \0) */ FLECS_API -char *ecs_strbuf_get_small( +char* ecs_strbuf_get_small( ecs_strbuf_t *buffer); /* Reset buffer without returning a string */ @@ -1463,23 +2474,42 @@ FLECS_API void ecs_strbuf_list_next( ecs_strbuf_t *buffer); +/* Append character to as new element in list. */ +FLECS_API +void ecs_strbuf_list_appendch( + ecs_strbuf_t *buffer, + char ch); + /* Append formatted string as a new element in list */ FLECS_API -bool ecs_strbuf_list_append( +void ecs_strbuf_list_append( ecs_strbuf_t *buffer, const char *fmt, ...); /* Append string as a new element in list */ FLECS_API -bool ecs_strbuf_list_appendstr( +void ecs_strbuf_list_appendstr( ecs_strbuf_t *buffer, const char *str); +/* Append string as a new element in list */ +FLECS_API +void ecs_strbuf_list_appendstrn( + ecs_strbuf_t *buffer, + const char *str, + int32_t n); + FLECS_API int32_t ecs_strbuf_written( const ecs_strbuf_t *buffer); +#define ecs_strbuf_appendlit(buf, str)\ + ecs_strbuf_appendstrn(buf, str, (int32_t)(sizeof(str) - 1)) + +#define ecs_strbuf_list_appendlit(buf, str)\ + ecs_strbuf_list_appendstrn(buf, str, (int32_t)(sizeof(str) - 1)) + #ifdef __cplusplus } #endif @@ -1490,19 +2520,24 @@ int32_t ecs_strbuf_written( * @file os_api.h * @brief Operating system abstraction API. * - * This file contains the operating system abstraction API. The flecs core + * This file contains the operating system abstraction API. The flecs core * library avoids OS/runtime specific API calls as much as possible. Instead it * provides an interface that can be implemented by applications. * - * Examples for how to implement this interface can be found in the + * Examples for how to implement this interface can be found in the * examples/os_api folder. */ #ifndef FLECS_OS_API_H #define FLECS_OS_API_H -#include -#include +/** + * @defgroup c_os_api OS API + * @ingroup c + * Interface for providing OS specific functionality. + * + * @{ + */ #if defined(ECS_TARGET_WINDOWS) #include @@ -1516,133 +2551,184 @@ int32_t ecs_strbuf_written( extern "C" { #endif +/** Time type. */ typedef struct ecs_time_t { - uint32_t sec; - uint32_t nanosec; + uint32_t sec; /**< Second part. */ + uint32_t nanosec; /**< Nanosecond part. */ } ecs_time_t; -/* Allocation counters (not thread safe) */ -extern int64_t ecs_os_api_malloc_count; -extern int64_t ecs_os_api_realloc_count; -extern int64_t ecs_os_api_calloc_count; -extern int64_t ecs_os_api_free_count; +/* Allocation counters */ +extern int64_t ecs_os_api_malloc_count; /**< malloc count. */ +extern int64_t ecs_os_api_realloc_count; /**< realloc count. */ +extern int64_t ecs_os_api_calloc_count; /**< calloc count. */ +extern int64_t ecs_os_api_free_count; /**< free count. */ + +/* Enabling this flag will add a header to each allocation that allows the code + * to track exactly how much memory has been allocated. Increases memory + * utilization by 16 bytes per allocation, and is not thread safe. */ +// #define FLECS_TRACK_OS_ALLOC +#ifdef FLECS_TRACK_OS_ALLOC +FLECS_API extern ecs_size_t ecs_os_allocated_bytes; +#endif /* Use handle types that _at least_ can store pointers */ -typedef uintptr_t ecs_os_thread_t; -typedef uintptr_t ecs_os_cond_t; -typedef uintptr_t ecs_os_mutex_t; -typedef uintptr_t ecs_os_dl_t; -typedef uintptr_t ecs_os_sock_t; +typedef uintptr_t ecs_os_thread_t; /**< OS thread. */ +typedef uintptr_t ecs_os_cond_t; /**< OS cond. */ +typedef uintptr_t ecs_os_mutex_t; /**< OS mutex. */ +typedef uintptr_t ecs_os_dl_t; /**< OS dynamic library. */ +typedef uintptr_t ecs_os_sock_t; /**< OS socket. */ + +/** 64 bit thread id. */ +typedef uint64_t ecs_os_thread_id_t; -/* Generic function pointer type */ +/** Generic function pointer type. */ typedef void (*ecs_os_proc_t)(void); -/* OS API init */ -typedef +/** OS API init. */ +typedef void (*ecs_os_api_init_t)(void); -/* OS API deinit */ -typedef +/** OS API deinit. */ +typedef void (*ecs_os_api_fini_t)(void); -/* Memory management */ -typedef +/** OS API malloc function type. */ +typedef void* (*ecs_os_api_malloc_t)( ecs_size_t size); -typedef +/** OS API free function type. */ +typedef void (*ecs_os_api_free_t)( void *ptr); +/** OS API realloc function type. */ typedef void* (*ecs_os_api_realloc_t)( - void *ptr, + void *ptr, ecs_size_t size); +/** OS API calloc function type. */ typedef void* (*ecs_os_api_calloc_t)( ecs_size_t size); +/** OS API strdup function type. */ typedef char* (*ecs_os_api_strdup_t)( const char *str); -/* Threads */ +/** OS API thread_callback function type. */ typedef void* (*ecs_os_thread_callback_t)( void*); +/** OS API thread_new function type. */ typedef ecs_os_thread_t (*ecs_os_api_thread_new_t)( ecs_os_thread_callback_t callback, void *param); +/** OS API thread_join function type. */ typedef void* (*ecs_os_api_thread_join_t)( ecs_os_thread_t thread); +/** OS API thread_self function type. */ +typedef +ecs_os_thread_id_t (*ecs_os_api_thread_self_t)(void); + +/** OS API task_new function type. */ +typedef +ecs_os_thread_t (*ecs_os_api_task_new_t)( + ecs_os_thread_callback_t callback, + void *param); + +/** OS API task_join function type. */ +typedef +void* (*ecs_os_api_task_join_t)( + ecs_os_thread_t thread); + /* Atomic increment / decrement */ +/** OS API ainc function type. */ typedef -int (*ecs_os_api_ainc_t)( +int32_t (*ecs_os_api_ainc_t)( int32_t *value); +/** OS API lainc function type. */ +typedef +int64_t (*ecs_os_api_lainc_t)( + int64_t *value); /* Mutex */ +/** OS API mutex_new function type. */ typedef ecs_os_mutex_t (*ecs_os_api_mutex_new_t)( void); +/** OS API mutex_lock function type. */ typedef void (*ecs_os_api_mutex_lock_t)( ecs_os_mutex_t mutex); +/** OS API mutex_unlock function type. */ typedef void (*ecs_os_api_mutex_unlock_t)( ecs_os_mutex_t mutex); +/** OS API mutex_free function type. */ typedef void (*ecs_os_api_mutex_free_t)( ecs_os_mutex_t mutex); /* Condition variable */ +/** OS API cond_new function type. */ typedef ecs_os_cond_t (*ecs_os_api_cond_new_t)( void); +/** OS API cond_free function type. */ typedef void (*ecs_os_api_cond_free_t)( ecs_os_cond_t cond); +/** OS API cond_signal function type. */ typedef void (*ecs_os_api_cond_signal_t)( ecs_os_cond_t cond); +/** OS API cond_broadcast function type. */ typedef void (*ecs_os_api_cond_broadcast_t)( ecs_os_cond_t cond); +/** OS API cond_wait function type. */ typedef void (*ecs_os_api_cond_wait_t)( ecs_os_cond_t cond, ecs_os_mutex_t mutex); -typedef +/** OS API sleep function type. */ +typedef void (*ecs_os_api_sleep_t)( int32_t sec, int32_t nanosec); -typedef +/** OS API enable_high_timer_resolution function type. */ +typedef void (*ecs_os_api_enable_high_timer_resolution_t)( bool enable); +/** OS API get_time function type. */ typedef void (*ecs_os_api_get_time_t)( ecs_time_t *time_out); +/** OS API now function type. */ typedef uint64_t (*ecs_os_api_now_t)(void); -/* Logging */ +/** OS API log function type. */ typedef void (*ecs_os_api_log_t)( int32_t level, /* Logging level */ @@ -1650,126 +2736,186 @@ void (*ecs_os_api_log_t)( int32_t line, /* Line it was logged */ const char *msg); -/* Application termination */ +/** OS API abort function type. */ typedef void (*ecs_os_api_abort_t)( void); -/* Dynamic libraries */ +/** OS API dlopen function type. */ typedef ecs_os_dl_t (*ecs_os_api_dlopen_t)( const char *libname); +/** OS API dlproc function type. */ typedef ecs_os_proc_t (*ecs_os_api_dlproc_t)( ecs_os_dl_t lib, const char *procname); +/** OS API dlclose function type. */ typedef void (*ecs_os_api_dlclose_t)( ecs_os_dl_t lib); +/** OS API module_to_path function type. */ typedef char* (*ecs_os_api_module_to_path_t)( const char *module_id); -/* Prefix members of struct with 'ecs_' as some system headers may define - * macro's for functions like "strdup", "log" or "_free" */ +/* Performance tracing */ +typedef void (*ecs_os_api_perf_trace_t)( + const char *filename, + size_t line, + const char *name); + +/* Prefix members of struct with 'ecs_' as some system headers may define + * macros for functions like "strdup", "log" or "_free" */ +/** OS API interface. */ typedef struct ecs_os_api_t { /* API init / deinit */ - ecs_os_api_init_t init_; - ecs_os_api_fini_t fini_; + ecs_os_api_init_t init_; /**< init callback. */ + ecs_os_api_fini_t fini_; /**< fini callback. */ /* Memory management */ - ecs_os_api_malloc_t malloc_; - ecs_os_api_realloc_t realloc_; - ecs_os_api_calloc_t calloc_; - ecs_os_api_free_t free_; + ecs_os_api_malloc_t malloc_; /**< malloc callback. */ + ecs_os_api_realloc_t realloc_; /**< realloc callback. */ + ecs_os_api_calloc_t calloc_; /**< calloc callback. */ + ecs_os_api_free_t free_; /**< free callback. */ /* Strings */ - ecs_os_api_strdup_t strdup_; + ecs_os_api_strdup_t strdup_; /**< strdup callback. */ /* Threads */ - ecs_os_api_thread_new_t thread_new_; - ecs_os_api_thread_join_t thread_join_; + ecs_os_api_thread_new_t thread_new_; /**< thread_new callback. */ + ecs_os_api_thread_join_t thread_join_; /**< thread_join callback. */ + ecs_os_api_thread_self_t thread_self_; /**< thread_self callback. */ + + /* Tasks */ + ecs_os_api_thread_new_t task_new_; /**< task_new callback. */ + ecs_os_api_thread_join_t task_join_; /**< task_join callback. */ - /* Atomic incremenet / decrement */ - ecs_os_api_ainc_t ainc_; - ecs_os_api_ainc_t adec_; + /* Atomic increment / decrement */ + ecs_os_api_ainc_t ainc_; /**< ainc callback. */ + ecs_os_api_ainc_t adec_; /**< adec callback. */ + ecs_os_api_lainc_t lainc_; /**< lainc callback. */ + ecs_os_api_lainc_t ladec_; /**< ladec callback. */ /* Mutex */ - ecs_os_api_mutex_new_t mutex_new_; - ecs_os_api_mutex_free_t mutex_free_; - ecs_os_api_mutex_lock_t mutex_lock_; - ecs_os_api_mutex_lock_t mutex_unlock_; + ecs_os_api_mutex_new_t mutex_new_; /**< mutex_new callback. */ + ecs_os_api_mutex_free_t mutex_free_; /**< mutex_free callback. */ + ecs_os_api_mutex_lock_t mutex_lock_; /**< mutex_lock callback. */ + ecs_os_api_mutex_lock_t mutex_unlock_; /**< mutex_unlock callback. */ /* Condition variable */ - ecs_os_api_cond_new_t cond_new_; - ecs_os_api_cond_free_t cond_free_; - ecs_os_api_cond_signal_t cond_signal_; - ecs_os_api_cond_broadcast_t cond_broadcast_; - ecs_os_api_cond_wait_t cond_wait_; + ecs_os_api_cond_new_t cond_new_; /**< cond_new callback. */ + ecs_os_api_cond_free_t cond_free_; /**< cond_free callback. */ + ecs_os_api_cond_signal_t cond_signal_; /**< cond_signal callback. */ + ecs_os_api_cond_broadcast_t cond_broadcast_; /**< cond_broadcast callback. */ + ecs_os_api_cond_wait_t cond_wait_; /**< cond_wait callback. */ /* Time */ - ecs_os_api_sleep_t sleep_; - ecs_os_api_now_t now_; - ecs_os_api_get_time_t get_time_; - ecs_os_api_enable_high_timer_resolution_t enable_high_timer_resolution_; + ecs_os_api_sleep_t sleep_; /**< sleep callback. */ + ecs_os_api_now_t now_; /**< now callback. */ + ecs_os_api_get_time_t get_time_; /**< get_time callback. */ /* Logging */ - ecs_os_api_log_t log_; /* Logging function. The level should be interpreted as: */ - /* >0: Debug tracing. Only enabled in debug builds. */ - /* 0: Tracing. Enabled in debug/release builds. */ - /* -2: Warning. An issue occurred, but operation was successful. */ - /* -3: Error. An issue occurred, and operation was unsuccessful. */ - /* -4: Fatal. An issue occurred, and application must quit. */ + ecs_os_api_log_t log_; /**< log callback. + * The level should be interpreted as: + * >0: Debug tracing. Only enabled in debug builds. + * 0: Tracing. Enabled in debug/release builds. + * -2: Warning. An issue occurred, but operation was successful. + * -3: Error. An issue occurred, and operation was unsuccessful. + * -4: Fatal. An issue occurred, and application must quit. */ /* Application termination */ - ecs_os_api_abort_t abort_; + ecs_os_api_abort_t abort_; /**< abort callback. */ /* Dynamic library loading */ - ecs_os_api_dlopen_t dlopen_; - ecs_os_api_dlproc_t dlproc_; - ecs_os_api_dlclose_t dlclose_; + ecs_os_api_dlopen_t dlopen_; /**< dlopen callback. */ + ecs_os_api_dlproc_t dlproc_; /**< dlproc callback. */ + ecs_os_api_dlclose_t dlclose_; /**< dlclose callback. */ /* Overridable function that translates from a logical module id to a * shared library filename */ - ecs_os_api_module_to_path_t module_to_dl_; + ecs_os_api_module_to_path_t module_to_dl_; /**< module_to_dl callback. */ /* Overridable function that translates from a logical module id to a * path that contains module-specif resources or assets */ - ecs_os_api_module_to_path_t module_to_etc_; + ecs_os_api_module_to_path_t module_to_etc_; /**< module_to_etc callback. */ - /* Trace level */ - int32_t log_level_; + /* Performance tracing */ + ecs_os_api_perf_trace_t perf_trace_push_; - /* Trace indentation */ - int32_t log_indent_; + /* Performance tracing */ + ecs_os_api_perf_trace_t perf_trace_pop_; - /* Last error code */ - int32_t log_last_error_; + int32_t log_level_; /**< Tracing level. */ + int32_t log_indent_; /**< Tracing indentation level. */ + int32_t log_last_error_; /**< Last logged error code. */ + int64_t log_last_timestamp_; /**< Last logged timestamp. */ - /* Enable tracing with color */ - bool log_with_color_; + ecs_flags32_t flags_; /**< OS API flags */ + + void *log_out_; /**< File used for logging output (type is FILE*) + * (hint, log_ decides where to write) */ } ecs_os_api_t; +/** Static OS API variable with configured callbacks. */ FLECS_API extern ecs_os_api_t ecs_os_api; +/** Initialize OS API. + * This operation is not usually called by an application. To override callbacks + * of the OS API, use the following pattern: + * + * @code + * ecs_os_set_api_defaults(); + * ecs_os_api_t os_api = ecs_os_get_api(); + * os_api.abort_ = my_abort; + * ecs_os_set_api(&os_api); + * @endcode + */ FLECS_API void ecs_os_init(void); +/** Deinitialize OS API. + * This operation is not usually called by an application. + */ FLECS_API void ecs_os_fini(void); +/** Override OS API. + * This overrides the OS API struct with new values for callbacks. See + * ecs_os_init() on how to use the function. + * + * @param os_api Pointer to struct with values to set. + */ FLECS_API void ecs_os_set_api( ecs_os_api_t *os_api); +/** Get OS API. + * + * @return A value with the current OS API callbacks + * @see ecs_os_init() + */ +FLECS_API +ecs_os_api_t ecs_os_get_api(void); + +/** Set default values for OS API. + * This initializes the OS API struct with default values for callbacks like + * malloc and free. + * + * @see ecs_os_init() + */ FLECS_API void ecs_os_set_api_defaults(void); +/** Macro utilities + * \cond + */ + /* Memory management */ #ifndef ecs_os_malloc #define ecs_os_malloc(size) ecs_os_api.malloc_(size) @@ -1794,7 +2940,7 @@ void ecs_os_set_api_defaults(void); #define ecs_os_calloc_t(T) ECS_CAST(T*, ecs_os_calloc(ECS_SIZEOF(T))) #define ecs_os_calloc_n(T, count) ECS_CAST(T*, ecs_os_calloc(ECS_SIZEOF(T) * (count))) -#define ecs_os_realloc_t(ptr, T) ECS_CAST(T*, ecs_os_realloc([ptr, ECS_SIZEOF(T))) +#define ecs_os_realloc_t(ptr, T) ECS_CAST(T*, ecs_os_realloc(ptr, ECS_SIZEOF(T))) #define ecs_os_realloc_n(ptr, T, count) ECS_CAST(T*, ecs_os_realloc(ptr, ECS_SIZEOF(T) * (count))) #define ecs_os_alloca_t(T) ECS_CAST(T*, ecs_os_alloca(ECS_SIZEOF(T))) #define ecs_os_alloca_n(T, count) ECS_CAST(T*, ecs_os_alloca(ECS_SIZEOF(T) * (count))) @@ -1804,8 +2950,6 @@ void ecs_os_set_api_defaults(void); #define ecs_os_strdup(str) ecs_os_api.strdup_(str) #endif -#define ecs_os_strset(dst, src) ecs_os_free(*dst); *dst = ecs_os_strdup(src) - #ifdef __cplusplus #define ecs_os_strlen(str) static_cast(strlen(str)) #define ecs_os_strncmp(str1, str2, num) strncmp(str1, str2, static_cast(num)) @@ -1823,12 +2967,16 @@ void ecs_os_set_api_defaults(void); #endif #define ecs_os_memcpy_t(ptr1, ptr2, T) ecs_os_memcpy(ptr1, ptr2, ECS_SIZEOF(T)) -#define ecs_os_memcpy_n(ptr1, ptr2, T, count) ecs_os_memcpy(ptr1, ptr2, ECS_SIZEOF(T) * count) +#define ecs_os_memcpy_n(ptr1, ptr2, T, count) ecs_os_memcpy(ptr1, ptr2, ECS_SIZEOF(T) * (size_t)count) #define ecs_os_memcmp_t(ptr1, ptr2, T) ecs_os_memcmp(ptr1, ptr2, ECS_SIZEOF(T)) +#define ecs_os_memmove_t(ptr1, ptr2, T) ecs_os_memmove(ptr1, ptr2, ECS_SIZEOF(T)) +#define ecs_os_memmove_n(ptr1, ptr2, T, count) ecs_os_memmove(ptr1, ptr2, ECS_SIZEOF(T) * (size_t)count) +#define ecs_os_memmove_t(ptr1, ptr2, T) ecs_os_memmove(ptr1, ptr2, ECS_SIZEOF(T)) + #define ecs_os_strcmp(str1, str2) strcmp(str1, str2) #define ecs_os_memset_t(ptr, value, T) ecs_os_memset(ptr, value, ECS_SIZEOF(T)) -#define ecs_os_memset_n(ptr, value, T, count) ecs_os_memset(ptr, value, ECS_SIZEOF(T) * count) +#define ecs_os_memset_n(ptr, value, T, count) ecs_os_memset(ptr, value, ECS_SIZEOF(T) * (size_t)count) #define ecs_os_zeromem(ptr) ecs_os_memset(ptr, 0, ECS_SIZEOF(*ptr)) #define ecs_os_memdup_t(ptr, T) ecs_os_memdup(ptr, ECS_SIZEOF(T)) @@ -1837,30 +2985,22 @@ void ecs_os_set_api_defaults(void); #define ecs_offset(ptr, T, index)\ ECS_CAST(T*, ECS_OFFSET(ptr, ECS_SIZEOF(T) * index)) -#if defined(ECS_TARGET_MSVC) +#if !defined(ECS_TARGET_POSIX) && !defined(ECS_TARGET_MINGW) #define ecs_os_strcat(str1, str2) strcat_s(str1, INT_MAX, str2) -#define ecs_os_sprintf(ptr, ...) sprintf_s(ptr, INT_MAX, __VA_ARGS__) -#define ecs_os_vsprintf(ptr, fmt, args) vsprintf_s(ptr, INT_MAX, fmt, args) +#define ecs_os_snprintf(ptr, len, ...) sprintf_s(ptr, ECS_CAST(size_t, len), __VA_ARGS__) +#define ecs_os_vsnprintf(ptr, len, fmt, args) vsnprintf(ptr, ECS_CAST(size_t, len), fmt, args) #define ecs_os_strcpy(str1, str2) strcpy_s(str1, INT_MAX, str2) -#ifdef __cplusplus -#define ecs_os_strncpy(str1, str2, num) strncpy_s(str1, INT_MAX, str2, static_cast(num)) -#else -#define ecs_os_strncpy(str1, str2, num) strncpy_s(str1, INT_MAX, str2, (size_t)(num)) -#endif +#define ecs_os_strncpy(str1, str2, len) strncpy_s(str1, INT_MAX, str2, ECS_CAST(size_t, len)) #else #define ecs_os_strcat(str1, str2) strcat(str1, str2) -#define ecs_os_sprintf(ptr, ...) sprintf(ptr, __VA_ARGS__) -#define ecs_os_vsprintf(ptr, fmt, args) vsprintf(ptr, fmt, args) +#define ecs_os_snprintf(ptr, len, ...) snprintf(ptr, ECS_CAST(size_t, len), __VA_ARGS__) +#define ecs_os_vsnprintf(ptr, len, fmt, args) vsnprintf(ptr, ECS_CAST(size_t, len), fmt, args) #define ecs_os_strcpy(str1, str2) strcpy(str1, str2) -#ifdef __cplusplus -#define ecs_os_strncpy(str1, str2, num) strncpy(str1, str2, static_cast(num)) -#else -#define ecs_os_strncpy(str1, str2, num) strncpy(str1, str2, (size_t)(num)) -#endif +#define ecs_os_strncpy(str1, str2, len) strncpy(str1, str2, ECS_CAST(size_t, len)) #endif /* Files */ -#if defined(ECS_TARGET_MSVC) +#ifndef ECS_TARGET_POSIX #define ecs_os_fopen(result, file, mode) fopen_s(result, file, mode) #else #define ecs_os_fopen(result, file, mode) (*(result)) = fopen(file, mode) @@ -1869,10 +3009,17 @@ void ecs_os_set_api_defaults(void); /* Threads */ #define ecs_os_thread_new(callback, param) ecs_os_api.thread_new_(callback, param) #define ecs_os_thread_join(thread) ecs_os_api.thread_join_(thread) +#define ecs_os_thread_self() ecs_os_api.thread_self_() + +/* Tasks */ +#define ecs_os_task_new(callback, param) ecs_os_api.task_new_(callback, param) +#define ecs_os_task_join(thread) ecs_os_api.task_join_(thread) /* Atomic increment / decrement */ #define ecs_os_ainc(value) ecs_os_api.ainc_(value) #define ecs_os_adec(value) ecs_os_api.adec_(value) +#define ecs_os_lainc(value) ecs_os_api.lainc_(value) +#define ecs_os_ladec(value) ecs_os_api.ladec_(value) /* Mutex */ #define ecs_os_mutex_new() ecs_os_api.mutex_new_() @@ -1892,64 +3039,210 @@ void ecs_os_set_api_defaults(void); #define ecs_os_now() ecs_os_api.now_() #define ecs_os_get_time(time_out) ecs_os_api.get_time_(time_out) -FLECS_API -void ecs_os_enable_high_timer_resolution(bool enable); +#ifndef FLECS_DISABLE_COUNTERS +#ifdef FLECS_ACCURATE_COUNTERS +#define ecs_os_inc(v) (ecs_os_ainc(v)) +#define ecs_os_linc(v) (ecs_os_lainc(v)) +#define ecs_os_dec(v) (ecs_os_adec(v)) +#define ecs_os_ldec(v) (ecs_os_ladec(v)) +#else +#define ecs_os_inc(v) (++(*v)) +#define ecs_os_linc(v) (++(*v)) +#define ecs_os_dec(v) (--(*v)) +#define ecs_os_ldec(v) (--(*v)) +#endif +#else +#define ecs_os_inc(v) +#define ecs_os_linc(v) +#define ecs_os_dec(v) +#define ecs_os_ldec(v) +#endif + + +#ifdef ECS_TARGET_MINGW +/* mingw bug: without this a conversion error is thrown, but isnan/isinf should + * accept float, double and long double. */ +#define ecs_os_isnan(val) (isnan((float)val)) +#define ecs_os_isinf(val) (isinf((float)val)) +#else +#define ecs_os_isnan(val) (isnan(val)) +#define ecs_os_isinf(val) (isinf(val)) +#endif + +/* Application termination */ +#define ecs_os_abort() ecs_os_api.abort_() + +/* Dynamic libraries */ +#define ecs_os_dlopen(libname) ecs_os_api.dlopen_(libname) +#define ecs_os_dlproc(lib, procname) ecs_os_api.dlproc_(lib, procname) +#define ecs_os_dlclose(lib) ecs_os_api.dlclose_(lib) + +/* Module id translation */ +#define ecs_os_module_to_dl(lib) ecs_os_api.module_to_dl_(lib) +#define ecs_os_module_to_etc(lib) ecs_os_api.module_to_etc_(lib) + +/** Macro utilities + * \endcond + */ + /* Logging */ + +/** Log at debug level. + * + * @param file The file to log. + * @param line The line to log. + * @param msg The message to log. +*/ FLECS_API -void ecs_os_dbg(const char *file, int32_t line, const char *msg); +void ecs_os_dbg( + const char *file, + int32_t line, + const char *msg); +/** Log at trace level. + * + * @param file The file to log. + * @param line The line to log. + * @param msg The message to log. +*/ FLECS_API -void ecs_os_trace(const char *file, int32_t line, const char *msg); +void ecs_os_trace( + const char *file, + int32_t line, + const char *msg); +/** Log at warning level. + * + * @param file The file to log. + * @param line The line to log. + * @param msg The message to log. +*/ FLECS_API -void ecs_os_warn(const char *file, int32_t line, const char *msg); +void ecs_os_warn( + const char *file, + int32_t line, + const char *msg); +/** Log at error level. + * + * @param file The file to log. + * @param line The line to log. + * @param msg The message to log. +*/ FLECS_API -void ecs_os_err(const char *file, int32_t line, const char *msg); +void ecs_os_err( + const char *file, + int32_t line, + const char *msg); +/** Log at fatal level. + * + * @param file The file to log. + * @param line The line to log. + * @param msg The message to log. +*/ FLECS_API -void ecs_os_fatal(const char *file, int32_t line, const char *msg); +void ecs_os_fatal( + const char *file, + int32_t line, + const char *msg); +/** Convert errno to string. + * + * @param err The error number. + * @return A string describing the error. + */ FLECS_API -const char* ecs_os_strerror(int err); +const char* ecs_os_strerror( + int err); -/* Application termination */ -#define ecs_os_abort() ecs_os_api.abort_() +/** Utility for assigning strings. + * This operation frees an existing string and duplicates the input string. + * + * @param str Pointer to a string value. + * @param value The string value to assign. + */ +FLECS_API +void ecs_os_strset( + char **str, + const char *value); -/* Dynamic libraries */ -#define ecs_os_dlopen(libname) ecs_os_api.dlopen_(libname) -#define ecs_os_dlproc(lib, procname) ecs_os_api.dlproc_(lib, procname) -#define ecs_os_dlclose(lib) ecs_os_api.dlclose_(lib) +/* Profile tracing */ +#ifdef FLECS_PERF_TRACE +#define ecs_os_perf_trace_push(name) ecs_os_perf_trace_push_(__FILE__, __LINE__, name) +#define ecs_os_perf_trace_pop(name) ecs_os_perf_trace_pop_(__FILE__, __LINE__, name) +#else +#define ecs_os_perf_trace_push(name) +#define ecs_os_perf_trace_pop(name) +#endif -/* Module id translation */ -#define ecs_os_module_to_dl(lib) ecs_os_api.module_to_dl_(lib) -#define ecs_os_module_to_etc(lib) ecs_os_api.module_to_etc_(lib) +void ecs_os_perf_trace_push_( + const char *file, + size_t line, + const char *name); + +void ecs_os_perf_trace_pop_( + const char *file, + size_t line, + const char *name); -/* Sleep with floating point time */ +/** Sleep with floating point time. + * + * @param t The time in seconds. + */ FLECS_API void ecs_sleepf( double t); -/* Measure time since provided timestamp */ +/** Measure time since provided timestamp. + * Use with a time value initialized to 0 to obtain the number of seconds since + * the epoch. The operation will write the current timestamp in start. + * + * Usage: + * @code + * ecs_time_t t = {}; + * ecs_time_measure(&t); + * // code + * double elapsed = ecs_time_measure(&t); + * @endcode + * + * @param start The starting timestamp. + * @return The time elapsed since start. + */ FLECS_API double ecs_time_measure( ecs_time_t *start); -/* Calculate difference between two timestamps */ +/** Calculate difference between two timestamps. + * + * @param t1 The first timestamp. + * @param t2 The first timestamp. + * @return The difference between timestamps. + */ FLECS_API ecs_time_t ecs_time_sub( ecs_time_t t1, ecs_time_t t2); -/* Convert time value to a double */ +/** Convert time value to a double. + * + * @param t The timestamp. + * @return The timestamp converted to a double. + */ FLECS_API double ecs_time_to_double( ecs_time_t t); +/** Return newly allocated memory that contains a copy of src. + * + * @param src The source pointer. + * @param size The number of bytes to copy. + * @return The duplicated memory. + */ FLECS_API void* ecs_os_memdup( - const void *src, + const void *src, ecs_size_t size); /** Are heap functions available? */ @@ -1960,6 +3253,10 @@ bool ecs_os_has_heap(void); FLECS_API bool ecs_os_has_threading(void); +/** Are task functions available? */ +FLECS_API +bool ecs_os_has_task_support(void); + /** Are time functions available? */ FLECS_API bool ecs_os_has_time(void); @@ -1980,6 +3277,8 @@ bool ecs_os_has_modules(void); } #endif +/** @} */ + #endif @@ -1988,190 +3287,100 @@ extern "C" { #endif /** - * @defgroup api_types Basic API types + * @defgroup api_types API types + * Public API types. + * * @{ */ -/** A poly object. - * A poly (short for polymorph) object is an object that has a variable list of - * capabilities, determined by a mixin table. This is the current list of types - * in the flecs API that can be used as an ecs_poly_t: - * - * - ecs_world_t - * - ecs_stage_t - * - ecs_query_t - * - ecs_filter_t - * - ecs_rule_t - * - (more to come) - * - * Functions that accept an ecs_poly_t argument can accept objects of these - * types. If the object does not have the requested mixin the API will throw an - * assert. - * - * The poly/mixin framework enables partially overlapping features to be - * implemented once, and enables objects of different types to interact with - * each other depending on what mixins they have, rather than their type - * (in some ways it's like a mini-ECS). Additionally, each poly object has a - * header that enables the API to do sanity checking on the input arguments. - */ -typedef void ecs_poly_t; - -/** An id. Ids are the things that can be added to an entity. An id can be an - * entity or pair, and can have an optional role. */ -typedef uint64_t ecs_id_t; - -/** An entity identifier. */ -typedef ecs_id_t ecs_entity_t; - -/** A vector containing component identifiers used to describe a type. */ -typedef const ecs_vector_t* ecs_type_t; -/** A world is the container for all ECS data and supporting features. */ -typedef struct ecs_world_t ecs_world_t; +/** + * @defgroup function_types Function types. + * Function callback types. + * + * @{ + */ -/** A table is where entities and components are stored */ -typedef struct ecs_table_t ecs_table_t; - -/** A term is a single element in a query */ -typedef struct ecs_term_t ecs_term_t; - -/** A query allows for cached iteration over ECS data */ -typedef struct ecs_query_t ecs_query_t; - -/** A filter allows for uncached, ad hoc iteration over ECS data */ -typedef struct ecs_filter_t ecs_filter_t; - -/** A rule implements a non-trivial filter */ -typedef struct ecs_rule_t ecs_rule_t; - -/** A trigger reacts to events matching a single filter term */ -typedef struct ecs_trigger_t ecs_trigger_t; - -/** An observer reacts to events matching multiple filter terms */ -typedef struct ecs_observer_t ecs_observer_t; - -/** An observable contains lists of triggers for specific events/components */ -typedef struct ecs_observable_t ecs_observable_t; - -/* An iterator lets an application iterate entities across tables. */ -typedef struct ecs_iter_t ecs_iter_t; - -/** Refs cache data that lets them access components faster than ecs_get. */ -typedef struct ecs_ref_t ecs_ref_t; - -/** Type information */ -typedef struct ecs_type_info_t ecs_type_info_t; - -/* Mixins */ -typedef struct ecs_mixins_t ecs_mixins_t; - -/** @} */ - - -/** - * @defgroup constants API constants - * @{ - */ - -/* Maximum number of components to add/remove in a single operation */ -#define ECS_ID_CACHE_SIZE (32) - -/* Maximum number of terms cached in static arrays */ -#define ECS_TERM_CACHE_SIZE (4) - -/* Maximum number of terms in desc (larger, as these are temp objects) */ -#define ECS_TERM_DESC_CACHE_SIZE (16) - -/* Maximum number of events to set in static array of trigger descriptor */ -#define ECS_TRIGGER_DESC_EVENT_COUNT_MAX (8) - -/* Maximum number of query variables per query */ -#define ECS_VARIABLE_COUNT_MAX (64) - -/* Number of query variables in iterator cache */ -#define ECS_VARIABLE_CACHE_SIZE (4) - -/** @} */ - - -/** - * @defgroup function_types Function Prototypes - * @{ - */ - -/** Function prototype for runnables (systems, observers). - * The run callback overrides the default behavior for iterating through the - * results of a runnable object. - * - * The default runnable iterates the iterator, and calls an iter_action (see - * below) for each returned result. - * - * @param it The iterator to be iterated by the runnable. - */ -typedef void (*ecs_run_action_t)( - ecs_iter_t *it); +/** Function prototype for runnables (systems, observers). + * The run callback overrides the default behavior for iterating through the + * results of a runnable object. + * + * The default runnable iterates the iterator, and calls an iter_action (see + * below) for each returned result. + * + * @param it The iterator to be iterated by the runnable. + */ +typedef void (*ecs_run_action_t)( + ecs_iter_t *it); /** Function prototype for iterables. * A system may invoke a callback multiple times, typically once for each * matched table. - * + * * @param it The iterator containing the data for the current match. */ typedef void (*ecs_iter_action_t)( ecs_iter_t *it); -/** Function prototype for creating an iterator from a poly. - * Used to create iterators from poly objects with the iterable mixin. When a - * filter is provided, an array of two iterators must be passed to the function. - * This allows the mixin implementation to create a chained iterator when - * necessary, which requires two iterator objects. - * - * @param world The world or stage for which to create the iterator. - * @param iterable An iterable poly object. - * @param it The iterator to create (out parameter) - * @param filter Optional term to filter results. - */ -typedef void (*ecs_iter_init_action_t)( - const ecs_world_t *world, - const ecs_poly_t *iterable, - ecs_iter_t *it, - ecs_term_t *filter); - /** Function prototype for iterating an iterator. - * Stored inside initialized iterators. This allows an application to * iterate + * Stored inside initialized iterators. This allows an application to iterate * an iterator without needing to know what created it. - * + * * @param it The iterator to iterate. * @return True if iterator has no more results, false if it does. */ typedef bool (*ecs_iter_next_action_t)( - ecs_iter_t *it); + ecs_iter_t *it); /** Function prototype for freeing an iterator. * Free iterator resources. - * + * * @param it The iterator to free. */ typedef void (*ecs_iter_fini_action_t)( - ecs_iter_t *it); + ecs_iter_t *it); -/** Callback used for sorting components */ +/** Callback used for comparing components */ typedef int (*ecs_order_by_action_t)( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2); -/** Callback used for ranking types */ +/** Callback used for sorting the entire table of components */ +typedef void (*ecs_sort_table_action_t)( + ecs_world_t* world, + ecs_table_t* table, + ecs_entity_t* entities, + void* ptr, + int32_t size, + int32_t lo, + int32_t hi, + ecs_order_by_action_t order_by); + +/** Callback used for grouping tables in a query */ typedef uint64_t (*ecs_group_by_action_t)( ecs_world_t *world, ecs_table_t *table, - ecs_id_t id, + ecs_id_t group_id, void *ctx); +/** Callback invoked when a query creates a new group. */ +typedef void* (*ecs_group_create_action_t)( + ecs_world_t *world, + uint64_t group_id, + void *group_by_ctx); /* from ecs_query_desc_t */ + +/** Callback invoked when a query deletes an existing group. */ +typedef void (*ecs_group_delete_action_t)( + ecs_world_t *world, + uint64_t group_id, + void *group_ctx, /* return value from ecs_group_create_action_t */ + void *group_by_ctx); /* from ecs_query_desc_t */ + /** Initialization action for modules */ typedef void (*ecs_module_action_t)( - ecs_world_t *world); + ecs_world_t *world); /** Action callback on world exit */ typedef void (*ecs_fini_action_t)( @@ -2189,7 +3398,7 @@ typedef int (*ecs_compare_action_t)( /** Callback used for hashing values */ typedef uint64_t (*ecs_hash_value_action_t)( - const void *ptr); + const void *ptr); /** Constructor/destructor callback */ typedef void (*ecs_xtor_t)( @@ -2211,204 +3420,351 @@ typedef void (*ecs_move_t)( int32_t count, const ecs_type_info_t *type_info); -/** @} */ - -/** - * @defgroup mixin Public mixin types. - * @{ - */ +/** Compare hook to compare component instances */ +typedef int (*ecs_cmp_t)( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info); -/** Header for ecs_poly_t objects. */ -typedef struct ecs_header_t { - int32_t magic; /* Magic number verifying it's a flecs object */ - int32_t type; /* Magic number indicating which type of flecs object */ - ecs_mixins_t *mixins; /* Table with offsets to (optional) mixins */ -} ecs_header_t; +/** Equals operator hook */ +typedef bool (*ecs_equals_t)( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info); -/** Iterable mixin. - * Allows its container to be iterated. */ -typedef struct ecs_iterable_t { - ecs_iter_init_action_t init; /* Callback that creates iterator. */ -} ecs_iterable_t; +/** Destructor function for poly objects. */ +typedef void (*flecs_poly_dtor_t)( + ecs_poly_t *poly); /** @} */ /** - * @defgroup filter_types Types used to describe filters, terms and triggers + * @defgroup query_types Query descriptor types. + * Types used to describe queries. + * * @{ */ -/** Set flags describe if & how a matched entity should be substituted */ -#define EcsDefaultSet (0) /* Default set, SuperSet|Self for This subject */ -#define EcsSelf (1) /* Select self (inclusive) */ -#define EcsSuperSet (2) /* Select superset until predicate match */ -#define EcsSubSet (4) /* Select subset until predicate match */ -#define EcsCascade (8) /* Use breadth-first ordering of relations */ -#define EcsAll (16) /* Walk full super/subset, regardless of match */ -#define EcsParent (32) /* Shortcut for SuperSet(ChildOf) */ -#define EcsNothing (64) /* Select from nothing */ - /** Specify read/write access for term */ typedef enum ecs_inout_kind_t { - EcsInOutDefault, /* InOut for regular terms, In for shared terms */ - EcsInOutFilter, /* Term is neither read nor written */ - EcsInOut, /* Term is both read and written */ - EcsIn, /* Term is only read */ - EcsOut, /* Term is only written */ + EcsInOutDefault, /**< InOut for regular terms, In for shared terms */ + EcsInOutNone, /**< Term is neither read nor written */ + EcsInOutFilter, /**< Same as InOutNone + prevents term from triggering observers */ + EcsInOut, /**< Term is both read and written */ + EcsIn, /**< Term is only read */ + EcsOut, /**< Term is only written */ } ecs_inout_kind_t; -/** Specifies whether term identifier is a variable or entity */ -typedef enum ecs_var_kind_t { - EcsVarDefault, /* Variable if name is all caps, otherwise an entity */ - EcsVarIsEntity, /* Term is an entity */ - EcsVarIsVariable /* Term is a variable */ -} ecs_var_kind_t; - -/** Type describing an operator used in an signature of a system signature */ +/** Specify operator for term */ typedef enum ecs_oper_kind_t { - EcsAnd, /* The term must match */ - EcsOr, /* One of the terms in an or chain must match */ - EcsNot, /* The term must not match */ - EcsOptional, /* The term may match */ - EcsAndFrom, /* Term must match all components from term id */ - EcsOrFrom, /* Term must match at least one component from term id */ - EcsNotFrom /* Term must match none of the components from term id */ + EcsAnd, /**< The term must match */ + EcsOr, /**< One of the terms in an or chain must match */ + EcsNot, /**< The term must not match */ + EcsOptional, /**< The term may match */ + EcsAndFrom, /**< Term must match all components from term id */ + EcsOrFrom, /**< Term must match at least one component from term id */ + EcsNotFrom, /**< Term must match none of the components from term id */ } ecs_oper_kind_t; -/** Substitution with set parameters. - * These parameters allow for substituting a term id with its super- or subsets - * for a specified relationship. This enables functionality such as selecting - * components from a base (IsA) or a parent (ChildOf) in a single term */ -typedef struct ecs_term_set_t { - ecs_entity_t relation; /* Relationship to substitute (default = IsA) */ - uint8_t mask; /* Substitute as self, subset, superset */ - int32_t min_depth; /* Min depth of subset/superset substitution */ - int32_t max_depth; /* Max depth of subset/superset substitution */ -} ecs_term_set_t; - -/** Type that describes a single identifier in a term */ -typedef struct ecs_term_id_t { - ecs_entity_t entity; /* Entity (default = This) */ - char *name; /* Name (default = ".") */ - ecs_var_kind_t var; /* Is id a variable (default yes if name is - * all caps & entity is 0) */ - ecs_term_set_t set; /* Set substitution parameters */ -} ecs_term_id_t; - -/** Type that describes a single column in the system signature */ +/** Specify cache policy for query */ +typedef enum ecs_query_cache_kind_t { + EcsQueryCacheDefault, /**< Behavior determined by query creation context */ + EcsQueryCacheAuto, /**< Cache query terms that are cacheable */ + EcsQueryCacheAll, /**< Require that all query terms can be cached */ + EcsQueryCacheNone, /**< No caching */ +} ecs_query_cache_kind_t; + +/* Term id flags */ + +/** Match on self. + * Can be combined with other term flags on the ecs_term_t::flags_ field. + * \ingroup queries + */ +#define EcsSelf (1llu << 63) + +/** Match by traversing upwards. + * Can be combined with other term flags on the ecs_term_ref_t::id field. + * \ingroup queries + */ +#define EcsUp (1llu << 62) + +/** Traverse relationship transitively. + * Can be combined with other term flags on the ecs_term_ref_t::id field. + * \ingroup queries + */ +#define EcsTrav (1llu << 61) + +/** Sort results breadth first. + * Can be combined with other term flags on the ecs_term_ref_t::id field. + * \ingroup queries + */ +#define EcsCascade (1llu << 60) + +/** Iterate groups in descending order. + * Can be combined with other term flags on the ecs_term_ref_t::id field. + * \ingroup queries + */ +#define EcsDesc (1llu << 59) + +/** Term id is a variable. + * Can be combined with other term flags on the ecs_term_ref_t::id field. + * \ingroup queries + */ +#define EcsIsVariable (1llu << 58) + +/** Term id is an entity. + * Can be combined with other term flags on the ecs_term_ref_t::id field. + * \ingroup queries + */ +#define EcsIsEntity (1llu << 57) + +/** Term id is a name (don't attempt to lookup as entity). + * Can be combined with other term flags on the ecs_term_ref_t::id field. + * \ingroup queries + */ +#define EcsIsName (1llu << 56) + +/** All term traversal flags. + * Can be combined with other term flags on the ecs_term_ref_t::id field. + * \ingroup queries + */ +#define EcsTraverseFlags (EcsSelf|EcsUp|EcsTrav|EcsCascade|EcsDesc) + +/** All term reference kind flags. + * Can be combined with other term flags on the ecs_term_ref_t::id field. + * \ingroup queries + */ +#define EcsTermRefFlags (EcsTraverseFlags|EcsIsVariable|EcsIsEntity|EcsIsName) + +/** Type that describes a reference to an entity or variable in a term. */ +typedef struct ecs_term_ref_t { + ecs_entity_t id; /**< Entity id. If left to 0 and flags does not + * specify whether id is an entity or a variable + * the id will be initialized to #EcsThis. + * To explicitly set the id to 0, leave the id + * member to 0 and set #EcsIsEntity in flags. */ + + const char *name; /**< Name. This can be either the variable name + * (when the #EcsIsVariable flag is set) or an + * entity name. When ecs_term_t::move is true, + * the API assumes ownership over the string and + * will free it when the term is destroyed. */ +} ecs_term_ref_t; + +/** Type that describes a term (single element in a query). */ struct ecs_term_t { - ecs_id_t id; /* Can be used instead of pred, args and role to - * set component/pair id. If not set, it will be - * computed from predicate, object. If set, the - * subject cannot be set, or be set to This. */ - - ecs_inout_kind_t inout; /* Access to contents matched with term */ - ecs_term_id_t pred; /* Predicate of term */ - ecs_term_id_t subj; /* Subject of term */ - ecs_term_id_t obj; /* Object of term */ - ecs_oper_kind_t oper; /* Operator of term */ - ecs_id_t role; /* Role of term */ - char *name; /* Name of term */ - - int32_t index; /* Computed term index in filter which takes - * into account folded OR terms */ - - bool move; /* When true, this signals to ecs_term_copy that - * the resources held by this term may be moved - * into the destination term. */ -}; + ecs_id_t id; /**< Component id to be matched by term. Can be + * set directly, or will be populated from the + * first/second members, which provide more + * flexibility. */ -/** Filters alllow for ad-hoc quick filtering of entity tables. */ -struct ecs_filter_t { - ecs_header_t hdr; - - ecs_term_t *terms; /* Array containing terms for filter */ - int32_t term_count; /* Number of elements in terms array */ - int32_t term_count_actual; /* Processed count, which folds OR terms */ - - ecs_term_t term_cache[ECS_TERM_CACHE_SIZE]; /* Cache for small filters */ - bool term_cache_used; - - bool match_this; /* Has terms that match EcsThis */ - bool match_only_this; /* Has only terms that match EcsThis */ - bool match_prefab; /* Does filter match prefabs */ - bool match_disabled; /* Does filter match disabled entities */ - bool match_anything; /* False if filter has no/only Not terms */ - - bool filter; /* When true, data fields won't be populated */ - bool instanced; /* See ecs_filter_desc_t */ - bool match_empty_tables; /* See ecs_filter_desc_t */ - - char *name; /* Name of filter (optional) */ - char *variable_names[1]; /* Array with variable names */ + ecs_term_ref_t src; /**< Source of term */ + ecs_term_ref_t first; /**< Component or first element of pair */ + ecs_term_ref_t second; /**< Second element of pair */ - ecs_iterable_t iterable; /* Iterable mixin */ + ecs_entity_t trav; /**< Relationship to traverse when looking for the + * component. The relationship must have + * the `Traversable` property. Default is `IsA`. */ + + int16_t inout; /**< Access to contents matched by term */ + int16_t oper; /**< Operator of term */ + + int8_t field_index; /**< Index of field for term in iterator */ + ecs_flags16_t flags_; /**< Flags that help eval, set by ecs_query_init() */ }; -/** A trigger reacts to events matching a single term */ -struct ecs_trigger_t { - ecs_term_t term; /* Term describing the trigger condition id */ +/** Queries are lists of constraints (terms) that match entities. + * Created with ecs_query_init(). + */ +struct ecs_query_t { + ecs_header_t hdr; /**< Object header */ + + ecs_term_t *terms; /**< Query terms */ + int32_t *sizes; /**< Component sizes. Indexed by field */ + ecs_id_t *ids; /**< Component ids. Indexed by field */ + + uint64_t bloom_filter; /**< Bitmask used to quickly discard tables */ + ecs_flags32_t flags; /**< Query flags */ + int8_t var_count; /**< Number of query variables */ + int8_t term_count; /**< Number of query terms */ + int8_t field_count; /**< Number of fields returned by query */ + + /* Bitmasks for quick field information lookups */ + ecs_termset_t fixed_fields; /**< Fields with a fixed source */ + ecs_termset_t var_fields; /**< Fields with non-$this variable source */ + ecs_termset_t static_id_fields; /**< Fields with a static (component) id */ + ecs_termset_t data_fields; /**< Fields that have data */ + ecs_termset_t write_fields; /**< Fields that write data */ + ecs_termset_t read_fields; /**< Fields that read data */ + ecs_termset_t row_fields; /**< Fields that must be acquired with field_at */ + ecs_termset_t shared_readonly_fields; /**< Fields that don't write shared data */ + ecs_termset_t set_fields; /**< Fields that will be set */ + + ecs_query_cache_kind_t cache_kind; /**< Caching policy of query */ + + char **vars; /**< Array with variable names for iterator */ - /* Trigger events */ - ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; - int32_t event_count; + void *ctx; /**< User context to pass to callback */ + void *binding_ctx; /**< Context to be used for language bindings */ - ecs_iter_action_t callback; /* Callback */ + ecs_entity_t entity; /**< Entity associated with query (optional) */ + ecs_world_t *real_world; /**< Actual world. */ + ecs_world_t *world; /**< World or stage query was created with. */ - void *ctx; /* Callback context */ - void *binding_ctx; /* Binding context (for language bindings) */ + int32_t eval_count; /**< Number of times query is evaluated */ +}; - ecs_ctx_free_t ctx_free; /* Callback to free ctx */ - ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ +/** An observer reacts to events matching a query. + * Created with ecs_observer_init(). + */ +struct ecs_observer_t { + ecs_header_t hdr; /**< Object header */ - ecs_entity_t entity; /* Trigger entity */ - ecs_entity_t self; /* Entity associated with observer */ + ecs_query_t *query; /**< Observer query */ + + /** Observer events */ + ecs_entity_t events[FLECS_EVENT_DESC_MAX]; + int32_t event_count; /**< Number of events */ + + ecs_iter_action_t callback; /**< See ecs_observer_desc_t::callback */ + ecs_run_action_t run; /**< See ecs_observer_desc_t::run */ - ecs_observable_t *observable; /* Observable for trigger */ + void *ctx; /**< Observer context */ + void *callback_ctx; /**< Callback language binding context */ + void *run_ctx; /**< Run language binding context */ - bool match_prefab; /* Should trigger ignore prefabs */ - bool match_disabled; /* Should trigger ignore disabled entities */ - bool instanced; /* See ecs_filter_desc_t */ + ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ + ecs_ctx_free_t callback_ctx_free; /**< Callback to free callback_ctx */ + ecs_ctx_free_t run_ctx_free; /**< Callback to free run_ctx */ - uint64_t id; /* Internal id */ - int32_t *last_event_id; /* Optional pointer to observer last_event_id */ + ecs_observable_t *observable; /**< Observable for observer */ + + ecs_world_t *world; /**< The world */ + ecs_entity_t entity; /**< Entity associated with observer */ }; -/* An observer reacts to events matching a filter */ -struct ecs_observer_t { - ecs_filter_t filter; +/** @} */ + +/** Type that contains component lifecycle callbacks. + * + * @ingroup components + */ + +/* Flags that can be used to check which hooks a type has set */ +#define ECS_TYPE_HOOK_CTOR ECS_CAST(ecs_flags32_t, 1 << 0) +#define ECS_TYPE_HOOK_DTOR ECS_CAST(ecs_flags32_t, 1 << 1) +#define ECS_TYPE_HOOK_COPY ECS_CAST(ecs_flags32_t, 1 << 2) +#define ECS_TYPE_HOOK_MOVE ECS_CAST(ecs_flags32_t, 1 << 3) +#define ECS_TYPE_HOOK_COPY_CTOR ECS_CAST(ecs_flags32_t, 1 << 4) +#define ECS_TYPE_HOOK_MOVE_CTOR ECS_CAST(ecs_flags32_t, 1 << 5) +#define ECS_TYPE_HOOK_CTOR_MOVE_DTOR ECS_CAST(ecs_flags32_t, 1 << 6) +#define ECS_TYPE_HOOK_MOVE_DTOR ECS_CAST(ecs_flags32_t, 1 << 7) +#define ECS_TYPE_HOOK_CMP ECS_CAST(ecs_flags32_t, 1 << 8) +#define ECS_TYPE_HOOK_EQUALS ECS_CAST(ecs_flags32_t, 1 << 9) + + +/* Flags that can be used to set/check which hooks of a type are invalid */ +#define ECS_TYPE_HOOK_CTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 10) +#define ECS_TYPE_HOOK_DTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 12) +#define ECS_TYPE_HOOK_COPY_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 13) +#define ECS_TYPE_HOOK_MOVE_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 14) +#define ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 15) +#define ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 16) +#define ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 17) +#define ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 18) +#define ECS_TYPE_HOOK_CMP_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 19) +#define ECS_TYPE_HOOK_EQUALS_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 20) + + +/* All valid hook flags */ +#define ECS_TYPE_HOOKS (ECS_TYPE_HOOK_CTOR|ECS_TYPE_HOOK_DTOR|\ + ECS_TYPE_HOOK_COPY|ECS_TYPE_HOOK_MOVE|ECS_TYPE_HOOK_COPY_CTOR|\ + ECS_TYPE_HOOK_MOVE_CTOR|ECS_TYPE_HOOK_CTOR_MOVE_DTOR|\ + ECS_TYPE_HOOK_MOVE_DTOR|ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS) + +/* All invalid hook flags */ +#define ECS_TYPE_HOOKS_ILLEGAL (ECS_TYPE_HOOK_CTOR_ILLEGAL|\ + ECS_TYPE_HOOK_DTOR_ILLEGAL|ECS_TYPE_HOOK_COPY_ILLEGAL|\ + ECS_TYPE_HOOK_MOVE_ILLEGAL|ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL|\ + ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL|ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL|\ + ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL|ECS_TYPE_HOOK_CMP_ILLEGAL|\ + ECS_TYPE_HOOK_EQUALS_ILLEGAL) +struct ecs_type_hooks_t { + ecs_xtor_t ctor; /**< ctor */ + ecs_xtor_t dtor; /**< dtor */ + ecs_copy_t copy; /**< copy assignment */ + ecs_move_t move; /**< move assignment */ + + /** Ctor + copy */ + ecs_copy_t copy_ctor; - /* Triggers created by observer */ - ecs_vector_t *triggers; + /** Ctor + move */ + ecs_move_t move_ctor; - /* Observer events */ - ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; - int32_t event_count; - - ecs_iter_action_t callback; /* See ecs_observer_desc_t::callback */ - ecs_run_action_t run; /* See ecs_observer_desc_t::run */ + /** Ctor + move + dtor (or move_ctor + dtor). + * This combination is typically used when a component is moved from one + * location to a new location, like when it is moved to a new table. If + * not set explicitly it will be derived from other callbacks. */ + ecs_move_t ctor_move_dtor; + + /** Move + dtor. + * This combination is typically used when a component is moved from one + * location to an existing location, like what happens during a remove. If + * not set explicitly it will be derived from other callbacks. */ + ecs_move_t move_dtor; - void *ctx; /* Callback context */ - void *binding_ctx; /* Binding context (for language bindings) */ + /** Compare hook */ + ecs_cmp_t cmp; - ecs_ctx_free_t ctx_free; /* Callback to free ctx */ - ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ + /** Equals hook */ + ecs_equals_t equals; + + /** Hook flags. + * Indicates which hooks are set for the type, and which hooks are illegal. + * When an ILLEGAL flag is set when calling ecs_set_hooks() a hook callback + * will be set that panics when called. */ + ecs_flags32_t flags; - ecs_entity_t entity; /* Observer entity */ - ecs_entity_t self; /* Entity associated with observer */ + /** Callback that is invoked when an instance of a component is added. This + * callback is invoked before triggers are invoked. */ + ecs_iter_action_t on_add; + + /** Callback that is invoked when an instance of the component is set. This + * callback is invoked before triggers are invoked, and enable the component + * to respond to changes on itself before others can. */ + ecs_iter_action_t on_set; - ecs_observable_t *observable; /* Observable for observer */ + /** Callback that is invoked when an instance of the component is removed. + * This callback is invoked after the triggers are invoked, and before the + * destructor is invoked. */ + ecs_iter_action_t on_remove; - uint64_t id; /* Internal id */ - int32_t last_event_id; /* Last handled event id */ + /** Callback that is invoked with the existing and new value before the + * value is assigned. Invoked after on_add and before on_set. Registering + * an on_replace hook prevents using operations that return a mutable + * pointer to the component like get_mut, ensure and emplace. */ + ecs_iter_action_t on_replace; - bool is_monitor; /* If true, the observer only triggers when the - * filter did not match with the entity before - * the event happened. */ -}; + void *ctx; /**< User defined context */ + void *binding_ctx; /**< Language binding context */ + void *lifecycle_ctx; /**< Component lifecycle context (see meta add-on)*/ -/** @} */ + ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ + ecs_ctx_free_t lifecycle_ctx_free; /**< Callback to free lifecycle_ctx */ +}; +/** Type that contains component information (passed to ctors/dtors/...) + * + * @ingroup components + */ +struct ecs_type_info_t { + ecs_size_t size; /**< Size of type */ + ecs_size_t alignment; /**< Alignment of type */ + ecs_type_hooks_t hooks; /**< Type hooks */ + ecs_entity_t component; /**< Handle to component (do not set) */ + const char *name; /**< Type name. */ +}; /** * @file api_types.h @@ -2427,51 +3783,40 @@ struct ecs_observer_t { extern "C" { #endif - //////////////////////////////////////////////////////////////////////////////// //// Opaque types //////////////////////////////////////////////////////////////////////////////// -/** A stage enables modification while iterating and from multiple threads */ -typedef struct ecs_stage_t ecs_stage_t; - -/** A record stores data to map an entity id to a location in a table */ -typedef struct ecs_record_t ecs_record_t; - -/** Table column */ -typedef struct ecs_column_t ecs_column_t; - /** Table data */ typedef struct ecs_data_t ecs_data_t; -/* Sparse set */ -typedef struct ecs_sparse_t ecs_sparse_t; - -/* Switch list */ -typedef struct ecs_switch_t ecs_switch_t; - -/* Internal structure to lookup tables for a (component) id */ -typedef struct ecs_id_record_t ecs_id_record_t; - /* Cached query table data */ -typedef struct ecs_query_table_node_t ecs_query_table_node_t; +typedef struct ecs_query_cache_match_t ecs_query_cache_match_t; -/* Internal table storage record */ -struct ecs_table_record_t; +/* Cached query group */ +typedef struct ecs_query_cache_group_t ecs_query_cache_group_t; //////////////////////////////////////////////////////////////////////////////// //// Non-opaque types //////////////////////////////////////////////////////////////////////////////// -/** Mixin for emitting events to triggers/observers */ -struct ecs_observable_t { - ecs_sparse_t *events; /* sparse */ -}; +/** All observers for a specific event */ +typedef struct ecs_event_record_t { + struct ecs_event_id_record_t *any; + struct ecs_event_id_record_t *wildcard; + struct ecs_event_id_record_t *wildcard_pair; + ecs_map_t event_ids; /* map */ + ecs_entity_t event; +} ecs_event_record_t; -/** Record for entity index */ -struct ecs_record_t { - ecs_table_t *table; /* Identifies a type (and table) in world */ - uint32_t row; /* Table row of the entity */ +struct ecs_observable_t { + ecs_event_record_t on_add; + ecs_event_record_t on_remove; + ecs_event_record_t on_set; + ecs_event_record_t on_wildcard; + ecs_sparse_t events; /* sparse */ + ecs_vec_t global_observers; /* vector */ + uint64_t last_observer_id; }; /** Range in table */ @@ -2494,22 +3839,15 @@ typedef struct ecs_var_t { /** Cached reference. */ struct ecs_ref_t { - ecs_entity_t entity; /* Entity of the reference */ - ecs_entity_t component; /* Component of the reference */ - void *table; /* Last known table */ - uint32_t row; /* Last known location in table */ - int32_t alloc_count; /* Last known alloc count of table */ - ecs_record_t *record; /* Pointer to record, if in main stage */ - const void *ptr; /* Cached ptr */ + ecs_entity_t entity; /* Entity */ + ecs_entity_t id; /* Component id */ + uint64_t table_id; /* Table id for detecting ABA issues */ + uint32_t table_version_fast; /* Fast change detection w/false positives */ + uint16_t table_version; /* Change detection */ + ecs_record_t *record; /* Entity index record */ + void *ptr; /* Cached component pointer */ }; -/** Array of entity ids that, other than a type, can live on the stack */ -typedef struct ecs_ids_t { - ecs_id_t *array; /* An array with entity ids */ - int32_t count; /* The number of entities in the array */ - int32_t size; /* The size of the array */ -} ecs_ids_t; - /* Page-iterator specific data */ typedef struct ecs_page_iter_t { int32_t offset; @@ -2525,208 +3863,68 @@ typedef struct ecs_worker_iter_t { /* Convenience struct to iterate table array for id */ typedef struct ecs_table_cache_iter_t { - struct ecs_table_cache_hdr_t *cur, *next; + const struct ecs_table_cache_hdr_t *cur, *next; + bool iter_fill; + bool iter_empty; } ecs_table_cache_iter_t; -/** Term-iterator specific data */ -typedef struct ecs_term_iter_t { - ecs_term_t term; - ecs_id_record_t *self_index; - ecs_id_record_t *set_index; - - ecs_id_record_t *cur; +/** Each iterator */ +typedef struct ecs_each_iter_t { ecs_table_cache_iter_t it; - int32_t index; - - ecs_table_t *table; - int32_t cur_match; - int32_t match_count; - int32_t last_column; - bool empty_tables; + /* Storage for iterator fields */ + ecs_id_t ids; + ecs_entity_t sources; + ecs_size_t sizes; + int32_t columns; + const ecs_table_record_t* trs; +} ecs_each_iter_t; - /* Storage */ - ecs_id_t id; - int32_t column; - ecs_entity_t subject; - ecs_size_t size; - void *ptr; -} ecs_term_iter_t; - -typedef enum ecs_iter_kind_t { - EcsIterEvalTables, - EcsIterEvalChain, - EcsIterEvalCondition, - EcsIterEvalNone -} ecs_iter_kind_t; - -/** Filter-iterator specific data */ -typedef struct ecs_filter_iter_t { - ecs_filter_t filter; - ecs_iter_kind_t kind; - ecs_term_iter_t term_iter; - int32_t matches_left; - int32_t pivot_term; -} ecs_filter_iter_t; - -/** Query-iterator specific data */ +typedef struct ecs_query_op_profile_t { + int32_t count[2]; /* 0 = enter, 1 = redo */ +} ecs_query_op_profile_t; + +/** Query iterator */ typedef struct ecs_query_iter_t { - ecs_query_t *query; - ecs_query_table_node_t *node, *prev; - int32_t sparse_smallest; - int32_t sparse_first; - int32_t bitset_first; - int32_t skip_count; + struct ecs_var_t *vars; /* Variable storage */ + const struct ecs_query_var_t *query_vars; /* Query variable metadata */ + const struct ecs_query_op_t *ops; /* Query plan operations */ + struct ecs_query_op_ctx_t *op_ctx; /* Operation-specific state */ + uint64_t *written; + + /* Cached iteration */ + ecs_query_cache_group_t *group; /* Currently iterated group */ + ecs_vec_t *tables; /* Currently iterated table vector (vec) */ + ecs_vec_t *all_tables; /* Different from .tables if iterating wildcard matches (vec) */ + ecs_query_cache_match_t *elem; /* Current cache entry */ + int32_t cur, all_cur; /* Indices into tables & all_tables */ + + ecs_query_op_profile_t *profile; + + int16_t op; /* Currently iterated query plan operation (index into ops) */ + bool iter_single_group; } ecs_query_iter_t; -/** Snapshot-iterator specific data */ -typedef struct ecs_snapshot_iter_t { - ecs_filter_t filter; - ecs_vector_t *tables; /* ecs_table_leaf_t */ - int32_t index; -} ecs_snapshot_iter_t; - -/** Type used for iterating ecs_sparse_t */ -typedef struct ecs_sparse_iter_t { - ecs_sparse_t *sparse; - const uint64_t *ids; - ecs_size_t size; - int32_t i; - int32_t count; -} ecs_sparse_iter_t; - -/** Rule-iterator specific data */ -typedef struct ecs_rule_iter_t { - const ecs_rule_t *rule; - struct ecs_var_t *registers; /* Variable storage (tables, entities) */ - struct ecs_rule_op_ctx_t *op_ctx; /* Operation-specific state */ - - int32_t *columns; /* Column indices */ - - ecs_entity_t entity; /* Result in case of 1 entity */ - - bool redo; - int32_t op; - int32_t sp; -} ecs_rule_iter_t; - -/* Bits for tracking whether a cache was used/whether the array was allocated. - * Used by flecs_iter_init, flecs_iter_validate and ecs_iter_fini. - * Constants are named to enable easy macro substitution. */ -#define flecs_iter_cache_ids (1u << 0u) -#define flecs_iter_cache_columns (1u << 1u) -#define flecs_iter_cache_subjects (1u << 2u) -#define flecs_iter_cache_sizes (1u << 3u) -#define flecs_iter_cache_ptrs (1u << 4u) -#define flecs_iter_cache_match_indices (1u << 5u) -#define flecs_iter_cache_variables (1u << 6u) -#define flecs_iter_cache_all (255) - -/* Inline iterator arrays to prevent allocations for small array sizes */ -typedef struct ecs_iter_cache_t { - ecs_id_t ids[ECS_TERM_CACHE_SIZE]; - int32_t columns[ECS_TERM_CACHE_SIZE]; - ecs_entity_t subjects[ECS_TERM_CACHE_SIZE]; - ecs_size_t sizes[ECS_TERM_CACHE_SIZE]; - void *ptrs[ECS_TERM_CACHE_SIZE]; - int32_t match_indices[ECS_TERM_CACHE_SIZE]; - ecs_var_t variables[ECS_VARIABLE_CACHE_SIZE]; - - ecs_flags8_t used; /* For which fields is the cache used */ - ecs_flags8_t allocated; /* Which fields are allocated */ -} ecs_iter_cache_t; - /* Private iterator data. Used by iterator implementations to keep track of * progress & to provide builtin storage. */ typedef struct ecs_iter_private_t { union { - ecs_term_iter_t term; - ecs_filter_iter_t filter; ecs_query_iter_t query; - ecs_rule_iter_t rule; - ecs_snapshot_iter_t snapshot; ecs_page_iter_t page; ecs_worker_iter_t worker; + ecs_each_iter_t each; } iter; /* Iterator specific data */ - ecs_iter_cache_t cache; /* Inline arrays to reduce allocations */ + void *entity_iter; /* Query applied after matching a table */ + ecs_stack_cursor_t *stack_cursor; /* Stack cursor to restore to */ } ecs_iter_private_t; -/* Bits for iterator flags */ -#define EcsIterIsValid (1u << 0u) /* Does iterator contain valid result */ -#define EcsIterIsFilter (1u << 1u) /* Is iterator filter (metadata only) */ -#define EcsIterIsInstanced (1u << 2u) /* Is iterator instanced */ -#define EcsIterHasShared (1u << 3u) /* Does result have shared terms */ -#define EcsIterTableOnly (1u << 4u) /* Result only populates table */ -#define EcsIterEntityOptional (1u << 5u) /* Treat terms with entity subject as optional */ - -/** Iterator. - */ -struct ecs_iter_t { - /* World */ - ecs_world_t *world; /* The world */ - ecs_world_t *real_world; /* Actual world. This differs from world when in staged mode */ - - /* Matched data */ - ecs_entity_t *entities; /* Entity identifiers */ - void **ptrs; /* Pointers to components. Array if from this, pointer if not. */ - ecs_size_t *sizes; /* Component sizes */ - ecs_table_t *table; /* Current table */ - ecs_type_t type; /* Current type */ - ecs_table_t *other_table; /* Prev or next table when adding/removing */ - ecs_id_t *ids; /* (Component) ids */ - ecs_var_t *variables; /* Values of variables (if any) */ - int32_t *columns; /* Query term to table column mapping */ - ecs_entity_t *subjects; /* Subject (source) entities */ - int32_t *match_indices; /* Indices of current match for term. Allows an iterator to iterate - * all permutations of wildcards in query. */ - ecs_ref_t *references; /* Cached refs to components (if iterating a cache) */ - ecs_flags64_t constrained_vars; /* Bitset that marks constrained variables */ - - /* Source information */ - ecs_entity_t system; /* The system (if applicable) */ - ecs_entity_t event; /* The event (if applicable) */ - ecs_id_t event_id; /* The (component) id for the event */ - ecs_entity_t self; /* Self entity (if set for system) */ - - /* Query information */ - ecs_term_t *terms; /* Terms of query being evaluated */ - int32_t table_count; /* Active table count for query */ - int32_t term_count; /* Number of terms in query */ - int32_t term_index; /* Index of term that triggered an event. - * This field will be set to the 'index' field - * of a trigger/observer term. */ - int32_t variable_count; /* Number of variables for query */ - char **variable_names; /* Names of variables (if any) */ - - /* Context */ - void *param; /* Param passed to ecs_run */ - void *ctx; /* System context */ - void *binding_ctx; /* Binding context */ - - /* Time */ - FLECS_FLOAT delta_time; /* Time elapsed since last frame */ - FLECS_FLOAT delta_system_time;/* Time elapsed since last system invocation */ - - /* Iterator counters */ - int32_t frame_offset; /* Offset relative to start of iteration */ - int32_t offset; /* Offset relative to current table */ - int32_t count; /* Number of entities to iterate */ - int32_t instance_count; /* Number of entities to iterate before next table */ - - /* Iterator flags */ - ecs_flags32_t flags; - - ecs_entity_t interrupted_by; /* When set, system execution is interrupted */ - - ecs_iter_private_t priv; /* Private data */ - - /* Chained iterators */ - ecs_iter_next_action_t next; /* Function to progress iterator */ - ecs_iter_action_t callback; /* Callback of system, trigger, observer */ - ecs_iter_fini_action_t fini; /* Function to cleanup iterator resources */ - ecs_iter_t *chain_it; /* Optional, allows for creating iterator chains */ -}; +/* Data structures that store the command queue */ +typedef struct ecs_commands_t { + ecs_vec_t queue; + ecs_stack_t stack; /* Temp memory used by deferred commands */ + ecs_sparse_t entries; /* - command batching */ +} ecs_commands_t; #ifdef __cplusplus } @@ -2734,14 +3932,14 @@ struct ecs_iter_t { #endif - /** * @file api_support.h * @brief Support functions and constants. * * Supporting types and functions that need to be exposed either in support of - * the public API or for unit tests, but that may change between minor / patch - * releases. + * the public API or for unit tests. + * + * Operations may change without warning. */ #ifndef FLECS_API_SUPPORT_H @@ -2752,12 +3950,10 @@ struct ecs_iter_t { extern "C" { #endif -/** This reserves entity ids for components. Regular entity ids will start after - * this constant. This affects performance of table traversal, as edges with ids - * lower than this constant are looked up in an array, whereas constants higher - * than this id are looked up in a map. Increasing this value can improve - * performance at the cost of (significantly) higher memory usage. */ -#define ECS_HI_COMPONENT_ID (256) /* Maximum number of components */ +/** This is the largest possible component id. Components for the most part + * occupy the same id range as entities, however they are not allowed to overlap + * with (8) bits reserved for id flags. */ +#define ECS_MAX_COMPONENT_ID (~((uint32_t)(ECS_ID_FLAGS_MASK >> 32))) /** The maximum number of nested function calls before the core will throw a * cycle detected error */ @@ -2766,420 +3962,380 @@ extern "C" { /** Maximum length of a parser token (used by parser-related addons) */ #define ECS_MAX_TOKEN_SIZE (256) -//////////////////////////////////////////////////////////////////////////////// -//// Global type handles -//////////////////////////////////////////////////////////////////////////////// - -/** This allows passing 0 as type to functions that accept ids */ -#define FLECS__E0 0 - -//////////////////////////////////////////////////////////////////////////////// -//// Functions used in declarative (macro) API -//////////////////////////////////////////////////////////////////////////////// - +/** Convert a C module name into a path. + * This operation converts a PascalCase name to a path, for example MyFooModule + * into my.foo.module. + * + * @param c_name The C module name + * @return The path. +*/ FLECS_API -char* ecs_module_path_from_c( +char* flecs_module_path_from_c( const char *c_name); +/** Constructor that zeromem's a component value. + * + * @param ptr Pointer to the value. + * @param count Number of elements to construct. + * @param type_info Type info for the component. + */ FLECS_API -bool ecs_component_has_actions( - const ecs_world_t *world, - ecs_entity_t component); - -//////////////////////////////////////////////////////////////////////////////// -//// Signature API -//////////////////////////////////////////////////////////////////////////////// - -bool ecs_identifier_is_0( - const char *id); - -const char* ecs_identifier_is_var( - const char *id); - -//////////////////////////////////////////////////////////////////////////////// -//// Ctor that initializes component to 0 -//////////////////////////////////////////////////////////////////////////////// - -FLECS_API -void ecs_default_ctor( +void flecs_default_ctor( void *ptr, int32_t count, - const ecs_type_info_t *ctx); + const ecs_type_info_t *type_info); -/** Calculate offset from address */ -#ifdef __cplusplus -#define ECS_OFFSET(o, offset) reinterpret_cast((reinterpret_cast(o)) + (static_cast(offset))) -#else -#define ECS_OFFSET(o, offset) (void*)(((uintptr_t)(o)) + ((uintptr_t)(offset))) -#endif +/** Create allocated string from format. + * + * @param fmt The format string. + * @param args Format arguments. + * @return The formatted string. + */ +FLECS_DBG_API +char* flecs_vasprintf( + const char *fmt, + va_list args); -#define ECS_ELEM(ptr, size, index) ECS_OFFSET(ptr, (size) * (index)) +/** Create allocated string from format. + * + * @param fmt The format string. + * @return The formatted string. + */ +FLECS_API +char* flecs_asprintf( + const char *fmt, + ...); -/** Enable/disable bitsets */ -#define ECS_BIT_SET(flags, bit) (flags) |= (bit) -#define ECS_BIT_CLEAR(flags, bit) (flags) &= ~(bit) -#define ECS_BIT_COND(flags, bit, cond) ((cond) \ - ? (ECS_BIT_SET(flags, bit)) \ - : (ECS_BIT_CLEAR(flags, bit))) -#define ECS_BIT_IS_SET(flags, bit) ((flags) & (bit)) - -#ifdef __cplusplus -} -#endif - -#endif - -/** - * @file sparse.h - * @brief Sparse set datastructure. - * - * This is an implementation of a paged sparse set that stores the payload in - * the sparse array. +/** Write an escaped character. + * Write a character to an output string, insert escape character if necessary. * - * A sparse set has a dense and a sparse array. The sparse array is directly - * indexed by a 64 bit identifier. The sparse element is linked with a dense - * element, which allows for liveliness checking. The liveliness check itself - * can be performed by doing (psuedo code): - * dense[sparse[sparse_id].dense] == sparse_id + * @param out The string to write the character to. + * @param in The input character. + * @param delimiter The delimiter used (for example '"') + * @return Pointer to the character after the last one written. + */ +FLECS_API +char* flecs_chresc( + char *out, + char in, + char delimiter); + +/** Parse an escaped character. + * Parse a character with a potential escape sequence. * - * To ensure that the sparse array doesn't have to grow to a large size when - * using large sparse_id's, the sparse set uses paging. This cuts up the array - * into several pages of 4096 elements. When an element is set, the sparse set - * ensures that the corresponding page is created. The page associated with an - * id is determined by shifting a bit 12 bits to the right. + * @param in Pointer to character in input string. + * @param out Output string. + * @return Pointer to the character after the last one read. + */ +const char* flecs_chrparse( + const char *in, + char *out); + +/** Write an escaped string. + * Write an input string to an output string, escape characters where necessary. + * To determine the size of the output string, call the operation with a NULL + * argument for 'out', and use the returned size to allocate a string that is + * large enough. * - * The sparse set keeps track of a generation count per id, which is increased - * each time an id is deleted. The generation is encoded in the returned id. + * @param out Pointer to output string (must be). + * @param size Maximum number of characters written to output. + * @param delimiter The delimiter used (for example '"'). + * @param in The input string. + * @return The number of characters that (would) have been written. + */ +FLECS_API +ecs_size_t flecs_stresc( + char *out, + ecs_size_t size, + char delimiter, + const char *in); + +/** Return escaped string. + * Return escaped version of input string. Same as flecs_stresc(), but returns an + * allocated string of the right size. * - * This sparse set implementation stores payload in the sparse array, which is - * not typical. The reason for this is to guarantee that (in combination with - * paging) the returned payload pointers are stable. This allows for various - * optimizations in the parts of the framework that uses the sparse set. + * @param delimiter The delimiter used (for example '"'). + * @param in The input string. + * @return Escaped string. + */ +FLECS_API +char* flecs_astresc( + char delimiter, + const char *in); + +/** Skip whitespace and newline characters. + * This function skips whitespace characters. * - * The sparse set has been designed so that new ids can be generated in bulk, in - * an O(1) operation. The way this works is that once a dense-sparse pair is - * created, it is never unpaired. Instead it is moved to the end of the dense - * array, and the sparse set stores an additional count to keep track of the - * last alive id in the sparse set. To generate new ids in bulk, the sparse set - * only needs to increase this count by the number of requested ids. + * @param ptr Pointer to (potential) whitespaces to skip. + * @return Pointer to the next non-whitespace character. */ +FLECS_API +const char* flecs_parse_ws_eol( + const char *ptr); -#ifndef FLECS_SPARSE_H -#define FLECS_SPARSE_H +/** Parse digit. + * This function will parse until the first non-digit character is found. The + * provided expression must contain at least one digit character. + * + * @param ptr The expression to parse. + * @param token The output buffer. + * @return Pointer to the first non-digit character. + */ +FLECS_API +const char* flecs_parse_digit( + const char *ptr, + char *token); +/* Convert identifier to snake case */ +FLECS_API +char* flecs_to_snake_case( + const char *str); -#ifdef __cplusplus -extern "C" { -#endif +/* Suspend/resume readonly state. To fully support implicit registration of + * components, it should be possible to register components while the world is + * in readonly mode. It is not uncommon that a component is used first from + * within a system, which are often ran while in readonly mode. + * + * Suspending readonly mode is only allowed when the world is not multithreaded. + * When a world is multithreaded, it is not safe to (even temporarily) leave + * readonly mode, so a multithreaded application should always explicitly + * register components in advance. + * + * These operations also suspend deferred mode. + * + * Functions are public to support language bindings. + */ +typedef struct ecs_suspend_readonly_state_t { + bool is_readonly; + bool is_deferred; + bool cmd_flushing; + int32_t defer_count; + ecs_entity_t scope; + ecs_entity_t with; + ecs_commands_t cmd_stack[2]; + ecs_commands_t *cmd; + ecs_stage_t *stage; +} ecs_suspend_readonly_state_t; -struct ecs_sparse_t { - ecs_vector_t *dense; /* Dense array with indices to sparse array. The - * dense array stores both alive and not alive - * sparse indices. The 'count' member keeps - * track of which indices are alive. */ +FLECS_API +ecs_world_t* flecs_suspend_readonly( + const ecs_world_t *world, + ecs_suspend_readonly_state_t *state); - ecs_vector_t *chunks; /* Chunks with sparse arrays & data */ - ecs_size_t size; /* Element size */ - int32_t count; /* Number of alive entries */ - uint64_t max_id_local; /* Local max index (if no global is set) */ - uint64_t *max_id; /* Maximum issued sparse index */ -}; +FLECS_API +void flecs_resume_readonly( + ecs_world_t *world, + ecs_suspend_readonly_state_t *state); -/** Initialize sparse set */ +/** Number of observed entities in table. + * Operation is public to support test cases. + * + * @param table The table. + */ FLECS_DBG_API -void _flecs_sparse_init( - ecs_sparse_t *sparse, - ecs_size_t elem_size); - -#define flecs_sparse_init(sparse, T)\ - _flecs_sparse_init(sparse, ECS_SIZEOF(T)) +int32_t flecs_table_observed_count( + const ecs_table_t *table); -/** Create new sparse set */ +/** Print backtrace to specified stream. + * + * @param stream The stream to use for printing the backtrace. + */ FLECS_DBG_API -ecs_sparse_t* _flecs_sparse_new( - ecs_size_t elem_size); +void flecs_dump_backtrace( + void *stream); -#define flecs_sparse_new(T)\ - _flecs_sparse_new(ECS_SIZEOF(T)) +/** Increase refcount of poly object. + * + * @param poly The poly object. + * @return The refcount after incrementing. + */ +FLECS_API +int32_t flecs_poly_claim_( + ecs_poly_t *poly); -FLECS_DBG_API -void _flecs_sparse_fini( - ecs_sparse_t *sparse); +/** Decrease refcount of poly object. + * + * @param poly The poly object. + * @return The refcount after decrementing. + */ +FLECS_API +int32_t flecs_poly_release_( + ecs_poly_t *poly); -#define flecs_sparse_fini(sparse)\ - _flecs_sparse_fini(sparse) +#define flecs_poly_claim(poly) \ + flecs_poly_claim_(ECS_CONST_CAST(void*, reinterpret_cast(poly))) -/** Free sparse set */ -FLECS_DBG_API -void flecs_sparse_free( - ecs_sparse_t *sparse); +#define flecs_poly_release(poly) \ + flecs_poly_release_(ECS_CONST_CAST(void*, reinterpret_cast(poly))) -/** Remove all elements from sparse set */ -FLECS_DBG_API -void flecs_sparse_clear( - ecs_sparse_t *sparse); +/** Return refcount of poly object. + * + * @param poly The poly object. + * @return Refcount of the poly object. + */ +FLECS_API +int32_t flecs_poly_refcount( + ecs_poly_t *poly); -/** Set id source. This allows the sparse set to use an external variable for - * issuing and increasing new ids. */ -FLECS_DBG_API -void flecs_sparse_set_id_source( - ecs_sparse_t *sparse, - uint64_t *id_source); +/** Get unused index for static world local component id array. + * This operation returns an unused index for the world-local component id + * array. This index can be used by language bindings to obtain a component id. + * + * @return Unused index for component id array. + */ +FLECS_API +int32_t flecs_component_ids_index_get(void); -/** Add element to sparse set, this generates or recycles an id */ -FLECS_DBG_API -void* _flecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t elem_size); +/** Get world local component id. + * + * @param world The world. + * @param index Component id array index. + * @return The component id. + */ +FLECS_API +ecs_entity_t flecs_component_ids_get( + const ecs_world_t *world, + int32_t index); -#define flecs_sparse_add(sparse, T)\ - ((T*)_flecs_sparse_add(sparse, ECS_SIZEOF(T))) +/** Get alive world local component id. + * Same as flecs_component_ids_get, but return 0 if component is no longer + * alive. + * + * @param world The world. + * @param index Component id array index. + * @return The component id. + */ +FLECS_API +ecs_entity_t flecs_component_ids_get_alive( + const ecs_world_t *world, + int32_t index); -/** Get last issued id. */ -FLECS_DBG_API -uint64_t flecs_sparse_last_id( - const ecs_sparse_t *sparse); +/** Set world local component id. + * + * @param world The world. + * @param index Component id array index. + * @param id The component id. + */ +FLECS_API +void flecs_component_ids_set( + ecs_world_t *world, + int32_t index, + ecs_entity_t id); -/** Generate or recycle a new id. */ -FLECS_DBG_API -uint64_t flecs_sparse_new_id( - ecs_sparse_t *sparse); +/** Query iterator function for trivially cached queries. + * This operation can be called if an iterator matches the conditions for + * trivial iteration: + * + * @param it The query iterator. + * @return Whether the query has more results. + */ +FLECS_API +bool flecs_query_trivial_cached_next( + ecs_iter_t *it); -/** Generate or recycle new ids in bulk. The returned pointer points directly to - * the internal dense array vector with sparse ids. Operations on the sparse set - * can (and likely will) modify the contents of the buffer. */ -FLECS_DBG_API -const uint64_t* flecs_sparse_new_ids( - ecs_sparse_t *sparse, - int32_t count); +#ifdef FLECS_DEBUG +/** Check if current thread has exclusive access to world. + * This operation checks if the current thread is allowed to access the world. + * The operation is called by internal functions before mutating the world, and + * will panic if the current thread does not have exclusive access to the world. + * + * Exclusive access is controlled by the ecs_exclusive_access_begin() and + * ecs_exclusive_access_end() operations. + * + * This operation is public so that it shows up in stack traces, but code such + * as language bindings or wrappers could also use it to verify that the world + * is accessed from the correct thread. + * + * @param world The world. + */ +FLECS_API +void flecs_check_exclusive_world_access_write( + const ecs_world_t *world); -/** Remove an element */ -FLECS_DBG_API -void flecs_sparse_remove( - ecs_sparse_t *sparse, - uint64_t id); +/** Same as flecs_check_exclusive_world_access_write, but for read access. + * + * @param world The world. + */ +FLECS_API +void flecs_check_exclusive_world_access_read( + const ecs_world_t *world); -/** Remove an element, return pointer to the value in the sparse array */ -FLECS_DBG_API -void* _flecs_sparse_remove_get( - ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); +#else +#define flecs_check_exclusive_world_access_write(world) +#define flecs_check_exclusive_world_access_read(world) +#endif -#define flecs_sparse_remove_get(sparse, T, index)\ - ((T*)_flecs_sparse_remove_get(sparse, ECS_SIZEOF(T), index)) +/** End deferred mode (executes commands when stage->deref becomes 0). */ +FLECS_API +bool flecs_defer_end( + ecs_world_t *world, + ecs_stage_t *stage); -/** Check whether an id has ever been issued. */ -FLECS_DBG_API -bool flecs_sparse_exists( - const ecs_sparse_t *sparse, - uint64_t id); +#ifdef FLECS_JOURNAL +/** Get current value of operation counter. + * The journaling addon keeps track of an operation counter which is incremented + * for each operation. Applications can use this counter to run up to the point + * where an error occurs for easier debugging. + * This value is not thread safe. + * + * @return The operation counter. + */ +FLECS_API +int flecs_journal_get_counter(void); +#endif -/** Test if id is alive, which requires the generation count to match. */ -FLECS_DBG_API -bool flecs_sparse_is_alive( - const ecs_sparse_t *sparse, - uint64_t id); +/** Calculate offset from address */ +#ifdef __cplusplus +#define ECS_OFFSET(o, offset) reinterpret_cast((reinterpret_cast(o)) + (static_cast(offset))) +#else +#define ECS_OFFSET(o, offset) (void*)(((uintptr_t)(o)) + ((uintptr_t)(offset))) +#endif +#define ECS_OFFSET_T(o, T) ECS_OFFSET(o, ECS_SIZEOF(T)) -/** Return identifier with current generation set. */ -FLECS_DBG_API -uint64_t flecs_sparse_get_alive( - const ecs_sparse_t *sparse, - uint64_t id); +#define ECS_ELEM(ptr, size, index) ECS_OFFSET(ptr, (size) * (index)) +#define ECS_ELEM_T(o, T, index) ECS_ELEM(o, ECS_SIZEOF(T), index) -/** Get value from sparse set by dense id. This function is useful in - * combination with flecs_sparse_count for iterating all values in the set. */ -FLECS_DBG_API -void* _flecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - int32_t index); +/** Enable/disable bitsets */ +#define ECS_BIT_SET(flags, bit) (flags) |= (bit) +#define ECS_BIT_CLEAR(flags, bit) (flags) &= ~(bit) +#define ECS_BIT_COND(flags, bit, cond) ((cond) \ + ? (ECS_BIT_SET(flags, bit)) \ + : (ECS_BIT_CLEAR(flags, bit))) -#define flecs_sparse_get_dense(sparse, T, index)\ - ((T*)_flecs_sparse_get_dense(sparse, ECS_SIZEOF(T), index)) +#define ECS_BIT_CLEAR16(flags, bit) (flags) &= (ecs_flags16_t)~(bit) +#define ECS_BIT_COND16(flags, bit, cond) ((cond) \ + ? (ECS_BIT_SET(flags, bit)) \ + : (ECS_BIT_CLEAR16(flags, bit))) -/** Get the number of alive elements in the sparse set. */ -FLECS_DBG_API -int32_t flecs_sparse_count( - const ecs_sparse_t *sparse); +#define ECS_BIT_IS_SET(flags, bit) ((flags) & (bit)) -/** Get the number of not alive alive elements in the sparse set. */ -FLECS_DBG_API -int32_t flecs_sparse_not_alive_count( - const ecs_sparse_t *sparse); +#define ECS_BIT_SETN(flags, n) ECS_BIT_SET(flags, 1llu << n) +#define ECS_BIT_CLEARN(flags, n) ECS_BIT_CLEAR(flags, 1llu << n) +#define ECS_BIT_CONDN(flags, n, cond) ECS_BIT_COND(flags, 1llu << n, cond) -/** Return total number of allocated elements in the dense array */ -FLECS_DBG_API -int32_t flecs_sparse_size( - const ecs_sparse_t *sparse); +#ifdef __cplusplus +} +#endif -/** Get element by (sparse) id. The returned pointer is stable for the duration - * of the sparse set, as it is stored in the sparse array. */ -FLECS_DBG_API -void* _flecs_sparse_get( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); +#endif -#define flecs_sparse_get(sparse, T, index)\ - ((T*)_flecs_sparse_get(sparse, ECS_SIZEOF(T), index)) +/** + * @file hashmap.h + * @brief Hashmap data structure. + */ -/** Like get_sparse, but don't care whether element is alive or not. */ -FLECS_DBG_API -void* _flecs_sparse_get_any( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); +#ifndef FLECS_HASHMAP_H +#define FLECS_HASHMAP_H -#define flecs_sparse_get_any(sparse, T, index)\ - ((T*)_flecs_sparse_get_any(sparse, ECS_SIZEOF(T), index)) -/** Get or create element by (sparse) id. */ -FLECS_DBG_API -void* _flecs_sparse_ensure( - ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); +#ifdef __cplusplus +extern "C" { +#endif -#define flecs_sparse_ensure(sparse, T, index)\ - ((T*)_flecs_sparse_ensure(sparse, ECS_SIZEOF(T), index)) - -/** Set value. */ -FLECS_DBG_API -void* _flecs_sparse_set( - ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id, - void *value); - -#define flecs_sparse_set(sparse, T, index, value)\ - ((T*)_flecs_sparse_set(sparse, ECS_SIZEOF(T), index, value)) - -/** Get pointer to ids (alive and not alive). Use with count() or size(). */ -FLECS_DBG_API -const uint64_t* flecs_sparse_ids( - const ecs_sparse_t *sparse); - -/** Set size of the dense array. */ -FLECS_DBG_API -void flecs_sparse_set_size( - ecs_sparse_t *sparse, - int32_t elem_count); - -/** Copy sparse set into a new sparse set. */ -FLECS_DBG_API -ecs_sparse_t* flecs_sparse_copy( - const ecs_sparse_t *src); - -/** Restore sparse set into destination sparse set. */ -FLECS_DBG_API -void flecs_sparse_restore( - ecs_sparse_t *dst, - const ecs_sparse_t *src); - -/** Get memory usage of sparse set. */ -FLECS_DBG_API -void flecs_sparse_memory( - ecs_sparse_t *sparse, - int32_t *allocd, - int32_t *used); - -FLECS_DBG_API -ecs_sparse_iter_t _flecs_sparse_iter( - ecs_sparse_t *sparse, - ecs_size_t elem_size); - -#define flecs_sparse_iter(sparse, T)\ - _flecs_sparse_iter(sparse, ECS_SIZEOF(T)) - -#ifndef FLECS_LEGACY -#define flecs_sparse_each(sparse, T, var, ...)\ - {\ - int var##_i, var##_count = ecs_sparse_count(sparse);\ - for (var##_i = 0; var##_i < var##_count; var##_i ++) {\ - T* var = ecs_sparse_get_dense(sparse, T, var##_i);\ - __VA_ARGS__\ - }\ - } -#endif - -/* Publicly exposed APIs - * The flecs_ functions aren't exposed directly as this can cause some - * optimizers to not consider them for link time optimization. */ - -FLECS_API -ecs_sparse_t* _ecs_sparse_new( - ecs_size_t elem_size); - -#define ecs_sparse_new(T)\ - _ecs_sparse_new(ECS_SIZEOF(T)) - -FLECS_API -void* _ecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t elem_size); - -#define ecs_sparse_add(sparse, T)\ - ((T*)_ecs_sparse_add(sparse, ECS_SIZEOF(T))) - -FLECS_API -uint64_t ecs_sparse_last_id( - const ecs_sparse_t *sparse); - -FLECS_API -int32_t ecs_sparse_count( - const ecs_sparse_t *sparse); - -/** Override the generation count for a specific id */ -FLECS_API -void flecs_sparse_set_generation( - ecs_sparse_t *sparse, - uint64_t id); - -FLECS_API -void* _ecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - int32_t index); - -#define ecs_sparse_get_dense(sparse, T, index)\ - ((T*)_ecs_sparse_get_dense(sparse, ECS_SIZEOF(T), index)) - -FLECS_API -void* _ecs_sparse_get( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); - -#define ecs_sparse_get(sparse, T, index)\ - ((T*)_ecs_sparse_get(sparse, ECS_SIZEOF(T), index)) - -#ifdef __cplusplus -} -#endif - -#endif - -/** - * @file hashmap.h - * @brief Hashmap datastructure. - * - * Datastructure that computes a hash to store & retrieve values. Similar to - * ecs_map_t, but allows for arbitrary keytypes. - */ - -#ifndef FLECS_HASHMAP_H -#define FLECS_HASHMAP_H - - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - ecs_vector_t *keys; - ecs_vector_t *values; -} ecs_hm_bucket_t; +typedef struct { + ecs_vec_t keys; + ecs_vec_t values; +} ecs_hm_bucket_t; typedef struct { ecs_hash_value_action_t hash; @@ -3202,42 +4358,43 @@ typedef struct { } flecs_hashmap_result_t; FLECS_DBG_API -void _flecs_hashmap_init( +void flecs_hashmap_init_( ecs_hashmap_t *hm, ecs_size_t key_size, ecs_size_t value_size, ecs_hash_value_action_t hash, - ecs_compare_action_t compare); + ecs_compare_action_t compare, + ecs_allocator_t *allocator); -#define flecs_hashmap_init(hm, K, V, compare, hash)\ - _flecs_hashmap_init(hm, ECS_SIZEOF(K), ECS_SIZEOF(V), compare, hash) +#define flecs_hashmap_init(hm, K, V, hash, compare, allocator)\ + flecs_hashmap_init_(hm, ECS_SIZEOF(K), ECS_SIZEOF(V), hash, compare, allocator) FLECS_DBG_API void flecs_hashmap_fini( ecs_hashmap_t *map); FLECS_DBG_API -void* _flecs_hashmap_get( +void* flecs_hashmap_get_( const ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size); #define flecs_hashmap_get(map, key, V)\ - (V*)_flecs_hashmap_get(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) + (V*)flecs_hashmap_get_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) FLECS_DBG_API -flecs_hashmap_result_t _flecs_hashmap_ensure( +flecs_hashmap_result_t flecs_hashmap_ensure_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size); #define flecs_hashmap_ensure(map, key, V)\ - _flecs_hashmap_ensure(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) + flecs_hashmap_ensure_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) FLECS_DBG_API -void _flecs_hashmap_set( +void flecs_hashmap_set_( ecs_hashmap_t *map, ecs_size_t key_size, void *key, @@ -3245,20 +4402,20 @@ void _flecs_hashmap_set( const void *value); #define flecs_hashmap_set(map, key, value)\ - _flecs_hashmap_set(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(*value), value) + flecs_hashmap_set_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(*value), value) FLECS_DBG_API -void _flecs_hashmap_remove( +void flecs_hashmap_remove_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size); #define flecs_hashmap_remove(map, key, V)\ - _flecs_hashmap_remove(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) + flecs_hashmap_remove_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V)) FLECS_DBG_API -void _flecs_hashmap_remove_w_hash( +void flecs_hashmap_remove_w_hash_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, @@ -3266,7 +4423,7 @@ void _flecs_hashmap_remove_w_hash( uint64_t hash); #define flecs_hashmap_remove_w_hash(map, key, V, hash)\ - _flecs_hashmap_remove_w_hash(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V), hash) + flecs_hashmap_remove_w_hash_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(V), hash) FLECS_DBG_API ecs_hm_bucket_t* flecs_hashmap_get_bucket( @@ -3282,25 +4439,25 @@ void flecs_hm_bucket_remove( FLECS_DBG_API void flecs_hashmap_copy( - const ecs_hashmap_t *src, - ecs_hashmap_t *dst); + ecs_hashmap_t *dst, + const ecs_hashmap_t *src); FLECS_DBG_API flecs_hashmap_iter_t flecs_hashmap_iter( ecs_hashmap_t *map); FLECS_DBG_API -void* _flecs_hashmap_next( +void* flecs_hashmap_next_( flecs_hashmap_iter_t *it, ecs_size_t key_size, void *key_out, ecs_size_t value_size); #define flecs_hashmap_next(map, V)\ - (V*)_flecs_hashmap_next(map, 0, NULL, ECS_SIZEOF(V)) + (V*)flecs_hashmap_next_(map, 0, NULL, ECS_SIZEOF(V)) #define flecs_hashmap_next_w_key(map, K, key, V)\ - (V*)_flecs_hashmap_next(map, ECS_SIZEOF(K), key, ECS_SIZEOF(V)) + (V*)flecs_hashmap_next_(map, ECS_SIZEOF(K), key, ECS_SIZEOF(V)) #ifdef __cplusplus } @@ -3308,437 +4465,1002 @@ void* _flecs_hashmap_next( #endif - /** - * @defgroup desc_types Types used for creating API constructs - * @{ + * @file api_internals.h + * @brief Access to internal data structures. + * + * Operations may change without warning. */ -/** Used with ecs_entity_init */ -typedef struct ecs_entity_desc_t { - int32_t _canary; +#ifndef FLECS_API_INTERNALS_H +#define FLECS_API_INTERNALS_H - ecs_entity_t entity; /* Optional existing entity handle. */ - const char *name; /* Name of the entity. If no entity is provided, an - * entity with this name will be looked up first. When - * an entity is provided, the name will be verified - * with the existing entity. */ +#ifdef __cplusplus +extern "C" { +#endif - const char *sep; /* Optional custom separator for hierarchical names */ - const char *root_sep; /* Optional, used for identifiers relative to root */ +/** Record for entity index. */ +struct ecs_record_t { + ecs_table_t *table; /**< Identifies a type (and table) in world */ + uint32_t row; /**< Table row of the entity */ + int32_t dense; /**< Index in dense array of entity index */ +}; - const char *symbol; /* Optional entity symbol. A symbol is an unscoped - * identifier that can be used to lookup an entity. The - * primary use case for this is to associate the entity - * with a language identifier, such as a type or - * function name, where these identifiers differ from - * the name they are registered with in flecs. For - * example, C type "EcsPosition" might be registered - * as "flecs.components.transform.Position", with the - * symbol set to "EcsPosition". */ +/** Header for table cache elements. */ +typedef struct ecs_table_cache_hdr_t { + struct ecs_component_record_t *cr; /**< Component record for component. */ + ecs_table_t *table; /**< Table associated with element. */ + struct ecs_table_cache_hdr_t *prev, *next; /**< Next/previous elements for id in table cache. */ +} ecs_table_cache_hdr_t; + +/** Record that stores location of a component in a table. + * Table records are registered with component records, which allows for quickly + * finding all tables for a specific component. */ +struct ecs_table_record_t { + ecs_table_cache_hdr_t hdr; /**< Table cache header */ + int16_t index; /**< First type index where id occurs in table */ + int16_t count; /**< Number of times id occurs in table */ + int16_t column; /**< First column index where id occurs */ +}; - bool use_low_id; /* When set to true, a low id (typically reserved for - * components) will be used to create the entity, if - * no id is specified. */ +/** Type that contains information about which components got added/removed on + * a table edge. */ +typedef struct ecs_table_diff_t { + ecs_type_t added; /* Components added between tables */ + ecs_type_t removed; /* Components removed between tables */ + ecs_flags32_t added_flags; + ecs_flags32_t removed_flags; +} ecs_table_diff_t; - /* Array of ids to add to the new or existing entity. */ - ecs_id_t add[ECS_ID_CACHE_SIZE]; +/* Tracks which/how many non-fragmenting children are stored in table for parent. */ +typedef struct ecs_parent_record_t { + uint32_t entity; /* If table only contains a single entity for parent, this will contain the entity id (without generation). */ + int32_t count; /* The number of children for a parent in the table. */ +} ecs_parent_record_t; - /* String expression with components to add */ - const char *add_expr; -} ecs_entity_desc_t; +/** Find record for entity. + * An entity record contains the table and row for the entity. + * + * To use ecs_record_t::row as the record in the table, use: + * ECS_RECORD_TO_ROW(r->row) + * + * This removes potential entity bitflags from the row field. + * + * @param world The world. + * @param entity The entity. + * @return The record, NULL if the entity does not exist. + */ +FLECS_API +ecs_record_t* ecs_record_find( + const ecs_world_t *world, + ecs_entity_t entity); -/** Used with ecs_bulk_init */ -typedef struct ecs_bulk_desc_t { - int32_t _canary; +/** Get entity corresponding with record. + * This operation only works for entities that are not empty. + * + * @param record The record for which to obtain the entity id. + * @return The entity id for the record. + */ +FLECS_API +ecs_entity_t ecs_record_get_entity( + const ecs_record_t *record); - ecs_entity_t *entities; /* Entities to bulk insert. Entity ids provided by - * the application application must be empty (cannot - * have components). If no entity ids are provided, the - * operation will create 'count' new entities. */ +/** Begin exclusive write access to entity. + * This operation provides safe exclusive access to the components of an entity + * without the overhead of deferring operations. + * + * When this operation is called simultaneously for the same entity more than + * once it will throw an assert. Note that for this to happen, asserts must be + * enabled. It is up to the application to ensure that access is exclusive, for + * example by using a read-write mutex. + * + * Exclusive access is enforced at the table level, so only one entity can be + * exclusively accessed per table. The exclusive access check is thread safe. + * + * This operation must be followed up with ecs_write_end(). + * + * @param world The world. + * @param entity The entity. + * @return A record to the entity. + */ +FLECS_API +ecs_record_t* ecs_write_begin( + ecs_world_t *world, + ecs_entity_t entity); - int32_t count; /* Number of entities to create/populate */ +/** End exclusive write access to entity. + * This operation ends exclusive access, and must be called after + * ecs_write_begin(). + * + * @param record Record to the entity. + */ +FLECS_API +void ecs_write_end( + ecs_record_t *record); - ecs_id_t ids[ECS_ID_CACHE_SIZE]; /* Ids to create the entities with */ +/** Begin read access to entity. + * This operation provides safe read access to the components of an entity. + * Multiple simultaneous reads are allowed per entity. + * + * This operation ensures that code attempting to mutate the entity's table will + * throw an assert. Note that for this to happen, asserts must be enabled. It is + * up to the application to ensure that this does not happen, for example by + * using a read-write mutex. + * + * This operation does *not* provide the same guarantees as a read-write mutex, + * as it is possible to call ecs_read_begin() after calling ecs_write_begin(). It is + * up to application has to ensure that this does not happen. + * + * This operation must be followed up with ecs_read_end(). + * + * @param world The world. + * @param entity The entity. + * @return A record to the entity. + */ +FLECS_API +const ecs_record_t* ecs_read_begin( + ecs_world_t *world, + ecs_entity_t entity); - void **data; /* Array with component data to insert. Each element in - * the array must correspond with an element in the ids - * array. If an element in the ids array is a tag, the - * data array must contain a NULL. An element may be - * set to NULL for a component, in which case the - * component will not be set by the operation. */ +/** End read access to entity. + * This operation ends read access, and must be called after ecs_read_begin(). + * + * @param record Record to the entity. + */ +FLECS_API +void ecs_read_end( + const ecs_record_t *record); - ecs_table_t *table; /* Table to insert the entities into. Should not be set - * at the same time as ids. When 'table' is set at the - * same time as 'data', the elements in the data array - * must correspond with the ids in the table's type. */ +/** Get component from entity record. + * This operation returns a pointer to a component for the entity + * associated with the provided record. For safe access to the component, obtain + * the record with ecs_read_begin() or ecs_write_begin(). + * + * Obtaining a component from a record is faster than obtaining it from the + * entity handle, as it reduces the number of lookups required. + * + * @param world The world. + * @param record Record to the entity. + * @param id The (component) id. + * @return Pointer to component, or NULL if entity does not have the component. + * + * @see ecs_record_ensure_id() + */ +FLECS_API +const void* ecs_record_get_id( + const ecs_world_t *world, + const ecs_record_t *record, + ecs_id_t id); -} ecs_bulk_desc_t; +/** Same as ecs_record_get_id(), but returns a mutable pointer. + * For safe access to the component, obtain the record with ecs_write_begin(). + * + * @param world The world. + * @param record Record to the entity. + * @param id The (component) id. + * @return Pointer to component, or NULL if entity does not have the component. + */ +FLECS_API +void* ecs_record_ensure_id( + ecs_world_t *world, + ecs_record_t *record, + ecs_id_t id); +/** Test if entity for record has a (component) id. + * + * @param world The world. + * @param record Record to the entity. + * @param id The (component) id. + * @return Whether the entity has the component. + */ +FLECS_API +bool ecs_record_has_id( + ecs_world_t *world, + const ecs_record_t *record, + ecs_id_t id); -/** Used with ecs_component_init. */ -typedef struct ecs_component_desc_t { - int32_t _canary; +/** Get component pointer from column/record. + * This returns a pointer to the component using a table column index. The + * table's column index can be found with ecs_table_get_column_index(). + * + * Usage: + * @code + * ecs_record_t *r = ecs_record_find(world, entity); + * int32_t column = ecs_table_get_column_index(world, table, ecs_id(Position)); + * Position *ptr = ecs_record_get_by_column(r, column, sizeof(Position)); + * @endcode + * + * @param record The record. + * @param column The column index in the entity's table. + * @param size The component size. + * @return The component pointer. + */ +FLECS_API +void* ecs_record_get_by_column( + const ecs_record_t *record, + int32_t column, + size_t size); - ecs_entity_desc_t entity; /* Parameters for component entity */ - size_t size; /* Component size */ - size_t alignment; /* Component alignment */ -} ecs_component_desc_t; +/** Get component record for component id. + * + * @param world The world. + * @param id The component id. + * @return The component record, or NULL if it doesn't exist. + */ +FLECS_API +FLECS_ALWAYS_INLINE ecs_component_record_t* flecs_components_get( + const ecs_world_t *world, + ecs_id_t id); +/* Ensure component record for component id + * + * @param world The world. + * @param id The component id. + * @return The new or existing component record. + */ +FLECS_API +FLECS_ALWAYS_INLINE ecs_component_record_t* flecs_components_ensure( + ecs_world_t *world, + ecs_id_t id); -/** Used with ecs_type_init. */ -typedef struct ecs_type_desc_t { - int32_t _canary; +/** Get component id from component record. + * + * @param cr The component record. + * @return The component id. + */ +FLECS_API +ecs_id_t flecs_component_get_id( + const ecs_component_record_t *cr); - ecs_entity_desc_t entity; /* Parameters for type entity */ - ecs_id_t ids[ECS_ID_CACHE_SIZE]; /* Ids to include in type */ - const char *ids_expr; /* Id expression to include in type */ -} ecs_type_desc_t; +/** Get component flags for component. + * + * @param id The component id. + * @return The flags for the component id. + */ +FLECS_API +ecs_flags32_t flecs_component_get_flags( + const ecs_world_t *world, + ecs_id_t id); +/** Get type info for component record. + * + * @param cr The component record. + * @return The type info struct, or NULL if component is a tag. + */ +FLECS_API +const ecs_type_info_t* flecs_component_get_type_info( + const ecs_component_record_t *cr); -/** Used with ecs_filter_init. */ -typedef struct ecs_filter_desc_t { - int32_t _canary; +/** Find table record for component record. + * This operation returns the table record for the table/component record if it + * exists. If the record exists, it means the table has the component. + * + * @param cr The component record. + * @param table The table. + * @return The table record if the table has the component, or NULL if not. + */ +FLECS_API +FLECS_ALWAYS_INLINE const ecs_table_record_t* flecs_component_get_table( + const ecs_component_record_t *cr, + const ecs_table_t *table); - /* Terms of the filter. If a filter has more terms than - * ECS_TERM_CACHE_SIZE use terms_buffer */ - ecs_term_t terms[ECS_TERM_DESC_CACHE_SIZE]; +/** Ger parent record for component/table. + * A parent record stores how many children for a parent are stored in the + * specified table. If the table only stores a single child, the parent record + * will also store the entity id of that child. + * + * This information is used by queries to determine whether an O(n) search + * through the table is required to find all children for the parent. If the + * table only contains a single child the query can use + * ecs_parent_record_t::entity directly, otherwise it has to do a scan. + * + * The component record specified to this function must be a ChildOf pair. Only + * tables with children that use the non-fragmenting hierarchy storage will have + * parent records. + * + * @param cr The ChildOf component record. + * @param table The table to check the number of children for. + * @return The parent record if it exists, NULL if it does not. +*/ +FLECS_API +FLECS_ALWAYS_INLINE ecs_parent_record_t* flecs_component_get_parent_record( + const ecs_component_record_t *cr, + const ecs_table_t *table); - /* For filters with lots of terms an outside array can be provided. */ - ecs_term_t *terms_buffer; - int32_t terms_buffer_count; +/** Return hierarchy depth for component record. + * The specified component record must be a ChildOf pair. This function does not + * compute the depth, it just returns the precomputed depth that is updated + * automatically when hierarchy changes happen. + * + * @param cr The ChildOf component record. + * @return The depth of the parent's children in the hierarchy. + */ +FLECS_API +FLECS_ALWAYS_INLINE int32_t flecs_component_get_childof_depth( + const ecs_component_record_t *cr); - /* When true, don't populate data fields of iterator. This is useful for - * filters that are only interested in finding the set of matching tables or - * entities, and not in the component data. */ - bool filter; +/** Create component record iterator. + * A component record iterator iterates all tables for the specified component + * record. + * + * The iterator should be used like this: + * + * @code + * ecs_table_cache_iter_t it; + * if (flecs_component_iter(cr, &it)) { + * const ecs_table_record_t *tr; + * while ((tr = flecs_component_next(&it))) { + * ecs_table_t *table = tr->hdr.table; + * // ... + * } + * } + * @endcode + * + * @param cr The component record. + * @param iter_out Out parameter for the iterator. + * @return True if there are results, false if there are no results. + */ +FLECS_API +bool flecs_component_iter( + const ecs_component_record_t *cr, + ecs_table_cache_iter_t *iter_out); - /* When true, terms returned by an iterator may either contain 1 or N - * elements, where terms with N elements are owned, and terms with 1 element - * are shared, for example from a parent or base entity. When false, the - * iterator will at most return 1 element when the result contains both - * owned and shared terms. */ - bool instanced; +/** Get next table record for iterator. + * Returns next table record for iterator. + * + * @param iter The iterator. + * @return The next table record, or NULL if there are no more results. + */ +FLECS_API +const ecs_table_record_t* flecs_component_next( + ecs_table_cache_iter_t *iter); - /* Match empty tables. By default empty tables are not returned. */ - bool match_empty_tables; +/** Struct returned by flecs_table_records(). */ +typedef struct ecs_table_records_t { + const ecs_table_record_t *array; + int32_t count; +} ecs_table_records_t; - /* Filter expression. Should not be set at the same time as terms array */ - const char *expr; +/** Get table records. + * This operation returns an array with all records for the specified table. + * + * @param table The table. + * @return The table records for the table. + */ +FLECS_API +ecs_table_records_t flecs_table_records( + ecs_table_t* table); - /* Optional name of filter, used for debugging. If a filter is created for - * a system, the provided name should match the system name. */ - const char *name; -} ecs_filter_desc_t; +/** Get component record from table record. + * + * @param tr The table record. + * @return The component record. + */ +FLECS_API +ecs_component_record_t* flecs_table_record_get_component( + const ecs_table_record_t *tr); +/** Get table id. + * This operation returns a unique numerical identifier for a table. + * + * @param table The table. + * @return The table records for the table. + */ +FLECS_API +uint64_t flecs_table_id( + ecs_table_t* table); -/** Used with ecs_query_init. */ -typedef struct ecs_query_desc_t { - int32_t _canary; +/** Find table by adding id to current table. + * Same as ecs_table_add_id, but with additional diff parameter that contains + * information about the traversed edge. + * + * @param world The world. + * @param table The table. + * @param id_ptr Pointer to component id to add. + * @param diff Information about traversed edge (out parameter). + * @return The table that was traversed to. + */ +FLECS_API +FLECS_ALWAYS_INLINE ecs_table_t *flecs_table_traverse_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t *id_ptr, + ecs_table_diff_t *diff); - /* Filter for the query */ - ecs_filter_desc_t filter; +#ifdef __cplusplus +} +#endif - /* Component to be used by order_by */ - ecs_entity_t order_by_component; +#endif - /* Callback used for ordering query results. If order_by_id is 0, the - * pointer provided to the callback will be NULL. If the callback is not - * set, results will not be ordered. */ - ecs_order_by_action_t order_by; - /* Id to be used by group_by. This id is passed to the group_by function and - * can be used identify the part of an entity type that should be used for - * grouping. */ - ecs_id_t group_by_id; +/** Utility to hold a value of a dynamic type. */ +typedef struct ecs_value_t { + ecs_entity_t type; /**< Type of value. */ + void *ptr; /**< Pointer to value. */ +} ecs_value_t; - /* Callback used for grouping results. If the callback is not set, results - * will not be grouped. When set, this callback will be used to calculate a - * "rank" for each entity (table) based on its components. This rank is then - * used to sort entities (tables), so that entities (tables) of the same - * rank are "grouped" together when iterated. */ - ecs_group_by_action_t group_by; +/** Used with ecs_entity_init(). + * + * @ingroup entities + */ +typedef struct ecs_entity_desc_t { + int32_t _canary; /**< Used for validity testing. Must be 0. */ - /* Context to pass to group_by */ - void *group_by_ctx; + ecs_entity_t id; /**< Set to modify existing entity (optional) */ - /* Function to free group_by_ctx */ - ecs_ctx_free_t group_by_ctx_free; + ecs_entity_t parent; /**< Parent entity. */ - /* If set, the query will be created as a subquery. A subquery matches at - * most a subset of its parent query. Subqueries do not directly receive - * (table) notifications from the world. Instead parent queries forward - * results to subqueries. This can improve matching performance, as fewer - * queries need to be matched with new tables. - * Subqueries can be nested. */ - ecs_query_t *parent; - - /* INTERNAL PROPERTY - system to be associated with query. Do not set, as - * this will change in future versions. */ - ecs_entity_t system; -} ecs_query_desc_t; + const char *name; /**< Name of the entity. If no entity is provided, an + * entity with this name will be looked up first. When + * an entity is provided, the name will be verified + * with the existing entity. */ + const char *sep; /**< Optional custom separator for hierarchical names. + * Leave to NULL for default ('.') separator. Set to + * an empty string to prevent tokenization of name. */ -/** Used with ecs_trigger_init. */ -typedef struct ecs_trigger_desc_t { - int32_t _canary; + const char *root_sep; /**< Optional, used for identifiers relative to root */ + + const char *symbol; /**< Optional entity symbol. A symbol is an unscoped + * identifier that can be used to lookup an entity. The + * primary use case for this is to associate the entity + * with a language identifier, such as a type or + * function name, where these identifiers differ from + * the name they are registered with in flecs. For + * example, C type "EcsPosition" might be registered + * as "flecs.components.transform.Position", with the + * symbol set to "EcsPosition". */ + + bool use_low_id; /**< When set to true, a low id (typically reserved for + * components) will be used to create the entity, if + * no id is specified. */ + + /** 0-terminated array of ids to add to the entity. */ + const ecs_id_t *add; + + /** 0-terminated array of values to set on the entity. */ + const ecs_value_t *set; + + /** String expression with components to add */ + const char *add_expr; +} ecs_entity_desc_t; + +/** Used with ecs_bulk_init(). + * + * @ingroup entities + */ +typedef struct ecs_bulk_desc_t { + int32_t _canary; /**< Used for validity testing. Must be 0. */ + + ecs_entity_t *entities; /**< Entities to bulk insert. Entity ids provided by + * the application must be empty (cannot + * have components). If no entity ids are provided, the + * operation will create 'count' new entities. */ + + int32_t count; /**< Number of entities to create/populate */ + + ecs_id_t ids[FLECS_ID_DESC_MAX]; /**< Ids to create the entities with */ + + void **data; /**< Array with component data to insert. Each element in + * the array must correspond with an element in the ids + * array. If an element in the ids array is a tag, the + * data array must contain a NULL. An element may be + * set to NULL for a component, in which case the + * component will not be set by the operation. */ + + ecs_table_t *table; /**< Table to insert the entities into. Should not be set + * at the same time as ids. When 'table' is set at the + * same time as 'data', the elements in the data array + * must correspond with the ids in the table's type. */ + +} ecs_bulk_desc_t; + +/** Used with ecs_component_init(). + * + * @ingroup components + */ +typedef struct ecs_component_desc_t { + int32_t _canary; /**< Used for validity testing. Must be 0. */ + + /** Existing entity to associate with observer (optional) */ + ecs_entity_t entity; + + /** Parameters for type (size, hooks, ...) */ + ecs_type_info_t type; +} ecs_component_desc_t; + +/** Iterator. + * Used for iterating queries. The ecs_iter_t type contains all the information + * that is provided by a query, and contains all the state required for the + * iterator code. + * + * Functions that create iterators accept as first argument the world, and as + * second argument the object they iterate. For example: + * + * @code + * ecs_iter_t it = ecs_query_iter(world, q); + * @endcode + * + * When this code is called from a system, it is important to use the world + * provided by its iterator object to ensure thread safety. For example: + * + * @code + * void Collide(ecs_iter_t *it) { + * ecs_iter_t qit = ecs_query_iter(it->world, Colliders); + * } + * @endcode + * + * An iterator contains resources that need to be released. By default this + * is handled by the last call to next() that returns false. When iteration is + * ended before iteration has completed, an application has to manually call + * ecs_iter_fini() to release the iterator resources: + * + * @code + * ecs_iter_t it = ecs_query_iter(world, q); + * while (ecs_query_next(&it)) { + * if (cond) { + * ecs_iter_fini(&it); + * break; + * } + * } + * @endcode + * + * @ingroup queries + */ +struct ecs_iter_t { + /* World */ + ecs_world_t *world; /**< The world. Can point to stage when in deferred/readonly mode. */ + ecs_world_t *real_world; /**< Actual world. Never points to a stage. */ + + /* Matched data */ + int32_t offset; /**< Offset relative to current table */ + int32_t count; /**< Number of entities to iterate */ + const ecs_entity_t *entities; /**< Entity identifiers */ + void **ptrs; /**< Component pointers. If not set or if it's NULL for a field, use it.trs. */ + const ecs_table_record_t **trs; /**< Info on where to find field in table */ + const ecs_size_t *sizes; /**< Component sizes */ + ecs_table_t *table; /**< Current table */ + ecs_table_t *other_table; /**< Prev or next table when adding/removing */ + ecs_id_t *ids; /**< (Component) ids */ + ecs_entity_t *sources; /**< Entity on which the id was matched (0 if same as entities) */ + ecs_flags64_t constrained_vars; /**< Bitset that marks constrained variables */ + ecs_termset_t set_fields; /**< Fields that are set */ + ecs_termset_t ref_fields; /**< Bitset with fields that aren't component arrays */ + ecs_termset_t row_fields; /**< Fields that must be obtained with field_at */ + ecs_termset_t up_fields; /**< Bitset with fields matched through up traversal */ + + /* Input information */ + ecs_entity_t system; /**< The system (if applicable) */ + ecs_entity_t event; /**< The event (if applicable) */ + ecs_id_t event_id; /**< The (component) id for the event */ + int32_t event_cur; /**< Unique event id. Used to dedup observer calls */ + + /* Query information */ + int8_t field_count; /**< Number of fields in iterator */ + int8_t term_index; /**< Index of term that emitted an event. + * This field will be set to the 'index' field + * of an observer term. */ + const ecs_query_t *query; /**< Query being evaluated */ - /* Entity to associate with trigger */ - ecs_entity_desc_t entity; + /* Context */ + void *param; /**< Param passed to ecs_run */ + void *ctx; /**< System context */ + void *binding_ctx; /**< System binding context */ + void *callback_ctx; /**< Callback language binding context */ + void *run_ctx; /**< Run language binding context */ + + /* Time */ + ecs_ftime_t delta_time; /**< Time elapsed since last frame */ + ecs_ftime_t delta_system_time;/**< Time elapsed since last system invocation */ + + /* Iterator counters */ + int32_t frame_offset; /**< Offset relative to start of iteration */ + + /* Misc */ + ecs_flags32_t flags; /**< Iterator flags */ + ecs_entity_t interrupted_by; /**< When set, system execution is interrupted */ + ecs_iter_private_t priv_; /**< Private data */ + + /* Chained iterators */ + ecs_iter_next_action_t next; /**< Function to progress iterator */ + ecs_iter_action_t callback; /**< Callback of system or observer */ + ecs_iter_fini_action_t fini; /**< Function to cleanup iterator resources */ + ecs_iter_t *chain_it; /**< Optional, allows for creating iterator chains */ +}; + + +/** Query must match prefabs. + * Can be combined with other query flags on the ecs_query_desc_t::flags field. + * \ingroup queries + */ +#define EcsQueryMatchPrefab (1u << 1u) + +/** Query must match disabled entities. + * Can be combined with other query flags on the ecs_query_desc_t::flags field. + * \ingroup queries + */ +#define EcsQueryMatchDisabled (1u << 2u) + +/** Query must match empty tables. + * Can be combined with other query flags on the ecs_query_desc_t::flags field. + * \ingroup queries + */ +#define EcsQueryMatchEmptyTables (1u << 3u) + +/** Query may have unresolved entity identifiers. + * Can be combined with other query flags on the ecs_query_desc_t::flags field. + * \ingroup queries + */ +#define EcsQueryAllowUnresolvedByName (1u << 6u) + +/** Query only returns whole tables (ignores toggle/member fields). + * Can be combined with other query flags on the ecs_query_desc_t::flags field. + * \ingroup queries + */ +#define EcsQueryTableOnly (1u << 7u) + +/** Enable change detection for query. + * Can be combined with other query flags on the ecs_query_desc_t::flags field. + * + * Adding this flag makes it possible to use ecs_query_changed() and + * ecs_iter_changed() with the query. Change detection requires the query to be + * cached. If cache_kind is left to the default value, this flag will cause it + * to default to EcsQueryCacheAuto. + * + * \ingroup queries + */ +#define EcsQueryDetectChanges (1u << 8u) - /* Term specifying the id to subscribe for */ - ecs_term_t term; - /* Filter expression. May only contain a single term. If this field is set, - * the term field is ignored. */ +/** Used with ecs_query_init(). + * + * \ingroup queries + */ +typedef struct ecs_query_desc_t { + /** Used for validity testing. Must be 0. */ + int32_t _canary; + + /** Query terms */ + ecs_term_t terms[FLECS_TERM_COUNT_MAX]; + + /** Query DSL expression (optional) */ const char *expr; - /* Events to trigger on (OnAdd, OnRemove, OnSet, UnSet) */ - ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; + /** Caching policy of query */ + ecs_query_cache_kind_t cache_kind; + + /** Flags for enabling query features */ + ecs_flags32_t flags; + + /** Callback used for ordering query results. If order_by_id is 0, the + * pointer provided to the callback will be NULL. If the callback is not + * set, results will not be ordered. */ + ecs_order_by_action_t order_by_callback; - /* Should trigger match prefabs & disabled entities */ - bool match_prefab; - bool match_disabled; + /** Callback used for ordering query results. Same as order_by_callback, + * but more efficient. */ + ecs_sort_table_action_t order_by_table_callback; - /* See ecs_filter_desc_t::instanced */ - bool instanced; + /** Component to sort on, used together with order_by_callback or + * order_by_table_callback. */ + ecs_entity_t order_by; - /* When trigger is created, generate events from existing data. For example, - * EcsOnAdd Position would trigger for all existing instances of Position. - * This is only supported for events that are iterable (see EcsIterable) */ - bool yield_existing; + /** Component id to be used for grouping. Used together with the + * group_by_callback. */ + ecs_id_t group_by; - /* Callback to invoke on an event */ - ecs_iter_action_t callback; + /** Callback used for grouping results. If the callback is not set, results + * will not be grouped. When set, this callback will be used to calculate a + * "rank" for each entity (table) based on its components. This rank is then + * used to sort entities (tables), so that entities (tables) of the same + * rank are "grouped" together when iterated. */ + ecs_group_by_action_t group_by_callback; + + /** Callback that is invoked when a new group is created. The return value of + * the callback is stored as context for a group. */ + ecs_group_create_action_t on_group_create; + + /** Callback that is invoked when an existing group is deleted. The return + * value of the on_group_create callback is passed as context parameter. */ + ecs_group_delete_action_t on_group_delete; + + /** Context to pass to group_by */ + void *group_by_ctx; - /* Associate with entity */ - ecs_entity_t self; + /** Function to free group_by_ctx */ + ecs_ctx_free_t group_by_ctx_free; - /* User context to pass to callback */ + /** User context to pass to callback */ void *ctx; - /* Context to be used for language bindings */ + /** Context to be used for language bindings */ void *binding_ctx; - - /* Callback to free ctx */ + + /** Callback to free ctx */ ecs_ctx_free_t ctx_free; - /* Callback to free binding_ctx */ + /** Callback to free binding_ctx */ ecs_ctx_free_t binding_ctx_free; - /* Observable with which to register the trigger */ - ecs_poly_t *observable; - - /* This field is usually only set if a trigger is part of an observer, and - * points to the observer's last_event_id. This enables skipping triggers if - * a previous trigger for the same observer already notified it. */ - int32_t *last_event_id; -} ecs_trigger_desc_t; - + /** Entity associated with query (optional) */ + ecs_entity_t entity; +} ecs_query_desc_t; -/** Used with ecs_observer_init. */ +/** Used with ecs_observer_init(). + * + * @ingroup observers + */ typedef struct ecs_observer_desc_t { + /** Used for validity testing. Must be 0. */ int32_t _canary; - /* Entity to associate with observer */ - ecs_entity_desc_t entity; + /** Existing entity to associate with observer (optional) */ + ecs_entity_t entity; - /* Filter for observer */ - ecs_filter_desc_t filter; + /** Query for observer */ + ecs_query_desc_t query; - /* Events to observe (OnAdd, OnRemove, OnSet, UnSet) */ - ecs_entity_t events[ECS_TRIGGER_DESC_EVENT_COUNT_MAX]; + /** Events to observe (OnAdd, OnRemove, OnSet) */ + ecs_entity_t events[FLECS_EVENT_DESC_MAX]; - /* See ecs_trigger_desc_t */ + /** When observer is created, generate events from existing data. For example, + * #EcsOnAdd `Position` would match all existing instances of `Position`. */ bool yield_existing; - /* Callback to invoke on an event, invoked when the observer matches. */ + /** Global observers are tied to the lifespan of the world. Creating a + * global observer does not create an entity, and therefore + * ecs_observer_init will not return an entity handle. */ + bool global_observer; + + /** Callback to invoke on an event, invoked when the observer matches. */ ecs_iter_action_t callback; - /* Callback invoked on an event. When left to NULL the default runner - * is used which matches the event with the observer's filter, and calls - * 'callback' when it matches. + /** Callback invoked on an event. When left to NULL the default runner + * is used which matches the event with the observer's query, and calls + * 'callback' when it matches. * A reason to override the run function is to improve performance, if there * are more efficient way to test whether an event matches the observer than * the general purpose query matcher. */ ecs_run_action_t run; - /* Associate with entity */ - ecs_entity_t self; - - /* User context to pass to callback */ + /** User context to pass to callback */ void *ctx; - /* Context to be used for language bindings */ - void *binding_ctx; - - /* Callback to free ctx */ + /** Callback to free ctx */ ecs_ctx_free_t ctx_free; - /* Callback to free binding_ctx */ - ecs_ctx_free_t binding_ctx_free; - - /* Observable with which to register the trigger */ - ecs_poly_t *observable; -} ecs_observer_desc_t; + /** Context associated with callback (for language bindings). */ + void *callback_ctx; -/** @} */ + /** Callback to free callback ctx. */ + ecs_ctx_free_t callback_ctx_free; + /** Context associated with run (for language bindings). */ + void *run_ctx; -/** - * @defgroup builtin_components Builtin components - * @{ - */ + /** Callback to free run ctx. */ + ecs_ctx_free_t run_ctx_free; -/** A (string) identifier. Used as pair with EcsName and EcsSymbol tags */ -typedef struct EcsIdentifier { - char *value; - ecs_size_t length; - uint64_t hash; - uint64_t index_hash; /* Hash of existing record in current index */ - ecs_hashmap_t *index; /* Current index */ -} EcsIdentifier; + /** Used for internal purposes. Do not set. */ + int32_t *last_event_id; + int8_t term_index_; + ecs_flags32_t flags_; +} ecs_observer_desc_t; -/** Component information. */ -typedef struct EcsComponent { - ecs_size_t size; /* Component size */ - ecs_size_t alignment; /* Component alignment */ -} EcsComponent; +/** Used with ecs_emit(). + * + * @ingroup observers + */ +typedef struct ecs_event_desc_t { + /** The event id. Only observers for the specified event will be notified */ + ecs_entity_t event; -/** Component that stores an ecs_type_t. - * This component allows for the creation of entities that represent a type, and - * therefore the creation of named types. */ -typedef struct EcsType { - ecs_type_t type; /* Preserved nested types */ - ecs_table_t *normalized; /* Table with union of type + nested AND types */ -} EcsType; + /** Component ids. Only observers with a matching component id will be + * notified. Observers are guaranteed to get notified once, even if they + * match more than one id. */ + const ecs_type_t *ids; -/** Component that contains lifecycle callbacks for a component. */ -typedef struct EcsComponentLifecycle { - ecs_xtor_t ctor; /* ctor */ - ecs_xtor_t dtor; /* dtor */ - ecs_copy_t copy; /* copy assignment */ - ecs_move_t move; /* move assignment */ + /** The table for which to notify. */ + ecs_table_t *table; - /* Ctor + copy */ - ecs_copy_t copy_ctor; + /** Optional 2nd table to notify. This can be used to communicate the + * previous or next table, in case an entity is moved between tables. */ + ecs_table_t *other_table; - /* Ctor + move */ - ecs_move_t move_ctor; + /** Limit notified entities to ones starting from offset (row) in table */ + int32_t offset; - /* Ctor + move + dtor (or move_ctor + dtor). - * This combination is typically used when a component is moved from one - * location to a new location, like when it is moved to a new table. If - * not set explicitly it will be derived from other callbacks. */ - ecs_move_t ctor_move_dtor; + /** Limit number of notified entities to count. offset+count must be less + * than the total number of entities in the table. If left to 0, it will be + * automatically determined by doing `ecs_table_count(table) - offset`. */ + int32_t count; - /* Move + dtor. - * This combination is typically used when a component is moved from one - * location to an existing location, like what happens during a remove. If - * not set explicitly it will be derived from other callbacks. */ - ecs_move_t move_dtor; + /** Single-entity alternative to setting table / offset / count */ + ecs_entity_t entity; - /* Callback that is invoked when an instance of a component is added. This - * callback is invoked before triggers are invoked. */ - ecs_iter_action_t on_add; + /** Optional context. + * The type of the param must be the event, where the event is a component. + * When an event is enqueued, the value of param is coped to a temporary + * storage of the event type. */ + void *param; - /* Callback that is invoked when an instance of the component is set. This - * callback is invoked before triggers are invoked, and enable the component - * to respond to changes on itself before others can. */ - ecs_iter_action_t on_set; + /** Same as param, but with the guarantee that the value won't be modified. + * When an event with a const parameter is enqueued, the value of the param + * is copied to a temporary storage of the event type. */ + const void *const_param; - /* Callback that is invoked when an instance of the component is removed. - * This callback is invoked after the triggers are invoked, and before the - * destructor is invoked. */ - ecs_iter_action_t on_remove; + /** Observable (usually the world) */ + ecs_poly_t *observable; - /* User defined context */ - void *ctx; -} EcsComponentLifecycle; + /** Event flags */ + ecs_flags32_t flags; +} ecs_event_desc_t; -/** Type that contains component information (passed to ctors/dtors/...) */ -struct ecs_type_info_t { - ecs_size_t size; - ecs_size_t alignment; - EcsComponentLifecycle lifecycle; - ecs_entity_t component; - bool lifecycle_set; -}; -/** Component that stores reference to trigger */ -typedef struct EcsTrigger { - const ecs_trigger_t *trigger; -} EcsTrigger; +/** + * @defgroup misc_types Miscellaneous types + * Types used to create entities, observers, queries and more. + * + * @{ + */ -/** Component that stores reference to observer */ -typedef struct EcsObserver { - const ecs_observer_t *observer; -} EcsObserver; +/** Type with information about the current Flecs build */ +typedef struct ecs_build_info_t { + const char *compiler; /**< Compiler used to compile flecs */ + const char **addons; /**< Addons included in build */ + const char **flags; /**< Compile time settings */ + const char *version; /**< Stringified version */ + int16_t version_major; /**< Major flecs version */ + int16_t version_minor; /**< Minor flecs version */ + int16_t version_patch; /**< Patch flecs version */ + bool debug; /**< Is this a debug build */ + bool sanitize; /**< Is this a sanitize build */ + bool perf_trace; /**< Is this a perf tracing build */ +} ecs_build_info_t; -/** Component for storing a query */ -typedef struct EcsQuery { - ecs_query_t *query; -} EcsQuery; +/** Type that contains information about the world. */ +typedef struct ecs_world_info_t { + ecs_entity_t last_component_id; /**< Last issued component entity id */ + ecs_entity_t min_id; /**< First allowed entity id */ + ecs_entity_t max_id; /**< Last allowed entity id */ + + ecs_ftime_t delta_time_raw; /**< Raw delta time (no time scaling) */ + ecs_ftime_t delta_time; /**< Time passed to or computed by ecs_progress() */ + ecs_ftime_t time_scale; /**< Time scale applied to delta_time */ + ecs_ftime_t target_fps; /**< Target fps */ + ecs_ftime_t frame_time_total; /**< Total time spent processing a frame */ + ecs_ftime_t system_time_total; /**< Total time spent in systems */ + ecs_ftime_t emit_time_total; /**< Total time spent notifying observers */ + ecs_ftime_t merge_time_total; /**< Total time spent in merges */ + ecs_ftime_t rematch_time_total; /**< Time spent on query rematching */ + double world_time_total; /**< Time elapsed in simulation */ + double world_time_total_raw; /**< Time elapsed in simulation (no scaling) */ + + int64_t frame_count_total; /**< Total number of frames */ + int64_t merge_count_total; /**< Total number of merges */ + int64_t eval_comp_monitors_total; /**< Total number of monitor evaluations */ + int64_t rematch_count_total; /**< Total number of rematches */ + + int64_t id_create_total; /**< Total number of times a new id was created */ + int64_t id_delete_total; /**< Total number of times an id was deleted */ + int64_t table_create_total; /**< Total number of times a table was created */ + int64_t table_delete_total; /**< Total number of times a table was deleted */ + int64_t pipeline_build_count_total; /**< Total number of pipeline builds */ + int64_t systems_ran_total; /**< Total number of systems ran */ + int64_t observers_ran_total; /**< Total number of times observer was invoked */ + int64_t queries_ran_total; /**< Total number of times a query was evaluated */ + + int32_t tag_id_count; /**< Number of tag (no data) ids in the world */ + int32_t component_id_count; /**< Number of component (data) ids in the world */ + int32_t pair_id_count; /**< Number of pair ids in the world */ + + int32_t table_count; /**< Number of tables */ + + uint32_t creation_time; /**< Time when world was created */ + + /* -- Command counts -- */ + struct { + int64_t add_count; /**< Add commands processed */ + int64_t remove_count; /**< Remove commands processed */ + int64_t delete_count; /**< Delete commands processed */ + int64_t clear_count; /**< Clear commands processed */ + int64_t set_count; /**< Set commands processed */ + int64_t ensure_count; /**< Ensure/emplace commands processed */ + int64_t modified_count; /**< Modified commands processed */ + int64_t discard_count; /**< Commands discarded, happens when entity is no longer alive when running the command */ + int64_t event_count; /**< Enqueued custom events */ + int64_t other_count; /**< Other commands processed */ + int64_t batched_entity_count; /**< Entities for which commands were batched */ + int64_t batched_command_count; /**< Commands batched */ + } cmd; /**< Command statistics. */ + + const char *name_prefix; /**< Value set by ecs_set_name_prefix(). Used + * to remove library prefixes of symbol + * names (such as `Ecs`, `ecs_`) when + * registering them as names. */ +} ecs_world_info_t; -/** Component for iterable entities */ -typedef ecs_iterable_t EcsIterable; +/** Type that contains information about a query group. */ +typedef struct ecs_query_group_info_t { + uint64_t id; + int32_t match_count; /**< How often tables have been matched/unmatched */ + int32_t table_count; /**< Number of tables in group */ + void *ctx; /**< Group context, returned by on_group_create */ +} ecs_query_group_info_t; /** @} */ - /** - * @defgroup misc_types Miscalleneous types + * @defgroup builtin_components Builtin component types. + * Types that represent builtin components. + * * @{ */ -/** Type that contains information about the world. */ -typedef struct ecs_world_info_t { - ecs_entity_t last_component_id; /* Last issued component entity id */ - ecs_entity_t last_id; /* Last issued entity id */ - ecs_entity_t min_id; /* First allowed entity id */ - ecs_entity_t max_id; /* Last allowed entity id */ - - FLECS_FLOAT delta_time_raw; /* Raw delta time (no time scaling) */ - FLECS_FLOAT delta_time; /* Time passed to or computed by ecs_progress */ - FLECS_FLOAT time_scale; /* Time scale applied to delta_time */ - FLECS_FLOAT target_fps; /* Target fps */ - FLECS_FLOAT frame_time_total; /* Total time spent processing a frame */ - float system_time_total; /* Total time spent in systems */ - float merge_time_total; /* Total time spent in merges */ - FLECS_FLOAT world_time_total; /* Time elapsed in simulation */ - FLECS_FLOAT world_time_total_raw; /* Time elapsed in simulation (no scaling) */ - - int32_t frame_count_total; /* Total number of frames */ - int32_t merge_count_total; /* Total number of merges */ - - int32_t id_create_total; /* Total number of times a new id was created */ - int32_t id_delete_total; /* Total number of times an id was deleted */ - int32_t table_create_total; /* Total number of times a table was created */ - int32_t table_delete_total; /* Total number of times a table was deleted */ - int32_t pipeline_build_count_total; /* Total number of pipeline builds */ - int32_t systems_ran_frame; /* Total number of systems ran in last frame */ - - int32_t id_count; /* Number of ids in the world (excluding wildcards) */ - int32_t tag_id_count; /* Number of tag (no data) ids in the world */ - int32_t component_id_count; /* Number of component (data) ids in the world */ - int32_t pair_id_count; /* Number of pair ids in the world */ - int32_t wildcard_id_count; /* Number of wildcard ids */ - - int32_t table_count; /* Number of tables */ - int32_t tag_table_count; /* Number of tag-only tables */ - int32_t trivial_table_count; /* Number of tables with trivial components (no lifecycle callbacks) */ - int32_t empty_table_count; /* Number of tables without entities */ - int32_t table_record_count; /* Total number of table records (entries in table caches) */ - int32_t table_storage_count; /* Total number of table storages */ -} ecs_world_info_t; +/** A (string) identifier. Used as pair with #EcsName and #EcsSymbol tags */ +typedef struct EcsIdentifier { + char *value; /**< Identifier string */ + ecs_size_t length; /**< Length of identifier */ + uint64_t hash; /**< Hash of current value */ + uint64_t index_hash; /**< Hash of existing record in current index */ + ecs_hashmap_t *index; /**< Current index */ +} EcsIdentifier; + +/** Component information. */ +typedef struct EcsComponent { + ecs_size_t size; /**< Component size */ + ecs_size_t alignment; /**< Component alignment */ +} EcsComponent; + +/** Component for storing a poly object */ +typedef struct EcsPoly { + ecs_poly_t *poly; /**< Pointer to poly object */ +} EcsPoly; + +/** When added to an entity this informs serialization formats which component + * to use when a value is assigned to an entity without specifying the + * component. This is intended as a hint, serialization formats are not required + * to use it. Adding this component does not change the behavior of core ECS + * operations. */ +typedef struct EcsDefaultChildComponent { + ecs_id_t component; /**< Default component id. */ +} EcsDefaultChildComponent; + +/* Non-fragmenting ChildOf relationship. */ +typedef struct EcsParent { + ecs_entity_t value; +} EcsParent; + +/* Component with data to instantiate a non-fragmenting tree. */ +typedef struct { + const char *child_name; /* Name of prefab child */ + ecs_table_t *table; /* Table in which child will be stored */ + uint32_t child; /* Prefab child entity (without generation) */ + int32_t parent_index; /* Index into children vector */ +} ecs_tree_spawner_child_t; + +typedef struct { + ecs_vec_t children; /* vector */ +} ecs_tree_spawner_t; + +typedef struct EcsTreeSpawner { + /* Tree instantiation cache, indexed by depth. Tables will have a + * (ParentDepth, depth) pair indicating the hierarchy depth. This means that + * for different depths, the tables the children are created in will also be + * different. Caching tables for different depths therefore speeds up + * instantiating trees even when the top level entity is not at the root. */ + ecs_tree_spawner_t data[FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE]; +} EcsTreeSpawner; +/** @} */ /** @} */ /* Only include deprecated definitions if deprecated addon is required */ #ifdef FLECS_DEPRECATED /** - * @file deprecated.h + * @file addons/deprecated.h * @brief The deprecated addon contains deprecated operations. */ @@ -3761,332 +5483,424 @@ extern "C" { #endif - /** - * @defgroup type_roles Type Roles + * @defgroup api_constants API Constants + * Public API constants. + * * @{ */ -/* Type roles are used to indicate the role of an entity in a type. If no flag - * is specified, the entity is interpreted as a regular component or tag. Flags - * are added to an entity by using a bitwise OR (|). */ - -/** Role bit added to roles to differentiate between roles and generations */ -#define ECS_ROLE (1ull << 63) - -/** Cases are used to switch between mutually exclusive components */ -FLECS_API extern const ecs_id_t ECS_CASE; - -/** Switches allow for fast switching between mutually exclusive components */ -FLECS_API extern const ecs_id_t ECS_SWITCH; +/** + * @defgroup id_flags Component id flags. + * Id flags are bits that can be set on an id (ecs_id_t). + * + * @{ + */ -/** The PAIR role indicates that the entity is a pair identifier. */ +/** Indicates that the id is a pair. */ FLECS_API extern const ecs_id_t ECS_PAIR; -/** Enforce ownership of a component */ -FLECS_API extern const ecs_id_t ECS_OVERRIDE; +/** Automatically override component when it is inherited */ +FLECS_API extern const ecs_id_t ECS_AUTO_OVERRIDE; -/** Track whether component is enabled or not */ -FLECS_API extern const ecs_id_t ECS_DISABLED; +/** Adds bitset to storage which allows component to be enabled/disabled */ +FLECS_API extern const ecs_id_t ECS_TOGGLE; -/** @} */ +/** Indicates that the target of a pair is an integer value. */ +FLECS_API extern const ecs_id_t ECS_VALUE_PAIR; +/** @} */ /** - * @defgroup builtin_tags Builtin components & tags + * @defgroup builtin_tags Builtin component ids. * @{ */ -/** Builtin component ids */ +/* Builtin component ids */ + +/** Component component id. */ FLECS_API extern const ecs_entity_t ecs_id(EcsComponent); -FLECS_API extern const ecs_entity_t ecs_id(EcsComponentLifecycle); -FLECS_API extern const ecs_entity_t ecs_id(EcsType); + +/** Identifier component id. */ FLECS_API extern const ecs_entity_t ecs_id(EcsIdentifier); -FLECS_API extern const ecs_entity_t ecs_id(EcsTrigger); -FLECS_API extern const ecs_entity_t ecs_id(EcsQuery); -FLECS_API extern const ecs_entity_t ecs_id(EcsObserver); -FLECS_API extern const ecs_entity_t ecs_id(EcsIterable); -/* System module component ids */ -FLECS_API extern const ecs_entity_t ecs_id(EcsSystem); +/** Poly component id. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsPoly); + +/** Parent component id. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsParent); + +/** Component with data to instantiate a tree. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsTreeSpawner); + +/** DefaultChildComponent component id. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsDefaultChildComponent); + +/** Relationship storing the entity's depth in a non-fragmenting hierarchy. */ +FLECS_API extern const ecs_entity_t EcsParentDepth; + +/** Tag added to queries. */ +FLECS_API extern const ecs_entity_t EcsQuery; + +/** Tag added to observers. */ +FLECS_API extern const ecs_entity_t EcsObserver; + +/** Tag added to systems. */ +FLECS_API extern const ecs_entity_t EcsSystem; + +/** TickSource component id. */ FLECS_API extern const ecs_entity_t ecs_id(EcsTickSource); /** Pipeline module component ids */ FLECS_API extern const ecs_entity_t ecs_id(EcsPipelineQuery); -/** Timer module component ids */ +/** Timer component id. */ FLECS_API extern const ecs_entity_t ecs_id(EcsTimer); + +/** RateFilter component id. */ FLECS_API extern const ecs_entity_t ecs_id(EcsRateFilter); /** Root scope for builtin flecs entities */ FLECS_API extern const ecs_entity_t EcsFlecs; -/* Core module scope */ +/** Core module scope */ FLECS_API extern const ecs_entity_t EcsFlecsCore; -/* Hidden module scope. This is used as the default location for (anonymous) - * entities associated with triggers & observers to keeps other scopes clean. */ -FLECS_API extern const ecs_entity_t EcsFlecsHidden; - -/* Entity associated with world (used for "attaching" components to world) */ +/** Entity associated with world (used for "attaching" components to world) */ FLECS_API extern const ecs_entity_t EcsWorld; -/* Wildcard entity ("*"). Matches any id, returns all matches. */ +/** Wildcard entity ("*"). Matches any id, returns all matches. */ FLECS_API extern const ecs_entity_t EcsWildcard; -/* Any entity ("_"). Matches any id, returns only the first. */ +/** Any entity ("_"). Matches any id, returns only the first. */ FLECS_API extern const ecs_entity_t EcsAny; -/* This entity ("."). Used in expressions to indicate This variable */ +/** This entity. Default source for queries. */ FLECS_API extern const ecs_entity_t EcsThis; -/* Variable entity ("$"). Used in expressions to prefix variable names */ +/** Variable entity ("$"). Used in expressions to prefix variable names */ FLECS_API extern const ecs_entity_t EcsVariable; -/* Marks a relationship as transitive. - * Behavior: +/** Marks a relationship as transitive. + * Behavior: + * + * @code * if R(X, Y) and R(Y, Z) then R(X, Z) + * @endcode */ FLECS_API extern const ecs_entity_t EcsTransitive; -/* Marks a relatoinship as reflexive. - * Behavior: +/** Marks a relationship as reflexive. + * Behavior: + * + * @code * R(X, X) == true + * @endcode */ FLECS_API extern const ecs_entity_t EcsReflexive; -/** Ensures that entity/component cannot be used as object in IsA relation. - * Final can improve the performance of rule-based queries, as they will not - * attempt to substitute a final component with its subsets. - * - * Behavior: +/** Ensures that entity/component cannot be used as target in `IsA` relationship. + * Final can improve the performance of queries as they will not attempt to + * substitute a final component with its subsets. + * + * Behavior: + * + * @code * if IsA(X, Y) and Final(Y) throw error + * @endcode */ FLECS_API extern const ecs_entity_t EcsFinal; -/** Ensures that component is never inherited from an IsA object. - * - * Behavior: - * if DontInherit(X) and X(B) and IsA(A, B) then X(A) is false. +/** Mark component as inheritable. + * This is the opposite of Final. This trait can be used to enforce that queries + * take into account component inheritance before inheritance (IsA) + * relationships are added with the component as target. */ +FLECS_API extern const ecs_entity_t EcsInheritable; + +/** Relationship that specifies component inheritance behavior. */ +FLECS_API extern const ecs_entity_t EcsOnInstantiate; + +/** Override component on instantiate. + * This will copy the component from the base entity `(IsA target)` to the + * instance. The base component will never be inherited from the prefab. */ +FLECS_API extern const ecs_entity_t EcsOverride; + +/** Inherit component on instantiate. + * This will inherit (share) the component from the base entity `(IsA target)`. + * The component can be manually overridden by adding it to the instance. */ +FLECS_API extern const ecs_entity_t EcsInherit; + +/** Never inherit component on instantiate. + * This will not copy or share the component from the base entity `(IsA target)`. + * When the component is added to an instance, its value will never be copied + * from the base entity. */ FLECS_API extern const ecs_entity_t EcsDontInherit; -/* Marks relationship as commutative. +/** Marks relationship as commutative. * Behavior: + * + * @code * if R(X, Y) then R(Y, X) + * @endcode */ FLECS_API extern const ecs_entity_t EcsSymmetric; -/* Can be added to relation to indicate that the relationship can only occur - * once on an entity. Adding a 2nd instance will replace the 1st. +/** Can be added to relationship to indicate that the relationship can only occur + * once on an entity. Adding a 2nd instance will replace the 1st. * * Behavior: + * + * @code * R(X, Y) + R(X, Z) = R(X, Z) + * @endcode */ FLECS_API extern const ecs_entity_t EcsExclusive; -/* Marks a relation as acyclic. Acyclic relations may not form cycles. */ +/** Marks a relationship as acyclic. Acyclic relationships may not form cycles. */ FLECS_API extern const ecs_entity_t EcsAcyclic; -/* Ensure that a component always is added together with another component. - * +/** Marks a relationship as traversable. Traversable relationships may be + * traversed with "up" queries. Traversable relationships are acyclic. */ +FLECS_API extern const ecs_entity_t EcsTraversable; + +/** Ensure that a component always is added together with another component. + * * Behavior: + * + * @code * If With(R, O) and R(X) then O(X) * If With(R, O) and R(X, Y) then O(X, Y) + * @endcode */ FLECS_API extern const ecs_entity_t EcsWith; -/* Ensure that relationship target is child of specified entity. - * +/** Ensure that relationship target is child of specified entity. + * * Behavior: + * + * @code * If OneOf(R, O) and R(X, Y), Y must be a child of O * If OneOf(R) and R(X, Y), Y must be a child of R + * @endcode */ FLECS_API extern const ecs_entity_t EcsOneOf; -/* Can be added to relation to indicate that it should never hold data, even - * when it or the relation object is a component. */ -FLECS_API extern const ecs_entity_t EcsTag; - -/* Tag to indicate name identifier */ -FLECS_API extern const ecs_entity_t EcsName; +/** Mark a component as toggleable with ecs_enable_id(). */ +FLECS_API extern const ecs_entity_t EcsCanToggle; -/* Tag to indicate symbol identifier */ -FLECS_API extern const ecs_entity_t EcsSymbol; +/** Can be added to components to indicate it is a trait. Traits are components + * and/or tags that are added to other components to modify their behavior. + */ +FLECS_API extern const ecs_entity_t EcsTrait; -/* Tag to indicate alias identifier */ -FLECS_API extern const ecs_entity_t EcsAlias; - -/* Used to express parent-child relations. */ -FLECS_API extern const ecs_entity_t EcsChildOf; - -/* Used to express is-a relations. An IsA relation indicates that the subject is - * a subset of the relation object. For example: - * ecs_add_pair(world, Freighter, EcsIsA, SpaceShip); +/** Ensure that an entity is always used in pair as relationship. * - * Here the Freighter is considered a subset of SpaceShip, meaning that every - * entity that has Freighter also implicitly has SpaceShip. + * Behavior: * - * The subject of the relation (Freighter) inherits all components from any IsA - * object (SpaceShip). If SpaceShip has a component "MaxSpeed", this component - * will also appear on Freighter after adding (IsA, SpaceShip) to Freighter. + * @code + * e.add(R) panics + * e.add(X, R) panics, unless X has the "Trait" trait + * @endcode + */ +FLECS_API extern const ecs_entity_t EcsRelationship; + +/** Ensure that an entity is always used in pair as target. * - * The IsA relation is transitive. This means that if SpaceShip IsA Machine, - * then Freigther is also a Machine. As a result, Freighter also inherits all - * components from Machine, just as it does from SpaceShip. + * Behavior: * - * Queries/filters may implicitly substitute predicates, subjects and objects - * with their IsA super/subsets. This behavior can be controlled by the "set" - * member of a query term. + * @code + * e.add(T) panics + * e.add(T, X) panics + * @endcode */ +FLECS_API extern const ecs_entity_t EcsTarget; + +/** Can be added to relationship to indicate that it should never hold data, + * even when it or the relationship target is a component. */ +FLECS_API extern const ecs_entity_t EcsPairIsTag; + +/** Tag to indicate name identifier */ +FLECS_API extern const ecs_entity_t EcsName; + +/** Tag to indicate symbol identifier */ +FLECS_API extern const ecs_entity_t EcsSymbol; + +/** Tag to indicate alias identifier */ +FLECS_API extern const ecs_entity_t EcsAlias; + +/** Used to express parent-child relationships. */ +FLECS_API extern const ecs_entity_t EcsChildOf; + +/** Used to express inheritance relationships. */ FLECS_API extern const ecs_entity_t EcsIsA; -/* Tag added to module entities */ -FLECS_API extern const ecs_entity_t EcsModule; +/** Used to express dependency relationships */ +FLECS_API extern const ecs_entity_t EcsDependsOn; + +/** Used to express a slot (used with prefab inheritance) */ +FLECS_API extern const ecs_entity_t EcsSlotOf; + +/** Tag that when added to a parent ensures stable order of ecs_children result. */ +FLECS_API extern const ecs_entity_t EcsOrderedChildren; -/* Tag to indicate an entity/component/system is private to a module */ -FLECS_API extern const ecs_entity_t EcsPrivate; +/** Tag added to module entities */ +FLECS_API extern const ecs_entity_t EcsModule; -/* Tag added to prefab entities. Any entity with this tag is automatically - * ignored by filters/queries, unless EcsPrefab is explicitly added. */ +/** Tag added to prefab entities. Any entity with this tag is automatically + * ignored by queries, unless #EcsPrefab is explicitly queried for. */ FLECS_API extern const ecs_entity_t EcsPrefab; -/* When this tag is added to an entity it is skipped by all queries/filters */ +/** When this tag is added to an entity it is skipped by queries, unless + * #EcsDisabled is explicitly queried for. */ FLECS_API extern const ecs_entity_t EcsDisabled; -/* Event. Triggers when an id (component, tag, pair) is added to an entity */ +/** Trait added to entities that should never be returned by queries. Reserved + * for internal entities that have special meaning to the query engine, such as + * #EcsThis, #EcsWildcard, #EcsAny. */ +FLECS_API extern const ecs_entity_t EcsNotQueryable; + +/** Event that triggers when an id is added to an entity */ FLECS_API extern const ecs_entity_t EcsOnAdd; -/* Event. Triggers when an id (component, tag, pair) is removed from an entity */ +/** Event that triggers when an id is removed from an entity */ FLECS_API extern const ecs_entity_t EcsOnRemove; -/* Event. Triggers when a component is set for an entity */ +/** Event that triggers when a component is set for an entity */ FLECS_API extern const ecs_entity_t EcsOnSet; -/* Event. Triggers when a component is unset for an entity */ -FLECS_API extern const ecs_entity_t EcsUnSet; - -/* Event. Exactly-once trigger for when an entity matches/unmatches a filter */ +/** Event that triggers observer when an entity starts/stops matching a query */ FLECS_API extern const ecs_entity_t EcsMonitor; -/* Event. Triggers when an entity is deleted. - * Also used as relation for defining cleanup behavior, see: - * https://github.com/SanderMertens/flecs/blob/master/docs/Relations.md#relation-cleanup-properties - */ +/** Event that triggers when a table is created. */ +FLECS_API extern const ecs_entity_t EcsOnTableCreate; + +/** Event that triggers when a table is deleted. */ +FLECS_API extern const ecs_entity_t EcsOnTableDelete; + +/** Relationship used for specifying cleanup behavior. */ FLECS_API extern const ecs_entity_t EcsOnDelete; -/* Event. Triggers when a table is created. */ -// FLECS_API extern const ecs_entity_t EcsOnCreateTable; +/** Relationship used to define what should happen when a target entity (second + * element of a pair) is deleted. */ +FLECS_API extern const ecs_entity_t EcsOnDeleteTarget; + +/** Remove cleanup policy. Must be used as target in pair with #EcsOnDelete or + * #EcsOnDeleteTarget. */ +FLECS_API extern const ecs_entity_t EcsRemove; + +/** Delete cleanup policy. Must be used as target in pair with #EcsOnDelete or + * #EcsOnDeleteTarget. */ +FLECS_API extern const ecs_entity_t EcsDelete; + +/** Panic cleanup policy. Must be used as target in pair with #EcsOnDelete or + * #EcsOnDeleteTarget. */ +FLECS_API extern const ecs_entity_t EcsPanic; + +/** Mark component as singleton. Singleton components may only be added to + * themselves. */ +FLECS_API extern const ecs_entity_t EcsSingleton; -/* Event. Triggers when a table is deleted. */ -// FLECS_API extern const ecs_entity_t EcsOnDeleteTable; +/** Mark component as sparse */ +FLECS_API extern const ecs_entity_t EcsSparse; -/* Event. Triggers when a table becomes empty (doesn't trigger on creation). */ -FLECS_API extern const ecs_entity_t EcsOnTableEmpty; +/** Mark component as non-fragmenting */ +FLECS_API extern const ecs_entity_t EcsDontFragment; -/* Event. Triggers when a table becomes non-empty. */ -FLECS_API extern const ecs_entity_t EcsOnTableFill; +/** Marker used to indicate `$var == ...` matching in queries. */ +FLECS_API extern const ecs_entity_t EcsPredEq; -/* Event. Triggers when a trigger is created. */ -// FLECS_API extern const ecs_entity_t EcsOnCreateTrigger; +/** Marker used to indicate `$var == "name"` matching in queries. */ +FLECS_API extern const ecs_entity_t EcsPredMatch; -/* Event. Triggers when a trigger is deleted. */ -// FLECS_API extern const ecs_entity_t EcsOnDeleteTrigger; +/** Marker used to indicate `$var ~= "pattern"` matching in queries. */ +FLECS_API extern const ecs_entity_t EcsPredLookup; -/* Event. Triggers when observable is deleted. */ -// FLECS_API extern const ecs_entity_t EcsOnDeleteObservable; +/** Marker used to indicate the start of a scope (`{`) in queries. */ +FLECS_API extern const ecs_entity_t EcsScopeOpen; -/* Event. Triggers when lifecycle methods for a component are registered */ -// FLECS_API extern const ecs_entity_t EcsOnComponentLifecycle; +/** Marker used to indicate the end of a scope (`}`) in queries. */ +FLECS_API extern const ecs_entity_t EcsScopeClose; -/* Relationship used to define what should happen when an entity is deleted that - * is added to other entities. For details see: - * https://github.com/SanderMertens/flecs/blob/master/docs/Relations.md#relation-cleanup-properties +/** Tag used to indicate query is empty. + * This tag is removed automatically when a query becomes non-empty, and is not + * automatically re-added when it becomes empty. */ -FLECS_API extern const ecs_entity_t EcsOnDeleteObject; +FLECS_API extern const ecs_entity_t EcsEmpty; -/* Specifies that a component/relation/object of relation should be removed when - * it is deleted. Must be combined with EcsOnDelete or EcsOnDeleteObject. */ -FLECS_API extern const ecs_entity_t EcsRemove; +FLECS_API extern const ecs_entity_t ecs_id(EcsPipeline); /**< Pipeline component id. */ +FLECS_API extern const ecs_entity_t EcsOnStart; /**< OnStart pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsPreFrame; /**< PreFrame pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsOnLoad; /**< OnLoad pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsPostLoad; /**< PostLoad pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsPreUpdate; /**< PreUpdate pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsOnUpdate; /**< OnUpdate pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsOnValidate; /**< OnValidate pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsPostUpdate; /**< PostUpdate pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsPreStore; /**< PreStore pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsOnStore; /**< OnStore pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsPostFrame; /**< PostFrame pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsPhase; /**< Phase pipeline phase. */ -/* Specifies that entities with a component/relation/object of relation should - * be deleted when the component/relation/object of relation is deleted. Must be - * combined with EcsOnDelete or EcsOnDeleteObject. */ -FLECS_API extern const ecs_entity_t EcsDelete; +FLECS_API extern const ecs_entity_t EcsConstant; /**< Tag added to enum/bitmask constants. */ -/* Specifies that whenever a component/relation/object of relation is deleted an - * error should be thrown. Must be combined with EcsOnDelete or - * EcsOnDeleteObject. */ -FLECS_API extern const ecs_entity_t EcsThrow; - -/* Used like (EcsDefaultChildComponent, Component). When added to an entity, - * this informs serialization formats which component to use when a value is - * assigned to an entity without specifying the component. This is intended as - * a hint, serialization formats are not required to use it. Adding this - * component does not change the behavior of core ECS operations. */ -FLECS_API extern const ecs_entity_t EcsDefaultChildComponent; - -/* System module tags */ -FLECS_API extern const ecs_entity_t EcsInactive; - -/* Pipeline module tags */ -FLECS_API extern const ecs_entity_t EcsPipeline; -FLECS_API extern const ecs_entity_t EcsPreFrame; -FLECS_API extern const ecs_entity_t EcsOnLoad; -FLECS_API extern const ecs_entity_t EcsPostLoad; -FLECS_API extern const ecs_entity_t EcsPreUpdate; -FLECS_API extern const ecs_entity_t EcsOnUpdate; -FLECS_API extern const ecs_entity_t EcsOnValidate; -FLECS_API extern const ecs_entity_t EcsPostUpdate; -FLECS_API extern const ecs_entity_t EcsPreStore; -FLECS_API extern const ecs_entity_t EcsOnStore; -FLECS_API extern const ecs_entity_t EcsPostFrame; - -/* Value used to quickly check if component is builtin. This is used to quickly - * filter out tables with builtin components (for example for ecs_delete) */ -#define EcsLastInternalComponentId (ecs_id(EcsSystem)) - -/* The first user-defined component starts from this id. Ids up to this number - * are reserved for builtin components */ -#define EcsFirstUserComponentId (32) +/** Value used to quickly check if component is builtin. This is used to quickly + * filter out tables with builtin components (for example for ecs_delete()) */ +#define EcsLastInternalComponentId (ecs_id(EcsTreeSpawner)) -/* The first user-defined entity starts from this id. Ids up to this number +/** The first user-defined component starts from this id. Ids up to this number * are reserved for builtin components */ -#define EcsFirstUserEntityId (ECS_HI_COMPONENT_ID + 128) +#define EcsFirstUserComponentId (8) + +/** The first user-defined entity starts from this id. Ids up to this number + * are reserved for builtin entities */ +#define EcsFirstUserEntityId (FLECS_HI_COMPONENT_ID + 128) + +/* When visualized the reserved id ranges look like this: + * - [1..8]: Builtin components + * - [9..FLECS_HI_COMPONENT_ID]: Low ids reserved for application components + * - [FLECS_HI_COMPONENT_ID + 1..EcsFirstUserEntityId]: Builtin entities + */ /** @} */ +/** @} */ +/** + * @defgroup world_api World + * Functions for working with `ecs_world_t`. + * + * @{ + */ /** - * @defgroup world_api World API + * @defgroup world_creation_deletion Creation & Deletion * @{ */ /** Create a new world. - * A world manages all the ECS data and supporting infrastructure. Applications - * must have at least one world. Entities, component and system handles are - * local to a world and should not be shared between worlds. - * - * This operation creates a world with all builtin modules loaded. + * This operation automatically imports modules from addons Flecs has been built + * with, except when the module specifies otherwise. * - * @return A new world object + * @return A new world */ FLECS_API ecs_world_t* ecs_init(void); -/** Same as ecs_init, but with minimal set of modules loaded. +/** Create a new world with just the core module. + * Same as ecs_init(), but doesn't import modules from addons. This operation is + * faster than ecs_init() and results in less memory utilization. * - * @return A new world object + * @return A new tiny world */ FLECS_API ecs_world_t* ecs_mini(void); /** Create a new world with arguments. - * Same as ecs_init, but allows passing in command line arguments. These can be - * used to dynamically enable flecs features to an application. Currently these - * arguments are not used. + * Same as ecs_init(), but allows passing in command line arguments. Command line + * arguments are used to: + * - automatically derive the name of the application from argv[0] * - * @return A new world object + * @return A new world */ FLECS_API ecs_world_t* ecs_init_w_args( @@ -4104,6 +5918,8 @@ int ecs_fini( ecs_world_t *world); /** Returns whether the world is being deleted. + * This operation can be used in callbacks like type hooks or observers to + * detect if they are invoked while the world is being deleted. * * @param world The world. * @return True if being deleted, false if not. @@ -4115,7 +5931,7 @@ bool ecs_is_fini( /** Register action to be executed when world is destroyed. * Fini actions are typically used when a module needs to clean up before a * world shuts down. - * + * * @param world The world. * @param action The function to execute. * @param ctx Userdata to pass to the function */ @@ -4125,207 +5941,129 @@ void ecs_atfini( ecs_fini_action_t action, void *ctx); -/** Register action to be executed once after frame. - * Post frame actions are typically used for calling operations that cannot be - * invoked during iteration, such as changing the number of threads. +/** Type returned by ecs_get_entities(). */ +typedef struct ecs_entities_t { + const ecs_entity_t *ids; /**< Array with all entity ids in the world. */ + int32_t count; /**< Total number of entity ids. */ + int32_t alive_count; /**< Number of alive entity ids. */ +} ecs_entities_t; + +/** Return entity identifiers in world. + * This operation returns an array with all entity ids that exist in the world. + * Note that the returned array will change and may get invalidated as a result + * of entity creation & deletion. + * + * To iterate all alive entity ids, do: + * @code + * ecs_entities_t entities = ecs_get_entities(world); + * for (int i = 0; i < entities.alive_count; i ++) { + * ecs_entity_t id = entities.ids[i]; + * } + * @endcode + * + * To iterate not-alive ids, do: + * @code + * for (int i = entities.alive_count + 1; i < entities.count; i ++) { + * ecs_entity_t id = entities.ids[i]; + * } + * @endcode + * + * The returned array does not need to be freed. Mutating the returned array + * will return in undefined behavior (and likely crashes). * * @param world The world. - * @param action The function to execute. - * @param ctx Userdata to pass to the function */ -FLECS_API -void ecs_run_post_frame( - ecs_world_t *world, - ecs_fini_action_t action, - void *ctx); - -/** Signal exit - * This operation signals that the application should quit. It will cause - * ecs_progress to return false. - * - * @param world The world to quit. - */ -FLECS_API -void ecs_quit( - ecs_world_t *world); - -/** Return whether a quit has been signaled. - * - * @param world The world. - */ -FLECS_API -bool ecs_should_quit( - const ecs_world_t *world); - -/** Register ctor, dtor, copy & move actions for component. - * - * @param world The world. - * @param id The component id for which to register the actions - * @param actions Type that contains the component actions. - */ -FLECS_API -void ecs_set_component_actions_w_id( - ecs_world_t *world, - ecs_id_t id, - EcsComponentLifecycle *actions); - -/** Set a world context. - * This operation allows an application to register custom data with a world - * that can be accessed anywhere where the application has the world object. - * - * @param world The world. - * @param ctx A pointer to a user defined structure. - */ -FLECS_API -void ecs_set_context( - ecs_world_t *world, - void *ctx); - -/** Get the world context. - * This operation retrieves a previously set world context. - * - * @param world The world. - * @return The context set with ecs_set_context. If no context was set, the - * function returns NULL. + * @return Struct with entity id array. */ FLECS_API -void* ecs_get_context( +ecs_entities_t ecs_get_entities( const ecs_world_t *world); -/** Get world info. +/** Get flags set on the world. + * This operation returns the internal flags (see api_flags.h) that are + * set on the world. * * @param world The world. - * @return Pointer to the world info. This pointer will remain valid for as long - * as the world is valid. + * @return Flags set on the world. */ FLECS_API -const ecs_world_info_t* ecs_get_world_info( +ecs_flags32_t ecs_world_get_flags( const ecs_world_t *world); -/** Dimension the world for a specified number of entities. - * This operation will preallocate memory in the world for the specified number - * of entities. Specifying a number lower than the current number of entities in - * the world will have no effect. - * - * @param world The world. - * @param entity_count The number of entities to preallocate. - */ -FLECS_API -void ecs_dim( - ecs_world_t *world, - int32_t entity_count); - -/** Set a range for issueing new entity ids. - * This function constrains the entity identifiers returned by ecs_new to the - * specified range. This operation can be used to ensure that multiple processes - * can run in the same simulation without requiring a central service that - * coordinates issueing identifiers. - * - * If id_end is set to 0, the range is infinite. If id_end is set to a non-zero - * value, it has to be larger than id_start. If id_end is set and ecs_new is - * invoked after an id is issued that is equal to id_end, the application will - * abort. - * - * @param world The world. - * @param id_start The start of the range. - * @param id_end The end of the range. - */ -FLECS_API -void ecs_set_entity_range( - ecs_world_t *world, - ecs_entity_t id_start, - ecs_entity_t id_end); +/** @} */ -/** Sets the entity's generation in the world's sparse set. - * Used for managing manual id pools. - * - * @param world The world. - * @param entity_with_generation Entity for which to set the generation with the new generation to set. +/** + * @defgroup world_frame Frame functions + * @{ */ -FLECS_API -void ecs_set_entity_generation( - ecs_world_t *world, - ecs_entity_t entity_with_generation); -/** Enable/disable range limits. - * When an application is both a receiver of range-limited entities and a - * producer of range-limited entities, range checking needs to be temporarily - * disabled when inserting received entities. Range checking is disabled on a - * stage, so setting this value is thread safe. +/** Begin frame. + * When an application does not use ecs_progress() to control the main loop, it + * can still use Flecs features such as FPS limiting and time measurements. This + * operation needs to be invoked whenever a new frame is about to get processed. * - * @param world The world. - * @param enable True if range checking should be enabled, false to disable. - * @return The previous value. - */ -FLECS_API -bool ecs_enable_range_check( - ecs_world_t *world, - bool enable); - -/** Enable world locking while in progress. - * When locking is enabled, Flecs will lock the world while in progress. This - * allows applications to interact with the world from other threads without - * running into race conditions. + * Calls to ecs_frame_begin() must always be followed by ecs_frame_end(). * - * This is a better alternative to applications putting a lock around calls to - * ecs_progress, since ecs_progress can sleep when FPS control is enabled, - * which is time during which other threads could perform work. + * The function accepts a delta_time parameter, which will get passed to + * systems. This value is also used to compute the amount of time the function + * needs to sleep to ensure it does not exceed the target_fps, when it is set. + * When 0 is provided for delta_time, the time will be measured. * - * Locking must be enabled before applications can use the ecs_lock and - * ecs_unlock functions. Locking is turned off by default. + * This function should only be ran from the main thread. * * @param world The world. - * @param enable True if locking is to be enabled. - * @result The previous value of the setting. + * @param delta_time Time elapsed since the last frame. + * @return The provided delta_time, or measured time if 0 was provided. */ FLECS_API -bool ecs_enable_locking( +ecs_ftime_t ecs_frame_begin( ecs_world_t *world, - bool enable); + ecs_ftime_t delta_time); -/** Locks the world. - * See ecs_enable_locking for details. +/** End frame. + * This operation must be called at the end of the frame, and always after + * ecs_frame_begin(). * * @param world The world. */ FLECS_API -void ecs_lock( +void ecs_frame_end( ecs_world_t *world); -/** Unlocks the world. - * See ecs_enable_locking for details. - * +/** Register action to be executed once after frame. + * Post frame actions are typically used for calling operations that cannot be + * invoked during iteration, such as changing the number of threads. + * * @param world The world. - */ + * @param action The function to execute. + * @param ctx Userdata to pass to the function */ FLECS_API -void ecs_unlock( - ecs_world_t *world); +void ecs_run_post_frame( + ecs_world_t *world, + ecs_fini_action_t action, + void *ctx); -/** Wait until world becomes available. - * When a non-flecs thread needs to interact with the world, it should invoke - * this function to wait until the world becomes available (as in, it is not - * progressing the frame). Invoking this function guarantees that the thread - * will not starve. (as opposed to simply taking the world lock). +/** Signal exit + * This operation signals that the application should quit. It will cause + * ecs_progress() to return false. * - * An application will have to invoke ecs_end_wait after this function returns. - * - * @param world The world. + * @param world The world to quit. */ -FLECS_API -void ecs_begin_wait( +FLECS_API +void ecs_quit( ecs_world_t *world); -/** Release world after calling ecs_begin_wait. - * This operation should be invoked after invoking ecs_begin_wait, and will - * release the world back to the thread running the main loop. +/** Return whether a quit has been requested. * * @param world The world. + * @return Whether a quit has been requested. + * @see ecs_quit() */ -FLECS_API -void ecs_end_wait( - ecs_world_t *world); +FLECS_API +bool ecs_should_quit( + const ecs_world_t *world); -/** Measure frame time. - * Frame time measurements measure the total time passed in a single frame, and +/** Measure frame time. + * Frame time measurements measure the total time passed in a single frame, and * how much of that time was spent on systems and on merging. * * Frame time measurements add a small constant-time overhead to an application. @@ -4339,10 +6077,10 @@ FLECS_API void ecs_measure_frame_time( ecs_world_t *world, bool enable); -/** Measure system time. +/** Measure system time. * System time measurements measure the time spent in each system. * - * System time measurements add overhead to every system invocation and + * System time measurements add overhead to every system invocation and * therefore have a small but measurable impact on application performance. * System time measurements must be enabled before obtaining system statistics. * @@ -4351,17 +6089,17 @@ FLECS_API void ecs_measure_frame_time( */ FLECS_API void ecs_measure_system_time( ecs_world_t *world, - bool enable); + bool enable); /** Set target frames per second (FPS) for application. - * Setting the target FPS ensures that ecs_progress is not invoked faster than - * the specified FPS. When enabled, ecs_progress tracks the time passed since + * Setting the target FPS ensures that ecs_progress() is not invoked faster than + * the specified FPS. When enabled, ecs_progress() tracks the time passed since * the last invocation, and sleeps the remaining time of the frame (if any). * * This feature ensures systems are ran at a consistent interval, as well as * conserving CPU time by not running systems more often than required. * - * Note that ecs_progress only sleeps if there is time left in the frame. Both + * Note that ecs_progress() only sleeps if there is time left in the frame. Both * time spent in flecs as time spent outside of flecs are taken into * account. * @@ -4371,960 +6109,1028 @@ FLECS_API void ecs_measure_system_time( FLECS_API void ecs_set_target_fps( ecs_world_t *world, - FLECS_FLOAT fps); - -/** Get current number of threads. */ -FLECS_API -int32_t ecs_get_threads( - ecs_world_t *world); - -/** Force aperiodic actions. - * The world may delay certain operations until they are necessary for the - * application to function correctly. This may cause observable side effects - * such as delayed triggering of events, which can be inconvenient when for - * example running a test suite. - * - * This operation forces runs all aperiodic actions to run. - * - * @param world The world. - */ -FLECS_API -void ecs_force_aperiodic( - ecs_world_t *world); + ecs_ftime_t fps); -/** Cleanup empty tables. - * This operation cleans up empty tables that meet certain conditions. Having - * large amounts of empty tables does not negatively impact performance of the - * ECS, but can take up considerable amounts of memory, especially in - * applications with many components, and many components per entity. - * - * The generation specifies the minimum number of times this operation has - * to be called before an empty table is cleaned up. If a table becomes non - * empty, the generation is reset. - * - * The operation allows for both a "clear" generation and a "delete" - * generation. When the clear generation is reached, the table's - * resources are freed (like component arrays) but the table itself is not - * deleted. When the delete generation is reached, the empty table is deleted. - * - * By specifying a non-zero id the cleanup logic can be limited to tables with - * a specific (component) id. The operation will only increase the generation - * count of matching tables. - * - * The min_id_count specifies a lower bound for the number of components a table - * should have. Often the more components a table has, the more specific it is - * and therefore less likely to be reused. - * - * The time budget specifies how long the operation should take at most. +/** Set default query flags. + * Set a default value for the ecs_filter_desc_t::flags field. Default flags + * are applied in addition to the flags provided in the descriptor. For a + * list of available flags, see include/flecs/private/api_flags.h. Typical flags + * to use are: + * + * - `EcsQueryMatchEmptyTables` + * - `EcsQueryMatchDisabled` + * - `EcsQueryMatchPrefab` * * @param world The world. - * @param id Optional component filter for the tables to evaluate. - * @param clear_generation Free table data when generation > clear_generation. - * @param delete_generation Delete table when generation > delete_generation. - * @param min_id_count Minimum number of component ids the table should have. - * @param time_budget_seconds Amount of time operation is allowed to spend. - * @return Number of deleted tables. + * @param flags The query flags. */ FLECS_API -int32_t ecs_delete_empty_tables( +void ecs_set_default_query_flags( ecs_world_t *world, - ecs_id_t id, - uint16_t clear_generation, - uint16_t delete_generation, - int32_t min_id_count, - double time_budget_seconds); + ecs_flags32_t flags); /** @} */ /** - * @defgroup creating_entities Creating Entities + * @defgroup commands Commands * @{ */ -/** Create new entity id. - * This operation returns an unused entity id. +/** Begin readonly mode. + * This operation puts the world in readonly mode, which disallows mutations on + * the world. Readonly mode exists so that internal mechanisms can implement + * optimizations that certain aspects of the world to not change, while also + * providing a mechanism for applications to prevent accidental mutations in, + * for example, multithreaded applications. + * + * Readonly mode is a stronger version of deferred mode. In deferred mode + * ECS operations such as add/remove/set/delete etc. are added to a command + * queue to be executed later. In readonly mode, operations that could break + * scheduler logic (such as creating systems, queries) are also disallowed. + * + * Readonly mode itself has a single threaded and a multi threaded mode. In + * single threaded mode certain mutations on the world are still allowed, for + * example: + * - Entity liveliness operations (such as new, make_alive), so that systems are + * able to create new entities. + * - Implicit component registration, so that this works from systems + * - Mutations to supporting data structures for the evaluation of uncached + * queries (filters), so that these can be created on the fly. + * + * These mutations are safe in a single threaded applications, but for + * multithreaded applications the world needs to be entirely immutable. For this + * purpose multi threaded readonly mode exists, which disallows all mutations on + * the world. This means that in multi threaded applications, entity liveliness + * operations, implicit component registration, and on-the-fly query creation + * are not guaranteed to work. + * + * While in readonly mode, applications can still enqueue ECS operations on a + * stage. Stages are managed automatically when using the pipeline addon and + * ecs_progress(), but they can also be configured manually as shown here: + * + * @code + * // Number of stages typically corresponds with number of threads + * ecs_set_stage_count(world, 2); + * ecs_stage_t *stage = ecs_get_stage(world, 1); + * + * ecs_readonly_begin(world); + * ecs_add(world, e, Tag); // readonly assert + * ecs_add(stage, e, Tag); // OK + * @endcode + * + * When an attempt is made to perform an operation on a world in readonly mode, + * the code will throw an assert saying that the world is in readonly mode. + * + * A call to ecs_readonly_begin() must be followed up with ecs_readonly_end(). + * When ecs_readonly_end() is called, all enqueued commands from configured + * stages are merged back into the world. Calls to ecs_readonly_begin() and + * ecs_readonly_end() should always happen from a context where the code has + * exclusive access to the world. The functions themselves are not thread safe. + * + * In a typical application, a (non-exhaustive) call stack that uses + * ecs_readonly_begin() and ecs_readonly_end() will look like this: + * + * @code + * ecs_progress() + * ecs_readonly_begin() + * ecs_defer_begin() + * + * // user code + * + * ecs_readonly_end() + * ecs_defer_end() + *@endcode * - * @param world The world. - * @return The new entity id. + * @param world The world + * @param multi_threaded Whether to enable readonly/multi threaded mode. + * @return Whether world is in readonly mode. */ FLECS_API -ecs_entity_t ecs_new_id( - ecs_world_t *world); +bool ecs_readonly_begin( + ecs_world_t *world, + bool multi_threaded); -/** Create new low id. - * This operation returns a new low id. Entity ids start after the - * ECS_HI_COMPONENT_ID constant. This reserves a range of low ids for things - * like components, and allows parts of the code to optimize operations. - * - * Note that ECS_HI_COMPONENT_ID does not represent the maximum number of - * components that can be created, only the maximum number of components that - * can take advantage of these optimizations. - * - * This operation does not recycle ids. +/** End readonly mode. + * This operation ends readonly mode, and must be called after + * ecs_readonly_begin(). Operations that were deferred while the world was in + * readonly mode will be flushed. * - * @param world The world. - * @return The new component id. + * @param world The world */ FLECS_API -ecs_entity_t ecs_new_low_id( +void ecs_readonly_end( ecs_world_t *world); -/** Create new entity. - * This operation creates a new entity with a single entity in its type. The - * entity may contain type roles. This operation recycles ids. +/** Merge stage. + * This will merge all commands enqueued for a stage. * - * @param world The world. - * @param id The component id to initialize the new entity with. - * @return The new entity. + * @param stage The stage. */ FLECS_API -ecs_entity_t ecs_new_w_id( - ecs_world_t *world, - ecs_id_t id); +void ecs_merge( + ecs_world_t *stage); -/** Find or create an entity. - * This operation creates a new entity, or modifies an existing one. When a name - * is set in the ecs_entity_desc_t::name field and ecs_entity_desc_t::entity is - * not set, the operation will first attempt to find an existing entity by that - * name. If no entity with that name can be found, it will be created. +/** Defer operations until end of frame. + * When this operation is invoked while iterating, operations inbetween the + * ecs_defer_begin() and ecs_defer_end() operations are executed at the end + * of the frame. * - * If both a name and entity handle are provided, the operation will check if - * the entity name matches with the provided name. If the names do not match, - * the function will fail and return 0. - * - * If an id to a non-existing entity is provided, that entity id become alive. - * - * See the documentation of ecs_entity_desc_t for more details. + * This operation is thread safe. * * @param world The world. - * @param desc Entity init parameters. - * @return A handle to the new or existing entity, or 0 if failed. + * @return true if world changed from non-deferred mode to deferred mode. + * + * @see ecs_defer_end() + * @see ecs_is_deferred() + * @see ecs_defer_resume() + * @see ecs_defer_suspend() + * @see ecs_is_defer_suspended() */ FLECS_API -ecs_entity_t ecs_entity_init( - ecs_world_t *world, - const ecs_entity_desc_t *desc); +bool ecs_defer_begin( + ecs_world_t *world); -/** Bulk create/populate new entities. - * This operation bulk inserts a list of new or predefined entities into a - * single table. - * - * The operation does not take ownership of component arrays provided by the - * application. Components that are non-trivially copyable will be moved into - * the storage. - * - * The operation will emit OnAdd events for each added id, and OnSet events for - * each component that has been set. - * - * If no entity ids are provided by the application, the returned array of ids - * points to an internal datastructure which changes when new entities are - * created/deleted. - * - * If as a result of the operation triggers are invoked that deletes - * entities and no entity ids were provided by the application, the returned - * array of identifiers may be incorrect. To avoid this problem, an application - * can first call ecs_bulk_init to create empty entities, copy the array to one - * that is owned by the application, and then use this array to populate the - * entities. - * +/** End block of operations to defer. + * See ecs_defer_begin(). + * + * This operation is thread safe. + * * @param world The world. - * @param desc Bulk creation parameters. - * @return Array with the list of entity ids created/populated. + * @return true if world changed from deferred mode to non-deferred mode. + * + * @see ecs_defer_begin() + * @see ecs_defer_is_deferred() + * @see ecs_defer_resume() + * @see ecs_defer_suspend() */ FLECS_API -const ecs_entity_t* ecs_bulk_init( - ecs_world_t *world, - const ecs_bulk_desc_t *desc); +bool ecs_defer_end( + ecs_world_t *world); -/** Find or create a component. - * This operation creates a new component, or finds an existing one. The find or - * create behavior is the same as ecs_entity_init. - * - * When an existing component is found, the size and alignment are verified with - * the provided values. If the values do not match, the operation will fail. +/** Suspend deferring but do not flush queue. + * This operation can be used to do an undeferred operation while not flushing + * the operations in the queue. * - * See the documentation of ecs_component_desc_t for more details. + * An application should invoke ecs_defer_resume() before ecs_defer_end() is called. + * The operation may only be called when deferring is enabled. * * @param world The world. - * @param desc Component init parameters. - * @return A handle to the new or existing component, or 0 if failed. + * + * @see ecs_defer_begin() + * @see ecs_defer_end() + * @see ecs_defer_is_deferred() + * @see ecs_defer_resume() */ FLECS_API -ecs_entity_t ecs_component_init( - ecs_world_t *world, - const ecs_component_desc_t *desc); +void ecs_defer_suspend( + ecs_world_t *world); -/** Create a new type entity. - * This operation creates a new type entity, or finds an existing one. The find - * or create behavior is the same as ecs_entity_init. - * - * A type entity is an entity with the EcsType component. This component - * a pointer to an ecs_type_t, which allows for the creation of named types. - * Named types are used in a few places, such as for pipelines and filter terms - * with the EcsAndFrom or EcsOrFrom operators. +/** Resume deferring. + * See ecs_defer_suspend(). * - * When an existing type entity is found, its types are verified with the - * provided values. If the values do not match, the operation will fail. + * @param world The world. * - * See the documentation of ecs_type_desc_t for more details. + * @see ecs_defer_begin() + * @see ecs_defer_end() + * @see ecs_defer_is_deferred() + * @see ecs_defer_suspend() + */ +FLECS_API +void ecs_defer_resume( + ecs_world_t *world); + +/** Test if deferring is enabled for current stage. * * @param world The world. - * @param desc Type entity init parameters. - * @return A handle to the new or existing type, or 0 if failed. -*/ + * @return True if deferred, false if not. + * + * @see ecs_defer_begin() + * @see ecs_defer_end() + * @see ecs_defer_resume() + * @see ecs_defer_suspend() + * @see ecs_is_defer_suspended() + */ FLECS_API -ecs_entity_t ecs_type_init( - ecs_world_t *world, - const ecs_type_desc_t *desc); +bool ecs_is_deferred( + const ecs_world_t *world); -/** Create N new entities. - * This operation is the same as ecs_new_w_id, but creates N entities - * instead of one and does not recycle ids. - * +/** Test if deferring is suspended for current stage. + * * @param world The world. - * @param id The component id to create the entities with. - * @param count The number of entities to create. - * @return The first entity id of the newly created entities. + * @return True if suspended, false if not. + * + * @see ecs_defer_begin() + * @see ecs_defer_end() + * @see ecs_is_deferred() + * @see ecs_defer_resume() + * @see ecs_defer_suspend() */ FLECS_API -const ecs_entity_t* ecs_bulk_new_w_id( - ecs_world_t *world, - ecs_id_t id, - int32_t count); +bool ecs_is_defer_suspended( + const ecs_world_t *world); -/** Clone an entity - * This operation clones the components of one entity into another entity. If - * no destination entity is provided, a new entity will be created. Component - * values are not copied unless copy_value is true. +/** Configure world to have N stages. + * This initializes N stages, which allows applications to defer operations to + * multiple isolated defer queues. This is typically used for applications with + * multiple threads, where each thread gets its own queue, and commands are + * merged when threads are synchronized. + * + * Note that the ecs_set_threads() function already creates the appropriate + * number of stages. The ecs_set_stage_count() operation is useful for applications + * that want to manage their own stages and/or threads. * * @param world The world. - * @param dst The entity to copy the components to. - * @param src The entity to copy the components from. - * @param copy_value If true, the value of components will be copied to dst. - * @return The destination entity. + * @param stages The number of stages. */ FLECS_API -ecs_entity_t ecs_clone( +void ecs_set_stage_count( ecs_world_t *world, - ecs_entity_t dst, - ecs_entity_t src, - bool copy_value); - -/** @} */ - -/** - * @defgroup adding_removing Adding & Removing - * @{ - */ + int32_t stages); -/** Add an entity to an entity. - * This operation adds a single entity to the type of an entity. Type roles may - * be used in combination with the added entity. If the entity already has the - * entity, this operation will have no side effects. +/** Get number of configured stages. + * Return number of stages set by ecs_set_stage_count(). * * @param world The world. - * @param entity The entity. - * @param id The id to add. + * @return The number of stages used for threading. */ FLECS_API -void ecs_add_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); +int32_t ecs_get_stage_count( + const ecs_world_t *world); -/** Remove an entity from an entity. - * This operation removes a single entity from the type of an entity. Type roles - * may be used in combination with the added entity. If the entity does not have - * the entity, this operation will have no side effects. +/** Get stage-specific world pointer. + * Flecs threads can safely invoke the API as long as they have a private + * context to write to, also referred to as the stage. This function returns a + * pointer to a stage, disguised as a world pointer. + * + * Note that this function does not(!) create a new world. It simply wraps the + * existing world in a thread-specific context, which the API knows how to + * unwrap. The reason the stage is returned as an ecs_world_t is so that it + * can be passed transparently to the existing API functions, vs. having to + * create a dedicated API for threading. * * @param world The world. - * @param entity The entity. - * @param id The id to remove. + * @param stage_id The index of the stage to retrieve. + * @return A thread-specific pointer to the world. */ FLECS_API -void ecs_remove_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** @} */ - +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id); -/** - * @defgroup enabling_disabling Enabling & Disabling components. - * @{ +/** Test whether the current world is readonly. + * This function allows the code to test whether the currently used world + * is readonly or whether it allows for writing. + * + * @param world A pointer to a stage or the world. + * @return True if the world or stage is readonly. */ +FLECS_API +bool ecs_stage_is_readonly( + const ecs_world_t *world); -/** Enable or disable component. - * Enabling or disabling a component does not add or remove a component from an - * entity, but prevents it from being matched with queries. This operation can - * be useful when a component must be temporarily disabled without destroying - * its value. It is also a more performant operation for when an application - * needs to add/remove components at high frequency, as enabling/disabling is - * cheaper than a regular add or remove. +/** Create unmanaged stage. + * Create a stage whose lifecycle is not managed by the world. Must be freed + * with ecs_stage_free(). * * @param world The world. - * @param entity The entity. - * @param id The component. - * @param enable True to enable the component, false to disable. + * @return The stage. */ -FLECS_API -void ecs_enable_component_w_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - bool enable); +FLECS_API +ecs_world_t* ecs_stage_new( + ecs_world_t *world); -/** Test if component is enabled. - * Test whether a component is currently enabled or disabled. This operation - * will return true when the entity has the component and if it has not been - * disabled by ecs_enable_component. +/** Free unmanaged stage. * - * @param world The world. - * @param entity The entity. - * @param id The component. - * @return True if the component is enabled, otherwise false. + * @param stage The stage to free. */ -FLECS_API -bool ecs_is_component_enabled_w_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** @} */ - +FLECS_API +void ecs_stage_free( + ecs_world_t *stage); -/** - * @defgroup pairs Pairs - * @{ - */ - -/** Make a pair identifier. - * This function is equivalent to using the ecs_pair macro, and is added for - * convenience to make it easier for non C/C++ bindings to work with pairs. +/** Get stage id. + * The stage id can be used by an application to learn about which stage it is + * using, which typically corresponds with the worker thread id. * - * @param relation The relation of the pair. - * @param object The object of the pair. + * @param world The world. + * @return The stage id. */ FLECS_API -ecs_id_t ecs_make_pair( - ecs_entity_t relation, - ecs_entity_t object); +int32_t ecs_stage_get_id( + const ecs_world_t *world); /** @} */ - /** - * @defgroup deleting Deleting Entities and components + * @defgroup world_misc Misc * @{ */ -/** Clear all components. - * This operation will clear all components from an entity but will not delete - * the entity itself. This effectively prevents the entity id from being - * recycled. +/** Set a world context. + * This operation allows an application to register custom data with a world + * that can be accessed anywhere where the application has the world. * * @param world The world. - * @param entity The entity. + * @param ctx A pointer to a user defined structure. + * @param ctx_free A function that is invoked with ctx when the world is freed. */ FLECS_API -void ecs_clear( +void ecs_set_ctx( ecs_world_t *world, - ecs_entity_t entity); + void *ctx, + ecs_ctx_free_t ctx_free); -/** Delete an entity. - * This operation will delete an entity and all of its components. The entity id - * will be recycled. Repeatedly calling ecs_delete without ecs_new or - * ecs_new_w_id will cause a memory leak as it will cause - * the list with ids that can be recycled to grow unbounded. +/** Set a world binding context. + * Same as ecs_set_ctx() but for binding context. A binding context is intended + * specifically for language bindings to store binding specific data. * * @param world The world. - * @param entity The entity. + * @param ctx A pointer to a user defined structure. + * @param ctx_free A function that is invoked with ctx when the world is freed. */ FLECS_API -void ecs_delete( +void ecs_set_binding_ctx( ecs_world_t *world, - ecs_entity_t entity); + void *ctx, + ecs_ctx_free_t ctx_free); -/** Delete all entities with the specified id. - * This will delete all entities (tables) that have the specified id. The id - * may be a wildcard and/or a pair. - * +/** Get the world context. + * This operation retrieves a previously set world context. + * * @param world The world. - * @param id The id. + * @return The context set with ecs_set_ctx(). If no context was set, the + * function returns NULL. */ FLECS_API -void ecs_delete_with( - ecs_world_t *world, - ecs_id_t id); +void* ecs_get_ctx( + const ecs_world_t *world); -/** Remove all instances of the specified id. - * This will remove the specified id from all entities (tables). Teh id may be - * a wildcard and/or a pair. - * +/** Get the world binding context. + * This operation retrieves a previously set world binding context. + * * @param world The world. - * @param id The id. + * @return The context set with ecs_set_binding_ctx(). If no context was set, the + * function returns NULL. */ FLECS_API -void ecs_remove_all( - ecs_world_t *world, - ecs_id_t id); - -/** @} */ - - -/** - * @defgroup getting Getting Components - * @{ - */ +void* ecs_get_binding_ctx( + const ecs_world_t *world); -/** Get an immutable pointer to a component. - * This operation obtains a const pointer to the requested component. The - * operation accepts the component entity id. - * - * @param world The world. - * @param entity The entity. - * @param id The id of the component to get. - * @return The component pointer, NULL if the entity does not have the component. +/** Get build info. + * Returns information about the current Flecs build. + * + * @return A struct with information about the current Flecs build. */ FLECS_API -const void* ecs_get_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); +const ecs_build_info_t* ecs_get_build_info(void); - -/** Get an immutable reference to a component. - * This operation is similar to ecs_get_id but it stores temporary - * information in a `ecs_ref_t` value which allows subsequent lookups to be - * faster. +/** Get world info. * * @param world The world. - * @param ref Pointer to a ecs_ref_t value. Must be initialized. - * @param entity The entity. - * @param id The id of the component to get. - * @return The component pointer, NULL if the entity does not have the component. + * @return Pointer to the world info. Valid for as long as the world exists. */ FLECS_API -const void* ecs_get_ref_id( - const ecs_world_t *world, - ecs_ref_t *ref, - ecs_entity_t entity, - ecs_id_t id); +const ecs_world_info_t* ecs_get_world_info( + const ecs_world_t *world); -/** Get case for switch. - * This operation gets the current case for the specified switch. If the current - * switch is not set for the entity, the operation will return 0. +/** Dimension the world for a specified number of entities. + * This operation will preallocate memory in the world for the specified number + * of entities. Specifying a number lower than the current number of entities in + * the world will have no effect. * * @param world The world. - * @param e The entity. - * @param sw The switch for which to obtain the case. - * @return The current case for the specified switch. + * @param entity_count The number of entities to preallocate. */ FLECS_API -ecs_entity_t ecs_get_case( - const ecs_world_t *world, - ecs_entity_t e, - ecs_entity_t sw); - -/** @} */ - +void ecs_dim( + ecs_world_t *world, + int32_t entity_count); -/** - * @defgroup setting Setting Components - * @{ +/** Free unused memory. + * This operation frees allocated memory that is no longer in use by the world. + * Examples of allocations that get cleaned up are: + * - Unused pages in the entity index + * - Component columns + * - Empty tables + * + * Flecs uses allocators internally for speeding up allocations. Allocators are + * not evaluated by this function, which means that the memory reported by the + * OS may not go down. For this reason, this function is most effective when + * combined with FLECS_USE_OS_ALLOC, which disables internal allocators. + * + * @param world The world. */ +FLECS_API +void ecs_shrink( + ecs_world_t *world); -/** Get a mutable pointer to a component. - * This operation is similar to ecs_get_id but it returns a mutable - * pointer. If this operation is invoked from inside a system, the entity will - * be staged and a pointer to the staged component will be returned. +/** Set a range for issuing new entity ids. + * This function constrains the entity identifiers returned by ecs_new_w() to the + * specified range. This operation can be used to ensure that multiple processes + * can run in the same simulation without requiring a central service that + * coordinates issuing identifiers. * - * If the entity did not yet have the component, the component will be added by - * this operation. In this case the is_added out parameter will be set to true. + * If `id_end` is set to 0, the range is infinite. If `id_end` is set to a non-zero + * value, it has to be larger than `id_start`. If `id_end` is set and ecs_new() is + * invoked after an id is issued that is equal to `id_end`, the application will + * abort. * * @param world The world. - * @param entity The entity. - * @param id The entity id of the component to obtain. - * @param is_added Out parameter that returns true if the component was added. - * @return The component pointer. + * @param id_start The start of the range. + * @param id_end The end of the range. */ FLECS_API -void* ecs_get_mut_id( +void ecs_set_entity_range( ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - bool *is_added); + ecs_entity_t id_start, + ecs_entity_t id_end); -/** Emplace a component. - * Emplace is similar to get_mut except that the component constructor is not - * invoked for the returned pointer, allowing the component to be "constructed" - * directly in the storage. - * - * Emplace can only be used if the entity does not yet have the component. If - * the entity has the component, the operation will fail. +/** Enable/disable range limits. + * When an application is both a receiver of range-limited entities and a + * producer of range-limited entities, range checking needs to be temporarily + * disabled when inserting received entities. Range checking is disabled on a + * stage, so setting this value is thread safe. * * @param world The world. - * @param entity The entity. - * @param id The component to obtain. - * @return The (uninitialized) component pointer. + * @param enable True if range checking should be enabled, false to disable. + * @return The previous value. */ FLECS_API -void* ecs_emplace_id( +bool ecs_enable_range_check( ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); + bool enable); -/** Signal that a component has been modified. - * This operation allows an application to signal to Flecs that a component has - * been modified. As a result, OnSet systems will be invoked. - * - * This operation is commonly used together with ecs_get_mut. +/** Get the largest issued entity id (not counting generation). * * @param world The world. - * @param entity The entity. - * @param id The id of the component that was modified. + * @return The largest issued entity id. */ -FLECS_API -void ecs_modified_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); +FLECS_API +ecs_entity_t ecs_get_max_id( + const ecs_world_t *world); -/** Set the value of a component. - * This operation allows an application to set the value of a component. The - * operation is equivalent to calling ecs_get_mut and ecs_modified. +/** Force aperiodic actions. + * The world may delay certain operations until they are necessary for the + * application to function correctly. This may cause observable side effects + * such as delayed triggering of events, which can be inconvenient when for + * example running a test suite. * - * If the provided entity is 0, a new entity will be created. + * The flags parameter specifies which aperiodic actions to run. Specify 0 to + * run all actions. Supported flags start with 'EcsAperiodic'. Flags identify + * internal mechanisms and may change unannounced. * * @param world The world. - * @param entity The entity. - * @param id The id of the component to set. - * @param size The size of the pointer to the value. - * @param ptr The pointer to the value. - * @return The entity. A new entity if no entity was provided. + * @param flags The flags specifying which actions to run. */ FLECS_API -ecs_entity_t ecs_set_id( +void ecs_run_aperiodic( ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - size_t size, - const void *ptr); + ecs_flags32_t flags); -/** @} */ +/** Used with ecs_delete_empty_tables(). */ +typedef struct ecs_delete_empty_tables_desc_t { + /** Free table data when generation > clear_generation. */ + uint16_t clear_generation; + /** Delete table when generation > delete_generation. */ + uint16_t delete_generation; -/** - * @defgroup metadata Entity Metadata - * @{ - */ + /** Amount of time operation is allowed to spend. */ + double time_budget_seconds; +} ecs_delete_empty_tables_desc_t; -/** Test whether an entity is valid. - * Entities that are valid can be used with API functions. +/** Cleanup empty tables. + * This operation cleans up empty tables that meet certain conditions. Having + * large amounts of empty tables does not negatively impact performance of the + * ECS, but can take up considerable amounts of memory, especially in + * applications with many components, and many components per entity. + * + * The generation specifies the minimum number of times this operation has + * to be called before an empty table is cleaned up. If a table becomes non + * empty, the generation is reset. + * + * The operation allows for both a "clear" generation and a "delete" + * generation. When the clear generation is reached, the table's + * resources are freed (like component arrays) but the table itself is not + * deleted. When the delete generation is reached, the empty table is deleted. * - * An entity is valid if it is not 0 and if it is alive. If the provided id has - * a role or a pair, the contents of the role or the pair will be checked for - * validity. + * By specifying a non-zero id the cleanup logic can be limited to tables with + * a specific (component) id. The operation will only increase the generation + * count of matching tables. * - * is_valid will return true for ids that don't exist (alive or not alive). This - * allows for using ids that have never been created by ecs_new or similar. In - * this the function differs from ecs_is_alive, which will return false for - * entities that do not yet exist. + * The min_id_count specifies a lower bound for the number of components a table + * should have. Often the more components a table has, the more specific it is + * and therefore less likely to be reused. * - * The operation will return false for an id that exists and is not alive, as - * using this id with an API operation would cause it to assert. + * The time budget specifies how long the operation should take at most. * * @param world The world. - * @param e The entity. - * @return True if the entity is valid, false if the entity is not valid. + * @param desc Configuration parameters. + * @return Number of deleted tables. */ FLECS_API -bool ecs_is_valid( - const ecs_world_t *world, - ecs_entity_t e); +int32_t ecs_delete_empty_tables( + ecs_world_t *world, + const ecs_delete_empty_tables_desc_t *desc); -/** Test whether an entity is alive. - * An entity is alive when it has been returned by ecs_new (or similar) or if - * it is not empty (componentts have been explicitly added to the id). +/** Get world from poly. * - * @param world The world. - * @param e The entity. - * @return True if the entity is alive, false if the entity is not alive. + * @param poly A pointer to a poly object. + * @return The world. */ FLECS_API -bool ecs_is_alive( - const ecs_world_t *world, - ecs_entity_t e); +const ecs_world_t* ecs_get_world( + const ecs_poly_t *poly); -/** Remove generation from entity id. +/** Get entity from poly. * - * @param e The entity id. - * @return The entity id without the generation count. + * @param poly A pointer to a poly object. + * @return Entity associated with the poly object. */ FLECS_API -ecs_id_t ecs_strip_generation( - ecs_entity_t e); +ecs_entity_t ecs_get_entity( + const ecs_poly_t *poly); -/** Get alive identifier. - * In some cases an application may need to work with identifiers from which - * the generation has been stripped. A typical scenario in which this happens is - * when iterating relationships in an entity type. +/** Test if pointer is of specified type. + * Usage: * - * For example, when obtaining the parent id from a ChildOf relation, the parent - * (object part of the pair) will have been stored in a 32 bit value, which - * cannot store the entity generation. This function can retrieve the identifier - * with the current generation for that id. + * @code + * flecs_poly_is(ptr, ecs_world_t) + * @endcode * - * If the provided identifier is not alive, the function will return 0. + * This operation only works for poly types. * - * @param world The world. - * @param e The for which to obtain the current alive entity id. - * @return The alive entity id if there is one, or 0 if the id is not alive. + * @param object The object to test. + * @param type The id of the type. + * @return True if the pointer is of the specified type. */ FLECS_API -ecs_entity_t ecs_get_alive( - const ecs_world_t *world, - ecs_entity_t e); +bool flecs_poly_is_( + const ecs_poly_t *object, + int32_t type); -/** Ensure id is alive. - * This operation ensures that the provided id is alive. This is useful in - * scenarios where an application has an existing id that has not been created - * with ecs_new (such as a global constant or an id from a remote application). - * - * When this operation is successful it guarantees that the provided id exists, - * is valid and is alive. - * - * Before this operation the id must either not be alive or have a generation - * that is equal to the passed in entity. +/** Test if pointer is of specified type. + * @see flecs_poly_is_() + */ +#define flecs_poly_is(object, type)\ + flecs_poly_is_(object, type##_magic) + +/** Make a pair id. + * This function is equivalent to using the ecs_pair() macro, and is added for + * convenience to make it easier for non C/C++ bindings to work with pairs. * - * If the provided id has a non-zero generation count and the id does not exist - * in the world, the id will be created with the specified generation. + * @param first The first element of the pair of the pair. + * @param second The target of the pair. + * @return A pair id. + */ +FLECS_API +ecs_id_t ecs_make_pair( + ecs_entity_t first, + ecs_entity_t second); + +/** Begin exclusive thread access. + * This operation ensures that only the thread from which this operation is + * called can access the world. Attempts to access the world from other threads + * will panic. + * + * ecs_exclusive_access_begin() must be called in pairs with + * ecs_exclusive_access_end(). Calling ecs_exclusive_access_begin() from another + * thread without first calling ecs_exclusive_access_end() will panic. + * + * A thread name can be provided to the function to improve debug messages. The + * function does not(!) copy the thread name, which means the memory for the + * name must remain alive for as long as the thread has exclusive access. + * + * This operation should only be called once per thread. Calling it multiple + * times for the same thread will cause a panic. + * + * Note that this feature only works in builds where asserts are enabled. The + * feature requires the OS API thread_self_ callback to be set. * - * If the provided id is alive and has a generation count that does not match - * the provided id, the operation will fail. - * * @param world The world. - * @param entity The entity id to make alive. + * @param thread_name Name of the thread obtaining exclusive access. */ FLECS_API -void ecs_ensure( +void ecs_exclusive_access_begin( ecs_world_t *world, - ecs_entity_t entity); + const char *thread_name); -/** Same as ecs_ensure, but for (component) ids. - * An id can be an entity or pair, and can contain type flags. This operation - * ensures that the entity (or entities, for a pair) are alive. +/** End exclusive thread access. + * This operation should be called after ecs_exclusive_access_begin(). After + * calling this operation other threads are no longer prevented from mutating + * the world. * - * When this operation is successful it guarantees that the provided id can be - * used in operations that accept an id. + * When "lock_world" is set to true, no thread will be able to mutate the world + * until ecs_exclusive_access_begin is called again. While the world is locked + * only readonly operations are allowed. For example, ecs_get_id() is allowed, + * but ecs_get_mut_id() is not allowed. * - * Since entities in a pair do not encode their generation ids, this operation - * will not fail when an entity with non-zero generation count already exists in - * the world. + * A locked world can be unlocked by calling ecs_exclusive_access_end again with + * lock_world set to false. Note that this only works for locked worlds, if\ + * ecs_exclusive_access_end() is called on a world that has exclusive thread + * access from a different thread, a panic will happen. * - * This is different from ecs_ensure, which will fail if attempted with an id - * that has generation 0 and an entity with a non-zero generation is currently - * alive. + * This operation must be called from the same thread that called + * ecs_exclusive_access_begin(). Calling it from a different thread will cause + * a panic. * * @param world The world. - * @param id The id to make alive. + * @param lock_world When true, any mutations on the world will be blocked. */ FLECS_API -void ecs_ensure_id( +void ecs_exclusive_access_end( ecs_world_t *world, - ecs_id_t id); + bool lock_world); -/** Test whether an entity exists. - * Similar as ecs_is_alive, but ignores entity generation count. +/** @} */ + +/** @} */ + +/** + * @defgroup entities Entities + * Functions for working with `ecs_entity_t`. * - * @param world The world. - * @param entity The entity. - * @return True if the entity exists, false if the entity does not exist. + * @{ */ -FLECS_API -bool ecs_exists( - const ecs_world_t *world, - ecs_entity_t entity); -/** Get the type of an entity. +/** + * @defgroup creating_entities Creating & Deleting + * Functions for creating and deleting entities. * - * @param world The world. - * @param entity The entity. - * @return The type of the entity, NULL if the entity has no components. + * @{ */ -FLECS_API -ecs_type_t ecs_get_type( - const ecs_world_t *world, - ecs_entity_t entity); -/** Get the table of an entity. +/** Create new entity id. + * This operation returns an unused entity id. This operation is guaranteed to + * return an empty entity as it does not use values set by ecs_set_scope() or + * ecs_set_with(). * * @param world The world. - * @param entity The entity. - * @return The table of the entity, NULL if the entity has no components/tags. + * @return The new entity id. */ FLECS_API -ecs_table_t* ecs_get_table( - const ecs_world_t *world, - ecs_entity_t entity); +ecs_entity_t ecs_new( + ecs_world_t *world); -/** Get the storage table of an entity. - * The storage table of an entity has a type that only contains components, and - * excludes tags/entities/pairs that have no data. +/** Create new low id. + * This operation returns a new low id. Entity ids start after the + * FLECS_HI_COMPONENT_ID constant. This reserves a range of low ids for things + * like components, and allows parts of the code to optimize operations. + * + * Note that FLECS_HI_COMPONENT_ID does not represent the maximum number of + * components that can be created, only the maximum number of components that + * can take advantage of these optimizations. + * + * This operation is guaranteed to return an empty entity as it does not use + * values set by ecs_set_scope() or ecs_set_with(). + * + * This operation does not recycle ids. * * @param world The world. - * @param entity The entity. - * @return The storage table of the entity, NULL if the entity has no components. + * @return The new component id. */ FLECS_API -ecs_table_t* ecs_get_storage_table( - const ecs_world_t *world, - ecs_entity_t entity); +ecs_entity_t ecs_new_low_id( + ecs_world_t *world); -/** Get the type for an id. - * This operation returns the component id for an id, if the id is associated - * with a type. For a regular component with a non-zero size (an entity with the - * EcsComponent component) the operation will return the entity itself. - * - * For an entity that does not have the EcsComponent component, or with an - * EcsComponent value with size 0, the operation will return 0. - * - * For a pair id the operation will return the type associated with the pair, by - * applying the following rules in order: - * - The relation entity is returned if it is a component - * - 0 is returned if the relation entity has the Tag property - * - The object entity is returned if it is a component - * - 0 is returned. +/** Create new entity with (component) id. + * This operation creates a new entity with an optional (component) id. When 0 + * is passed to the id parameter, no component is added to the new entity. * * @param world The world. - * @param id The id. - * @return The typeid of the entity. + * @param component The component to create the new entity with. + * @return The new entity. */ FLECS_API -ecs_entity_t ecs_get_typeid( - const ecs_world_t *world, - ecs_id_t id); +ecs_entity_t ecs_new_w_id( + ecs_world_t *world, + ecs_id_t component); -/** Returns whether specified id a tag. - * This operation returns whether the specified type is a tag (a component - * without data/size). - * - * An id is a tag when: - * - it is an entity without the EcsComponent component - * - it has an EcsComponent with size member set to 0 - * - it is a pair where both elements are a tag - * - it is a pair where the first element has the EcsTag tag - * +/** Create new entity in table. + * This operation creates a new entity in the specified table. + * * @param world The world. - * @param id The id. - * @return Whether the provided id is a tag. + * @param table The table to which to add the new entity. + * @return The new entity. */ FLECS_API -ecs_entity_t ecs_id_is_tag( - const ecs_world_t *world, - ecs_id_t id); +ecs_entity_t ecs_new_w_table( + ecs_world_t *world, + ecs_table_t *table); -/** Returns whether specified id is in use. - * This operation returns whether an id is in use in the world. An id is in use - * if it has been added to one or more tables. - * +/** Find or create an entity. + * This operation creates a new entity, or modifies an existing one. When a name + * is set in the ecs_entity_desc_t::name field and ecs_entity_desc_t::entity is + * not set, the operation will first attempt to find an existing entity by that + * name. If no entity with that name can be found, it will be created. + * + * If both a name and entity handle are provided, the operation will check if + * the entity name matches with the provided name. If the names do not match, + * the function will fail and return 0. + * + * If an id to a non-existing entity is provided, that entity id become alive. + * + * See the documentation of ecs_entity_desc_t for more details. + * * @param world The world. - * @param id The id. - * @return Whether the id is in use. + * @param desc Entity init parameters. + * @return A handle to the new or existing entity, or 0 if failed. */ FLECS_API -bool ecs_id_in_use( +ecs_entity_t ecs_entity_init( ecs_world_t *world, - ecs_id_t id); + const ecs_entity_desc_t *desc); -/** Get the name of an entity. - * This will return the name stored in (EcsIdentifier, EcsName). +/** Bulk create/populate new entities. + * This operation bulk inserts a list of new or predefined entities into a + * single table. * - * @param world The world. - * @param entity The entity. - * @return The type of the entity, NULL if the entity has no name. + * The operation does not take ownership of component arrays provided by the + * application. Components that are non-trivially copyable will be moved into + * the storage. + * + * The operation will emit OnAdd events for each added id, and OnSet events for + * each component that has been set. + * + * If no entity ids are provided by the application, the returned array of ids + * points to an internal data structure which changes when new entities are + * created/deleted. + * + * If as a result of the operation triggers are invoked that deletes + * entities and no entity ids were provided by the application, the returned + * array of identifiers may be incorrect. To avoid this problem, an application + * can first call ecs_bulk_init() to create empty entities, copy the array to one + * that is owned by the application, and then use this array to populate the + * entities. + * + * @param world The world. + * @param desc Bulk creation parameters. + * @return Array with the list of entity ids created/populated. */ FLECS_API -const char* ecs_get_name( - const ecs_world_t *world, - ecs_entity_t entity); +const ecs_entity_t* ecs_bulk_init( + ecs_world_t *world, + const ecs_bulk_desc_t *desc); -/** Get the symbol of an entity. - * This will return the symbol stored in (EcsIdentifier, EcsSymbol). +/** Create N new entities. + * This operation is the same as ecs_new_w_id(), but creates N entities + * instead of one. * * @param world The world. - * @param entity The entity. - * @return The type of the entity, NULL if the entity has no name. + * @param component The component to create the entities with. + * @param count The number of entities to create. + * @return The first entity id of the newly created entities. */ FLECS_API -const char* ecs_get_symbol( - const ecs_world_t *world, - ecs_entity_t entity); +const ecs_entity_t* ecs_bulk_new_w_id( + ecs_world_t *world, + ecs_id_t component, + int32_t count); -/** Set the name of an entity. - * This will set or overwrite the name of an entity. If no entity is provided, - * a new entity will be created. +/** Clone an entity + * This operation clones the components of one entity into another entity. If + * no destination entity is provided, a new entity will be created. Component + * values are not copied unless copy_value is true. * - * The name is stored in (EcsIdentifier, EcsName). + * If the source entity has a name, it will not be copied to the destination + * entity. This is to prevent having two entities with the same name under the + * same parent, which is not allowed. * * @param world The world. - * @param entity The entity. - * @param name The name. - * @return The provided entity, or a new entity if 0 was provided. + * @param dst The entity to copy the components to. + * @param src The entity to copy the components from. + * @param copy_value If true, the value of components will be copied to dst. + * @return The destination entity. */ FLECS_API -ecs_entity_t ecs_set_name( +ecs_entity_t ecs_clone( ecs_world_t *world, - ecs_entity_t entity, - const char *name); + ecs_entity_t dst, + ecs_entity_t src, + bool copy_value); -/** Set the symbol of an entity. - * This will set or overwrite the symbol of an entity. If no entity is provided, - * a new entity will be created. - * - * The symbol is stored in (EcsIdentifier, EcsSymbol). +/** Delete an entity. + * This operation will delete an entity and all of its components. The entity id + * will be made available for recycling. If the entity passed to ecs_delete() is + * not alive, the operation will have no side effects. * * @param world The world. * @param entity The entity. - * @param symbol The symbol. - * @return The provided entity, or a new entity if 0 was provided. */ FLECS_API -ecs_entity_t ecs_set_symbol( +void ecs_delete( ecs_world_t *world, - ecs_entity_t entity, - const char *symbol); + ecs_entity_t entity); -/** Set alias for entity. - * An entity can be looked up using its alias from the root scope without - * providing the fully qualified name if its parent. An entity can only have - * a single alias. +/** Delete all entities with the specified component. + * This will delete all entities (tables) that have the specified id. The + * component may be a wildcard and/or a pair. + * + * @param world The world. + * @param component The component. + */ +FLECS_API +void ecs_delete_with( + ecs_world_t *world, + ecs_id_t component); + +/** Set child order for parent with OrderedChildren. + * If the parent has the OrderedChildren trait, the order of the children + * will be updated to the order in the specified children array. The operation + * will fail if the parent does not have the OrderedChildren trait. + * + * This operation always takes place immediately, and is not deferred. When the + * operation is called from a multithreaded system it will fail. * - * The symbol is stored in (EcsIdentifier, EcsAlias). + * The reason for not deferring this operation is that by the time the deferred + * command would be executed, the children of the parent could have been changed + * which would cause the operation to fail. * * @param world The world. - * @param entity The entity. - * @param alias The alias. + * @param parent The parent. + * @param children An array with children. + * @param child_count The number of children in the provided array. */ FLECS_API -void ecs_set_alias( +void ecs_set_child_order( ecs_world_t *world, - ecs_entity_t entity, - const char *alias); + ecs_entity_t parent, + const ecs_entity_t *children, + int32_t child_count); -/** Convert role to string. - * This operation converts a role to a string. +/** Get ordered children. + * If a parent has the OrderedChildren trait, this operation can be used to + * obtain the array with child entities. If this operation is used on a parent + * that does not have the OrderedChildren trait, it will fail.asm * - * @param role The role id. - * @return The role string, or NULL if no valid role is provided. + * @param world The world. + * @param parent The parent. + * @return The array with child entities. */ FLECS_API -const char* ecs_role_str( - ecs_id_t role); +ecs_entities_t ecs_get_ordered_children( + const ecs_world_t *world, + ecs_entity_t parent); + +/** @} */ + +/** + * @defgroup adding_removing Adding & Removing + * Functions for adding and removing components. + * + * @{ + */ -/** Convert id to string. - * This operation interprets the structure of an id and converts it to a string. +/** Add a (component) id to an entity. + * This operation adds a single (component) id to an entity. If the entity + * already has the id, this operation will have no side effects. * * @param world The world. - * @param id The id to convert to a string. - * @return The id converted to a string. + * @param entity The entity. + * @param component The component id to add. */ FLECS_API -char* ecs_id_str( - const ecs_world_t *world, - ecs_id_t id); +void ecs_add_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component); -/** Write id string to buffer. - * Same as ecs_id_str but writes result to ecs_strbuf_t. +/** Remove a component from an entity. + * This operation removes a single component from an entity. If the entity + * does not have the component, this operation will have no side effects. * * @param world The world. - * @param id The id to convert to a string. - * @param buf The buffer to write to. + * @param entity The entity. + * @param component The component to remove. */ FLECS_API -void ecs_id_str_buf( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf); +void ecs_remove_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component); -/** Convert type to string. - * The result of this operation must be freed with ecs_os_free. +/** Add auto override for component. + * An auto override is a component that is automatically added to an entity when + * it is instantiated from a prefab. Auto overrides are added to the entity that + * is inherited from (usually a prefab). For example: + * + * @code + * ecs_entity_t prefab = ecs_insert(world, + * ecs_value(Position, {10, 20}), + * ecs_value(Mass, {100})); + * + * ecs_auto_override(world, prefab, Position); + * + * ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, prefab); + * assert(ecs_owns(world, inst, Position)); // true + * assert(ecs_owns(world, inst, Mass)); // false + * @endcode + * + * An auto override is equivalent to a manual override: + * + * @code + * ecs_entity_t prefab = ecs_insert(world, + * ecs_value(Position, {10, 20}), + * ecs_value(Mass, {100})); * + * ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, prefab); + * assert(ecs_owns(world, inst, Position)); // false + * ecs_add(world, inst, Position); // manual override + * assert(ecs_owns(world, inst, Position)); // true + * assert(ecs_owns(world, inst, Mass)); // false + * @endcode + * + * This operation is equivalent to manually adding the id with the AUTO_OVERRIDE + * bit applied: + * + * @code + * ecs_add_id(world, entity, ECS_AUTO_OVERRIDE | id); + * @endcode + * + * When a component is overridden and inherited from a prefab, the value from + * the prefab component is copied to the instance. When the component is not + * inherited from a prefab, it is added to the instance as if using ecs_add_id(). + * + * Overriding is the default behavior on prefab instantiation. Auto overriding + * is only useful for components with the `(OnInstantiate, Inherit)` trait. + * When a component has the `(OnInstantiate, DontInherit)` trait and is overridden + * the component is added, but the value from the prefab will not be copied. + * * @param world The world. - * @param type The type. - * @return The stringified type. + * @param entity The entity. + * @param component The component to auto override. */ FLECS_API -char* ecs_type_str( - const ecs_world_t *world, - ecs_type_t type); +void ecs_auto_override_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component); -/** Test if an entity has an entity. - * This operation returns true if the entity has the provided entity in its - * type. +/** Clear all components. + * This operation will remove all components from an entity. * * @param world The world. * @param entity The entity. - * @param id The id to test for. - * @return True if the entity has the entity, false if not. */ FLECS_API -bool ecs_has_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity); -/** Get the object of a relation. - * This will return a object of the entity for the specified relation. The index - * allows for iterating through the objects, if a single entity has multiple - * objects for the same relation. - * - * If the index is larger than the total number of instances the entity has for - * the relation, the operation will return 0. +/** Remove all instances of the specified component. + * This will remove the specified id from all entities (tables). The id may be + * a wildcard and/or a pair. * * @param world The world. - * @param entity The entity. - * @param rel The relation between the entity and the object. - * @param index The index of the relation instance. - * @return The object for the relation at the specified index. + * @param component The component. */ FLECS_API -ecs_entity_t ecs_get_object( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel, - int32_t index); +void ecs_remove_all( + ecs_world_t *world, + ecs_id_t component); -/** Get the object of a relation for a given id. - * This operation returns the first entity that has the provided id by following - * the specified relationship. If the entity itself has the id then entity will - * be returned. If the id cannot be found on the entity or by following the - * relation, the operation will return 0. - * - * This operation can be used to lookup, for example, which prefab is providing - * a component by specifying the IsA relation: - * - * // Is Position provided by the entity or one of its base entities? - * ecs_get_object_for_id(world, entity, EcsIsA, ecs_id(Position)) +/** Create new entities with specified component. + * Entities created with ecs_entity_init() will be created with the specified + * component. This does not apply to entities created with ecs_new(). * + * Only one component can be specified at a time. If this operation is called + * while a component is already configured, the new component will override the + * old component. + * * @param world The world. - * @param entity The entity. - * @param rel The relationship to follow. - * @param id The id to lookup. - * @return The entity for which the object has been found. + * @param component The component. + * @return The previously set component. + * @see ecs_entity_init() + * @see ecs_set_with() */ FLECS_API -ecs_entity_t ecs_get_object_for_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel, - ecs_id_t id); +ecs_entity_t ecs_set_with( + ecs_world_t *world, + ecs_id_t component); + +/** Get component set with ecs_set_with(). + * Get the component set with ecs_set_with(). + * + * @param world The world. + * @return The last component provided to ecs_set_with(). + * @see ecs_set_with() + */ +FLECS_API +ecs_id_t ecs_get_with( + const ecs_world_t *world); -/** Enable or disable an entity. +/** @} */ + +/** + * @defgroup enabling_disabling Enabling & Disabling + * Functions for enabling/disabling entities and components. + * + * @{ + */ + +/** Enable or disable entity. * This operation enables or disables an entity by adding or removing the - * EcsDisabled tag. A disabled entity will not be matched with any systems, - * unless the system explicitly specifies the EcsDisabled tag. + * #EcsDisabled tag. A disabled entity will not be matched with any systems, + * unless the system explicitly specifies the #EcsDisabled tag. * * @param world The world. * @param entity The entity to enable or disable. @@ -5336,2500 +7142,3913 @@ void ecs_enable( ecs_entity_t entity, bool enabled); -/** Count entities that have the specified id. - * Returns the number of entities that have the specified id. +/** Enable or disable component. + * Enabling or disabling a component does not add or remove a component from an + * entity, but prevents it from being matched with queries. This operation can + * be useful when a component must be temporarily disabled without destroying + * its value. It is also a more performant operation for when an application + * needs to add/remove components at high frequency, as enabling/disabling is + * cheaper than a regular add or remove. * * @param world The world. - * @param entity The id to search for. - * @return The number of entities that have the id. + * @param entity The entity. + * @param component The component to enable/disable. + * @param enable True to enable the component, false to disable. */ FLECS_API -int32_t ecs_count_id( +void ecs_enable_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component, + bool enable); + +/** Test if component is enabled. + * Test whether a component is currently enabled or disabled. This operation + * will return true when the entity has the component and if it has not been + * disabled by ecs_enable_component(). + * + * @param world The world. + * @param entity The entity. + * @param component The component. + * @return True if the component is enabled, otherwise false. + */ +FLECS_API +bool ecs_is_enabled_id( const ecs_world_t *world, - ecs_id_t entity); + ecs_entity_t entity, + ecs_id_t component); /** @} */ - /** - * @defgroup lookup Lookups + * @defgroup getting Getting & Setting + * Functions for getting/setting components. + * * @{ */ -/** Lookup an entity by name. - * Returns an entity that matches the specified name. Only looks for entities in - * the current scope (root if no scope is provided). +/** Get an immutable pointer to a component. + * This operation obtains a const pointer to the requested component. The + * operation accepts the component entity id. + * + * This operation can return inherited components reachable through an `IsA` + * relationship. * * @param world The world. - * @param name The entity name. - * @return The entity with the specified name, or 0 if no entity was found. + * @param entity The entity. + * @param component The component to get. + * @return The component pointer, NULL if the entity does not have the component. + * + * @see ecs_get_mut_id() */ FLECS_API -ecs_entity_t ecs_lookup( +FLECS_ALWAYS_INLINE const void* ecs_get_id( const ecs_world_t *world, - const char *name); + ecs_entity_t entity, + ecs_id_t component); -/** Lookup a child entity by name. - * Returns an entity that matches the specified name. Only looks for entities in - * the provided parent. If no parent is provided, look in the current scope ( - * root if no scope is provided). +/** Get a mutable pointer to a component. + * This operation obtains a mutable pointer to the requested component. The + * operation accepts the component entity id. + * + * Unlike ecs_get_id(), this operation does not return inherited components. + * This is to prevent errors where an application accidentally resolves an + * inherited component shared with many entities and modifies it, while thinking + * it is modifying an owned component. * * @param world The world. - * @param name The entity name. - * @return The entity with the specified name, or 0 if no entity was found. + * @param entity The entity. + * @param component The component to get. + * @return The component pointer, NULL if the entity does not have the component. */ FLECS_API -ecs_entity_t ecs_lookup_child( +FLECS_ALWAYS_INLINE void* ecs_get_mut_id( const ecs_world_t *world, - ecs_entity_t parent, - const char *name); + ecs_entity_t entity, + ecs_id_t component); -/** Lookup an entity from a path. - * Lookup an entity from a provided path, relative to the provided parent. The - * operation will use the provided separator to tokenize the path expression. If - * the provided path contains the prefix, the search will start from the root. +/** Ensure entity has component, return pointer. + * This operation returns a mutable pointer to a component. If the entity did + * not yet have the component, it will be added. * - * If the entity is not found in the provided parent, the operation will - * continue to search in the parent of the parent, until the root is reached. If - * the entity is still not found, the lookup will search in the flecs.core - * scope. If the entity is not found there either, the function returns 0. + * If ensure is called when the world is in deferred/readonly mode, the + * function will: + * - return a pointer to a temp storage if the component does not yet exist, or + * - return a pointer to the existing component if it exists * * @param world The world. - * @param parent The entity from which to resolve the path. - * @param path The path to resolve. - * @param sep The path separator. - * @param prefix The path prefix. - * @param recursive Recursively traverse up the tree until entity is found. - * @return The entity if found, else 0. + * @param entity The entity. + * @param component The component to get/add. + * @return The component pointer. + * + * @see ecs_emplace_id() */ FLECS_API -ecs_entity_t ecs_lookup_path_w_sep( - const ecs_world_t *world, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix, - bool recursive); +void* ecs_ensure_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component, + size_t size); -/** Lookup an entity by its symbol name. - * This looks up an entity by symbol stored in (EcsIdentifier, EcsSymbol). The - * operation does not take into account hierarchies. +/** Create a component ref. + * A ref is a handle to an entity + component which caches a small amount of + * data to reduce overhead of repeatedly accessing the component. Use + * ecs_ref_get() to get the component data. * - * This operation can be useful to resolve, for example, a type by its C - * identifier, which does not include the Flecs namespacing. + * @param world The world. + * @param entity The entity. + * @param component The component to create a ref for. + * @return The reference. */ -FLECS_API -ecs_entity_t ecs_lookup_symbol( +FLECS_ALWAYS_INLINE FLECS_API +ecs_ref_t ecs_ref_init_id( const ecs_world_t *world, - const char *symbol, - bool lookup_as_path); - -/** @} */ - + ecs_entity_t entity, + ecs_id_t component); -/** - * @defgroup paths Paths - * @{ +/** Get component from ref. + * Get component pointer from ref. The ref must be created with ecs_ref_init(). + * The specified component must match the component with which the ref was + * created. + * + * @param world The world. + * @param ref The ref. + * @param component The component to get. + * @return The component pointer, NULL if the entity does not have the component. */ +FLECS_ALWAYS_INLINE FLECS_API +void* ecs_ref_get_id( + const ecs_world_t *world, + ecs_ref_t *ref, + ecs_id_t component); -/** Get a path identifier for an entity. - * This operation creates a path that contains the names of the entities from - * the specified parent to the provided entity, separated by the provided - * separator. If no parent is provided the path will be relative to the root. If - * a prefix is provided, the path will be prefixed by the prefix. - * - * If the parent is equal to the provided child, the operation will return an - * empty string. If a nonzero component is provided, the path will be created by - * looking for parents with that component. - * - * The returned path should be freed by the application. +/** Update ref. + * Ensures contents of ref are up to date. Same as ecs_ref_get_id(), but does not + * return pointer to component id. * * @param world The world. - * @param parent The entity from which to create the path. - * @param child The entity to which to create the path. - * @param sep The separator to use between path elements. - * @param prefix The initial character to use for root elements. - * @return The relative entity path. + * @param ref The ref. */ -FLECS_API -char* ecs_get_path_w_sep( +FLECS_ALWAYS_INLINE FLECS_API +void ecs_ref_update( const ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t child, - const char *sep, - const char *prefix); + ecs_ref_t *ref); -/** Write path identifier to buffer. - * Same as ecs_get_path_w_sep, but writes result to an ecs_strbuf_t. +/** Emplace a component. + * Emplace is similar to ecs_ensure_id() except that the component constructor + * is not invoked for the returned pointer, allowing the component to be + * constructed directly in the storage. * + * When the `is_new` parameter is not provided, the operation will assert when the + * component already exists. When the `is_new` parameter is provided, it will + * indicate whether the returned storage has been constructed. + * + * When `is_new` indicates that the storage has not yet been constructed, it must + * be constructed by the code invoking this operation. Not constructing the + * component will result in undefined behavior. + * * @param world The world. - * @param parent The entity from which to create the path. - * @param child The entity to which to create the path. - * @param sep The separator to use between path elements. - * @param prefix The initial character to use for root elements. - * @param buf The buffer to write to. + * @param entity The entity. + * @param component The component to get/add. + * @param size The component size. + * @param is_new Whether this is an existing or new component. + * @return The (uninitialized) component pointer. */ -void ecs_get_path_w_sep_buf( - const ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t child, - const char *sep, - const char *prefix, - ecs_strbuf_t *buf); +FLECS_API +void* ecs_emplace_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component, + size_t size, + bool *is_new); -/** Find or create entity from path. - * This operation will find or create an entity from a path, and will create any - * intermediate entities if required. If the entity already exists, no entities - * will be created. - * - * If the path starts with the prefix, then the entity will be created from the - * root scope. +/** Signal that a component has been modified. + * This operation is usually used after modifying a component value obtained by + * ecs_ensure_id(). The operation will mark the component as dirty, and invoke + * OnSet observers and hooks. * * @param world The world. - * @param parent The entity relative to which the entity should be created. - * @param path The path to create the entity for. - * @param sep The separator used in the path. - * @param prefix The prefix used in the path. - * @return The entity. + * @param entity The entity. + * @param component The component that was modified. */ FLECS_API -ecs_entity_t ecs_new_from_path_w_sep( +void ecs_modified_id( ecs_world_t *world, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix); + ecs_entity_t entity, + ecs_id_t component); -/** Add specified path to entity. - * This operation is similar to ecs_new_from_path, but will instead add the path - * to an existing entity. +/** Set the value of a component. + * This operation allows an application to set the value of a component. The + * operation is equivalent to calling ecs_ensure_id() followed by + * ecs_modified_id(). The operation will not modify the value of the passed in + * component. If the component has a copy hook registered, it will be used to + * copy in the component. * - * If an entity already exists for the path, it will be returned instead. + * If the provided entity is 0, a new entity will be created. * * @param world The world. - * @param entity The entity to which to add the path. - * @param parent The entity relative to which the entity should be created. - * @param path The path to create the entity for. - * @param sep The separator used in the path. - * @param prefix The prefix used in the path. - * @return The entity. - */ + * @param entity The entity. + * @param component The component to set. + * @param size The size of the pointed-to value. + * @param ptr The pointer to the value. + */ FLECS_API -ecs_entity_t ecs_add_path_w_sep( +void ecs_set_id( ecs_world_t *world, ecs_entity_t entity, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix); + ecs_id_t component, + size_t size, + const void *ptr); /** @} */ - /** - * @defgroup scopes Scopes + * @defgroup liveliness Entity Liveliness + * Functions for testing and modifying entity liveliness. + * * @{ */ -/** Set the current scope. - * This operation sets the scope of the current stage to the provided entity. - * As a result new entities will be created in this scope, and lookups will be - * relative to the provided scope. +/** Test whether an entity is valid. + * This operation tests whether the entity id: + * - is not 0 + * - has a valid bit pattern + * - is alive (see ecs_is_alive()) + * + * If this operation returns true, it is safe to use the entity with other + * other operations. + * + * This operation should only be used if an application cannot be sure that an + * entity is initialized with a valid value. In all other cases where an entity + * was initialized with a valid value, but the application wants to check if the + * entity is (still) alive, use ecs_is_alive. * - * It is considered good practice to restore the scope to the old value. + * @param world The world. + * @param e The entity. + * @return True if the entity is valid, false if the entity is not valid. + * @see ecs_is_alive() + */ +FLECS_API +bool ecs_is_valid( + const ecs_world_t *world, + ecs_entity_t e); + +/** Test whether an entity is alive. + * Entities are alive after they are created, and become not alive when they are + * deleted. Operations that return alive ids are (amongst others) ecs_new(), + * ecs_new_low_id() and ecs_entity_init(). Ids can be made alive with the + * ecs_make_alive() * function. + * + * After an id is deleted it can be recycled. Recycled ids are different from + * the original id in that they have a different generation count. This makes it + * possible for the API to distinguish between the two. An example: + * + * @code + * ecs_entity_t e1 = ecs_new(world); + * ecs_is_alive(world, e1); // true + * ecs_delete(world, e1); + * ecs_is_alive(world, e1); // false + * + * ecs_entity_t e2 = ecs_new(world); // recycles e1 + * ecs_is_alive(world, e2); // true + * ecs_is_alive(world, e1); // false + * @endcode + * + * Other than ecs_is_valid(), this operation will panic if the passed in entity + * id is 0 or has an invalid bit pattern. * * @param world The world. - * @param scope The entity to use as scope. - * @return The previous scope. + * @param e The entity. + * @return True if the entity is alive, false if the entity is not alive. + * @see ecs_is_valid() */ FLECS_API -ecs_entity_t ecs_set_scope( - ecs_world_t *world, - ecs_entity_t scope); +bool ecs_is_alive( + const ecs_world_t *world, + ecs_entity_t e); -/** Get the current scope. - * Get the scope set by ecs_set_scope. If no scope is set, this operation will - * return 0. +/** Remove generation from entity id. + * + * @param e The entity id. + * @return The entity id without the generation count. + */ +FLECS_API +ecs_id_t ecs_strip_generation( + ecs_entity_t e); + +/** Get alive identifier. + * In some cases an application may need to work with identifiers from which + * the generation has been stripped. A typical scenario in which this happens is + * when iterating relationships in an entity type. + * + * For example, when obtaining the parent id from a `ChildOf` relationship, the parent + * (second element of the pair) will have been stored in a 32 bit value, which + * cannot store the entity generation. This function can retrieve the identifier + * with the current generation for that id. + * + * If the provided identifier is not alive, the function will return 0. * * @param world The world. - * @return The current scope. + * @param e The for which to obtain the current alive entity id. + * @return The alive entity id if there is one, or 0 if the id is not alive. */ FLECS_API -ecs_entity_t ecs_get_scope( - const ecs_world_t *world); +ecs_entity_t ecs_get_alive( + const ecs_world_t *world, + ecs_entity_t e); -/** Set current with id. - * New entities are automatically created with the specified id. +/** Ensure id is alive. + * This operation ensures that the provided id is alive. This is useful in + * scenarios where an application has an existing id that has not been created + * with ecs_new_w() (such as a global constant or an id from a remote application). + * + * When this operation is successful it guarantees that the provided id exists, + * is valid and is alive. + * + * Before this operation the id must either not be alive or have a generation + * that is equal to the passed in entity. + * + * If the provided id has a non-zero generation count and the id does not exist + * in the world, the id will be created with the specified generation. + * + * If the provided id is alive and has a generation count that does not match + * the provided id, the operation will fail. * * @param world The world. - * @param id The id. - * @return The previous id. + * @param entity The entity id to make alive. + * + * @see ecs_make_alive_id() */ FLECS_API -ecs_entity_t ecs_set_with( +void ecs_make_alive( ecs_world_t *world, - ecs_id_t id); + ecs_entity_t entity); -/** Get current with id. - * Get the id set with ecs_set_with. +/** Same as ecs_make_alive(), but for components. + * An id can be an entity or pair, and can contain id flags. This operation + * ensures that the entity (or entities, for a pair) are alive. + * + * When this operation is successful it guarantees that the provided id can be + * used in operations that accept an id. + * + * Since entities in a pair do not encode their generation ids, this operation + * will not fail when an entity with non-zero generation count already exists in + * the world. + * + * This is different from ecs_make_alive(), which will fail if attempted with an id + * that has generation 0 and an entity with a non-zero generation is currently + * alive. * * @param world The world. - * @return The last id provided to ecs_set_with. + * @param component The component to make alive. */ FLECS_API -ecs_id_t ecs_get_with( - const ecs_world_t *world); +void ecs_make_alive_id( + ecs_world_t *world, + ecs_id_t component); -/** Set a name prefix for newly created entities. - * This is a utility that lets C modules use prefixed names for C types and - * C functions, while using names for the entity names that do not have the - * prefix. The name prefix is currently only used by ECS_COMPONENT. +/** Test whether an entity exists. + * Similar as ecs_is_alive(), but ignores entity generation count. * * @param world The world. - * @param prefix The name prefix to use. - * @return The previous prefix. + * @param entity The entity. + * @return True if the entity exists, false if the entity does not exist. */ FLECS_API -const char* ecs_set_name_prefix( - ecs_world_t *world, - const char *prefix); +bool ecs_exists( + const ecs_world_t *world, + ecs_entity_t entity); -/** Set search path for lookup operations. - * This operation accepts an array of entity ids that will be used as search - * scopes by lookup operations. The operation returns the current search path. - * It is good practice to restore the old search path. - * - * The search path will be evaluated starting from the last element. - * - * The default search path includes flecs.core. When a custom search path is - * provided it overwrites the existing search path. Operations that rely on - * looking up names from flecs.core without providing the namespace may fail if - * the custom search path does not include flecs.core (EcsFlecsCore). - * - * The search path array is not copied into managed memory. The application must - * ensure that the provided array is valid for as long as it is used as the - * search path. - * - * The provided array must be terminated with a 0 element. This enables an - * application to push/pop elements to an existing array without invoking the - * ecs_set_lookup_path operation again. +/** Override the generation of an entity. + * The generation count of an entity is increased each time an entity is deleted + * and is used to test whether an entity id is alive. + * + * This operation overrides the current generation of an entity with the + * specified generation, which can be useful if an entity is externally managed, + * like for external pools, savefiles or netcode. * + * This operation is similar to ecs_make_alive(), except that it will also + * override the generation of an alive entity. + * * @param world The world. - * @param lookup_path 0-terminated array with entity ids for the lookup path. - * @return Current lookup path array. + * @param entity Entity for which to set the generation with the new generation. */ FLECS_API -ecs_entity_t* ecs_set_lookup_path( +void ecs_set_version( ecs_world_t *world, - const ecs_entity_t *lookup_path); + ecs_entity_t entity); -/** Get current lookup path. - * Returns value set by ecs_set_lookup_path. - * - * @param world The world. - * @return The current lookup path. +/** Get generation of an entity. + * + * @param entity Entity for which to get the generation of. + * @return The generation of the entity. */ FLECS_API -ecs_entity_t* ecs_get_lookup_path( - const ecs_world_t *world); +uint32_t ecs_get_version( + ecs_entity_t entity); /** @} */ - /** - * @defgroup terms Terms + * @defgroup entity_info Entity Information. + * Get information from entity. + * * @{ */ -/** Iterator for a single (component) id. - * A term iterator returns all entities (tables) that match a single (component) - * id. The search for the matching set of entities (tables) is performed in - * constant time. - * - * Currently only trivial terms are supported (see ecs_term_is_trivial). Only - * the id field of the term needs to be initialized. +/** Get the type of an entity. * * @param world The world. - * @param term The term. - * @return The iterator. + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no components. */ FLECS_API -ecs_iter_t ecs_term_iter( +const ecs_type_t* ecs_get_type( const ecs_world_t *world, - ecs_term_t *term); + ecs_entity_t entity); -/** Return a chained term iterator. - * A chained iterator applies a filter to the results of the input iterator. The - * resulting iterator must be iterated with ecs_term_next. - * - * @param it The input iterator - * @param term The term filter to apply to the iterator. - * @return The chained iterator. - */ -FLECS_API -ecs_iter_t ecs_term_chain_iter( - const ecs_iter_t *it, - ecs_term_t *term); - -/** Progress the term iterator. - * This operation progresses the term iterator to the next table. The - * iterator must have been initialized with `ecs_term_iter`. This operation - * must be invoked at least once before interpreting the contents of the - * iterator. +/** Get the table of an entity. * - * @param it The iterator. - * @returns True if more data is available, false if not. + * @param world The world. + * @param entity The entity. + * @return The table of the entity, NULL if the entity has no components/tags. */ FLECS_API -bool ecs_term_next( - ecs_iter_t *it); - -/** Test whether term id is set. - * - * @param id The term id. - * @return True when set, false when not set. - */ -FLECS_API -bool ecs_term_id_is_set( - const ecs_term_id_t *id); +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity); -/** Test whether a term is set. - * This operation can be used to test whether a term has been initialized with - * values or whether it is empty. - * - * An application generally does not need to invoke this operation. It is useful - * when initializing a 0-initialized array of terms (like in ecs_term_desc_t) as - * this operation can be used to find the last initialized element. +/** Convert type to string. + * The result of this operation must be freed with ecs_os_free(). * - * @param term The term. - * @return True when set, false when not set. + * @param world The world. + * @param type The type. + * @return The stringified type. */ FLECS_API -bool ecs_term_is_initialized( - const ecs_term_t *term); +char* ecs_type_str( + const ecs_world_t *world, + const ecs_type_t* type); -/** Test whether a term is a trivial term. - * A trivial term is a term that only contains a type id. Trivial terms must not - * have read/write annotations, relation substitutions and subjects other than - * 'This'. Examples of trivial terms are: - * - 'Position' - * - 'Position(This)' - * - '(Likes, IceCream)' - * - 'Likes(This, IceCream)' - * - * Examples of non-trivial terms are: - * - '[in] Position' - * - 'Position(MyEntity)' - * - 'Position(self|superset)' +/** Convert table to string. + * Same as `ecs_type_str(world, ecs_table_get_type(table))`. The result of this + * operation must be freed with ecs_os_free(). * - * Trivial terms are useful in expressions that should just represent a list of - * components, such as when parsing the list of components to add to an entity. - * - * The term passed to this operation must be finalized. Terms returned by the - * parser are guaranteed to be finalized. + * @param world The world. + * @param table The table. + * @return The stringified table type. * - * @param term The term. - * @return True if term is trivial, false if it is not. + * @see ecs_table_get_type() + * @see ecs_type_str() */ FLECS_API -bool ecs_term_is_trivial( - const ecs_term_t *term); +char* ecs_table_str( + const ecs_world_t *world, + const ecs_table_t *table); -/** Finalize term. - * Ensure that all fields of a term are consistent and filled out. This - * operation should be invoked before using and after assigning members to, or - * parsing a term. When a term contains unresolved identifiers, this operation - * will resolve and assign the identifiers. If the term contains any identifiers - * that cannot be resolved, the operation will fail. - * - * An application generally does not need to invoke this operation as the APIs - * that use terms (such as filters, queries and triggers) will finalize terms - * when they are created. +/** Convert entity to string. + * Same as combining: + * - ecs_get_path(world, entity) + * - ecs_type_str(world, ecs_get_type(world, entity)) * - * The name and expr parameters are optional, and only used for giving more - * descriptive error messages. + * The result of this operation must be freed with ecs_os_free(). * * @param world The world. - * @param name The name of the entity that uses the term (such as a system). - * @param term The term to finalize. - * @return Zero if success, nonzero if an error occurred. - */ -FLECS_API -int ecs_term_finalize( - const ecs_world_t *world, - const char *name, - ecs_term_t *term); - -/** Copy resources of a term to another term. - * This operation copies one term to another term. If the source term contains - * allocated resources (such as identifiers), they will be duplicated so that - * no memory is shared between the terms. + * @param entity The entity. + * @return The entity path with stringified type. * - * @param src The term to copy from. - * @return The destination term. + * @see ecs_get_path() + * @see ecs_type_str() */ -FLECS_API -ecs_term_t ecs_term_copy( - const ecs_term_t *src); +FLECS_API +char* ecs_entity_str( + const ecs_world_t *world, + ecs_entity_t entity); -/** Move resources of a term to another term. - * Same as copy, but moves resources from src, if src->move is set to true. If - * src->move is not set to true, this operation will do a copy. +/** Test if an entity has a component. + * This operation returns true if the entity has or inherits the component. * - * The conditional move reduces redundant allocations in scenarios where a list - * of terms is partially created with allocated resources. - * - * @param src The term to move from. - * @return The destination term. - */ -FLECS_API -ecs_term_t ecs_term_move( - ecs_term_t *src); - -/** Free resources of term. - * This operation frees all resources (such as identifiers) of a term. The term - * object itself is not freed. + * @param world The world. + * @param entity The entity. + * @param component The component to test for. + * @return True if the entity has the component, false if not. * - * @param term The term to free. + * @see ecs_owns_id() */ FLECS_API -void ecs_term_fini( - ecs_term_t *term); +FLECS_ALWAYS_INLINE bool ecs_has_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component); -/** Utility to match an id with a pattern. - * This operation returns true if the provided pattern matches the provided - * id. The pattern may contain a wildcard (or wildcards, when a pair). +/** Test if an entity owns a component. + * This operation returns true if the entity has the component. The operation + * behaves the same as ecs_has_id(), except that it will return false for + * components that are inherited through an `IsA` relationship. * - * @param id The id. - * @param pattern The pattern to compare with. + * @param world The world. + * @param entity The entity. + * @param component The component to test for. + * @return True if the entity has the component, false if not. */ FLECS_API -bool ecs_id_match( - ecs_id_t id, - ecs_id_t pattern); +FLECS_ALWAYS_INLINE bool ecs_owns_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component); -/** Utility to check if id is a pair. +/** Get the target of a relationship. + * This will return a target (second element of a pair) of the entity for the + * specified relationship. The index allows for iterating through the targets, + * if a single entity has multiple targets for the same relationship. * - * @param id The id. - * @return True if id is a pair. - */ -FLECS_API -bool ecs_id_is_pair( - ecs_id_t id); - -/** Utility to check if id is a wildcard. + * If the index is larger than the total number of instances the entity has for + * the relationship, the operation will return 0. * - * @param id The id. - * @return True if id is a wildcard or a pair containing a wildcard. + * @param world The world. + * @param entity The entity. + * @param rel The relationship between the entity and the target. + * @param index The index of the relationship instance. + * @return The target for the relationship at the specified index. */ FLECS_API -bool ecs_id_is_wildcard( - ecs_id_t id); +ecs_entity_t ecs_get_target( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + int32_t index); -/** Utility to check if id is valid. - * A valid id is an id that can be added to an entity. Invalid ids are: - * - ids that contain wildcards - * - ids that contain invalid entities - * - ids that are 0 or contain 0 entities +/** Get parent (target of `ChildOf` relationship) for entity. + * This operation is the same as calling: * - * Note that the same rules apply to removing from an entity, with the exception - * of wildcards. + * @code + * ecs_get_target(world, entity, EcsChildOf, 0); + * @endcode * * @param world The world. - * @param id The id. - * @return True if the id is valid. + * @param entity The entity. + * @return The parent of the entity, 0 if the entity has no parent. + * + * @see ecs_get_target() */ FLECS_API -bool ecs_id_is_valid( +ecs_entity_t ecs_get_parent( const ecs_world_t *world, - ecs_id_t id); - -/** @} */ - + ecs_entity_t entity); -/** - * @defgroup filters Filters - * @{ +/** Create child with Parent component. + * This creates or returns an existing child for the specified parent. If a new + * child is created, the Parent component is used to create the parent + * relationship. + * + * If a child entity already exists with the specified name, it will be + * returned. + * + * @param world The world. + * @param parent The parent for which to create the child. + * @param name The name with which to create the entity (may be NULL). + * @return A new or existing child entity. */ +FLECS_API +ecs_entity_t ecs_new_w_parent( + ecs_world_t *world, + ecs_entity_t parent, + const char *name); -/** Initialize filter - * A filter is a lightweight object that can be used to query for entities in - * a world. Filters, as opposed to queries, do not cache results. They are - * therefore slower to iterate, but are faster to create. +/** Get the target of a relationship for a given component. + * This operation returns the first entity that has the provided component by + * following the relationship. If the entity itself has the component then it + * will be returned. If the component cannot be found on the entity or by + * following the relationship, the operation will return 0. * - * This operation will allocate an array to hold filter terms if the number of - * terms in the filter exceed ECS_TERM_DESC_CACHE_SIZE. If the number of terms - * is below that constant, the "terms" pointer is set to an inline array. - * - * When a filter is copied by value, make sure to use "ecs_filter_move" to - * ensure that the terms pointer still points to the inline array: - * - * ecs_filter_move(&dst_filter, &src_filter) - * - * Alternatively, the ecs_filter_move function can be called with both arguments - * set to the same filter, to ensure the pointer is valid: - * - * ecs_filter_move(&f, &f) - * - * When a filter contains entity or variable names memory is allocated to store - * those. To cleanup memory associated with a filter, call ecs_filter_fini. + * This operation can be used to lookup, for example, which prefab is providing + * a component by specifying the `IsA` relationship: * - * It is possible to create a filter without allocating any memory, by setting - * the "terms" and "term_count" members directly. When doing so an application - * should not call ecs_filter_init but ecs_filter_finalize. This will ensure - * that all fields are consistent and properly filled out. + * @code + * // Is Position provided by the entity or one of its base entities? + * ecs_get_target_for_id(world, entity, EcsIsA, ecs_id(Position)) + * @endcode * * @param world The world. - * @param desc Properties for the filter to create. - * @param filter_out The filter. - * @return Zero if successful, non-zero if failed. + * @param entity The entity. + * @param rel The relationship to follow. + * @param component The component to lookup. + * @return The entity for which the target has been found. */ FLECS_API -int ecs_filter_init( +ecs_entity_t ecs_get_target_for_id( const ecs_world_t *world, - ecs_filter_t *filter_out, - const ecs_filter_desc_t *desc); + ecs_entity_t entity, + ecs_entity_t rel, + ecs_id_t component); -/** Deinitialize filter. - * Free resources associated with filter. +/** Return depth for entity in tree for the specified relationship. + * Depth is determined by counting the number of targets encountered while + * traversing up the relationship tree for rel. Only acyclic relationships are + * supported. * - * @param filter The filter to deinitialize. + * @param world The world. + * @param entity The entity. + * @param rel The relationship. + * @return The depth of the entity in the tree. */ FLECS_API -void ecs_filter_fini( - ecs_filter_t *filter); +int32_t ecs_get_depth( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel); -/** Finalize filter. - * When manually assigning an array of terms to the filter struct (so not when - * using ecs_filter_init), this operation should be used to ensure that all - * terms are assigned properly and all (derived) fields have been set. - * - * When ecs_filter_init is used to create the filter, this function should not - * be called. The purpose of this operation is to support creation of filters - * without allocating memory. +/** Count entities that have the specified id. + * Returns the number of entities that have the specified id. * - * @param filter The filter to finalize. - * @return Zero if filter is valid, non-zero if it contains errors. - * @ + * @param world The world. + * @param entity The id to search for. + * @return The number of entities that have the id. */ -FLECS_API -int ecs_filter_finalize( +FLECS_API +int32_t ecs_count_id( const ecs_world_t *world, - ecs_filter_t *filter); + ecs_id_t entity); -/** Find index for This variable. - * This operation looks up the index of the This variable. This index can - * be used in operations like ecs_iter_set_var and ecs_iter_get_var. - * - * The operation will return -1 if the variable was not found. This happens when - * a filter only has terms that are not matched on the This variable, like a - * filter that exclusively matches singleton components. - * - * @param filter The rule. - * @return The index of the This variable. - */ -FLECS_API -int32_t ecs_filter_find_this_var( - const ecs_filter_t *filter); +/** @} */ -/** Convert ter, to string expression. - * Convert term to a string expression. The resulting expression is equivalent - * to the same term, with the exception of And & Or operators. - */ -FLECS_API -char* ecs_term_str( - const ecs_world_t *world, - const ecs_term_t *term); -/** Convert filter to string expression. - * Convert filter terms to a string expression. The resulting expression can be - * parsed to create the same filter. +/** + * @defgroup paths Entity Names + * Functions for working with entity names and paths. + * + * @{ */ -FLECS_API -char* ecs_filter_str( - const ecs_world_t *world, - const ecs_filter_t *filter); -/** Return a filter iterator. - * A filter iterator lets an application iterate over entities that match the - * specified filter. If NULL is provided for the filter, the iterator will - * iterate all tables in the world. - * +/** Get the name of an entity. + * This will return the name stored in `(EcsIdentifier, EcsName)`. + * * @param world The world. - * @param filter The filter. - * @return An iterator that can be used with ecs_filter_next. + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no name. + * + * @see ecs_set_name() */ FLECS_API -ecs_iter_t ecs_filter_iter( +const char* ecs_get_name( const ecs_world_t *world, - const ecs_filter_t *filter); - -/** Return a chained filter iterator. - * A chained iterator applies a filter to the results of the input iterator. The - * resulting iterator must be iterated with ecs_filter_next. - * - * @param it The input iterator - * @param filter The filter to apply to the iterator. - * @return The chained iterator. - */ -FLECS_API -ecs_iter_t ecs_filter_chain_iter( - const ecs_iter_t *it, - const ecs_filter_t *filter); + ecs_entity_t entity); -/** Get pivot term for filter. - * The pivot term is the term that matches the smallest set of tables, and is - * a good default starting point for a search. - * - * The following conditions must be met for a term to be considered as pivot: - * - It must have a This subject - * - It must have the And operator - * - * When a filter does not have any terms that match those conditions, it will - * return -1. - * - * If one or more terms in the filter have no matching tables the filter won't - * yield any results. In this case the operation will return -2 which gives a - * search function the option to early out. - * +/** Get the symbol of an entity. + * This will return the symbol stored in `(EcsIdentifier, EcsSymbol)`. + * * @param world The world. - * @param filter The filter. - * @return Index of the pivot term (use with filter->terms) + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no name. + * + * @see ecs_set_symbol() */ FLECS_API -int32_t ecs_filter_pivot_term( +const char* ecs_get_symbol( const ecs_world_t *world, - const ecs_filter_t *filter); + ecs_entity_t entity); -/** Iterate tables matched by filter. - * This operation progresses the filter iterator to the next table. The - * iterator must have been initialized with `ecs_filter_iter`. This operation - * must be invoked at least once before interpreting the contents of the - * iterator. +/** Set the name of an entity. + * This will set or overwrite the name of an entity. If no entity is provided, + * a new entity will be created. * - * @param it The iterator - * @return True if more data is available, false if not. - */ -FLECS_API -bool ecs_filter_next( - ecs_iter_t *it); - -/** Same as ecs_filter_next, but always instanced. - * See instanced property of ecs_filter_desc_t. + * The name is stored in `(EcsIdentifier, EcsName)`. + * + * @param world The world. + * @param entity The entity. + * @param name The name. + * @return The provided entity, or a new entity if 0 was provided. + * + * @see ecs_get_name() */ FLECS_API -bool ecs_filter_next_instanced( - ecs_iter_t *it); - -/** Move resources of one filter to another. */ -FLECS_API -void ecs_filter_move( - ecs_filter_t *dst, - ecs_filter_t *src); - -/** Copy resources of one filter to another. */ -FLECS_API -void ecs_filter_copy( - ecs_filter_t *dst, - const ecs_filter_t *src); - -/** @} */ - -/** - * @defgroup queries Queries - * @{ - */ +ecs_entity_t ecs_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name); -/** Create a query. - * This operation creates a query. Queries are used to iterate over entities - * that match a filter and are the fastest way to find and iterate over entities - * and their components. - * - * Queries should be created once, and reused multiple times. While iterating a - * query is a cheap operation, creating and deleting a query is expensive. The - * reason for this is that queries are "prematched", which means that a query - * stores state about which entities (or rather, tables) match with the query. - * Building up this state happens during query creation. - * - * Once a query is created, matching only happens when new tables are created. - * In most applications this is an infrequent process, since it only occurs when - * a new combination of components is introduced. While matching is expensive, - * it is importent to note that matching does not happen on a per-entity basis, - * but on a per-table basis. This means that the average time spent on matching - * per frame should rapidly approach zero over the lifetime of an application. - * - * A query provides direct access to the component arrays. When an application - * creates/deletes entities or adds/removes components, these arrays can shift - * component values around, or may grow in size. This can cause unexpected or - * undefined behavior to occur if these operations are performed while - * iterating. To prevent this from happening an application should either not - * perform these operations while iterating, or use deferred operations (see - * ecs_defer_begin and ecs_defer_end). +/** Set the symbol of an entity. + * This will set or overwrite the symbol of an entity. If no entity is provided, + * a new entity will be created. * - * Queries can be created and deleted dynamically. If a query was not deleted - * (using ecs_query_fini) before the world is deleted, it will be deleted - * automatically. + * The symbol is stored in (EcsIdentifier, EcsSymbol). * * @param world The world. - * @param desc A structure describing the query properties. - * @return The new query. - */ -FLECS_API -ecs_query_t* ecs_query_init( - ecs_world_t *world, - const ecs_query_desc_t *desc); - -/** Destroy a query. - * This operation destroys a query and its resources. If the query is used as - * the parent of subqueries, those subqueries will be orphaned and must be - * deinitialized as well. + * @param entity The entity. + * @param symbol The symbol. + * @return The provided entity, or a new entity if 0 was provided. * - * @param query The query. + * @see ecs_get_symbol() */ FLECS_API -void ecs_query_fini( - ecs_query_t *query); +ecs_entity_t ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t entity, + const char *symbol); -/** Get filter object of query. - * This operation obtains a pointer to the internally constructed filter object - * of the query and can be used to introspect the query terms. +/** Set alias for entity. + * An entity can be looked up using its alias from the root scope without + * providing the fully qualified name if its parent. An entity can only have + * a single alias. * - * @param query The query. + * The symbol is stored in `(EcsIdentifier, EcsAlias)`. + * + * @param world The world. + * @param entity The entity. + * @param alias The alias. */ FLECS_API -const ecs_filter_t* ecs_query_get_filter( - ecs_query_t *query); +void ecs_set_alias( + ecs_world_t *world, + ecs_entity_t entity, + const char *alias); -/** Return a query iterator. - * A query iterator lets an application iterate over entities that match the - * specified query. If a sorting function is specified, the query will check - * whether a resort is required upon creating the iterator. +/** Lookup an entity by it's path. + * This operation is equivalent to calling: * - * Creating a query iterator is a cheap operation that does not allocate any - * resources. An application does not need to deinitialize or free a query - * iterator before it goes out of scope. + * @code + * ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true); + * @endcode * - * To iterate the iterator, an application should use ecs_query_next to progress - * the iterator and test if it has data. - * - * Query iteration requires an outer and an inner loop. The outer loop uses - * ecs_query_next to test if new tables are available. The inner loop iterates - * the entities in the table, and is usually a for loop that uses iter.count to - * loop through the entities and component arrays. - * - * The two loops are necessary because of how data is stored internally. - * Entities are grouped by the components they have, in tables. A single query - * can (and often does) match with multiple tables. Because each table has its - * own set of arrays, an application has to reobtain pointers to those arrays - * for each matching table. + * @param world The world. + * @param path The entity path. + * @return The entity with the specified path, or 0 if no entity was found. * - * @param world The world or stage, when iterating in readonly mode. - * @param query The query to iterate. - * @return The query iterator. + * @see ecs_lookup_child() + * @see ecs_lookup_path_w_sep() + * @see ecs_lookup_symbol() */ FLECS_API -ecs_iter_t ecs_query_iter( +ecs_entity_t ecs_lookup( const ecs_world_t *world, - ecs_query_t *query); + const char *path); -/** Progress the query iterator. - * This operation progresses the query iterator to the next table. The - * iterator must have been initialized with `ecs_query_iter`. This operation - * must be invoked at least once before interpreting the contents of the - * iterator. +/** Lookup a child entity by name. + * Returns an entity that matches the specified name. Only looks for entities in + * the provided parent. If no parent is provided, look in the current scope ( + * root if no scope is provided). * - * @param iter The iterator. - * @returns True if more data is available, false if not. + * @param world The world. + * @param parent The parent for which to lookup the child. + * @param name The entity name. + * @return The entity with the specified name, or 0 if no entity was found. + * + * @see ecs_lookup() + * @see ecs_lookup_path_w_sep() + * @see ecs_lookup_symbol() */ FLECS_API -bool ecs_query_next( - ecs_iter_t *iter); - -/** Same as ecs_query_next, but always instanced. - * See "instanced" property of ecs_filter_desc_t. +ecs_entity_t ecs_lookup_child( + const ecs_world_t *world, + ecs_entity_t parent, + const char *name); + +/** Lookup an entity from a path. + * Lookup an entity from a provided path, relative to the provided parent. The + * operation will use the provided separator to tokenize the path expression. If + * the provided path contains the prefix, the search will start from the root. + * + * If the entity is not found in the provided parent, the operation will + * continue to search in the parent of the parent, until the root is reached. If + * the entity is still not found, the lookup will search in the flecs.core + * scope. If the entity is not found there either, the function returns 0. + * + * @param world The world. + * @param parent The entity from which to resolve the path. + * @param path The path to resolve. + * @param sep The path separator. + * @param prefix The path prefix. + * @param recursive Recursively traverse up the tree until entity is found. + * @return The entity if found, else 0. + * + * @see ecs_lookup() + * @see ecs_lookup_child() + * @see ecs_lookup_symbol() */ FLECS_API -bool ecs_query_next_instanced( - ecs_iter_t *iter); +ecs_entity_t ecs_lookup_path_w_sep( + const ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix, + bool recursive); -/** Returns whether the query data changed since the last iteration. - * The operation will return true after: - * - new entities have been matched with - * - new tables have been matched/unmatched with - * - matched entities were deleted - * - matched components were changed - * - * The operation will not return true after a write-only (EcsOut) or filter - * (EcsInOutFilter) term has changed, when a term is not matched with the - * current table (This subject) or for tag terms. - * - * The changed state of a table is reset after it is iterated. If a iterator was - * not iterated until completion, tables may still be marked as changed. - * - * If no iterator is provided the operation will return the changed state of the - * all matched tables of the query. - * - * If an iterator is provided, the operation will return the changed state of - * the currently returned iterator result. The following preconditions must be - * met before using an iterator with change detection: - * - * - The iterator is a query iterator (created with ecs_query_iter) - * - The iterator must be valid (ecs_query_next must have returned true) - * - The iterator must be instanced - * - * @param query The query (optional if 'it' is provided). - * @param it The iterator result to test (optional if 'query' is provided). - * @return true if entities changed, otherwise false. +/** Lookup an entity by its symbol name. + * This looks up an entity by symbol stored in `(EcsIdentifier, EcsSymbol)`. The + * operation does not take into account hierarchies. + * + * This operation can be useful to resolve, for example, a type by its C + * identifier, which does not include the Flecs namespacing. + * + * @param world The world. + * @param symbol The symbol. + * @param lookup_as_path If not found as a symbol, lookup as path. + * @param recursive If looking up as path, recursively traverse up the tree. + * @return The entity if found, else 0. + * + * @see ecs_lookup() + * @see ecs_lookup_child() + * @see ecs_lookup_path_w_sep() */ FLECS_API -bool ecs_query_changed( - ecs_query_t *query, - const ecs_iter_t *it); +ecs_entity_t ecs_lookup_symbol( + const ecs_world_t *world, + const char *symbol, + bool lookup_as_path, + bool recursive); -/** Skip a table while iterating. - * This operation lets the query iterator know that a table was skipped while - * iterating. A skipped table will not reset its changed state, and the query - * will not update the dirty flags of the table for its out columns. - * - * Only valid iterators must be provided (next has to be called at least once & - * return true) and the iterator must be a query iterator. - * - * @param it The iterator result to skip. +/** Get a path identifier for an entity. + * This operation creates a path that contains the names of the entities from + * the specified parent to the provided entity, separated by the provided + * separator. If no parent is provided the path will be relative to the root. If + * a prefix is provided, the path will be prefixed by the prefix. + * + * If the parent is equal to the provided child, the operation will return an + * empty string. If a nonzero component is provided, the path will be created by + * looking for parents with that component. + * + * The returned path should be freed by the application. + * + * @param world The world. + * @param parent The entity from which to create the path. + * @param child The entity to which to create the path. + * @param sep The separator to use between path elements. + * @param prefix The initial character to use for root elements. + * @return The relative entity path. + * + * @see ecs_get_path_w_sep_buf() */ FLECS_API -void ecs_query_skip( - ecs_iter_t *it); +char* ecs_get_path_w_sep( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix); -/** Returns whether query is orphaned. - * When the parent query of a subquery is deleted, it is left in an orphaned - * state. The only valid operation on an orphaned query is deleting it. Only - * subqueries can be orphaned. +/** Write path identifier to buffer. + * Same as ecs_get_path_w_sep(), but writes result to an ecs_strbuf_t. * - * @param query The query. - * @return true if query is orphaned, otherwise false. + * @param world The world. + * @param parent The entity from which to create the path. + * @param child The entity to which to create the path. + * @param sep The separator to use between path elements. + * @param prefix The initial character to use for root elements. + * @param buf The buffer to write to. + * + * @see ecs_get_path_w_sep() */ FLECS_API -bool ecs_query_orphaned( - ecs_query_t *query); - -/** @} */ - +void ecs_get_path_w_sep_buf( + const ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf, + bool escape); -/** - * @defgroup trigger Triggers +/** Find or create entity from path. + * This operation will find or create an entity from a path, and will create any + * intermediate entities if required. If the entity already exists, no entities + * will be created. + * + * If the path starts with the prefix, then the entity will be created from the + * root scope. + * + * @param world The world. + * @param parent The entity relative to which the entity should be created. + * @param path The path to create the entity for. + * @param sep The separator used in the path. + * @param prefix The prefix used in the path. + * @return The entity. */ +FLECS_API +ecs_entity_t ecs_new_from_path_w_sep( + ecs_world_t *world, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix); -/** Create trigger. - * Triggers notify the application when certain events happen such as adding or - * removing components. - * - * An application can change the trigger callback or context pointer by calling - * ecs_trigger_init for an existing trigger entity, by setting the - * ecs_trigger_desc_t::entity.entity field in combination with callback and/or - * ctx. +/** Add specified path to entity. + * This operation is similar to ecs_new_from_path(), but will instead add the path + * to an existing entity. * - * See the documentation for ecs_trigger_desc_t for more details. + * If an entity already exists for the path, it will be returned instead. * * @param world The world. - * @param desc The trigger creation parameters. + * @param entity The entity to which to add the path. + * @param parent The entity relative to which the entity should be created. + * @param path The path to create the entity for. + * @param sep The separator used in the path. + * @param prefix The prefix used in the path. + * @return The entity. */ FLECS_API -ecs_entity_t ecs_trigger_init( +ecs_entity_t ecs_add_path_w_sep( ecs_world_t *world, - const ecs_trigger_desc_t *desc); + ecs_entity_t entity, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix); -/** Get trigger context. - * This operation returns the context pointer set for the trigger. If - * the provided entity is not a trigger, the function will return NULL. +/** Set the current scope. + * This operation sets the scope of the current stage to the provided entity. + * As a result new entities will be created in this scope, and lookups will be + * relative to the provided scope. + * + * It is considered good practice to restore the scope to the old value. * * @param world The world. - * @param trigger The trigger from which to obtain the context. - * @return The context. + * @param scope The entity to use as scope. + * @return The previous scope. + * + * @see ecs_get_scope() */ FLECS_API -void* ecs_get_trigger_ctx( - const ecs_world_t *world, - ecs_entity_t trigger); +ecs_entity_t ecs_set_scope( + ecs_world_t *world, + ecs_entity_t scope); -/** Same as ecs_get_trigger_ctx, but for binding ctx. - * The binding context is a context typically used to attach any language - * binding specific data that is needed when invoking a callback that is - * implemented in another language. - * +/** Get the current scope. + * Get the scope set by ecs_set_scope(). If no scope is set, this operation will + * return 0. + * * @param world The world. - * @param trigger The trigger from which to obtain the context. - * @return The context. + * @return The current scope. */ FLECS_API -void* ecs_get_trigger_binding_ctx( - const ecs_world_t *world, - ecs_entity_t trigger); - -typedef struct ecs_event_desc_t { - /* The event id. Only triggers for the specified event will be notified */ - ecs_entity_t event; - - /* Component ids. Only triggers with a matching component id will be - * notified. Observers are guaranteed to get notified once, even if they - * match more than one id. */ - ecs_ids_t *ids; - - /* The table for which to notify. */ - ecs_table_t *table; - - /* Optional 2nd table to notify. This can be used to communicate the - * previous or next table, in case an entity is moved between tables. */ - ecs_table_t *other_table; - - /* Limit notified entities to ones starting from offset (row) in table */ - int32_t offset; - - /* Limit number of notified entities to count. offset+count must be less - * than the total number of entities in the table. If left to 0, it will be - * automatically determined by doing ecs_table_count(table) - offset. */ - int32_t count; - - /* Optional context. Assigned to iter param member */ - const void *param; - - /* Observable (usually the world) */ - ecs_poly_t *observable; - - /* Table events apply to tables, not the entities in the table. When - * enabled, (super)set triggers are not notified. */ - bool table_event; - - /* When set, events will only be propagated by traversing the relation */ - ecs_entity_t relation; -} ecs_event_desc_t; +ecs_entity_t ecs_get_scope( + const ecs_world_t *world); -/** Send event. - * This sends an event to matching triggers & is the mechanism used by flecs - * itself to send OnAdd, OnRemove, etc events. - * - * Applications can use this function to send custom events, where a custom - * event can be any regular entity. - * - * Applications should not send builtin flecs events, as this may violate - * assumptions the code makes about the conditions under which those events are - * sent. - * - * Triggers are invoked synchronously. It is therefore safe to use stack-based - * data as event context, which can be set in the "param" member. - * - * To send a notification for a single entity, an application should set the - * following members in the event descriptor: - * - * - table: set to the table of the entity - * - offset: set to the row of the entity in the table - * - count: set to 1 - * - * The table & row of the entity can be obtained like this: - * ecs_record_t *r = ecs_record_find(world, e); - * desc.table = r->table; - * desc.offset = ECS_RECORD_TO_ROW(r->row); - * +/** Set a name prefix for newly created entities. + * This is a utility that lets C modules use prefixed names for C types and + * C functions, while using names for the entity names that do not have the + * prefix. The name prefix is currently only used by ECS_COMPONENT. + * * @param world The world. - * @param desc Event parameters. + * @param prefix The name prefix to use. + * @return The previous prefix. */ FLECS_API -void ecs_emit( +const char* ecs_set_name_prefix( ecs_world_t *world, - ecs_event_desc_t *desc); - -/** @} */ - - -/** - * @defgroup observer Observers - */ + const char *prefix); -/** Create observer. - * Observers are like triggers, but can subscribe for multiple terms. An - * observer only triggers when the source of the event meets all terms. +/** Set search path for lookup operations. + * This operation accepts an array of entity ids that will be used as search + * scopes by lookup operations. The operation returns the current search path. + * It is good practice to restore the old search path. * - * See the documentation for ecs_observer_desc_t for more details. + * The search path will be evaluated starting from the last element. + * + * The default search path includes flecs.core. When a custom search path is + * provided it overwrites the existing search path. Operations that rely on + * looking up names from flecs.core without providing the namespace may fail if + * the custom search path does not include flecs.core (EcsFlecsCore). + * + * The search path array is not copied into managed memory. The application must + * ensure that the provided array is valid for as long as it is used as the + * search path. + * + * The provided array must be terminated with a 0 element. This enables an + * application to push/pop elements to an existing array without invoking the + * ecs_set_lookup_path() operation again. * * @param world The world. - * @param desc The observer creation parameters. + * @param lookup_path 0-terminated array with entity ids for the lookup path. + * @return Current lookup path array. + * + * @see ecs_get_lookup_path() */ FLECS_API -ecs_entity_t ecs_observer_init( +ecs_entity_t* ecs_set_lookup_path( ecs_world_t *world, - const ecs_observer_desc_t *desc); + const ecs_entity_t *lookup_path); -/** Default run action for observer. - * This function can be called from a custom observer run action (see - * ecs_observer_desc_t::run for more details). This function ensures that the - * observer's filter is applied to the iterator's table, filters out duplicate - * events and implements EcsMonitor logic. - * - * @param it The iterator. - * @return True if the observer was invoked. +/** Get current lookup path. + * Returns value set by ecs_set_lookup_path(). + * + * @param world The world. + * @return The current lookup path. */ FLECS_API -bool ecs_observer_default_run_action( - ecs_iter_t *it); +ecs_entity_t* ecs_get_lookup_path( + const ecs_world_t *world); + +/** @} */ + +/** @} */ + +/** + * @defgroup components Components + * Functions for registering and working with components. + * + * @{ + */ + +/** Find or create a component. + * This operation creates a new component, or finds an existing one. The find or + * create behavior is the same as ecs_entity_init(). + * + * When an existing component is found, the size and alignment are verified with + * the provided values. If the values do not match, the operation will fail. + * + * See the documentation of ecs_component_desc_t for more details. + * + * @param world The world. + * @param desc Component init parameters. + * @return A handle to the new or existing component, or 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_component_init( + ecs_world_t *world, + const ecs_component_desc_t *desc); +/** Get the type info for an component. + * This function returns the type information for a component. The component can + * be a regular component or pair. For the rules on how type information is + * determined based on a component id, see ecs_get_typeid(). + * + * @param world The world. + * @param component The component. + * @return The type information of the id. + */ FLECS_API -void* ecs_get_observer_ctx( +const ecs_type_info_t* ecs_get_type_info( const ecs_world_t *world, - ecs_entity_t observer); + ecs_id_t component); + +/** Register hooks for component. + * Hooks allow for the execution of user code when components are constructed, + * copied, moved, destructed, added, removed or set. Hooks can be assigned as + * as long as a component has not yet been used (added to an entity). + * + * The hooks that are currently set can be accessed with ecs_get_type_info(). + * + * @param world The world. + * @param component The component for which to register the actions + * @param hooks Type that contains the component actions. + */ +FLECS_API +void ecs_set_hooks_id( + ecs_world_t *world, + ecs_entity_t component, + const ecs_type_hooks_t *hooks); +/** Get hooks for component. + * + * @param world The world. + * @param component The component for which to retrieve the hooks. + * @return The hooks for the component, or NULL if not registered. + */ FLECS_API -void* ecs_get_observer_binding_ctx( +const ecs_type_hooks_t* ecs_get_hooks_id( const ecs_world_t *world, - ecs_entity_t observer); + ecs_entity_t component); /** @} */ - /** - * @defgroup iterator Iterators + * @defgroup ids Ids + * Functions for working with `ecs_id_t`. + * * @{ */ -/** Create iterator from poly object. - * The provided poly object must have the iterable mixin. If an object is - * provided that does not have the mixin, the function will assert. - * - * When a filter is provided, an array of two iterators must be passed to the - * function. This allows the mixin implementation to create a chained iterator - * when necessary, which requires two iterator objects. - * - * If a filter is provided, the first element in the array of two iterators is - * the one that should be iterated. The mixin implementation may or may not set - * the second element, depending on whether an iterator chain is required. - * - * Additionally, when a filter is provided the returned iterator will be for a - * single term with the provided filter id. If the iterator is chained, the - * previous iterator in the chain can be accessed through it->chain_it. - * - * @param world The world or stage for which to create the iterator. - * @param poly The poly object from which to create the iterator. - * @param iter The iterator (out, ecs_iter_t[2] when filter is set). - * @param filter Optional term used for filtering the results. +/** Returns whether specified component is a tag. + * This operation returns whether the specified component is a tag (a component + * without data/size). + * + * An id is a tag when: + * - it is an entity without the EcsComponent component + * - it has an EcsComponent with size member set to 0 + * - it is a pair where both elements are a tag + * - it is a pair where the first element has the #EcsPairIsTag tag + * + * @param world The world. + * @param component The component. + * @return Whether the provided id is a tag. */ FLECS_API -void ecs_iter_poly( +bool ecs_id_is_tag( const ecs_world_t *world, - const ecs_poly_t *poly, - ecs_iter_t *iter, - ecs_term_t *filter); + ecs_id_t component); -/** Progress any iterator. - * This operation is useful in combination with iterators for which it is not - * known what created them. Example use cases are functions that should accept - * any kind of iterator (such as serializers) or iterators created from poly - * objects. - * - * This operation is slightly slower than using a type-specific iterator (e.g. - * ecs_filter_next, ecs_query_next) as it has to call a function pointer which - * introduces a level of indirection. - * - * @param it The iterator. - * @return True if iterator has more results, false if not. +/** Returns whether specified component is in use. + * This operation returns whether a component is in use in the world. A + * component is in use if it has been added to one or more tables. + * + * @param world The world. + * @param component The component. + * @return Whether the component is in use. */ FLECS_API -bool ecs_iter_next( - ecs_iter_t *it); +bool ecs_id_in_use( + const ecs_world_t *world, + ecs_id_t component); -/** Cleanup iterator resources. - * This operation cleans up any resources associated with the iterator. - * Iterators may contain allocated resources when the number of matched terms - * exceeds ECS_TERM_CACHE_SIZE and/or when the source for the iterator requires - * to keep state while iterating. - * - * This operation should only be used when an iterator is not iterated until - * completion (next has not yet returned false). When an iterator is iterated - * until completion, resources are automatically freed. - * - * @param it The iterator. +/** Get the type for a component. + * This operation returns the type for a component id, if the id is associated + * with a type. For a regular component with a non-zero size (an entity with the + * EcsComponent component) the operation will return the component id itself. + * + * For an entity that does not have the EcsComponent component, or with an + * EcsComponent value with size 0, the operation will return 0. + * + * For a pair id the operation will return the type associated with the pair, by + * applying the following queries in order: + * - The first pair element is returned if it is a component + * - 0 is returned if the relationship entity has the Tag property + * - The second pair element is returned if it is a component + * - 0 is returned. + * + * @param world The world. + * @param component The component. + * @return The type of the component. */ FLECS_API -void ecs_iter_fini( - ecs_iter_t *it); +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t component); -/** Count number of matched entities in query. - * This operation returns the number of matched entities. If a query contains no - * matched entities but still yields results (e.g. it has no terms with This - * subjects) the operation will return 0. - * - * To determine the number of matched entities, the operation iterates the - * iterator until it yields no more results. - * - * @param it The iterator. - * @return True if iterator has more results, false if not. +/** Utility to match a component with a pattern. + * This operation returns true if the provided pattern matches the provided + * component. The pattern may contain a wildcard (or wildcards, when a pair). + * + * @param component The component. + * @param pattern The pattern to compare with. + * @return Whether the id matches the pattern. */ FLECS_API -bool ecs_iter_count( - ecs_iter_t *it); +bool ecs_id_match( + ecs_id_t component, + ecs_id_t pattern); -/** Test if iterator is true. - * This operation will return true if the iterator returns at least one result. - * This is especially useful in combination with fact-checking rules (see the - * rules addon). - * - * The operation requires a valid iterator. After the operation is invoked, the - * application should no longer invoke next on the iterator and should treat it - * as if the iterator is iterated until completion. - * - * @param it The iterator. - * @return true if the iterator returns at least one result. +/** Utility to check if component is a pair. + * + * @param component The component. + * @return True if component is a pair. */ FLECS_API -bool ecs_iter_is_true( - ecs_iter_t *it); +bool ecs_id_is_pair( + ecs_id_t component); -/** Set value for iterator variable. - * This constrains the iterator to return only results for which the variable - * equals the specified value. The default value for all variables is - * EcsWildcard, which means the variable can assume any value. - * - * Example: - * - * // Rule that matches (Eats, *) - * ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t) { - * .terms = { - * { .pred.entity = Eats, .obj.name = "_Food" } - * } - * }); - * - * int food_var = ecs_rule_find_var(r, "Food"); - * - * // Set Food to Apples, so we're only matching (Eats, Apples) - * ecs_iter_t it = ecs_rule_iter(world, r); - * ecs_iter_set_var(&it, food_var, Apples); - * - * while (ecs_rule_next(&it)) { - * for (int i = 0; i < it.count; i ++) { - * // iterate as usual - * } - * } - * - * The variable must be initialized after creating the iterator and before the - * first call to next. - * - * @param it The iterator. - * @param var_id The variable index. - * @param entity The entity variable value. +/** Utility to check if component is a wildcard. + * + * @param component The component. + * @return True if component is a wildcard or a pair containing a wildcard. */ FLECS_API -void ecs_iter_set_var( - ecs_iter_t *it, - int32_t var_id, - ecs_entity_t entity); +bool ecs_id_is_wildcard( + ecs_id_t component); -/** Same as ecs_iter_set_var, but for a table. - * This constrains the variable to all entities in a table. +/** Utility to check if component is an any wildcard. * - * @param it The iterator. - * @param var_id The variable index. - * @param table The table variable value. + * @param component The component. + * @return True if component is an any wildcard or a pair containing an any wildcard. */ -FLECS_API -void ecs_iter_set_var_as_table( - ecs_iter_t *it, - int32_t var_id, - const ecs_table_t *table); +bool ecs_id_is_any( + ecs_id_t component); -/** Same as ecs_iter_set_var, but for a range of entities - * This constrains the variable to a range of entities in a table. - * - * @param it The iterator. - * @param var_id The variable index. - * @param range The range variable value. +/** Utility to check if id is valid. + * A valid id is an id that can be added to an entity. Invalid ids are: + * - ids that contain wildcards + * - ids that contain invalid entities + * - ids that are 0 or contain 0 entities + * + * Note that the same rules apply to removing from an entity, with the exception + * of wildcards. + * + * @param world The world. + * @param component The component. + * @return True if the id is valid. */ FLECS_API -void ecs_iter_set_var_as_range( - ecs_iter_t *it, - int32_t var_id, - const ecs_table_range_t *range); +bool ecs_id_is_valid( + const ecs_world_t *world, + ecs_id_t component); -/** Get value of iterator variable as entity. - * A variable can be interpreted as entity if it is set to an entity, or if it - * is set to a table range with count 1. - * - * This operation can only be invoked on valid iterators. The variable index - * must be smaller than the total number of variables provided by the iterator - * (as set in ecs_iter_t::variable_count). - * - * @param it The iterator. - * @param var_id The variable index. +/** Get flags associated with id. + * This operation returns the internal flags (see api_flags.h) that are + * associated with the provided id. + * + * @param world The world. + * @param component The component. + * @return Flags associated with the id, or 0 if the id is not in use. */ FLECS_API -ecs_entity_t ecs_iter_get_var( - ecs_iter_t *it, - int32_t var_id); +ecs_flags32_t ecs_id_get_flags( + const ecs_world_t *world, + ecs_id_t component); -/** Get value of iterator variable as table. - * A variable can be interpreted as table if it is set as table range with - * both offset and count set to 0, or if offset is 0 and count matches the - * number of elements in the table. +/** Convert component flag to string. + * This operation converts a component flag to a string. Possible outputs are: * - * This operation can only be invoked on valid iterators. The variable index - * must be smaller than the total number of variables provided by the iterator - * (as set in ecs_iter_t::variable_count). - * - * @param it The iterator. - * @param var_id The variable index. + * - PAIR + * - TOGGLE + * - AUTO_OVERRIDE + * + * @param component_flags The component flag. + * @return The id flag string, or NULL if no valid id is provided. */ FLECS_API -ecs_table_t* ecs_iter_get_var_as_table( - ecs_iter_t *it, - int32_t var_id); +const char* ecs_id_flag_str( + uint64_t component_flags); -/** Get value of iterator variable as table range. - * A value can be interpreted as table range if it is set as table range, or if - * it is set to an entity with a non-empty type (the entity must have at least - * one component, tag or relationship in its type). +/** Convert component id to string. + * This operation converts the provided component id to a string. It can output + * strings of the following formats: * - * This operation can only be invoked on valid iterators. The variable index - * must be smaller than the total number of variables provided by the iterator - * (as set in ecs_iter_t::variable_count). + * - "ComponentName" + * - "FLAG|ComponentName" + * - "(Relationship, Target)" + * - "FLAG|(Relationship, Target)" * - * @param it The iterator. - * @param var_id The variable index. + * The PAIR flag never added to the string. + * + * @param world The world. + * @param component The component to convert to a string. + * @return The component converted to a string. */ FLECS_API -ecs_table_range_t ecs_iter_get_var_as_range( - ecs_iter_t *it, - int32_t var_id); - +char* ecs_id_str( + const ecs_world_t *world, + ecs_id_t component); -/** Returns whether variable is constrained. - * This operation returns true for variables set by one of the ecs_iter_set_var* - * operations. - * - * A constrained variable is guaranteed not to change values while results are - * being iterated. - * - * @param it The iterator. - * @param var_id The variable index. - * @return Whether the variable is constrained to a specified value. +/** Write component string to buffer. + * Same as ecs_id_str() but writes result to ecs_strbuf_t. + * + * @param world The world. + * @param component The component to convert to a string. + * @param buf The buffer to write to. */ FLECS_API -bool ecs_iter_var_is_constrained( - ecs_iter_t *it, - int32_t var_id); +void ecs_id_str_buf( + const ecs_world_t *world, + ecs_id_t component, + ecs_strbuf_t *buf); -/** Create a paged iterator. - * Paged iterators limit the results to those starting from 'offset', and will - * return at most 'limit' results. - * - * The iterator must be iterated with ecs_page_next. - * - * A paged iterator acts as a passthrough for data exposed by the parent - * iterator, so that any data provided by the parent will also be provided by - * the paged iterator. - * - * @param it The source iterator. - * @param offset The number of entities to skip. - * @param limit The maximum number of entities to iterate. - * @return A page iterator. +/** Convert string to a component. + * This operation is the reverse of ecs_id_str(). The FLECS_SCRIPT addon + * is required for this operation to work. + * + * @param world The world. + * @param expr The string to convert to an id. */ FLECS_API -ecs_iter_t ecs_page_iter( - const ecs_iter_t *it, - int32_t offset, - int32_t limit); +ecs_id_t ecs_id_from_str( + const ecs_world_t *world, + const char *expr); -/** Progress a paged iterator. - * Progresses an iterator created by ecs_page_iter. - * - * @param it The iterator. - * @return true if iterator has more results, false if not. +/** @} */ + +/** + * @defgroup queries Queries + * @brief Functions for working with `ecs_term_t` and `ecs_query_t`. + * @{ */ -FLECS_API -bool ecs_page_next( - ecs_iter_t *it); -/** Create a worker iterator. - * Worker iterators can be used to equally divide the number of matched entities - * across N resources (usually threads). Each resource will process the total - * number of matched entities divided by 'count'. - * - * Entities are distributed across resources such that the distribution is - * stable between queries. Two queries that match the same table are guaranteed - * to match the same entities in that table. - * - * The iterator must be iterated with ecs_worker_next. - * - * A worker iterator acts as a passthrough for data exposed by the parent - * iterator, so that any data provided by the parent will also be provided by - * the worker iterator. - * - * @param it The source iterator. - * @param index The index of the current resource. - * @param count The total number of resources to divide entities between. - * @return A worker iterator. +/** Test whether term ref is set. + * A term ref is a reference to an entity, component or variable for one of the + * three parts of a term (src, first, second). + * + * @param ref The term ref. + * @return True when set, false when not set. */ -FLECS_API -ecs_iter_t ecs_worker_iter( - const ecs_iter_t *it, - int32_t index, - int32_t count); +FLECS_API +bool ecs_term_ref_is_set( + const ecs_term_ref_t *ref); -/** Progress a worker iterator. - * Progresses an iterator created by ecs_worker_iter. - * - * @param it The iterator. - * @return true if iterator has more results, false if not. +/** Test whether a term is set. + * This operation can be used to test whether a term has been initialized with + * values or whether it is empty. + * + * An application generally does not need to invoke this operation. It is useful + * when initializing a 0-initialized array of terms (like in ecs_term_desc_t) as + * this operation can be used to find the last initialized element. + * + * @param term The term. + * @return True when set, false when not set. */ FLECS_API -bool ecs_worker_next( - ecs_iter_t *it); +bool ecs_term_is_initialized( + const ecs_term_t *term); -/** Obtain data for a query term. - * This operation retrieves a pointer to an array of data that belongs to the - * term in the query. The index refers to the location of the term in the query, - * and starts counting from one. - * - * For example, the query "Position, Velocity" will return the Position array - * for index 1, and the Velocity array for index 2. +/** Is term matched on $this variable. + * This operation checks whether a term is matched on the $this variable, which + * is the default source for queries. * - * When the specified term is not owned by the entity this function returns a - * pointer instead of an array. This happens when the source of a term is not - * the entity being iterated, such as a shared component (from a prefab), a - * component from a parent, or another entity. The ecs_term_is_owned operation - * can be used to test dynamically if a term is owned. + * A term has a $this source when: + * - ecs_term_t::src::id is EcsThis + * - ecs_term_t::src::flags is EcsIsVariable * - * The provided size must be either 0 or must match the size of the datatype - * of the returned array. If the size does not match, the operation may assert. - * The size can be dynamically obtained with ecs_term_size. + * If ecs_term_t::src is not populated, it will be automatically initialized to + * the $this source for the created query. * - * @param it The iterator. - * @param size The size of the returned array. - * @param index The index of the term in the query. - * @return A pointer to the data associated with the term. + * @param term The term. + * @return True if term matches $this, false if not. */ FLECS_API -void* ecs_term_w_size( - const ecs_iter_t *it, - size_t size, - int32_t index); +bool ecs_term_match_this( + const ecs_term_t *term); -/** Test whether the term is readonly. - * This operation returns whether this is a readonly term. Readonly terms are - * annotated with [in], or are added as a const type in the C++ API. +/** Is term matched on 0 source. + * This operation checks whether a term is matched on a 0 source. A 0 source is + * a term that isn't matched against anything, and can be used just to pass + * (component) ids to a query iterator. * - * @param it The iterator. - * @param index The index of the term in the query. - * @return Whether the term is readonly. + * A term has a 0 source when: + * - ecs_term_t::src::id is 0 + * - ecs_term_t::src::flags has EcsIsEntity set + * + * @param term The term. + * @return True if term has 0 source, false if not. */ FLECS_API -bool ecs_term_is_readonly( - const ecs_iter_t *it, - int32_t index); +bool ecs_term_match_0( + const ecs_term_t *term); -/** Test whether the term is writeonly. - * This operation returns whether this is a writeonly term. Writeonly terms are - * annotated with [out]. - * - * Serializers are not required to serialize the values of a writeonly term. +/** Convert term to string expression. + * Convert term to a string expression. The resulting expression is equivalent + * to the same term, with the exception of And & Or operators. * - * @param it The iterator. - * @param index The index of the term in the query. - * @return Whether the term is writeonly. + * @param world The world. + * @param term The term. + * @return The term converted to a string. */ FLECS_API -bool ecs_term_is_writeonly( - const ecs_iter_t *it, - int32_t index); +char* ecs_term_str( + const ecs_world_t *world, + const ecs_term_t *term); -/** Test whether term is set. +/** Convert query to string expression. + * Convert query to a string expression. The resulting expression can be + * parsed to create the same query. * - * @param it The iterator. - * @param index The index of the term in the query. - * @return Whether the term is set. + * @param query The query. + * @return The query converted to a string. */ -FLECS_API -bool ecs_term_is_set( - const ecs_iter_t *it, - int32_t index); +FLECS_API +char* ecs_query_str( + const ecs_query_t *query); -/** Test whether the term is owned - * This operation returns whether the term is owned by the currently iterated - * entity. This function will return false when the term is owned by another - * entity, such as a parent or a prefab. - * - * @param it The iterator. - * @param index The index of the term in the query. - * @return Whether the term is owned by the iterated over entity/entities. +/** @} */ + +/** + * @defgroup each_iter Each iterator + * @brief Find all entities that have a single (component) id. + * @{ */ -FLECS_API -bool ecs_term_is_owned( - const ecs_iter_t *it, - int32_t index); -/** Convert iterator to string. - * Prints the contents of an iterator to a string. Useful for debugging and/or - * testing the output of an iterator. +/** Iterate all entities with specified (component id). + * This returns an iterator that yields all entities with a single specified + * component. This is a much lighter weight operation than creating and + * iterating a query. * - * The function only converts the currently iterated data to a string. To - * convert all data, the application has to manually call the next function and - * call ecs_iter_str on each result. + * Usage: + * @code + * ecs_iter_t it = ecs_each(world, Player); + * while (ecs_each_next(&it)) { + * for (int i = 0; i < it.count; i ++) { + * // Iterate as usual. + * } + * } + * @endcode * - * @param it The iterator. - * @return A string representing the contents of the iterator. - */ + * If the specified id is a component, it is possible to access the component + * pointer with ecs_field just like with regular queries: + * + * @code + * ecs_iter_t it = ecs_each(world, Position); + * while (ecs_each_next(&it)) { + * Position *p = ecs_field(&it, Position, 0); + * for (int i = 0; i < it.count; i ++) { + * // Iterate as usual. + * } + * } + * @endcode + * + * @param world The world. + * @param component The component to iterate. + * @return An iterator that iterates all entities with the (component) id. +*/ FLECS_API -char* ecs_iter_str( - const ecs_iter_t *it); +ecs_iter_t ecs_each_id( + const ecs_world_t *world, + ecs_id_t component); -/** Find the column index for a given id. - * This operation finds the index of a column in the current type for the - * specified id. For example, if an entity has type Position, Velocity, and the - * application requests the id for the Velocity component, this function will - * return 1. - * - * Note that the column index returned by this function starts from 0, as - * opposed to 1 for the terms. The reason for this is that the returned index - * is equivalent to using the ecs_type_get_index function. - * - * This operation can be used to request columns that are not requested by a - * query. For example, a query may request Position, Velocity, but an entity - * may also have Mass. With this function the iterator can request the data for - * Mass as well, when used in combination with ecs_iter_column. - * +/** Progress an iterator created with ecs_each_id(). + * * @param it The iterator. - * @return The type of the currently iterated entity/entities. + * @return True if the iterator has more results, false if not. */ FLECS_API -int32_t ecs_iter_find_column( - const ecs_iter_t *it, - ecs_id_t id); +bool ecs_each_next( + ecs_iter_t *it); -/** Obtain data for a column index. - * This operation can be used with the id obtained from ecs_iter_find_column to - * request data from the currently iterated over entity/entities that is not - * requested by the query. - * - * The data in the returned pointer can be accessed using the same index as - * the one used to access the arrays returned by the ecs_term function. - * - * The provided size must be either 0 or must match the size of the datatype - * of the returned array. If the size does not match, the operation may assert. - * The size can be dynamically obtained with ecs_iter_column_size. - * - * Note that this function can be used together with iter::type to - * dynamically iterate all data that the matched entities have. An application - * can use the ecs_vector_count function to obtain the number of elements in a - * type. All indices from 0..ecs_vector_count(type) are valid column indices. - * - * Additionally, note that this provides unprotected access to the column data. - * An iterator cannot know or prevent accessing columns that are not queried for - * and thus applications should only use this when it can be guaranteed that - * there are no other threads reading/writing the same column data. +/** Iterate children of parent. + * This operation is usually equivalent to doing: + * @code + * ecs_iter_t it = ecs_each_id(world, ecs_pair(EcsChildOf, parent)); + * @endcode + * + * The only exception is when the parent has the EcsOrderedChildren trait, in + * which case this operation will return a single result with the ordered + * child entity ids. + * + * This operation is equivalent to doing: + * + * @code + * ecs_children_w_rel(world, EcsChildOf, parent); + * @endcode + * + * @param world The world. + * @param parent The parent. + * @return An iterator that iterates all children of the parent. * - * @param it The iterator. - * @param size The size of the column. - * @param index The index of the column. - * @return The data belonging to the column. + * @see ecs_each_id() +*/ +FLECS_API +FLECS_ALWAYS_INLINE ecs_iter_t ecs_children( + const ecs_world_t *world, + ecs_entity_t parent); + +/** Same as ecs_children() but with custom relationship argument. + * + * @param world The world. + * @param relationship The relationship. + * @param parent The parent. + * @return An iterator that iterates all children of the parent. */ FLECS_API -void* ecs_iter_column_w_size( - const ecs_iter_t *it, - size_t size, - int32_t index); +FLECS_ALWAYS_INLINE ecs_iter_t ecs_children_w_rel( + const ecs_world_t *world, + ecs_entity_t relationship, + ecs_entity_t parent); -/** Obtain size for a column index. - * This operation obtains the size for a column. The size is equal to the size - * of the datatype associated with the column. - * +/** Progress an iterator created with ecs_children(). + * * @param it The iterator. - * @param index The index of the column. - * @return The size belonging to the column. + * @return True if the iterator has more results, false if not. */ FLECS_API -size_t ecs_iter_column_size( - const ecs_iter_t *it, - int32_t index); +bool ecs_children_next( + ecs_iter_t *it); /** @} */ - /** - * @defgroup staging Staging + * @defgroup queries Queries + * Functions for working with `ecs_query_t`. + * * @{ */ -/** Begin frame. - * When an application does not use ecs_progress to control the main loop, it - * can still use Flecs features such as FPS limiting and time measurements. This - * operation needs to be invoked whenever a new frame is about to get processed. - * - * Calls to ecs_frame_begin must always be followed by ecs_frame_end. - * - * The function accepts a delta_time parameter, which will get passed to - * systems. This value is also used to compute the amount of time the function - * needs to sleep to ensure it does not exceed the target_fps, when it is set. - * When 0 is provided for delta_time, the time will be measured. - * - * This function should only be ran from the main thread. - * +/** Create a query. + * * @param world The world. - * @param delta_time Time elapsed since the last frame. - * @return The provided delta_time, or measured time if 0 was provided. + * @param desc The descriptor (see ecs_query_desc_t) + * @return The query. */ FLECS_API -FLECS_FLOAT ecs_frame_begin( +ecs_query_t* ecs_query_init( ecs_world_t *world, - FLECS_FLOAT delta_time); + const ecs_query_desc_t *desc); -/** End frame. - * This operation must be called at the end of the frame, and always after - * ecs_frame_begin. +/** Delete a query. * - * @param world The world. + * @param query The query. */ FLECS_API -void ecs_frame_end( - ecs_world_t *world); +void ecs_query_fini( + ecs_query_t *query); -/** Begin staging. - * When an application does not use ecs_progress to control the main loop, it - * can still use Flecs features such as the defer queue. When an application - * needs to stage changes, it needs to call this function after ecs_frame_begin. - * A call to ecs_staging_begin must be followed by a call to ecs_staging_end. - * - * When staging is enabled, modifications to entities are stored to a stage. - * This ensures that arrays are not modified while iterating. Modifications are - * merged back to the "main stage" when ecs_staging_end is invoked. - * - * While the world is in staging mode, no structural changes (add/remove/...) - * can be made to the world itself. Operations must be executed on a stage - * instead (see ecs_get_stage). - * - * This function should only be ran from the main thread. +/** Find variable index. + * This operation looks up the index of a variable in the query. This index can + * be used in operations like ecs_iter_set_var() and ecs_iter_get_var(). * - * @param world The world - * @return Whether world is currently staged. + * @param query The query. + * @param name The variable name. + * @return The variable index. */ FLECS_API -bool ecs_staging_begin( - ecs_world_t *world); +int32_t ecs_query_find_var( + const ecs_query_t *query, + const char *name); -/** End staging. - * Leaves staging mode. After this operation the world may be directly mutated - * again. By default this operation also merges data back into the world, unless - * automerging was disabled explicitly. - * - * This function should only be ran from the main thread. +/** Get variable name. + * This operation returns the variable name for an index. * - * @param world The world + * @param query The query. + * @param var_id The variable index. + * @return The variable name. */ FLECS_API -void ecs_staging_end( - ecs_world_t *world); +const char* ecs_query_var_name( + const ecs_query_t *query, + int32_t var_id); -/** Merge world or stage. - * When automatic merging is disabled, an application can call this - * operation on either an individual stage, or on the world which will merge - * all stages. This operation may only be called when staging is not enabled - * (either after progress() or after staging_end()). - * - * This operation may be called on an already merged stage or world. +/** Test if variable is an entity. + * Internally the query engine has entity variables and table variables. When + * iterating through query variables (by using ecs_query_variable_count()) only + * the values for entity variables are accessible. This operation enables an + * application to check if a variable is an entity variable. * - * @param world The world. + * @param query The query. + * @param var_id The variable id. + * @return Whether the variable is an entity variable. */ FLECS_API -void ecs_merge( - ecs_world_t *world); +bool ecs_query_var_is_entity( + const ecs_query_t *query, + int32_t var_id); -/** Defer operations until end of frame. - * When this operation is invoked while iterating, operations inbetween the - * defer_begin and defer_end operations are executed at the end of the frame. - * - * This operation is thread safe. +/** Create a query iterator. + * Use an iterator to iterate through the entities that match an entity. Queries + * can return multiple results, and have to be iterated by repeatedly calling + * ecs_query_next() until the operation returns false. * - * @param world The world. - * @return true if world changed from non-deferred mode to deferred mode. - */ -FLECS_API -bool ecs_defer_begin( - ecs_world_t *world); - -/** Test if deferring is enabled for current stage. + * Depending on the query, a single result can contain an entire table, a range + * of entities in a table, or a single entity. Iteration code has an inner and + * an outer loop. The outer loop loops through the query results, and typically + * corresponds with a table. The inner loop loops entities in the result. * - * @param world The world. - * @return True if deferred, false if not. - */ -FLECS_API -bool ecs_is_deferred( - const ecs_world_t *world); - -/** End block of operations to defer. - * See defer_begin. - * - * This operation is thread safe. + * Example: + * @code + * ecs_iter_t it = ecs_query_iter(world, q); + * + * while (ecs_query_next(&it)) { + * Position *p = ecs_field(&it, Position, 0); + * Velocity *v = ecs_field(&it, Velocity, 1); + * + * for (int i = 0; i < it.count; i ++) { + * p[i].x += v[i].x; + * p[i].y += v[i].y; + * } + * } + * @endcode + * + * The world passed into the operation must be either the actual world or the + * current stage, when iterating from a system. The stage is accessible through + * the it.world member. + * + * Example: + * @code + * void MySystem(ecs_iter_t *it) { + * ecs_query_t *q = it->ctx; // Query passed as system context + * + * // Create query iterator from system stage + * ecs_iter_t qit = ecs_query_iter(it->world, q); + * while (ecs_query_next(&qit)) { + * // Iterate as usual + * } + * } + * @endcode + * + * If query iteration is stopped without the last call to ecs_query_next() + * returning false, iterator resources need to be cleaned up explicitly + * with ecs_iter_fini(). + * + * Example: + * @code + * ecs_iter_t it = ecs_query_iter(world, q); + * + * while (ecs_query_next(&it)) { + * if (!ecs_field_is_set(&it, 0)) { + * ecs_iter_fini(&it); // Free iterator resources + * break; + * } + * + * for (int i = 0; i < it.count; i ++) { + * // ... + * } + * } + * @endcode * * @param world The world. - * @return true if world changed from deferred mode to non-deferred mode. + * @param query The query. + * @return An iterator. + * + * @see ecs_query_next() */ FLECS_API -bool ecs_defer_end( - ecs_world_t *world); +ecs_iter_t ecs_query_iter( + const ecs_world_t *world, + const ecs_query_t *query); -/** Enable/disable automerging for world or stage. - * When automerging is enabled, staged data will automatically be merged with - * the world when staging ends. This happens at the end of progress(), at a - * sync point or when staging_end() is called. +/** Progress query iterator. * - * Applications can exercise more control over when data from a stage is merged - * by disabling automerging. This requires an application to explicitly call - * merge() on the stage. + * @param it The iterator. + * @return True if the iterator has more results, false if not. + * + * @see ecs_query_iter() + */ +FLECS_API +bool ecs_query_next( + ecs_iter_t *it); + +/** Match entity with query. + * This operation matches an entity with a query and returns the result of the + * match in the "it" out parameter. An application should free the iterator + * resources with ecs_iter_fini() if this function returns true. + * + * Usage: + * @code + * ecs_iter_t it; + * if (ecs_query_has(q, e, &it)) { + * ecs_iter_fini(&it); + * } + * @endcode + * + * @param query The query. + * @param entity The entity to match + * @param it The iterator with matched data. + * @return True if entity matches the query, false if not. + */ +FLECS_API +bool ecs_query_has( + const ecs_query_t *query, + ecs_entity_t entity, + ecs_iter_t *it); + +/** Match table with query. + * This operation matches a table with a query and returns the result of the + * match in the "it" out parameter. An application should free the iterator + * resources with ecs_iter_fini() if this function returns true. + * + * Usage: + * @code + * ecs_iter_t it; + * if (ecs_query_has_table(q, t, &it)) { + * ecs_iter_fini(&it); + * } + * @endcode + * + * @param query The query. + * @param table The table to match + * @param it The iterator with matched data. + * @return True if table matches the query, false if not. + */ +FLECS_API +bool ecs_query_has_table( + const ecs_query_t *query, + ecs_table_t *table, + ecs_iter_t *it); + +/** Match range with query. + * This operation matches a range with a query and returns the result of the + * match in the "it" out parameter. An application should free the iterator + * resources with ecs_iter_fini() if this function returns true. + * + * The entire range must match the query for the operation to return true. + * + * Usage: + * @code + * ecs_table_range_t range = { + * .table = table, + * .offset = 1, + * .count = 2 + * }; + * + * ecs_iter_t it; + * if (ecs_query_has_range(q, &range, &it)) { + * ecs_iter_fini(&it); + * } + * @endcode + * + * @param query The query. + * @param range The range to match + * @param it The iterator with matched data. + * @return True if range matches the query, false if not. + */ +FLECS_API +bool ecs_query_has_range( + const ecs_query_t *query, + ecs_table_range_t *range, + ecs_iter_t *it); + +/** Returns how often a match event happened for a cached query. + * This operation can be used to determine whether the query cache has been + * updated with new tables. + * + * @param query The query. + * @return The number of match events happened. + */ +FLECS_API +int32_t ecs_query_match_count( + const ecs_query_t *query); + +/** Convert query to a string. + * This will convert the query program to a string which can aid in debugging + * the behavior of a query. * - * When this function is invoked on the world, it sets all current stages to - * the provided value and sets the default for new stages. When this function is - * invoked on a stage, automerging is only set for that specific stage. + * The returned string must be freed with ecs_os_free(). * - * @param world The world. - * @param automerge Whether to enable or disable automerging. + * @param query The query. + * @return The query plan. */ FLECS_API -void ecs_set_automerge( - ecs_world_t *world, - bool automerge); +char* ecs_query_plan( + const ecs_query_t *query); -/** Configure world to have N stages. - * This initializes N stages, which allows applications to defer operations to - * multiple isolated defer queues. This is typically used for applications with - * multiple threads, where each thread gets its own queue, and commands are - * merged when threads are synchronized. +/** Convert query to string with profile. + * To use this you must set the EcsIterProfile flag on an iterator before + * starting iteration: * - * Note that the ecs_set_threads function already creates the appropriate - * number of stages. The set_stages() operation is useful for applications that - * want to manage their own stages and/or threads. + * @code + * it.flags |= EcsIterProfile + * @endcode * - * @param world The world. - * @param stages The number of stages. + * The returned string must be freed with ecs_os_free(). + * + * @param query The query. + * @param it The iterator with profile data. + * @return The query plan with profile data. */ FLECS_API -void ecs_set_stages( - ecs_world_t *world, - int32_t stages); +char* ecs_query_plan_w_profile( + const ecs_query_t *query, + const ecs_iter_t *it); -/** Get number of configured stages. - * Return number of stages set by ecs_set_stages. - * - * @param world The world. - * @return The number of stages used for threading. +/** Same as ecs_query_plan(), but includes plan for populating cache (if any). + * + * @param query The query. + * @return The query plan. */ FLECS_API -int32_t ecs_get_stage_count( - const ecs_world_t *world); +char* ecs_query_plans( + const ecs_query_t *query); -/** Get current stage id. - * The stage id can be used by an application to learn about which stage it is - * using, which typically corresponds with the worker thread id. +/** Populate variables from key-value string. + * Convenience function to set query variables from a key-value string separated + * by comma's. The string must have the following format: * - * @param world The world. - * @return The stage id. + * @code + * var_a: value, var_b: value + * @endcode + * + * The key-value list may optionally be enclosed in parenthesis. + * + * This function uses the script addon. + * + * @param query The query. + * @param it The iterator for which to set the variables. + * @param expr The key-value expression. + * @return Pointer to the next character after the last parsed one. */ FLECS_API -int32_t ecs_get_stage_id( - const ecs_world_t *world); +const char* ecs_query_args_parse( + ecs_query_t *query, + ecs_iter_t *it, + const char *expr); -/** Get stage-specific world pointer. - * Flecs threads can safely invoke the API as long as they have a private - * context to write to, also referred to as the stage. This function returns a - * pointer to a stage, disguised as a world pointer. +/** Returns whether the query data changed since the last iteration. + * The operation will return true after: + * - new entities have been matched with + * - new tables have been matched/unmatched with + * - matched entities were deleted + * - matched components were changed * - * Note that this function does not(!) create a new world. It simply wraps the - * existing world in a thread-specific context, which the API knows how to - * unwrap. The reason the stage is returned as an ecs_world_t is so that it - * can be passed transparently to the existing API functions, vs. having to - * create a dediated API for threading. + * The operation will not return true after a write-only (EcsOut) or filter + * (EcsInOutNone) term has changed, when a term is not matched with the + * current table (This subject) or for tag terms. * - * @param world The world. - * @param stage_id The index of the stage to retrieve. - * @return A thread-specific pointer to the world. + * The changed state of a table is reset after it is iterated. If an iterator was + * not iterated until completion, tables may still be marked as changed. + * + * If no iterator is provided the operation will return the changed state of the + * all matched tables of the query. + * + * If an iterator is provided, the operation will return the changed state of + * the currently returned iterator result. The following preconditions must be + * met before using an iterator with change detection: + * + * - The iterator is a query iterator (created with ecs_query_iter()) + * - The iterator must be valid (ecs_query_next() must have returned true) + * + * @param query The query (optional if 'it' is provided). + * @return true if entities changed, otherwise false. */ FLECS_API -ecs_world_t* ecs_get_stage( - const ecs_world_t *world, - int32_t stage_id); +bool ecs_query_changed( + ecs_query_t *query); -/** Get actual world from world. +/** Get query object. + * Returns the query object. Can be used to access various information about + * the query. * - * @param world A pointer to a stage or the world. - * @return The world. + * @param world The world. + * @param query The query. + * @return The query object. */ FLECS_API -const ecs_world_t* ecs_get_world( - const ecs_poly_t *world); +const ecs_query_t* ecs_query_get( + const ecs_world_t *world, + ecs_entity_t query); -/** Test whether the current world object is readonly. - * This function allows the code to test whether the currently used world object - * is readonly or whether it allows for writing. +/** Skip a table while iterating. + * This operation lets the query iterator know that a table was skipped while + * iterating. A skipped table will not reset its changed state, and the query + * will not update the dirty flags of the table for its out columns. * - * @param world A pointer to a stage or the world. - * @return True if the world or stage is readonly. + * Only valid iterators must be provided (next has to be called at least once & + * return true) and the iterator must be a query iterator. + * + * @param it The iterator result to skip. */ FLECS_API -bool ecs_stage_is_readonly( - const ecs_world_t *world); +void ecs_iter_skip( + ecs_iter_t *it); -/** Create asynchronous stage. - * An asynchronous stage can be used to asynchronously queue operations for - * later merging with the world. An asynchronous stage is similar to a regular - * stage, except that it does not allow reading from the world. +/** Set group to iterate for query iterator. + * This operation limits the results returned by the query to only the selected + * group id. The query must have a group_by function, and the iterator must + * be a query iterator. * - * Asynchronous stages are never merged automatically, and must therefore be - * manually merged with the ecs_merge function. It is not necessary to call - * defer_begin or defer_end before and after enqueuing commands, as an - * asynchronous stage unconditionally defers operations. + * Groups are sets of tables that are stored together in the query cache based + * on a group id, which is calculated per table by the group_by function. To + * iterate a group, an iterator only needs to know the first and last cache node + * for that group, which can both be found in a fast O(1) operation. * - * The application must ensure that no commands are added to the stage while the - * stage is being merged. + * As a result, group iteration is one of the most efficient mechanisms to + * filter out large numbers of entities, even if those entities are distributed + * across many tables. This makes it a good fit for things like dividing up + * a world into cells, and only iterating cells close to a player. * - * An asynchronous stage must be cleaned up by ecs_async_stage_free. + * The group to iterate must be set before the first call to ecs_query_next(). No + * operations that can add/remove components should be invoked between calling + * ecs_iter_set_group() and ecs_query_next(). * - * @param world The world. - * @return The stage. + * @param it The query iterator. + * @param group_id The group to iterate. */ FLECS_API -ecs_world_t* ecs_async_stage_new( - ecs_world_t *world); +void ecs_iter_set_group( + ecs_iter_t *it, + uint64_t group_id); + +/** Return map with query groups. + * This map can be used to iterate the active group identifiers of a query. The + * payload of the map is opaque. The map can be used as follows: + * + * @code + * const ecs_map_t *keys = ecs_query_get_groups(q); + * ecs_map_iter_t kit = ecs_map_iter(keys); + * while (ecs_map_next(&kit)) { + * uint64_t group_id = ecs_map_key(&kit); + * + * // Iterate query for group + * ecs_iter_t it = ecs_query_iter(world, q); + * ecs_iter_set_group(&it, group_id); + * while (ecs_query_next(&it)) { + * // Iterate as usual + * } + * } + * @endcode + * + * This operation is not valid for queries that do not use group_by. The + * returned map pointer will remain valid for as long as the query exists. + * + * @param query The query. + * @return The map with query groups. + */ +FLECS_API +const ecs_map_t* ecs_query_get_groups( + const ecs_query_t *query); -/** Free asynchronous stage. - * The provided stage must be an asynchronous stage. If a non-asynchronous stage - * is provided, the operation will fail. +/** Get context of query group. + * This operation returns the context of a query group as returned by the + * on_group_create callback. * - * @param stage The stage to free. + * @param query The query. + * @param group_id The group for which to obtain the context. + * @return The group context, NULL if the group doesn't exist. */ FLECS_API -void ecs_async_stage_free( - ecs_world_t *stage); +void* ecs_query_get_group_ctx( + const ecs_query_t *query, + uint64_t group_id); -/** Test whether provided stage is asynchronous. +/** Get information about query group. + * This operation returns information about a query group, including the group + * context returned by the on_group_create callback. * - * @param stage The stage. - * @return True when the stage is asynchronous, false for a regular stage or - * world. + * @param query The query. + * @param group_id The group for which to obtain the group info. + * @return The group info, NULL if the group doesn't exist. */ FLECS_API -bool ecs_stage_is_async( - ecs_world_t *stage); +const ecs_query_group_info_t* ecs_query_get_group_info( + const ecs_query_t *query, + uint64_t group_id); + +/** Struct returned by ecs_query_count(). */ +typedef struct ecs_query_count_t { + int32_t results; /**< Number of results returned by query. */ + int32_t entities; /**< Number of entities returned by query. */ + int32_t tables; /**< Number of tables returned by query. Only set for + * queries for which the table count can be reliably + * determined. */ +} ecs_query_count_t; + +/** Returns number of entities and results the query matches with. + * Only entities matching the $this variable as source are counted. + * + * @param query The query. + * @return The number of matched entities. + */ +FLECS_API +ecs_query_count_t ecs_query_count( + const ecs_query_t *query); + +/** Does query return one or more results. + * + * @param query The query. + * @return True if query matches anything, false if not. + */ +FLECS_API +bool ecs_query_is_true( + const ecs_query_t *query); + +/** Get query used to populate cache. + * This operation returns the query that is used to populate the query cache. + * For queries that are can be entirely cached, the returned query will be + * equivalent to the query passed to ecs_query_get_cache_query(). + * + * @param query The query. + * @return The query used to populate the cache, NULL if query is not cached. + */ +FLECS_API +const ecs_query_t* ecs_query_get_cache_query( + const ecs_query_t *query); /** @} */ /** - * @defgroup id_search_functions Search functions for component ids. - * @brief Low-level functions to search for component ids in table types. + * @defgroup observers Observers + * Functions for working with events and observers. + * * @{ */ -/** Search for component id in table type. - * This operation returns the index of first occurrance of the id in the table - * type. The id may be a wildcard. - * - * When id_out is provided, the function will assign it with the found id. The - * found id may be different from the provided id if it is a wildcard. - * - * This is a constant time operation. - * +/** Send event. + * This sends an event to matching triggers & is the mechanism used by flecs + * itself to send `OnAdd`, `OnRemove`, etc events. + * + * Applications can use this function to send custom events, where a custom + * event can be any regular entity. + * + * Applications should not send builtin flecs events, as this may violate + * assumptions the code makes about the conditions under which those events are + * sent. + * + * Triggers are invoked synchronously. It is therefore safe to use stack-based + * data as event context, which can be set in the "param" member. + * * @param world The world. - * @param table The table. - * @param id The id to search for. - * @param id_out If provided, it will be set to the found id (optional). - * @return The index of the id in the table type. + * @param desc Event parameters. + * + * @see ecs_enqueue() */ FLECS_API -int32_t ecs_search( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id, - ecs_id_t *id_out); +void ecs_emit( + ecs_world_t *world, + ecs_event_desc_t *desc); -/** Search for component id in table type starting from an offset. - * This operation is the same as ecs_search, but starts searching from an offset - * in the table type. - * - * This operation is typically called in a loop where the resulting index is - * used in the next iteration as offset: - * - * int32_t index = -1; - * while ((index = ecs_search_offset(world, table, offset, id, NULL))) { - * // do stuff - * } - * - * Depending on how the operation is used it is either linear or constant time. - * When the id has the form (id) or (rel, *) and the operation is invoked as - * in the above example, it is guaranteed to be constant time. - * - * If the provided id has the form (*, obj) the operation takes linear time. The - * reason for this is that ids for an object are not packed together, as they - * are sorted relation first. +/** Enqueue event. + * Same as ecs_emit(), but enqueues an event in the command queue instead. The + * event will be emitted when ecs_defer_end() is called. * - * If the id at the offset does not match the provided id, the operation will do - * a linear search to find a matching id. + * If this operation is called when the provided world is not in deferred mode + * it behaves just like ecs_emit(). * * @param world The world. - * @param table The table. - * @param offset Offset from where to start searching. - * @param id The id to search for. - * @param id_out If provided, it will be set to the found id (optional). - * @return The index of the id in the table type. + * @param desc Event parameters. +*/ +FLECS_API +void ecs_enqueue( + ecs_world_t *world, + ecs_event_desc_t *desc); + +/** Create observer. + * Observers are like triggers, but can subscribe for multiple terms. An + * observer only triggers when the source of the event meets all terms. + * + * See the documentation for ecs_observer_desc_t for more details. + * + * @param world The world. + * @param desc The observer creation parameters. + * @return The observer, or 0 if the operation failed. */ FLECS_API -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); +ecs_entity_t ecs_observer_init( + ecs_world_t *world, + const ecs_observer_desc_t *desc); -/** Search for component/relation id in table type starting from an offset. - * This operation is the same as ecs_search_offset, but has the additional - * capability of traversing relationships to find a component. For example, if - * an application wants to find a component for either the provided table or a - * prefab (using the IsA relation) of that table, it could use the operation - * like this: - * - * int32_t index = ecs_search_relation( - * world, // the world - * table, // the table - * 0, // offset 0 - * ecs_id(Position), // the component id - * EcsIsA, // the relation to traverse - * 0, // start at depth 0 (the table itself) - * 0, // no depth limit - * NULL, // (optional) entity on which component was found - * NULL, // see above - * NULL); // internal type with information about matched id - * - * The operation searches depth first. If a table type has 2 IsA relations, the - * operation will first search the IsA tree of the first relation. - * - * When choosing betwen ecs_search, ecs_search_offset and ecs_search_relation, - * the simpler the function the better its performance. - * - * @param world The world. - * @param table The table. - * @param offset Offset from where to start searching. - * @param id The id to search for. - * @param rel The relation to traverse (optional). - * @param min_depth The minimum search depth. Use 1 for only shared components. - * @param max_depth The maximum search depth. Zero means no maximum. - * @param subject_out If provided, it will be set to the matched entity. - * @param id_out If provided, it will be set to the found id (optional). - * @param depth_out If provided, it will be set to the traversal depth. - * @param tr_out Internal datatype. - * @return The index of the id in the table type. - */ -FLECS_API -int32_t ecs_search_relation( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_entity_t rel, - int32_t min_depth, - int32_t max_depth, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - int32_t *depth_out, - struct ecs_table_record_t **tr_out); - -/** Similar to ecs_search_relation, but find component at maximum depth. - * Instead of searching for the first occurrence of a component following a - * relationship, this operation returns the last (deepest) occurrence of the - * component. This operation can be used to determine the depth of a tree. - * +/** Get observer object. + * Returns the observer object. Can be used to access various information about + * the observer, like the query and context. + * * @param world The world. - * @param table The table. - * @param offset Offset from where to start searching. - * @param id The id to search for. - * @param rel The relation to traverse (optional). - * @param min_depth The minimum search depth. Use 1 for only shared components. - * @param max_depth The maximum search depth. Zero means no maximum. - * @param subject_out If provided, it will be set to the matched entity. - * @param id_out If provided, it will be set to the found id (optional). - * @param depth_out If provided, it will be set to the traversal depth. - * @param tr_out Internal datatype. + * @param observer The observer. + * @return The observer object. */ FLECS_API -int32_t ecs_search_relation_last( +const ecs_observer_t* ecs_observer_get( const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_entity_t rel, - int32_t min_depth, - int32_t max_depth, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - int32_t *depth_out, - struct ecs_table_record_t **tr_out); + ecs_entity_t observer); /** @} */ /** - * @defgroup table_functions Public table operations - * @brief Low-level table functions. These functions are intended to enable the - * creation of higher-level operations. It is not recommended to use - * these operations directly in application code as they do not provide - * the same safety guarantees as the other APIs. + * @defgroup iterator Iterators + * Functions for working with `ecs_iter_t`. + * * @{ */ -/** Get type for table. +/** Progress any iterator. + * This operation is useful in combination with iterators for which it is not + * known what created them. Example use cases are functions that should accept + * any kind of iterator (such as serializers) or iterators created from poly + * objects. * - * @param table The table. - * @return The type of the table. + * This operation is slightly slower than using a type-specific iterator (e.g. + * ecs_query_next, ecs_query_next) as it has to call a function pointer which + * introduces a level of indirection. + * + * @param it The iterator. + * @return True if iterator has more results, false if not. */ FLECS_API -ecs_type_t ecs_table_get_type( - const ecs_table_t *table); +bool ecs_iter_next( + ecs_iter_t *it); -/** Get storage type for table. +/** Cleanup iterator resources. + * This operation cleans up any resources associated with the iterator. * - * @param table The table. - * @return The storage type of the table (components only). + * This operation should only be used when an iterator is not iterated until + * completion (next has not yet returned false). When an iterator is iterated + * until completion, resources are automatically freed. + * + * @param it The iterator. */ FLECS_API -ecs_table_t* ecs_table_get_storage_table( - const ecs_table_t *table); - -/** Convert index in table type to index in table storage type. */ -int32_t ecs_table_type_to_storage_index( - const ecs_table_t *table, - int32_t index); - -/** Convert index in table storage type to index in table type. */ -int32_t ecs_table_storage_to_type_index( - const ecs_table_t *table, - int32_t index); +void ecs_iter_fini( + ecs_iter_t *it); -/** Returns the number of records in the table. - * This operation returns the number of records that have been populated through - * the regular (entity) API as well as the number of records that have been - * inserted using the direct access API. +/** Count number of matched entities in query. + * This operation returns the number of matched entities. If a query contains no + * matched entities but still yields results (e.g. it has no terms with This + * sources) the operation will return 0. * - * @param table The table. - * @return The number of records in a table. + * To determine the number of matched entities, the operation iterates the + * iterator until it yields no more results. + * + * @param it The iterator. + * @return True if iterator has more results, false if not. */ FLECS_API -int32_t ecs_table_count( - const ecs_table_t *table); +int32_t ecs_iter_count( + ecs_iter_t *it); -/** Get table that has all components of current table plus the specified id. - * If the provided table already has the provided id, the operation will return - * the provided table. +/** Test if iterator is true. + * This operation will return true if the iterator returns at least one result. + * This is especially useful in combination with fact-checking queries (see the + * queries addon). * - * @param world The world. - * @param table The table. - * @param id The id to add. - * @result The resulting table. + * The operation requires a valid iterator. After the operation is invoked, the + * application should no longer invoke next on the iterator and should treat it + * as if the iterator is iterated until completion. + * + * @param it The iterator. + * @return true if the iterator returns at least one result. */ FLECS_API -ecs_table_t* ecs_table_add_id( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id); +bool ecs_iter_is_true( + ecs_iter_t *it); -/** Get table that has all components of current table minus the specified id. - * If the provided table doesn't have the provided id, the operation will return - * the provided table. +/** Get first matching entity from iterator. + * After this operation the application should treat the iterator as if it has + * been iterated until completion. * - * @param world The world. - * @param table The table. - * @param id The id to remove. - * @result The resulting table. + * @param it The iterator. + * @return The first matching entity, or 0 if no entities were matched. */ FLECS_API -ecs_table_t* ecs_table_remove_id( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id); +ecs_entity_t ecs_iter_first( + ecs_iter_t *it); -/** Lock or unlock table. - * When a table is locked, modifications to it will trigger an assert. When the - * table is locked recursively, it will take an equal amount of unlock - * operations to actually unlock the table. +/** Set value for iterator variable. + * This constrains the iterator to return only results for which the variable + * equals the specified value. The default value for all variables is + * EcsWildcard, which means the variable can assume any value. * - * Table locks can be used to build safe iterators where it is guaranteed that - * the contents of a table are not modified while it is being iterated. + * Example: * - * The operation only works when called on the world, and has no side effects - * when called on a stage. The assumption is that when called on a stage, - * operations are deferred already. + * @code + * // Query that matches (Eats, *) + * ecs_query_t *q = ecs_query(world, { + * .terms = { + * { .first.id = Eats, .second.name = "$food" } + * } + * }); + * + * int food_var = ecs_query_find_var(r, "food"); + * + * // Set Food to Apples, so we're only matching (Eats, Apples) + * ecs_iter_t it = ecs_query_iter(world, q); + * ecs_iter_set_var(&it, food_var, Apples); + * + * while (ecs_query_next(&it)) { + * for (int i = 0; i < it.count; i ++) { + * // iterate as usual + * } + * } + * @endcode * - * @param world The world. - * @param table The table to lock. + * The variable must be initialized after creating the iterator and before the + * first call to next. + * + * @param it The iterator. + * @param var_id The variable index. + * @param entity The entity variable value. + * + * @see ecs_iter_set_var_as_range() + * @see ecs_iter_set_var_as_table() */ FLECS_API -void ecs_table_lock( - ecs_world_t *world, - ecs_table_t *table); +void ecs_iter_set_var( + ecs_iter_t *it, + int32_t var_id, + ecs_entity_t entity); -/** Unlock a table. - * Must be called after calling ecs_table_lock. +/** Same as ecs_iter_set_var(), but for a table. + * This constrains the variable to all entities in a table. * - * @param world The world. - * @param table The table to unlock. + * @param it The iterator. + * @param var_id The variable index. + * @param table The table variable value. + * + * @see ecs_iter_set_var() + * @see ecs_iter_set_var_as_range() */ FLECS_API -void ecs_table_unlock( - ecs_world_t *world, - ecs_table_t *table); +void ecs_iter_set_var_as_table( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_t *table); -/** Returns whether table is a module or contains module contents - * Returns true for tables that have module contents. Can be used to filter out - * tables that do not contain application data. +/** Same as ecs_iter_set_var(), but for a range of entities + * This constrains the variable to a range of entities in a table. * - * @param table The table. - * @return true if table contains module contents, false if not. + * @param it The iterator. + * @param var_id The variable index. + * @param range The range variable value. + * + * @see ecs_iter_set_var() + * @see ecs_iter_set_var_as_table() */ FLECS_API -bool ecs_table_has_module( - ecs_table_t *table); +void ecs_iter_set_var_as_range( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_range_t *range); -/** Commit (move) entity to a table. - * This operation moves an entity from its current table to the specified - * table. This may trigger the following actions: - * - Ctor for each component in the target table - * - Move for each overlapping component - * - Dtor for each component in the source table. - * - OnAdd triggers for non-overlapping components in the target table - * - OnRemove triggers for non-overlapping components in the source table. - * - * This operation is a faster than adding/removing components individually. +/** Get value of iterator variable as entity. + * A variable can be interpreted as entity if it is set to an entity, or if it + * is set to a table range with count 1. * - * The application must explicitly provide the difference in components between - * tables as the added/removed parameters. This can usually be derived directly - * from the result of ecs_table_add_id and esc_table_remove_id. These arrays are - * required to properly execute OnAdd/OnRemove triggers. + * This operation can only be invoked on valid iterators. The variable index + * must be smaller than the total number of variables provided by the iterator + * (as set in ecs_iter_t::variable_count). * - * @param world The world. - * @param entity The entity to commit. - * @param record The entity's record (optional, providing it saves a lookup). - * @param table The table to commit the entity to. - * @return True if the entity got moved, false otherwise. + * @param it The iterator. + * @param var_id The variable index. + * @return The variable value. */ FLECS_API -bool ecs_commit( - ecs_world_t *world, - ecs_entity_t entity, - ecs_record_t *record, - ecs_table_t *table, - ecs_ids_t *added, - ecs_ids_t *removed); +ecs_entity_t ecs_iter_get_var( + ecs_iter_t *it, + int32_t var_id); -/** Find record for entity. */ +/** Get variable name. + * + * @param it The iterator. + * @param var_id The variable index. + * @return The variable name. + */ FLECS_API -ecs_record_t* ecs_record_find( - const ecs_world_t *world, - ecs_entity_t entity); +const char* ecs_iter_get_var_name( + const ecs_iter_t *it, + int32_t var_id); -/** Get component pointer from column/record. */ +/** Get number of variables. + * + * @param it The iterator. + * @return The number of variables. +*/ FLECS_API -void* ecs_record_get_column( - ecs_record_t *r, - int32_t column, - size_t c_size); - -/** @} */ +int32_t ecs_iter_get_var_count( + const ecs_iter_t *it); -/** - * @file flecs_c.h - * @brief Extends the core API with convenience functions/macro's for C applications. +/** Get variable array. + * + * @param it The iterator. + * @return The variable array (if any). */ +FLECS_API +ecs_var_t* ecs_iter_get_vars( + const ecs_iter_t *it); -#ifndef FLECS_C_ -#define FLECS_C_ - -/** - * @defgroup create_macros Macro's that help with creation of ECS objects. - * @{ +/** Get value of iterator variable as table. + * A variable can be interpreted as table if it is set as table range with + * both offset and count set to 0, or if offset is 0 and count matches the + * number of elements in the table. + * + * This operation can only be invoked on valid iterators. The variable index + * must be smaller than the total number of variables provided by the iterator + * (as set in ecs_iter_t::variable_count). + * + * @param it The iterator. + * @param var_id The variable index. + * @return The variable value. */ +FLECS_API +ecs_table_t* ecs_iter_get_var_as_table( + ecs_iter_t *it, + int32_t var_id); -/* Use for declaring entity, tag, prefab / any other entity identifier */ -#define ECS_DECLARE(id)\ - ecs_entity_t id, ecs_id(id) - -#define ECS_ENTITY_DEFINE(world, id, ...) \ - { \ - ecs_entity_desc_t desc = {0}; \ - desc.entity = id; \ - desc.name = #id; \ - desc.add_expr = #__VA_ARGS__; \ - id = ecs_entity_init(world, &desc); \ - ecs_id(id) = id; \ - } \ - (void)id; \ - (void)ecs_id(id); +/** Get value of iterator variable as table range. + * A value can be interpreted as table range if it is set as table range, or if + * it is set to an entity with a non-empty type (the entity must have at least + * one component, tag or relationship in its type). + * + * This operation can only be invoked on valid iterators. The variable index + * must be smaller than the total number of variables provided by the iterator + * (as set in ecs_iter_t::variable_count). + * + * @param it The iterator. + * @param var_id The variable index. + * @return The variable value. + */ +FLECS_API +ecs_table_range_t ecs_iter_get_var_as_range( + ecs_iter_t *it, + int32_t var_id); -#define ECS_ENTITY(world, id, ...) \ - ecs_entity_t ecs_id(id); \ - ecs_entity_t id = 0; \ - ECS_ENTITY_DEFINE(world, id, __VA_ARGS__); +/** Returns whether variable is constrained. + * This operation returns true for variables set by one of the ecs_iter_set_var* + * operations. + * + * A constrained variable is guaranteed not to change values while results are + * being iterated. + * + * @param it The iterator. + * @param var_id The variable index. + * @return Whether the variable is constrained to a specified value. + */ +FLECS_API +bool ecs_iter_var_is_constrained( + ecs_iter_t *it, + int32_t var_id); -#define ECS_TAG_DEFINE(world, id) ECS_ENTITY_DEFINE(world, id, 0) -#define ECS_TAG(world, id) ECS_ENTITY(world, id, 0) +/** Return the group id for the currently iterated result. + * This operation returns the group id for queries that use group_by. If this + * operation is called on an iterator that is not iterating a query that uses + * group_by it will fail. + * + * For queries that use cascade, this operation will return the hierarchy depth + * of the currently iterated result. + * + * @param it The iterator. + * @return The group id of the currently iterated result. + */ +FLECS_API +uint64_t ecs_iter_get_group( + const ecs_iter_t *it); -#define ECS_PREFAB_DEFINE(world, id, ...) ECS_ENTITY_DEFINE(world, id, Prefab, __VA_ARGS__) -#define ECS_PREFAB(world, id, ...) ECS_ENTITY(world, id, Prefab, __VA_ARGS__) +/** Returns whether current iterator result has changed. + * This operation must be used in combination with a query that supports change + * detection (e.g. is cached). The operation returns whether the currently + * iterated result has changed since the last time it was iterated by the query. + * + * Change detection works on a per-table basis. Changes to individual entities + * cannot be detected this way. + * + * @param it The iterator. + * @return True if the result changed, false if it didn't. +*/ +FLECS_API +bool ecs_iter_changed( + ecs_iter_t *it); -/* Use for declaring component identifiers */ -#define ECS_COMPONENT_DECLARE(id) ecs_entity_t ecs_id(id) -#define ECS_COMPONENT_DEFINE(world, id) \ - {\ - ecs_component_desc_t desc = {0}; \ - desc.entity.entity = ecs_id(id); \ - desc.entity.name = #id; \ - desc.entity.symbol = #id; \ - desc.size = sizeof(id); \ - desc.alignment = ECS_ALIGNOF(id); \ - ecs_id(id) = ecs_component_init(world, &desc);\ - ecs_assert(ecs_id(id) != 0, ECS_INVALID_PARAMETER, NULL);\ - } +/** Convert iterator to string. + * Prints the contents of an iterator to a string. Useful for debugging and/or + * testing the output of an iterator. + * + * The function only converts the currently iterated data to a string. To + * convert all data, the application has to manually call the next function and + * call ecs_iter_str() on each result. + * + * @param it The iterator. + * @return A string representing the contents of the iterator. + */ +FLECS_API +char* ecs_iter_str( + const ecs_iter_t *it); -#define ECS_COMPONENT(world, id)\ - ecs_entity_t ecs_id(id) = 0;\ - ECS_COMPONENT_DEFINE(world, id);\ - (void)ecs_id(id) +/** Create a paged iterator. + * Paged iterators limit the results to those starting from 'offset', and will + * return at most 'limit' results. + * + * The iterator must be iterated with ecs_page_next(). + * + * A paged iterator acts as a passthrough for data exposed by the parent + * iterator, so that any data provided by the parent will also be provided by + * the paged iterator. + * + * @param it The source iterator. + * @param offset The number of entities to skip. + * @param limit The maximum number of entities to iterate. + * @return A page iterator. + */ +FLECS_API +ecs_iter_t ecs_page_iter( + const ecs_iter_t *it, + int32_t offset, + int32_t limit); -/* Use for declaring trigger, observer and system identifiers */ -#define ECS_SYSTEM_DECLARE(id) ecs_entity_t ecs_id(id) +/** Progress a paged iterator. + * Progresses an iterator created by ecs_page_iter(). + * + * @param it The iterator. + * @return true if iterator has more results, false if not. + */ +FLECS_API +bool ecs_page_next( + ecs_iter_t *it); -/* Triggers */ -#define ECS_TRIGGER_DEFINE(world, id, kind, component) \ - {\ - ecs_trigger_desc_t desc = {0}; \ - desc.entity.entity = ecs_id(id); \ - desc.entity.name = #id;\ - desc.callback = id;\ - desc.expr = #component;\ - desc.events[0] = kind;\ - ecs_id(id) = ecs_trigger_init(world, &desc);\ - ecs_assert(ecs_id(id) != 0, ECS_INVALID_PARAMETER, NULL);\ - } +/** Create a worker iterator. + * Worker iterators can be used to equally divide the number of matched entities + * across N resources (usually threads). Each resource will process the total + * number of matched entities divided by 'count'. + * + * Entities are distributed across resources such that the distribution is + * stable between queries. Two queries that match the same table are guaranteed + * to match the same entities in that table. + * + * The iterator must be iterated with ecs_worker_next(). + * + * A worker iterator acts as a passthrough for data exposed by the parent + * iterator, so that any data provided by the parent will also be provided by + * the worker iterator. + * + * @param it The source iterator. + * @param index The index of the current resource. + * @param count The total number of resources to divide entities between. + * @return A worker iterator. + */ +FLECS_API +ecs_iter_t ecs_worker_iter( + const ecs_iter_t *it, + int32_t index, + int32_t count); -#define ECS_TRIGGER(world, id, kind, component) \ - ecs_entity_t ecs_id(id) = 0; \ - ECS_TRIGGER_DEFINE(world, id, kind, component);\ - ecs_entity_t id = ecs_id(id);\ - (void)ecs_id(id);\ - (void)id; +/** Progress a worker iterator. + * Progresses an iterator created by ecs_worker_iter(). + * + * @param it The iterator. + * @return true if iterator has more results, false if not. + */ +FLECS_API +bool ecs_worker_next( + ecs_iter_t *it); -/* Observers */ -#define ECS_OBSERVER_DEFINE(world, id, kind, ...)\ - {\ - ecs_observer_desc_t desc = {0};\ - desc.entity.entity = ecs_id(id); \ - desc.entity.name = #id;\ - desc.callback = id;\ - desc.filter.expr = #__VA_ARGS__;\ - desc.events[0] = kind;\ - ecs_id(id) = ecs_observer_init(world, &desc);\ - ecs_assert(ecs_id(id) != 0, ECS_INVALID_PARAMETER, NULL);\ - } +/** Get data for field. + * This operation retrieves a pointer to an array of data that belongs to the + * term in the query. The index refers to the location of the term in the query, + * and starts counting from zero. + * + * For example, the query `"Position, Velocity"` will return the `Position` array + * for index 0, and the `Velocity` array for index 1. + * + * When the specified field is not owned by the entity this function returns a + * pointer instead of an array. This happens when the source of a field is not + * the entity being iterated, such as a shared component (from a prefab), a + * component from a parent, or another entity. The ecs_field_is_self() operation + * can be used to test dynamically if a field is owned. + * + * When a field contains a sparse component, use the ecs_field_at function. When + * a field is guaranteed to be set and owned, the ecs_field_self() function can be + * used. ecs_field_self() has slightly better performance, and provides stricter + * validity checking. + * + * The provided size must be either 0 or must match the size of the type + * of the returned array. If the size does not match, the operation may assert. + * The size can be dynamically obtained with ecs_field_size(). + * + * An example: + * + * @code + * while (ecs_query_next(&it)) { + * Position *p = ecs_field(&it, Position, 0); + * Velocity *v = ecs_field(&it, Velocity, 1); + * for (int32_t i = 0; i < it->count; i ++) { + * p[i].x += v[i].x; + * p[i].y += v[i].y; + * } + * } + * @endcode + * + * @param it The iterator. + * @param size The size of the field type. + * @param index The index of the field. + * @return A pointer to the data of the field. + */ +FLECS_API +void* ecs_field_w_size( + const ecs_iter_t *it, + size_t size, + int8_t index); + +/** Get data for field at specified row. + * This operation should be used instead of ecs_field_w_size for sparse + * component fields. This operation should be called for each returned row in a + * result. In the following example the Velocity component is sparse: + * + * @code + * while (ecs_query_next(&it)) { + * Position *p = ecs_field(&it, Position, 0); + * for (int32_t i = 0; i < it->count; i ++) { + * Velocity *v = ecs_field_at(&it, Velocity, 1); + * p[i].x += v->x; + * p[i].y += v->y; + * } + * } + * @endcode + * + * @param it the iterator. + * @param size The size of the field type. + * @param index The index of the field. + * @return A pointer to the data of the field. + */ +FLECS_API +void* ecs_field_at_w_size( + const ecs_iter_t *it, + size_t size, + int8_t index, + int32_t row); -#define ECS_OBSERVER(world, id, kind, ...)\ - ecs_entity_t ecs_id(id) = 0; \ - ECS_OBSERVER_DEFINE(world, id, kind, __VA_ARGS__);\ - ecs_entity_t id = ecs_id(id);\ - (void)ecs_id(id);\ - (void)id; +/** Test whether the field is readonly. + * This operation returns whether the field is readonly. Readonly fields are + * annotated with [in], or are added as a const type in the C++ API. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return Whether the field is readonly. + */ +FLECS_API +bool ecs_field_is_readonly( + const ecs_iter_t *it, + int8_t index); -/** @} */ +/** Test whether the field is writeonly. + * This operation returns whether this is a writeonly field. Writeonly terms are + * annotated with [out]. + * + * Serializers are not required to serialize the values of a writeonly field. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return Whether the field is writeonly. + */ +FLECS_API +bool ecs_field_is_writeonly( + const ecs_iter_t *it, + int8_t index); +/** Test whether field is set. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return Whether the field is set. + */ +FLECS_API +bool ecs_field_is_set( + const ecs_iter_t *it, + int8_t index); -/** - * @defgroup function_macros Convenience macro's that wrap ECS operations - * @{ +/** Return id matched for field. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return The id matched for the field. */ +FLECS_API +ecs_id_t ecs_field_id( + const ecs_iter_t *it, + int8_t index); +/** Return index of matched table column. + * This function only returns column indices for fields that have been matched + * on the $this variable. Fields matched on other tables will return -1. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return The index of the matched column, -1 if not matched. + */ +FLECS_API +int32_t ecs_field_column( + const ecs_iter_t *it, + int8_t index); -/* -- World API -- */ +/** Return field source. + * The field source is the entity on which the field was matched. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return The source for the field. + */ +FLECS_API +ecs_entity_t ecs_field_src( + const ecs_iter_t *it, + int8_t index); -#define ecs_set_component_actions(world, T, ...)\ - ecs_set_component_actions_w_id(world, ecs_id(T), &(EcsComponentLifecycle)__VA_ARGS__) +/** Return field type size. + * Return type size of the field. Returns 0 if the field has no data. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return The type size for the field. + */ +FLECS_API +size_t ecs_field_size( + const ecs_iter_t *it, + int8_t index); -/* -- New -- */ +/** Test whether the field is matched on self. + * This operation returns whether the field is matched on the currently iterated + * entity. This function will return false when the field is owned by another + * entity, such as a parent or a prefab. + * + * When this operation returns false, the field must be accessed as a single + * value instead of an array. Fields for which this operation returns true + * return arrays with it->count values. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return Whether the field is matched on self. + */ +FLECS_API +bool ecs_field_is_self( + const ecs_iter_t *it, + int8_t index); -#define ecs_new(world, T) ecs_new_w_id(world, ecs_id(T)) +/** @} */ -#define ecs_new_w_pair(world, relation, object)\ - ecs_new_w_id(world, ecs_pair(relation, object)) +/** + * @defgroup tables Tables + * Functions for working with `ecs_table_t`. + * + * @{ + */ -#define ecs_bulk_new(world, component, count)\ - ecs_bulk_new_w_id(world, ecs_id(component), count) +/** Get type for table. + * The table type is a vector that contains all component, tag and pair ids. + * + * @param table The table. + * @return The type of the table. + */ +FLECS_API +const ecs_type_t* ecs_table_get_type( + const ecs_table_t *table); -#define ecs_new_entity(world, n)\ - ecs_entity_init(world, &(ecs_entity_desc_t) {\ - .name = n,\ - }) +/** Get type index for component. + * This operation returns the index for a component in the table's type. + * + * @param world The world. + * @param table The table. + * @param component The component. + * @return The index of the component in the table type, or -1 if not found. + * + * @see ecs_table_has_id() + */ +FLECS_API +int32_t ecs_table_get_type_index( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t component); -#define ecs_new_prefab(world, n)\ - ecs_entity_init(world, &(ecs_entity_desc_t) {\ - .name = n,\ - .add = {EcsPrefab}\ - }) +/** Get column index for component. + * This operation returns the column index for a component in the table's type. + * If the component doesn't have data (it is a tag), the function will return -1. + * + * @param world The world. + * @param table The table. + * @param component The component. + * @return The column index of the id, or -1 if not found/not a component. + */ +FLECS_API +int32_t ecs_table_get_column_index( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t component); -/* -- Add -- */ +/** Return number of columns in table. + * Similar to `ecs_table_get_type(table)->count`, except that the column count + * only counts the number of components in a table. + * + * @param table The table. + * @return The number of columns in the table. + */ +FLECS_API +int32_t ecs_table_column_count( + const ecs_table_t *table); -#define ecs_add(world, entity, T)\ - ecs_add_id(world, entity, ecs_id(T)) - -#define ecs_add_pair(world, subject, relation, object)\ - ecs_add_id(world, subject, ecs_pair(relation, object)) +/** Convert type index to column index. + * Tables have an array of columns for each component in the table. This array + * does not include elements for tags, which means that the index for a + * component in the table type is not necessarily the same as the index in the + * column array. This operation converts from an index in the table type to an + * index in the column array. + * + * @param table The table. + * @param index The index in the table type. + * @return The index in the table column array. + * + * @see ecs_table_column_to_type_index() + */ +FLECS_API +int32_t ecs_table_type_to_column_index( + const ecs_table_t *table, + int32_t index); +/** Convert column index to type index. + * Same as ecs_table_type_to_column_index(), but converts from an index in the + * column array to an index in the table type. + * + * @param table The table. + * @param index The column index. + * @return The index in the table type. + */ +FLECS_API +int32_t ecs_table_column_to_type_index( + const ecs_table_t *table, + int32_t index); -/* -- Remove -- */ +/** Get column from table by column index. + * This operation returns the component array for the provided index. + * + * @param table The table. + * @param index The column index. + * @param offset The index of the first row to return (0 for entire column). + * @return The component array, or NULL if the index is not a component. + */ +FLECS_API +void* ecs_table_get_column( + const ecs_table_t *table, + int32_t index, + int32_t offset); -#define ecs_remove(world, entity, T)\ - ecs_remove_id(world, entity, ecs_id(T)) +/** Get column from table by component. + * This operation returns the component array for the provided component. + * + * @param world The world. + * @param table The table. + * @param component The component for the column. + * @param offset The index of the first row to return (0 for entire column). + * @return The component array, or NULL if the index is not a component. + */ +FLECS_API +void* ecs_table_get_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t component, + int32_t offset); -#define ecs_remove_pair(world, subject, relation, object)\ - ecs_remove_id(world, subject, ecs_pair(relation, object)) +/** Get column size from table. + * This operation returns the component size for the provided index. + * + * @param table The table. + * @param index The column index. + * @return The component size, or 0 if the index is not a component. + */ +FLECS_API +size_t ecs_table_get_column_size( + const ecs_table_t *table, + int32_t index); +/** Returns the number of entities in the table. + * This operation returns the number of entities in the table. + * + * @param table The table. + * @return The number of entities in the table. + */ +FLECS_API +int32_t ecs_table_count( + const ecs_table_t *table); -/* -- Bulk remove/delete -- */ +/** Returns allocated size of table. + * This operation returns the number of elements allocated in the table + * per column. + * + * @param table The table. + * @return The number of allocated elements in the table. + */ +FLECS_API +int32_t ecs_table_size( + const ecs_table_t *table); -#define ecs_delete_children(world, parent)\ - ecs_delete_with(world, ecs_pair(EcsChildOf, parent)) +/** Returns array with entity ids for table. + * The size of the returned array is the result of ecs_table_count(). + * + * @param table The table. + * @return Array with entity ids for table. + */ +FLECS_API +const ecs_entity_t* ecs_table_entities( + const ecs_table_t *table); +/** Test if table has component. + * Same as `ecs_table_get_type_index(world, table, component) != -1`. + * + * @param world The world. + * @param table The table. + * @param component The component. + * @return True if the table has the id, false if the table doesn't. + * + * @see ecs_table_get_type_index() + */ +FLECS_API +bool ecs_table_has_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t component); -/* -- Set -- */ +/** Get relationship target for table. + * + * @param world The world. + * @param table The table. + * @param relationship The relationship for which to obtain the target. + * @param index The index, in case the table has multiple instances of the relationship. + * @return The requested relationship target. + * + * @see ecs_get_target() + */ +FLECS_API +ecs_entity_t ecs_table_get_target( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t relationship, + int32_t index); -#define ecs_set_ptr(world, entity, component, ptr)\ - ecs_set_id(world, entity, ecs_id(component), sizeof(component), ptr) +/** Return depth for table in tree for relationship rel. + * Depth is determined by counting the number of targets encountered while + * traversing up the relationship tree for rel. Only acyclic relationships are + * supported. + * + * @param world The world. + * @param table The table. + * @param rel The relationship. + * @return The depth of the table in the tree. + */ +FLECS_API +int32_t ecs_table_get_depth( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t rel); -#define ecs_set(world, entity, component, ...)\ - ecs_set_id(world, entity, ecs_id(component), sizeof(component), &(component)__VA_ARGS__) +/** Get table that has all components of current table plus the specified id. + * If the provided table already has the provided id, the operation will return + * the provided table. + * + * @param world The world. + * @param table The table. + * @param component The component to add. + * @result The resulting table. + */ +FLECS_API +ecs_table_t* ecs_table_add_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t component); -#define ecs_set_pair(world, subject, relation, object, ...)\ - ecs_set_id(world, subject,\ - ecs_pair(ecs_id(relation), object),\ - sizeof(relation), &(relation)__VA_ARGS__) +/** Find table from id array. + * This operation finds or creates a table with the specified array of + * (component) ids. The ids in the array must be sorted, and it may not contain + * duplicate elements. + * + * @param world The world. + * @param ids The id array. + * @param id_count The number of elements in the id array. + * @return The table with the specified (component) ids. + */ +FLECS_API +ecs_table_t* ecs_table_find( + ecs_world_t *world, + const ecs_id_t *ids, + int32_t id_count); -#define ecs_set_pair_object(world, subject, relation, object, ...)\ - ecs_set_id(world, subject,\ - ecs_pair(relation, ecs_id(object)),\ - sizeof(object), &(object)__VA_ARGS__) +/** Get table that has all components of current table minus the specified component. + * If the provided table doesn't have the provided component, the operation will + * return the provided table. + * + * @param world The world. + * @param table The table. + * @param component The component to remove. + * @result The resulting table. + */ +FLECS_API +ecs_table_t* ecs_table_remove_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t component); -#define ecs_set_override(world, entity, T, ...)\ - ecs_add_id(world, entity, ECS_OVERRIDE | ecs_id(T));\ - ecs_set(world, entity, T, __VA_ARGS__) +/** Lock a table. + * When a table is locked, modifications to it will throw an assert. When the + * table is locked recursively, it will take an equal amount of unlock + * operations to actually unlock the table. + * + * Table locks can be used to build safe iterators where it is guaranteed that + * the contents of a table are not modified while it is being iterated. + * + * The operation only works when called on the world, and has no side effects + * when called on a stage. The assumption is that when called on a stage, + * operations are deferred already. + * + * @param world The world. + * @param table The table to lock. + */ +FLECS_API +void ecs_table_lock( + ecs_world_t *world, + ecs_table_t *table); -/* -- Emplace -- */ +/** Unlock a table. + * Must be called after calling ecs_table_lock(). + * + * @param world The world. + * @param table The table to unlock. + */ +FLECS_API +void ecs_table_unlock( + ecs_world_t *world, + ecs_table_t *table); -#define ecs_emplace(world, entity, T)\ - (ECS_CAST(T*, ecs_emplace_id(world, entity, ecs_id(T)))) +/** Test table for flags. + * Test if table has all of the provided flags. See + * include/flecs/private/api_flags.h for a list of table flags that can be used + * with this function. + * + * @param table The table. + * @param flags The flags to test for. + * @return Whether the specified flags are set for the table. + */ +FLECS_API +bool ecs_table_has_flags( + ecs_table_t *table, + ecs_flags32_t flags); +/** Check if table has traversable entities. + * Traversable entities are entities that are used as target in a pair with a + * relationship that has the Traversable trait. + * + * @param table The table. + * @return Whether the table has traversable entities. + */ +FLECS_API +bool ecs_table_has_traversable( + const ecs_table_t *table); -/* -- Get -- */ +/** Swaps two elements inside the table. This is useful for implementing custom + * table sorting algorithms. + * @param world The world + * @param table The table to swap elements in + * @param row_1 Table element to swap with row_2 + * @param row_2 Table element to swap with row_1 +*/ +FLECS_API +void ecs_table_swap_rows( + ecs_world_t* world, + ecs_table_t* table, + int32_t row_1, + int32_t row_2); -#define ecs_get_ref(world, ref, entity, T)\ - (ECS_CAST(const T*, ecs_get_ref_id(world, ref, entity, ecs_id(T)))) +/** Commit (move) entity to a table. + * This operation moves an entity from its current table to the specified + * table. This may cause the following actions: + * - Ctor for each component in the target table + * - Move for each overlapping component + * - Dtor for each component in the source table. + * - `OnAdd` triggers for non-overlapping components in the target table + * - `OnRemove` triggers for non-overlapping components in the source table. + * + * This operation is a faster than adding/removing components individually. + * + * The application must explicitly provide the difference in components between + * tables as the added/removed parameters. This can usually be derived directly + * from the result of ecs_table_add_id() and ecs_table_remove_id(). These arrays are + * required to properly execute `OnAdd`/`OnRemove` triggers. + * + * @param world The world. + * @param entity The entity to commit. + * @param record The entity's record (optional, providing it saves a lookup). + * @param table The table to commit the entity to. + * @return True if the entity got moved, false otherwise. + */ +FLECS_API +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); -#define ecs_get(world, entity, T)\ - (ECS_CAST(const T*, ecs_get_id(world, entity, ecs_id(T)))) -#define ecs_get_pair(world, subject, relation, object)\ - (ECS_CAST(relation*, ecs_get_id(world, subject,\ - ecs_pair(ecs_id(relation), object)))) +/** Search for component in table type. + * This operation returns the index of first occurrence of the component in the + * table type. The component may be a pair or wildcard. + * + * When component_out is provided, the function will assign it with the found + * component. The found component may be different from the provided component + * if it is a wildcard. + * + * This is a constant time operation. + * + * @param world The world. + * @param table The table. + * @param component The component to search for. + * @param component_out If provided, it will be set to the found component (optional). + * @return The index of the id in the table type. + * + * @see ecs_search_offset() + * @see ecs_search_relation() + */ +FLECS_API +int32_t ecs_search( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t component, + ecs_id_t *component_out); -#define ecs_get_pair_object(world, subject, relation, object)\ - (ECS_CAST(object*, ecs_get_id(world, subject,\ - ecs_pair(relation, ecs_id(object))))) +/** Search for component in table type starting from an offset. + * This operation is the same as ecs_search(), but starts searching from an offset + * in the table type. + * + * This operation is typically called in a loop where the resulting index is + * used in the next iteration as offset: + * + * @code + * int32_t index = -1; + * while ((index = ecs_search_offset(world, table, offset, id, NULL))) { + * // do stuff + * } + * @endcode + * + * Depending on how the operation is used it is either linear or constant time. + * When the id has the form `(id)` or `(rel, *)` and the operation is invoked as + * in the above example, it is guaranteed to be constant time. + * + * If the provided component has the form `(*, tgt)` the operation takes linear + * time. The reason for this is that ids for an target are not packed together, + * as they are sorted relationship first. + * + * If the component at the offset does not match the provided id, the operation + * will do a linear search to find a matching id. + * + * @param world The world. + * @param table The table. + * @param offset Offset from where to start searching. + * @param component The component to search for. + * @param component_out If provided, it will be set to the found component (optional). + * @return The index of the id in the table type. + * + * @see ecs_search() + * @see ecs_search_relation() + */ +FLECS_API +int32_t ecs_search_offset( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t component, + ecs_id_t *component_out); +/** Search for component/relationship id in table type starting from an offset. + * This operation is the same as ecs_search_offset(), but has the additional + * capability of traversing relationships to find a component. For example, if + * an application wants to find a component for either the provided table or a + * prefab (using the `IsA` relationship) of that table, it could use the operation + * like this: + * + * @code + * int32_t index = ecs_search_relation( + * world, // the world + * table, // the table + * 0, // offset 0 + * ecs_id(Position), // the component id + * EcsIsA, // the relationship to traverse + * 0, // start at depth 0 (the table itself) + * 0, // no depth limit + * NULL, // (optional) entity on which component was found + * NULL, // see above + * NULL); // internal type with information about matched id + * @endcode + * + * The operation searches depth first. If a table type has 2 `IsA` relationships, the + * operation will first search the `IsA` tree of the first relationship. + * + * When choosing between ecs_search(), ecs_search_offset() and ecs_search_relation(), + * the simpler the function the better its performance. + * + * @param world The world. + * @param table The table. + * @param offset Offset from where to start searching. + * @param component The component to search for. + * @param rel The relationship to traverse (optional). + * @param flags Whether to search EcsSelf and/or EcsUp. + * @param tgt_out If provided, it will be set to the matched entity. + * @param component_out If provided, it will be set to the found component (optional). + * @param tr_out Internal datatype. + * @return The index of the component in the table type. + * + * @see ecs_search() + * @see ecs_search_offset() + */ +FLECS_API +int32_t ecs_search_relation( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t component, + ecs_entity_t rel, + ecs_flags64_t flags, /* EcsSelf and/or EcsUp */ + ecs_entity_t *tgt_out, + ecs_id_t *component_out, + struct ecs_table_record_t **tr_out); -/* -- Get mut & Modified -- */ +/* Up traversal from entity */ +FLECS_API +int32_t ecs_search_relation_for_entity( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + ecs_entity_t rel, + bool self, + ecs_component_record_t *cr, + ecs_entity_t *tgt_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out); -#define ecs_get_mut(world, entity, T, is_added)\ - (ECS_CAST(T*, ecs_get_mut_id(world, entity, ecs_id(T), is_added))) +/** Remove all entities in a table. Does not deallocate table memory. + * Retaining table memory can be efficient when planning + * to refill the table with operations like ecs_bulk_init + * + * @param world The world. + * @param table The table to clear. + */ +FLECS_API +void ecs_table_clear_entities( + ecs_world_t* world, + ecs_table_t* table); -#define ecs_get_mut_pair(world, subject, relation, object, is_added)\ - (ECS_CAST(relation*, ecs_get_mut_id(world, subject,\ - ecs_pair(ecs_id(relation), object), is_added))) +/** @} */ -#define ecs_get_mut_pair_object(world, subject, relation, object, is_added)\ - (ECS_CAST(object*, ecs_get_mut_id(world, subject,\ - ecs_pair(relation, ecs_id(object)), is_added))) +/** + * @defgroup values Values + * Construct, destruct, copy and move dynamically created values. + * + * @{ + */ -#define ecs_modified(world, entity, component)\ - ecs_modified_id(world, entity, ecs_id(component)) +/** Construct a value in existing storage + * + * @param world The world. + * @param type The type of the value to create. + * @param ptr Pointer to a value of type 'type' + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_init( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr); -#define ecs_modified_pair(world, subject, relation, object)\ - ecs_modified_id(world, subject, ecs_pair(relation, object)) +/** Construct a value in existing storage + * + * @param world The world. + * @param ti The type info of the type to create. + * @param ptr Pointer to a value of type 'type' + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_init_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void *ptr); +/** Construct a value in new storage + * + * @param world The world. + * @param type The type of the value to create. + * @return Pointer to type if success, NULL if failed. + */ +FLECS_API +void* ecs_value_new( + ecs_world_t *world, + ecs_entity_t type); -/* -- Singletons -- */ +/** Construct a value in new storage + * + * @param world The world. + * @param ti The type info of the type to create. + * @return Pointer to type if success, NULL if failed. + */ +void* ecs_value_new_w_type_info( + ecs_world_t *world, + const ecs_type_info_t *ti); -#define ecs_singleton_add(world, comp)\ - ecs_add(world, ecs_id(comp), comp) +/** Destruct a value + * + * @param world The world. + * @param ti Type info of the value to destruct. + * @param ptr Pointer to constructed value of type 'type'. + * @return Zero if success, nonzero if failed. + */ +int ecs_value_fini_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void *ptr); -#define ecs_singleton_remove(world, comp)\ - ecs_remove(world, ecs_id(comp), comp) +/** Destruct a value + * + * @param world The world. + * @param type The type of the value to destruct. + * @param ptr Pointer to constructed value of type 'type'. + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_fini( + const ecs_world_t *world, + ecs_entity_t type, + void* ptr); -#define ecs_singleton_get(world, comp)\ - ecs_get(world, ecs_id(comp), comp) +/** Destruct a value, free storage + * + * @param world The world. + * @param type The type of the value to destruct. + * @param ptr A pointer to the value. + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_free( + ecs_world_t *world, + ecs_entity_t type, + void* ptr); -#define ecs_singleton_set(world, comp, ...)\ - ecs_set(world, ecs_id(comp), comp, __VA_ARGS__) +/** Copy value. + * + * @param world The world. + * @param ti Type info of the value to copy. + * @param dst Pointer to the storage to copy to. + * @param src Pointer to the value to copy. + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_copy_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + const void *src); -#define ecs_singleton_get_mut(world, comp)\ - ecs_get_mut(world, ecs_id(comp), comp, NULL) +/** Copy value. + * + * @param world The world. + * @param type The type of the value to copy. + * @param dst Pointer to the storage to copy to. + * @param src Pointer to the value to copy. + * @return Zero if success, nonzero if failed. + */ +FLECS_API +int ecs_value_copy( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + const void *src); -#define ecs_singleton_modified(world, comp)\ - ecs_modified(world, ecs_id(comp), comp) +/** Move value. + * + * @param world The world. + * @param ti Type info of the value to move. + * @param dst Pointer to the storage to move to. + * @param src Pointer to the value to move. + * @return Zero if success, nonzero if failed. + */ +int ecs_value_move_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + void *src); +/** Move value. + * + * @param world The world. + * @param type The type of the value to move. + * @param dst Pointer to the storage to move to. + * @param src Pointer to the value to move. + * @return Zero if success, nonzero if failed. + */ +int ecs_value_move( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + void *src); -/* -- Has, Owns & Shares -- */ +/** Move construct value. + * + * @param world The world. + * @param ti Type info of the value to move. + * @param dst Pointer to the storage to move to. + * @param src Pointer to the value to move. + * @return Zero if success, nonzero if failed. + */ +int ecs_value_move_ctor_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + void *src); -#define ecs_has(world, entity, T)\ - ecs_has_id(world, entity, ecs_id(T)) +/** Move construct value. + * + * @param world The world. + * @param type The type of the value to move. + * @param dst Pointer to the storage to move to. + * @param src Pointer to the value to move. + * @return Zero if success, nonzero if failed. + */ +int ecs_value_move_ctor( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + void *src); -#define ecs_has_pair(world, entity, relation, object)\ - ecs_has_id(world, entity, ecs_pair(relation, object)) +/** @} */ -#define ecs_owns_id(world, entity, id)\ - (ecs_search(world, ecs_get_table(world, entity), id, 0) != -1) +/** @} */ -#define ecs_owns_pair(world, entity, relation, object)\ - ecs_owns_id(world, entity, ecs_pair(relation, object)) +/** + * @defgroup c_addons Addons + * @ingroup c + * C APIs for addons. + * + * @{ + * @} + */ -#define ecs_owns(world, entity, T)\ - ecs_owns_id(world, entity, ecs_id(T)) +/** + * @file addons/flecs_c.h + * @brief Extends the core API with convenience macros for C applications. + */ -#define ecs_shares_id(world, entity, id)\ - (ecs_search_relation(world, ecs_get_table(world, entity), 0, ecs_id(id), \ - EcsIsA, 1, 0, 0, 0, 0) != -1) +#ifndef FLECS_C_ +#define FLECS_C_ -#define ecs_shares_pair(world, entity, relation, object)\ - (ecs_shares_id(world, entity, ecs_pair(relation, object))) +/** + * @defgroup flecs_c Macro API + * @ingroup c + * Convenience macro's for C API + * + * @{ + */ -#define ecs_shares(world, entity, T)\ - (ecs_shares_id(world, entity, ecs_id(T))) +/** + * @defgroup flecs_c_creation Creation macro's + * Convenience macro's for creating entities, components and observers + * + * @{ + */ +/* Use for declaring entity, tag, prefab / any other entity identifier */ +#define ECS_DECLARE(id)\ + ecs_entity_t id, ecs_id(id) -/* -- Enable / Disable component -- */ +/** Forward declare an entity. */ +#define ECS_ENTITY_DECLARE ECS_DECLARE -#define ecs_enable_component(world, entity, T, enable)\ - ecs_enable_component_w_id(world, entity, ecs_id(T), enable) +/** Define a forward declared entity. + * + * Example: + * + * @code + * ECS_ENTITY_DEFINE(world, MyEntity, Position, Velocity); + * @endcode + */ +#define ECS_ENTITY_DEFINE(world, id_, ...) \ + { \ + ecs_entity_desc_t desc = {0}; \ + desc.id = id_; \ + desc.name = #id_; \ + desc.add_expr = #__VA_ARGS__; \ + id_ = ecs_entity_init(world, &desc); \ + ecs_id(id_) = id_; \ + ecs_assert(id_ != 0, ECS_INVALID_PARAMETER, "failed to create entity %s", #id_); \ + } \ + (void)id_; \ + (void)ecs_id(id_) + +/** Declare & define an entity. + * + * Example: + * + * @code + * ECS_ENTITY(world, MyEntity, Position, Velocity); + * @endcode + */ +#define ECS_ENTITY(world, id, ...) \ + ecs_entity_t ecs_id(id); \ + ecs_entity_t id = 0; \ + ECS_ENTITY_DEFINE(world, id, __VA_ARGS__) -#define ecs_is_component_enabled(world, entity, T)\ - ecs_is_component_enabled_w_id(world, entity, ecs_id(T)) +/** Forward declare a tag. */ +#define ECS_TAG_DECLARE ECS_DECLARE +/** Define a forward declared tag. + * + * Example: + * + * @code + * ECS_TAG_DEFINE(world, MyTag); + * @endcode + */ +#define ECS_TAG_DEFINE(world, id) ECS_ENTITY_DEFINE(world, id, 0) -/* -- Count -- */ +/** Declare & define a tag. + * + * Example: + * + * @code + * ECS_TAG(world, MyTag); + * @endcode + */ +#define ECS_TAG(world, id) ECS_ENTITY(world, id, 0) -#define ecs_count(world, type)\ - ecs_count_id(world, ecs_id(type)) +/** Forward declare a prefab. */ +#define ECS_PREFAB_DECLARE ECS_DECLARE +/** Define a forward declared prefab. + * + * Example: + * + * @code + * ECS_PREFAB_DEFINE(world, MyPrefab, Position, Velocity); + * @endcode + */ +#define ECS_PREFAB_DEFINE(world, id, ...) ECS_ENTITY_DEFINE(world, id, Prefab, __VA_ARGS__) -/* -- Lookups & Paths -- */ +/** Declare & define a prefab. + * + * Example: + * + * @code + * ECS_PREFAB(world, MyPrefab, Position, Velocity); + * @endcode + */ +#define ECS_PREFAB(world, id, ...) ECS_ENTITY(world, id, Prefab, __VA_ARGS__) -#define ecs_lookup_path(world, parent, path)\ - ecs_lookup_path_w_sep(world, parent, path, ".", NULL, true) +/** Forward declare a component. */ +#define ECS_COMPONENT_DECLARE(id) ecs_entity_t ecs_id(id) -#define ecs_lookup_fullpath(world, path)\ - ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true) +/** Define a forward declared component. + * + * Example: + * + * @code + * ECS_COMPONENT_DEFINE(world, Position); + * @endcode + */ +#define ECS_COMPONENT_DEFINE(world, id_) \ + {\ + ecs_component_desc_t desc = {0}; \ + ecs_entity_desc_t edesc = {0}; \ + edesc.id = ecs_id(id_); \ + edesc.use_low_id = true; \ + edesc.name = #id_; \ + edesc.symbol = #id_; \ + desc.entity = ecs_entity_init(world, &edesc); \ + desc.type.size = ECS_SIZEOF(id_); \ + desc.type.alignment = ECS_ALIGNOF(id_); \ + ecs_id(id_) = ecs_component_init(world, &desc);\ + }\ + ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, "failed to create component %s", #id_) -#define ecs_get_path(world, parent, child)\ - ecs_get_path_w_sep(world, parent, child, ".", NULL) +/** Declare & define a component. + * + * Example: + * + * @code + * ECS_COMPONENT(world, Position); + * @endcode + */ +#define ECS_COMPONENT(world, id)\ + ecs_entity_t ecs_id(id) = 0;\ + ECS_COMPONENT_DEFINE(world, id);\ + (void)ecs_id(id) -#define ecs_get_fullpath(world, child)\ - ecs_get_path_w_sep(world, 0, child, ".", NULL) +/* Forward declare an observer. */ +#define ECS_OBSERVER_DECLARE(id) ecs_entity_t ecs_id(id) -#define ecs_get_fullpath_buf(world, child, buf)\ - ecs_get_path_w_sep_buf(world, 0, child, ".", NULL, buf) +/** Define a forward declared observer. + * + * Example: + * + * @code + * ECS_OBSERVER_DEFINE(world, AddPosition, EcsOnAdd, Position); + * @endcode + */ +#define ECS_OBSERVER_DEFINE(world, id_, kind, ...)\ + {\ + ecs_observer_desc_t desc = {0};\ + ecs_entity_desc_t edesc = {0}; \ + edesc.id = ecs_id(id_); \ + edesc.name = #id_; \ + desc.entity = ecs_entity_init(world, &edesc); \ + desc.callback = id_;\ + desc.query.expr = #__VA_ARGS__;\ + desc.events[0] = kind;\ + ecs_id(id_) = ecs_observer_init(world, &desc);\ + ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, "failed to create observer %s", #id_);\ + } -#define ecs_new_from_path(world, parent, path)\ - ecs_new_from_path_w_sep(world, parent, path, ".", NULL) +/** Declare & define an observer. + * + * Example: + * + * @code + * ECS_OBSERVER(world, AddPosition, EcsOnAdd, Position); + * @endcode + */ +#define ECS_OBSERVER(world, id, kind, ...)\ + ecs_entity_t ecs_id(id) = 0; \ + ECS_OBSERVER_DEFINE(world, id, kind, __VA_ARGS__);\ + ecs_entity_t id = ecs_id(id);\ + (void)ecs_id(id);\ + (void)id -#define ecs_new_from_fullpath(world, path)\ - ecs_new_from_path_w_sep(world, 0, path, ".", NULL) +/* Forward declare a query. */ +#define ECS_QUERY_DECLARE(name) ecs_query_t* name -#define ecs_add_path(world, entity, parent, path)\ - ecs_add_path_w_sep(world, entity, parent, path, ".", NULL) +/** Define a forward declared query. + * + * Example: + * + * @code + * ECS_QUERY_DEFINE(world, Move, Position, [in] Velocity); + * @endcode + */ +#define ECS_QUERY_DEFINE(world, name_, ...)\ + {\ + ecs_query_desc_t desc = {0};\ + ecs_entity_desc_t edesc = {0}; \ + edesc.name = #name_; \ + desc.entity = ecs_entity_init(world, &edesc); \ + desc.expr = #__VA_ARGS__;\ + name_ = ecs_query_init(world, &desc);\ + ecs_assert(name_ != NULL, ECS_INVALID_PARAMETER, "failed to create query %s", #name_);\ + } -#define ecs_add_fullpath(world, entity, path)\ - ecs_add_path_w_sep(world, entity, 0, path, ".", NULL) +/** Declare & define a query. + * + * Example: + * + * @code + * ECS_QUERY(world, Move, Position, [in] Velocity); + * @endcode + */ +#define ECS_QUERY(world, name, ...)\ + ecs_query_t* name = NULL; \ + ECS_QUERY_DEFINE(world, name, __VA_ARGS__);\ + (void)name + +/** Shorthand for creating an entity with ecs_entity_init(). + * + * Example: + * + * @code + * ecs_entity(world, { + * .name = "MyEntity" + * }); + * @endcode + */ +#define ecs_entity(world, ...)\ + ecs_entity_init(world, &(ecs_entity_desc_t) __VA_ARGS__ ) +/** Shorthand for creating a component with ecs_component_init(). + * + * Example: + * + * @code + * ecs_component(world, { + * .type.size = 4, + * .type.alignment = 4 + * }); + * @endcode + */ +#define ecs_component(world, ...)\ + ecs_component_init(world, &(ecs_component_desc_t) __VA_ARGS__ ) -/* -- Queries -- */ +/** Shorthand for creating a query with ecs_query_cache_init. + * + * Example: + * + * @code + * ecs_query(world, { + * .terms = {{ ecs_id(Position) }} + * }); + * @endcode + */ +#define ecs_query(world, ...)\ + ecs_query_init(world, &(ecs_query_desc_t) __VA_ARGS__ ) -#define ecs_query_table_count(query) query->cache.tables.count -#define ecs_query_empty_table_count(query) query->cache.empty_tables.count +/** Shorthand for creating an observer with ecs_observer_init(). + * + * Example: + * + * @code + * ecs_observer(world, { + * .terms = {{ ecs_id(Position) }}, + * .events = { EcsOnAdd }, + * .callback = AddPosition + * }); + * @endcode + */ +#define ecs_observer(world, ...)\ + ecs_observer_init(world, &(ecs_observer_desc_t) __VA_ARGS__ ) -/* -- Iterators -- */ +/** @} */ -#define ecs_term_id(it, index)\ - ((it)->ids[(index) - 1]) +/** + * @defgroup flecs_c_type_safe Type Safe API + * Macro's that wrap around core functions to provide a "type safe" API in C + * + * @{ + */ -#define ecs_term_source(it, index)\ - ((it)->subjects ? (it)->subjects[(index) - 1] : 0) +/** + * @defgroup flecs_c_entities Entity API + * @{ + */ -#define ecs_term_size(it, index)\ - ((index) == 0 ? sizeof(ecs_entity_t) : ECS_CAST(size_t, (it)->sizes[(index) - 1])) +/** + * @defgroup flecs_c_creation_deletion Creation & Deletion + * @{ + */ -#define ecs_term_is_owned(it, index)\ - ((it)->subjects == NULL || (it)->subjects[(index) - 1] == 0) +#define ecs_new_w(world, T) ecs_new_w_id(world, ecs_id(T)) -#define ecs_term(it, T, index)\ - (ECS_CAST(T*, ecs_term_w_size(it, sizeof(T), index))) +#define ecs_new_w_pair(world, first, second)\ + ecs_new_w_id(world, ecs_pair(first, second)) -#define ecs_iter_column(it, T, index)\ - (ECS_CAST(T*, ecs_iter_column_w_size(it, sizeof(T), index))) +#define ecs_bulk_new(world, component, count)\ + ecs_bulk_new_w_id(world, ecs_id(component), count) /** @} */ /** - * @defgroup utilities Utility macro's for commonly used operations + * @defgroup flecs_c_adding_removing Adding & Removing * @{ */ -#define ecs_childof(parent) ecs_pair(EcsChildOf, parent) +#define ecs_add(world, entity, T)\ + ecs_add_id(world, entity, ecs_id(T)) + +#define ecs_add_pair(world, subject, first, second)\ + ecs_add_id(world, subject, ecs_pair(first, second)) -#define ecs_isa(base) ecs_pair(EcsIsA, base) + +#define ecs_remove(world, entity, T)\ + ecs_remove_id(world, entity, ecs_id(T)) + +#define ecs_remove_pair(world, subject, first, second)\ + ecs_remove_id(world, subject, ecs_pair(first, second)) + + +#define ecs_auto_override(world, entity, T)\ + ecs_auto_override_id(world, entity, ecs_id(T)) + +#define ecs_auto_override_pair(world, subject, first, second)\ + ecs_auto_override_id(world, subject, ecs_pair(first, second)) /** @} */ /** - * @defgroup temporary_macros Temp macro's for easing the transition to v3 + * @defgroup flecs_c_getting_setting Getting & Setting * @{ */ -/** Declare a constructor. - * Example: - * ECS_CTOR(MyType, ptr, { ptr->value = NULL; }); - */ -#define ECS_CTOR(type, var, ...)\ - ECS_XTOR_IMPL(type, ctor, var, __VA_ARGS__) +/* insert */ +#define ecs_insert(world, ...)\ + ecs_entity(world, { .set = ecs_values(__VA_ARGS__)}) -/** Declare a destructor. - * Example: - * ECS_DTOR(MyType, ptr, { free(ptr->value); }); - */ -#define ECS_DTOR(type, var, ...)\ +/* set */ + +#define ecs_set_ptr(world, entity, component, ptr)\ + ecs_set_id(world, entity, ecs_id(component), sizeof(component), ptr) + +#define ecs_set(world, entity, component, ...)\ + ecs_set_id(world, entity, ecs_id(component), sizeof(component), &(component)__VA_ARGS__) + +#define ecs_set_pair(world, subject, First, second, ...)\ + ecs_set_id(world, subject,\ + ecs_pair(ecs_id(First), second),\ + sizeof(First), &(First)__VA_ARGS__) + +#define ecs_set_pair_second(world, subject, first, Second, ...)\ + ecs_set_id(world, subject,\ + ecs_pair(first, ecs_id(Second)),\ + sizeof(Second), &(Second)__VA_ARGS__) + +#define ecs_set_override(world, entity, T, ...)\ + ecs_add_id(world, entity, ECS_AUTO_OVERRIDE | ecs_id(T));\ + ecs_set(world, entity, T, __VA_ARGS__) + +/* emplace */ + +#define ecs_emplace(world, entity, T, is_new)\ + (ECS_CAST(T*, ecs_emplace_id(world, entity, ecs_id(T), sizeof(T), is_new))) + +#define ecs_emplace_pair(world, entity, First, second, is_new)\ + (ECS_CAST(First*, ecs_emplace_id(world, entity, ecs_pair_t(First, second), sizeof(First), is_new))) + +/* get */ + +#define ecs_get(world, entity, T)\ + (ECS_CAST(const T*, ecs_get_id(world, entity, ecs_id(T)))) + +#define ecs_get_pair(world, subject, First, second)\ + (ECS_CAST(const First*, ecs_get_id(world, subject,\ + ecs_pair(ecs_id(First), second)))) + +#define ecs_get_pair_second(world, subject, first, Second)\ + (ECS_CAST(const Second*, ecs_get_id(world, subject,\ + ecs_pair(first, ecs_id(Second))))) + +/* get_mut */ + +#define ecs_get_mut(world, entity, T)\ + (ECS_CAST(T*, ecs_get_mut_id(world, entity, ecs_id(T)))) + +#define ecs_get_mut_pair(world, subject, First, second)\ + (ECS_CAST(First*, ecs_get_mut_id(world, subject,\ + ecs_pair(ecs_id(First), second)))) + +#define ecs_get_mut_pair_second(world, subject, first, Second)\ + (ECS_CAST(Second*, ecs_get_mut_id(world, subject,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_get_mut(world, entity, T)\ + (ECS_CAST(T*, ecs_get_mut_id(world, entity, ecs_id(T)))) + +/* ensure */ + +#define ecs_ensure(world, entity, T)\ + (ECS_CAST(T*, ecs_ensure_id(world, entity, ecs_id(T), sizeof(T)))) + +#define ecs_ensure_pair(world, subject, First, second)\ + (ECS_CAST(First*, ecs_ensure_id(world, subject,\ + ecs_pair(ecs_id(First), second), sizeof(First)))) + +#define ecs_ensure_pair_second(world, subject, first, Second)\ + (ECS_CAST(Second*, ecs_ensure_id(world, subject,\ + ecs_pair(first, ecs_id(Second)), sizeof(Second)))) + +/* modified */ + +#define ecs_modified(world, entity, component)\ + ecs_modified_id(world, entity, ecs_id(component)) + +#define ecs_modified_pair(world, subject, first, second)\ + ecs_modified_id(world, subject, ecs_pair(first, second)) + +/* record */ + +#define ecs_record_get(world, record, T)\ + (ECS_CAST(const T*, ecs_record_get_id(world, record, ecs_id(T)))) + +#define ecs_record_has(world, record, T)\ + (ecs_record_has_id(world, record, ecs_id(T))) + +#define ecs_record_get_pair(world, record, First, second)\ + (ECS_CAST(const First*, ecs_record_get_id(world, record, \ + ecs_pair(ecs_id(First), second)))) + +#define ecs_record_get_pair_second(world, record, first, Second)\ + (ECS_CAST(const Second*, ecs_record_get_id(world, record,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_record_ensure(world, record, T)\ + (ECS_CAST(T*, ecs_record_ensure_id(world, record, ecs_id(T)))) + +#define ecs_record_ensure_pair(world, record, First, second)\ + (ECS_CAST(First*, ecs_record_ensure_id(world, record, \ + ecs_pair(ecs_id(First), second)))) + +#define ecs_record_ensure_pair_second(world, record, first, Second)\ + (ECS_CAST(Second*, ecs_record_ensure_id(world, record,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_ref_init(world, entity, T)\ + ecs_ref_init_id(world, entity, ecs_id(T)) + +#define ecs_ref_get(world, ref, T)\ + (ECS_CAST(T*, ecs_ref_get_id(world, ref, ecs_id(T)))) + +/** @} */ + +/** + * @defgroup flecs_c_singletons Singletons + * @{ + */ + +#define ecs_singleton_add(world, comp)\ + ecs_add(world, ecs_id(comp), comp) + +#define ecs_singleton_remove(world, comp)\ + ecs_remove(world, ecs_id(comp), comp) + +#define ecs_singleton_get(world, comp)\ + ecs_get(world, ecs_id(comp), comp) + +#define ecs_singleton_get_mut(world, comp)\ + ecs_get_mut(world, ecs_id(comp), comp) + +#define ecs_singleton_set_ptr(world, comp, ptr)\ + ecs_set_ptr(world, ecs_id(comp), comp, ptr) + +#define ecs_singleton_set(world, comp, ...)\ + ecs_set(world, ecs_id(comp), comp, __VA_ARGS__) + +#define ecs_singleton_ensure(world, comp)\ + ecs_ensure(world, ecs_id(comp), comp) + +#define ecs_singleton_emplace(world, comp, is_new)\ + ecs_emplace(world, ecs_id(comp), comp, is_new) + +#define ecs_singleton_modified(world, comp)\ + ecs_modified(world, ecs_id(comp), comp) + +/** @} */ + +/** + * @defgroup flecs_c_has Has, Owns, Shares + * @{ + */ + +#define ecs_has(world, entity, T)\ + ecs_has_id(world, entity, ecs_id(T)) + +#define ecs_has_pair(world, entity, first, second)\ + ecs_has_id(world, entity, ecs_pair(first, second)) + +#define ecs_owns_pair(world, entity, first, second)\ + ecs_owns_id(world, entity, ecs_pair(first, second)) + +#define ecs_owns(world, entity, T)\ + ecs_owns_id(world, entity, ecs_id(T)) + +#define ecs_shares_id(world, entity, id)\ + (ecs_search_relation(world, ecs_get_table(world, entity), 0, ecs_id(id), \ + EcsIsA, 1, 0, 0, 0, 0) != -1) + +#define ecs_shares_pair(world, entity, first, second)\ + (ecs_shares_id(world, entity, ecs_pair(first, second))) + +#define ecs_shares(world, entity, T)\ + (ecs_shares_id(world, entity, ecs_id(T))) + +#define ecs_get_target_for(world, entity, rel, T)\ + ecs_get_target_for_id(world, entity, rel, ecs_id(T)) + +/** @} */ + +/** + * @defgroup flecs_c_enable_disable Enabling & Disabling + * @{ + */ + +#define ecs_enable_component(world, entity, T, enable)\ + ecs_enable_id(world, entity, ecs_id(T), enable) + +#define ecs_is_enabled(world, entity, T)\ + ecs_is_enabled_id(world, entity, ecs_id(T)) + +#define ecs_enable_pair(world, entity, First, second, enable)\ + ecs_enable_id(world, entity, ecs_pair(ecs_id(First), second), enable) + +#define ecs_is_enabled_pair(world, entity, First, second)\ + ecs_is_enabled_id(world, entity, ecs_pair(ecs_id(First), second)) + +/** @} */ + +/** + * @defgroup flecs_c_entity_names Entity Names + * @{ + */ + +#define ecs_lookup_from(world, parent, path)\ + ecs_lookup_path_w_sep(world, parent, path, ".", NULL, true) + +#define ecs_get_path_from(world, parent, child)\ + ecs_get_path_w_sep(world, parent, child, ".", NULL) + +#define ecs_get_path(world, child)\ + ecs_get_path_w_sep(world, 0, child, ".", NULL) + +#define ecs_get_path_buf(world, child, buf)\ + ecs_get_path_w_sep_buf(world, 0, child, ".", NULL, buf, false) + +#define ecs_new_from_path(world, parent, path)\ + ecs_new_from_path_w_sep(world, parent, path, ".", NULL) + +#define ecs_add_path(world, entity, parent, path)\ + ecs_add_path_w_sep(world, entity, parent, path, ".", NULL) + +#define ecs_add_fullpath(world, entity, path)\ + ecs_add_path_w_sep(world, entity, 0, path, ".", NULL) + +/** @} */ + +/** @} */ + +/** + * @defgroup flecs_c_components Component API + * @{ + */ + +#define ecs_set_hooks(world, T, ...)\ + ecs_set_hooks_id(world, ecs_id(T), &(ecs_type_hooks_t)__VA_ARGS__) + +#define ecs_get_hooks(world, T)\ + ecs_get_hooks_id(world, ecs_id(T)); + +/** Declare a constructor. + * Example: + * + * @code + * ECS_CTOR(MyType, ptr, { ptr->value = NULL; }); + * @endcode + */ +#define ECS_CTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, ctor, var, __VA_ARGS__) + +/** Declare a destructor. + * Example: + * + * @code + * ECS_DTOR(MyType, ptr, { free(ptr->value); }); + * @endcode + */ +#define ECS_DTOR(type, var, ...)\ ECS_XTOR_IMPL(type, dtor, var, __VA_ARGS__) /** Declare a copy action. * Example: - * ECS_COPY(MyType, dst, src, { dst->value = strdup(src->value); }); + * + * @code + * ECS_COPY(MyType, dst, src, { dst->value = strdup(src->value); }); + * @endcode */ #define ECS_COPY(type, dst_var, src_var, ...)\ ECS_COPY_IMPL(type, dst_var, src_var, __VA_ARGS__) /** Declare a move action. * Example: - * ECS_MOVE(MyType, dst, src, { dst->value = src->value; src->value = 0; }); + * + * @code + * ECS_MOVE(MyType, dst, src, { dst->value = src->value; src->value = 0; }); + * @endcode */ #define ECS_MOVE(type, dst_var, src_var, ...)\ ECS_MOVE_IMPL(type, dst_var, src_var, __VA_ARGS__) -/** Declare an on_set action. +/** Declare component hooks. * Example: - * ECS_ON_SET(MyType, ptr, { printf("%d\n", ptr->value); }); + * + * @code + * ECS_ON_SET(MyType, ptr, { printf("%d\n", ptr->value); }); + * @endcode */ +#define ECS_ON_ADD(type, ptr, ...)\ + ECS_HOOK_IMPL(type, ecs_on_add(type), ptr, __VA_ARGS__) +#define ECS_ON_REMOVE(type, ptr, ...)\ + ECS_HOOK_IMPL(type, ecs_on_remove(type), ptr, __VA_ARGS__) #define ECS_ON_SET(type, ptr, ...)\ - ECS_ON_SET_IMPL(type, ptr, __VA_ARGS__) + ECS_HOOK_IMPL(type, ecs_on_set(type), ptr, __VA_ARGS__) /* Map from typename to function name of component lifecycle action */ #define ecs_ctor(type) type##_ctor @@ -7840,29 +11059,230 @@ void* ecs_record_get_column( #define ecs_on_add(type) type##_on_add #define ecs_on_remove(type) type##_on_remove -#define ecs_query_new(world, q_expr)\ - ecs_query_init(world, &(ecs_query_desc_t){\ - .filter.expr = q_expr\ - }) +/** @} */ + +/** + * @defgroup flecs_c_ids Id API + * @{ + */ + +#define ecs_count(world, type)\ + ecs_count_id(world, ecs_id(type)) + +/** @} */ + +/** + * @defgroup flecs_c_iterators Iterator API + * @{ + */ + +#define ecs_field(it, T, index)\ + (ECS_CAST(T*, ecs_field_w_size(it, sizeof(T), index))) + +#define ecs_field_self(it, T, index)\ + (ECS_CAST(T*, ecs_field_self_w_size(it, sizeof(T), index))) + +#define ecs_field_at(it, T, index, row)\ + (ECS_CAST(T*, ecs_field_at_w_size(it, sizeof(T), index, row))) + +/** @} */ + +/** + * @defgroup flecs_c_tables Table API + * @{ + */ + +#define ecs_table_get(world, table, T, offset)\ + (ECS_CAST(T*, ecs_table_get_id(world, table, ecs_id(T), offset))) + +#define ecs_table_get_pair(world, table, First, second, offset)\ + (ECS_CAST(First*, ecs_table_get_id(world, table, ecs_pair(ecs_id(First), second), offset))) + +#define ecs_table_get_pair_second(world, table, first, Second, offset)\ + (ECS_CAST(Second*, ecs_table_get_id(world, table, ecs_pair(first, ecs_id(Second)), offset))) + +/** @} */ + +/** + * @defgroup flecs_c_values Value API + * @{ + */ + +/** Convenience macro for creating compound literal id array */ +#define ecs_ids(...) (ecs_id_t[]){ __VA_ARGS__, 0 } + +/** Convenience macro for creating compound literal values array */ +#define ecs_values(...) (ecs_value_t[]){ __VA_ARGS__, {0, 0}} + +/** Convenience macro for creating compound literal value */ +#define ecs_value_ptr(T, ptr) ((ecs_value_t){ecs_id(T), ptr}) + +/** Convenience macro for creating compound literal pair value */ +#define ecs_pair_value(R, t, ...) ((ecs_value_t){ecs_pair_t(R, t), &(R)__VA_ARGS__}) + +/** Convenience macro for creating compound literal pair value */ +#define ecs_pair_value_2nd(r, T, ...) ((ecs_value_t){ecs_pair(r, ecs_id(T)), &(T)__VA_ARGS__}) + +/** Convenience macro for creating heap allocated value */ +#define ecs_value_new_t(world, T) ecs_value_new(world, ecs_id(T)) -#define ecs_rule_new(world, q_expr)\ - ecs_rule_init(world, &(ecs_filter_desc_t){\ - .expr = q_expr\ - }) +/** Convenience macro for creating compound literal value literal */ +#define ecs_value(T, ...) ((ecs_value_t){ecs_id(T), &(T)__VA_ARGS__}) -#define ECS_TYPE(world, id, ...) \ - ecs_entity_t id = ecs_type_init(world, &(ecs_type_desc_t){\ - .entity.name = #id,\ - .ids_expr = #__VA_ARGS__\ - });\ - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL);\ - (void)id; +/** @} */ + +/** @} */ + +/** + * @defgroup flecs_c_table_sorting Table sorting + * Convenience macro's for sorting tables. + * + * @{ + */ +#define ecs_sort_table(id) ecs_id(id##_sort_table) + +#define ecs_compare(id) ecs_id(id##_compare_fn) + +/* Declare efficient table sorting operation that uses provided compare function. + * For best results use LTO or make the function body visible in the same compilation unit. + * Variadic arguments are prepended before generated functions, use it to declare static + * or exported functions. + * Parameters of the comparison function: + * ecs_entity_t e1, const void* ptr1, + * ecs_entity_t e2, const void* ptr2 + * Parameters of the sort functions: + * ecs_world_t *world + * ecs_table_t *table + * ecs_entity_t *entities + * void *ptr + * int32_t elem_size + * int32_t lo + * int32_t hi + * ecs_order_by_action_t order_by - Pointer to the original comparison function. You are not supposed to use it. + * Example: + * + * @code + * int CompareMyType(ecs_entity_t e1, const void* ptr1, ecs_entity_t e2, const void* ptr2) { const MyType* p1 = ptr1; const MyType* p2 = ptr2; return p1->value - p2->value; } + * ECS_SORT_TABLE_WITH_COMPARE(MyType, MyCustomCompare, CompareMyType) + * @endcode + */ +#define ECS_SORT_TABLE_WITH_COMPARE(id, op_name, compare_fn, ...) \ + static int32_t ECS_CONCAT(op_name, _partition)( \ + ecs_world_t *world, \ + ecs_table_t *table, \ + ecs_entity_t *entities, \ + void *ptr, \ + int32_t elem_size, \ + int32_t lo, \ + int32_t hi, \ + ecs_order_by_action_t order_by) \ + { \ + (void)(order_by); \ + int32_t p = (hi + lo) / 2; \ + void *pivot = ECS_ELEM(ptr, elem_size, p); \ + ecs_entity_t pivot_e = entities[p]; \ + int32_t i = lo - 1, j = hi + 1; \ + void *el; \ + repeat: \ + { \ + do { \ + i ++; \ + el = ECS_ELEM(ptr, elem_size, i); \ + } while ( compare_fn(entities[i], el, pivot_e, pivot) < 0); \ + do { \ + j --; \ + el = ECS_ELEM(ptr, elem_size, j); \ + } while ( compare_fn(entities[j], el, pivot_e, pivot) > 0); \ + if (i >= j) { \ + return j; \ + } \ + ecs_table_swap_rows(world, table, i, j); \ + if (p == i) { \ + pivot = ECS_ELEM(ptr, elem_size, j); \ + pivot_e = entities[j]; \ + } else if (p == j) { \ + pivot = ECS_ELEM(ptr, elem_size, i); \ + pivot_e = entities[i]; \ + } \ + goto repeat; \ + } \ + } \ + __VA_ARGS__ void op_name( \ + ecs_world_t *world, \ + ecs_table_t *table, \ + ecs_entity_t *entities, \ + void *ptr, \ + int32_t size, \ + int32_t lo, \ + int32_t hi, \ + ecs_order_by_action_t order_by) \ + { \ + if ((hi - lo) < 1) { \ + return; \ + } \ + int32_t p = ECS_CONCAT(op_name, _partition)(world, table, entities, ptr, size, lo, hi, order_by); \ + op_name(world, table, entities, ptr, size, lo, p, order_by); \ + op_name(world, table, entities, ptr, size, p + 1, hi, order_by); \ + } + +/* Declare efficient table sorting operation that uses default component comparison operator. + * For best results use LTO or make the comparison operator visible in the same compilation unit. + * Variadic arguments are prepended before generated functions, use it to declare static + * or exported functions. + * Example: + * + * @code + * ECS_COMPARE(MyType, { const MyType* p1 = ptr1; const MyType* p2 = ptr2; return p1->value - p2->value; }); + * ECS_SORT_TABLE(MyType) + * @endcode + */ +#define ECS_SORT_TABLE(id, ...) \ + ECS_SORT_TABLE_WITH_COMPARE(id, ecs_sort_table(id), ecs_compare(id), __VA_ARGS__) + +/* Declare component comparison operations. + * Parameters: + * ecs_entity_t e1, const void* ptr1, + * ecs_entity_t e2, const void* ptr2 + * Example: + * + * @code + * ECS_COMPARE(MyType, { const MyType* p1 = ptr1; const MyType* p2 = ptr2; return p1->value - p2->value; }); + * @endcode + */ +#define ECS_COMPARE(id, ...) \ + int ecs_compare(id)(ecs_entity_t e1, const void* ptr1, ecs_entity_t e2, const void* ptr2) { \ + __VA_ARGS__ \ + } + +/** @} */ + +/** + * @defgroup flecs_c_misc Misc + * Misc convenience macro's. + * + * @{ + */ + +#define ecs_isa(e) ecs_pair(EcsIsA, e) +#define ecs_childof(e) ecs_pair(EcsChildOf, e) +#define ecs_dependson(e) ecs_pair(EcsDependsOn, e) +#define ecs_with(e) ecs_pair(EcsWith, e) + +#define ecs_each(world, id) ecs_each_id(world, ecs_id(id)) +#define ecs_each_pair(world, r, t) ecs_each_id(world, ecs_pair(r, t)) +#define ecs_each_pair_t(world, R, t) ecs_each_id(world, ecs_pair(ecs_id(R), t)) /** @} */ +/** @} */ #endif // FLECS_C_ + +#ifdef __cplusplus +} +#endif + /** * @file addons.h * @brief Include enabled addons. @@ -7873,35 +11293,24 @@ void* ecs_record_get_column( #ifndef FLECS_ADDONS_H #define FLECS_ADDONS_H -/* Don't enable web addons if we're running as a webasm app */ -#ifdef ECS_TARGET_EM -#ifndef FLECS_NO_HTTP -#define FLECS_NO_HTTP -#endif // FLECS_NO_HTTP - -#ifndef FLECS_NO_REST -#define FLECS_NO_REST -#endif // FLECS_NO_REST -#endif // ECS_TARGET_EM - -/* Blacklist macro's */ +/* Blacklist macros */ #ifdef FLECS_NO_CPP #undef FLECS_CPP #endif #ifdef FLECS_NO_MODULE #undef FLECS_MODULE #endif +#ifdef FLECS_NO_SCRIPT +#undef FLECS_SCRIPT +#endif #ifdef FLECS_NO_PARSER #undef FLECS_PARSER #endif -#ifdef FLECS_NO_PLECS -#undef FLECS_PLECS -#endif -#ifdef FLECS_NO_RULES -#undef FLECS_RULES +#ifdef FLECS_NO_QUERY_DSL +#undef FLECS_QUERY_DSL #endif -#ifdef FLECS_NO_SNAPSHOT -#undef FLECS_SNAPSHOT +#ifdef FLECS_NO_SCRIPT_MATH +#undef FLECS_SCRIPT_MATH #endif #ifdef FLECS_NO_STATS #undef FLECS_STATS @@ -7909,6 +11318,9 @@ void* ecs_record_get_column( #ifdef FLECS_NO_SYSTEM #undef FLECS_SYSTEM #endif +#ifdef FLECS_NO_ALERTS +#undef FLECS_ALERTS +#endif #ifdef FLECS_NO_PIPELINE #undef FLECS_PIPELINE #endif @@ -7918,24 +11330,15 @@ void* ecs_record_get_column( #ifdef FLECS_NO_META #undef FLECS_META #endif -#ifdef FLECS_NO_META_C -#undef FLECS_META_C -#endif #ifdef FLECS_NO_UNITS #undef FLECS_UNITS #endif -#ifdef FLECS_NO_EXPR -#undef FLECS_EXPR -#endif #ifdef FLECS_NO_JSON #undef FLECS_JSON #endif #ifdef FLECS_NO_DOC #undef FLECS_DOC #endif -#ifdef FLECS_NO_COREDOC -#undef FLECS_COREDOC -#endif #ifdef FLECS_NO_LOG #undef FLECS_LOG #endif @@ -7951,30 +11354,33 @@ void* ecs_record_get_column( #ifdef FLECS_NO_REST #undef FLECS_REST #endif +#ifdef FLECS_NO_JOURNAL +#undef FLECS_JOURNAL +#endif -/* Always included, if disabled log functions are replaced with dummy macro's */ +/* Always included, if disabled functions are replaced with dummy macros */ /** - * @file log.h + * @file addons/log.h * @brief Logging addon. - * + * * The logging addon provides an API for (debug) tracing and reporting errors * at various levels. When enabled, the logging addon can provide more detailed * information about the state of the ECS and any errors that may occur. - * + * * The logging addon can be disabled to reduce footprint of the library, but * limits information logged to only file, line and error code. - * + * * When enabled the logging addon can be configured to exclude levels of tracing - * from the build to reduce the impact on performance. By default all debug + * from the build to reduce the impact on performance. By default all debug * tracing is enabled for debug builds, tracing is enabled at release builds. - * - * Applications can change the logging level at runtime with ecs_log_set_level, - * but what is actually logged depends on what is compiled (when compiled - * without debug tracing, setting the runtime level to debug won't have an + * + * Applications can change the logging level at runtime with ecs_log_set_level(), + * but what is actually logged depends on what is compiled (when compiled + * without debug tracing, setting the runtime level to debug won't have an * effect). - * + * * The logging addon uses the OS API log_ function for all tracing. - * + * * Note that even when the logging addon is not enabled, its header/source must * be included in a build. To prevent unused variable warnings in the code, some * API functions are included when the addon is disabled, but have empty bodies. @@ -7989,36 +11395,45 @@ extern "C" { #ifdef FLECS_LOG +/** + * @defgroup c_addons_log Log + * @ingroup c_addons + * Logging functions. + * + * @{ + */ + //////////////////////////////////////////////////////////////////////////////// //// Tracing //////////////////////////////////////////////////////////////////////////////// +/** Log message indicating an operation is deprecated. */ FLECS_API -void _ecs_deprecated( - const char *file, - int32_t line, +void ecs_deprecated_( + const char *file, + int32_t line, const char *msg); /** Increase log stack. * This operation increases the indent_ value of the OS API and can be useful to * make nested behavior more visible. - * + * * @param level The log level. */ FLECS_API -void _ecs_log_push(int32_t level); +void ecs_log_push_(int32_t level); /** Decrease log stack. * This operation decreases the indent_ value of the OS API and can be useful to * make nested behavior more visible. - * + * * @param level The log level. */ FLECS_API -void _ecs_log_pop(int32_t level); +void ecs_log_pop_(int32_t level); /** Should current level be logged. - * This operation returns true when the specified log level should be logged + * This operation returns true when the specified log level should be logged * with the current log level. * * @param level The log level to check for. @@ -8039,16 +11454,16 @@ const char* ecs_strerror( #else // FLECS_LOG //////////////////////////////////////////////////////////////////////////////// -//// Dummy macro's for when logging is disabled +//// Dummy macros for when logging is disabled //////////////////////////////////////////////////////////////////////////////// -#define _ecs_deprecated(file, line, msg)\ +#define ecs_deprecated_(file, line, msg)\ (void)file;\ (void)line;\ (void)msg -#define _ecs_log_push(level) -#define _ecs_log_pop(level) +#define ecs_log_push_(level) +#define ecs_log_pop_(level) #define ecs_should_log(level) false #define ecs_strerror(error_code)\ @@ -8062,7 +11477,23 @@ const char* ecs_strerror( //////////////////////////////////////////////////////////////////////////////// FLECS_API -void _ecs_log( +void ecs_print_( + int32_t level, + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void ecs_printv_( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args); + +FLECS_API +void ecs_log_( int32_t level, const char *file, int32_t line, @@ -8070,7 +11501,7 @@ void _ecs_log( ...); FLECS_API -void _ecs_logv( +void ecs_logv_( int level, const char *file, int32_t line, @@ -8078,7 +11509,7 @@ void _ecs_logv( va_list args); FLECS_API -void _ecs_abort( +void ecs_abort_( int32_t error_code, const char *file, int32_t line, @@ -8086,8 +11517,7 @@ void _ecs_abort( ...); FLECS_API -bool _ecs_assert( - bool condition, +void ecs_assert_log_( int32_t error_code, const char *condition_str, const char *file, @@ -8096,55 +11526,77 @@ bool _ecs_assert( ...); FLECS_API -void _ecs_parser_error( +void ecs_parser_error_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + ...); + +FLECS_API +void ecs_parser_errorv_( + const char *name, + const char *expr, + int64_t column, + const char *fmt, + va_list args); + +FLECS_API +void ecs_parser_warning_( const char *name, - const char *expr, + const char *expr, int64_t column, const char *fmt, ...); FLECS_API -void _ecs_parser_errorv( +void ecs_parser_warningv_( const char *name, - const char *expr, + const char *expr, int64_t column, const char *fmt, va_list args); //////////////////////////////////////////////////////////////////////////////// -//// Logging Macro's +//// Logging macros //////////////////////////////////////////////////////////////////////////////// -#ifndef FLECS_LEGACY /* C89 doesn't support variadic macro's */ +#ifndef FLECS_LEGACY /* C89 doesn't support variadic macros */ /* Base logging function. Accepts a custom level */ +#define ecs_print(level, ...)\ + ecs_print_(level, __FILE__, __LINE__, __VA_ARGS__) + +#define ecs_printv(level, fmt, args)\ + ecs_printv_(level, __FILE__, __LINE__, fmt, args) + #define ecs_log(level, ...)\ - _ecs_log(level, __FILE__, __LINE__, __VA_ARGS__) + ecs_log_(level, __FILE__, __LINE__, __VA_ARGS__) #define ecs_logv(level, fmt, args)\ - _ecs_logv(level, __FILE__, __LINE__, fmt, args) + ecs_logv_(level, __FILE__, __LINE__, fmt, args) /* Tracing. Used for logging of infrequent events */ -#define _ecs_trace(file, line, ...) _ecs_log(0, file, line, __VA_ARGS__) -#define ecs_trace(...) _ecs_trace(__FILE__, __LINE__, __VA_ARGS__) +#define ecs_trace_(file, line, ...) ecs_log_(0, file, line, __VA_ARGS__) +#define ecs_trace(...) ecs_trace_(__FILE__, __LINE__, __VA_ARGS__) /* Warning. Used when an issue occurs, but operation is successful */ -#define _ecs_warn(file, line, ...) _ecs_log(-2, file, line, __VA_ARGS__) -#define ecs_warn(...) _ecs_warn(__FILE__, __LINE__, __VA_ARGS__) +#define ecs_warn_(file, line, ...) ecs_log_(-2, file, line, __VA_ARGS__) +#define ecs_warn(...) ecs_warn_(__FILE__, __LINE__, __VA_ARGS__) /* Error. Used when an issue occurs, and operation failed. */ -#define _ecs_err(file, line, ...) _ecs_log(-3, file, line, __VA_ARGS__) -#define ecs_err(...) _ecs_err(__FILE__, __LINE__, __VA_ARGS__) +#define ecs_err_(file, line, ...) ecs_log_(-3, file, line, __VA_ARGS__) +#define ecs_err(...) ecs_err_(__FILE__, __LINE__, __VA_ARGS__) /* Fatal. Used when an issue occurs, and the application cannot continue. */ -#define _ecs_fatal(file, line, ...) _ecs_log(-4, file, line, __VA_ARGS__) -#define ecs_fatal(...) _ecs_fatal(__FILE__, __LINE__, __VA_ARGS__) +#define ecs_fatal_(file, line, ...) ecs_log_(-4, file, line, __VA_ARGS__) +#define ecs_fatal(...) ecs_fatal_(__FILE__, __LINE__, __VA_ARGS__) /* Optionally include warnings about using deprecated features */ #ifndef FLECS_NO_DEPRECATED_WARNINGS #define ecs_deprecated(...)\ - _ecs_deprecated(__FILE__, __LINE__, __VA_ARGS__) + ecs_deprecated_(__FILE__, __LINE__, __VA_ARGS__) #else #define ecs_deprecated(...) #endif // FLECS_NO_DEPRECATED_WARNINGS @@ -8159,7 +11611,7 @@ void _ecs_parser_errorv( #endif // !(defined(FLECS_LOG_0) || defined(FLECS_LOG_1) || defined(FLECS_LOG_2) || defined(FLECS_LOG_3)) -/* Define/undefine macro's based on compiled-in tracing level. This can optimize +/* Define/undefine macros based on compiled-in tracing level. This can optimize * out tracing statements from a build, which improves performance. */ #if defined(FLECS_LOG_3) /* All debug tracing enabled */ @@ -8167,13 +11619,13 @@ void _ecs_parser_errorv( #define ecs_dbg_2(...) ecs_log(2, __VA_ARGS__); #define ecs_dbg_3(...) ecs_log(3, __VA_ARGS__); -#define ecs_log_push_1() _ecs_log_push(1); -#define ecs_log_push_2() _ecs_log_push(2); -#define ecs_log_push_3() _ecs_log_push(3); +#define ecs_log_push_1() ecs_log_push_(1); +#define ecs_log_push_2() ecs_log_push_(2); +#define ecs_log_push_3() ecs_log_push_(3); -#define ecs_log_pop_1() _ecs_log_pop(1); -#define ecs_log_pop_2() _ecs_log_pop(2); -#define ecs_log_pop_3() _ecs_log_pop(3); +#define ecs_log_pop_1() ecs_log_pop_(1); +#define ecs_log_pop_2() ecs_log_pop_(2); +#define ecs_log_pop_3() ecs_log_pop_(3); #define ecs_should_log_1() ecs_should_log(1) #define ecs_should_log_2() ecs_should_log(2) @@ -8188,12 +11640,12 @@ void _ecs_parser_errorv( #define ecs_dbg_2(...) ecs_log(2, __VA_ARGS__); #define ecs_dbg_3(...) -#define ecs_log_push_1() _ecs_log_push(1); -#define ecs_log_push_2() _ecs_log_push(2); +#define ecs_log_push_1() ecs_log_push_(1); +#define ecs_log_push_2() ecs_log_push_(2); #define ecs_log_push_3() -#define ecs_log_pop_1() _ecs_log_pop(1); -#define ecs_log_pop_2() _ecs_log_pop(2); +#define ecs_log_pop_1() ecs_log_pop_(1); +#define ecs_log_pop_2() ecs_log_pop_(2); #define ecs_log_pop_3() #define ecs_should_log_1() ecs_should_log(1) @@ -8208,11 +11660,11 @@ void _ecs_parser_errorv( #define ecs_dbg_2(...) #define ecs_dbg_3(...) -#define ecs_log_push_1() _ecs_log_push(1); +#define ecs_log_push_1() ecs_log_push_(1); #define ecs_log_push_2() #define ecs_log_push_3() -#define ecs_log_pop_1() _ecs_log_pop(1); +#define ecs_log_pop_1() ecs_log_pop_(1); #define ecs_log_pop_2() #define ecs_log_pop_3() @@ -8260,28 +11712,29 @@ void _ecs_parser_errorv( #define ecs_dbg ecs_dbg_1 /* Default level for push/pop is 0 */ -#define ecs_log_push() _ecs_log_push(0) -#define ecs_log_pop() _ecs_log_pop(0) +#define ecs_log_push() ecs_log_push_(0) +#define ecs_log_pop() ecs_log_pop_(0) -/** Abort +/** Abort. * Unconditionally aborts process. */ #define ecs_abort(error_code, ...)\ - _ecs_abort(error_code, __FILE__, __LINE__, __VA_ARGS__);\ + ecs_abort_(error_code, __FILE__, __LINE__, __VA_ARGS__);\ ecs_os_abort(); abort(); /* satisfy compiler/static analyzers */ -/** Assert +/** Assert. * Aborts if condition is false, disabled in debug mode. */ #if defined(FLECS_NDEBUG) && !defined(FLECS_KEEP_ASSERT) #define ecs_assert(condition, error_code, ...) #else #define ecs_assert(condition, error_code, ...)\ - if (!_ecs_assert(condition, error_code, #condition, __FILE__, __LINE__, __VA_ARGS__)) {\ + if (!(condition)) {\ + ecs_assert_log_(error_code, #condition, __FILE__, __LINE__, __VA_ARGS__);\ ecs_os_abort();\ }\ assert(condition) /* satisfy compiler/static analyzers */ #endif // FLECS_NDEBUG -/** Debug assert +/** Debug assert. * Assert that is only valid in debug mode (ignores FLECS_KEEP_ASSERT) */ #ifndef FLECS_NDEBUG #define ecs_dbg_assert(condition, error_code, ...) ecs_assert(condition, error_code, __VA_ARGS__) @@ -8289,37 +11742,52 @@ void _ecs_parser_errorv( #define ecs_dbg_assert(condition, error_code, ...) #endif +/** Sanitize assert. + * Assert that is only valid in sanitized mode (ignores FLECS_KEEP_ASSERT) */ +#ifdef FLECS_SANITIZE +#define ecs_san_assert(condition, error_code, ...) ecs_assert(condition, error_code, __VA_ARGS__) +#else +#define ecs_san_assert(condition, error_code, ...) +#endif + + /* Silence dead code/unused label warnings when compiling without checks. */ #define ecs_dummy_check\ if ((false)) {\ goto error;\ } -/** Check +/** Check. * goto error if condition is false. */ #if defined(FLECS_NDEBUG) && !defined(FLECS_KEEP_ASSERT) #define ecs_check(condition, error_code, ...) ecs_dummy_check #else #ifdef FLECS_SOFT_ASSERT #define ecs_check(condition, error_code, ...)\ - if (!_ecs_assert(condition, error_code, #condition, __FILE__, __LINE__, __VA_ARGS__)) {\ + if (!(condition)) {\ + ecs_assert_log_(error_code, #condition, __FILE__, __LINE__, __VA_ARGS__);\ goto error;\ - } + }\ + ecs_dummy_check #else // FLECS_SOFT_ASSERT #define ecs_check(condition, error_code, ...)\ - ecs_assert(condition, error_code, __VA_ARGS__);\ + if (!(condition)) {\ + ecs_assert_log_(error_code, #condition, __FILE__, __LINE__, __VA_ARGS__);\ + ecs_os_abort();\ + }\ + assert(condition); /* satisfy compiler/static analyzers */ \ ecs_dummy_check #endif #endif // FLECS_NDEBUG -/** Throw +/** Panic. * goto error when FLECS_SOFT_ASSERT is defined, otherwise abort */ #if defined(FLECS_NDEBUG) && !defined(FLECS_KEEP_ASSERT) #define ecs_throw(error_code, ...) ecs_dummy_check #else #ifdef FLECS_SOFT_ASSERT #define ecs_throw(error_code, ...)\ - _ecs_abort(error_code, __FILE__, __LINE__, __VA_ARGS__);\ + ecs_abort_(error_code, __FILE__, __LINE__, __VA_ARGS__);\ goto error; #else #define ecs_throw(error_code, ...)\ @@ -8330,10 +11798,16 @@ void _ecs_parser_errorv( /** Parser error */ #define ecs_parser_error(name, expr, column, ...)\ - _ecs_parser_error(name, expr, column, __VA_ARGS__) + ecs_parser_error_(name, expr, column, __VA_ARGS__) #define ecs_parser_errorv(name, expr, column, fmt, args)\ - _ecs_parser_errorv(name, expr, column, fmt, args) + ecs_parser_errorv_(name, expr, column, fmt, args) + +#define ecs_parser_warning(name, expr, column, ...)\ + ecs_parser_warning_(name, expr, column, __VA_ARGS__) + +#define ecs_parser_warningv(name, expr, column, fmt, args)\ + ecs_parser_warningv_(name, expr, column, fmt, args) #endif // FLECS_LEGACY @@ -8342,28 +11816,35 @@ void _ecs_parser_errorv( //// Functions that are always available //////////////////////////////////////////////////////////////////////////////// -/** Enable or disable tracing. - * This will enable builtin tracing. For tracing to work, it will have to be - * compiled in which requires defining one of the following macro's: +/** Enable or disable log. + * This will enable builtin log. For log to work, it will have to be + * compiled in which requires defining one of the following macros: * - * FLECS_LOG_0 - All tracing is disabled - * FLECS_LOG_1 - Enable tracing level 1 - * FLECS_LOG_2 - Enable tracing level 2 and below - * FLECS_LOG_3 - Enable tracing level 3 and below + * FLECS_LOG_0 - All log is disabled + * FLECS_LOG_1 - Enable log level 1 + * FLECS_LOG_2 - Enable log level 2 and below + * FLECS_LOG_3 - Enable log level 3 and below * - * If no tracing level is defined and this is a debug build, FLECS_LOG_3 will + * If no log level is defined and this is a debug build, FLECS_LOG_3 will * have been automatically defined. * - * The provided level corresponds with the tracing level. If -1 is provided as + * The provided level corresponds with the log level. If -1 is provided as * value, warnings are disabled. If -2 is provided, errors are disabled as well. * * @param level Desired tracing level. - * @return Previous tracing level. + * @return Previous log level. */ FLECS_API int ecs_log_set_level( int level); +/** Get current log level. + * + * @return Previous log level. + */ +FLECS_API +int ecs_log_get_level(void); + /** Enable/disable tracing with colors. * By default colors are enabled. * @@ -8374,6 +11855,34 @@ FLECS_API bool ecs_log_enable_colors( bool enabled); +/** Enable/disable logging timestamp. + * By default timestamps are disabled. Note that enabling timestamps introduces + * overhead as the logging code will need to obtain the current time. + * + * @param enabled Whether to enable tracing with timestamps. + * @return Previous timestamp setting. + */ +FLECS_API +bool ecs_log_enable_timestamp( + bool enabled); + +/** Enable/disable logging time since last log. + * By default deltatime is disabled. Note that enabling timestamps introduces + * overhead as the logging code will need to obtain the current time. + * + * When enabled, this logs the amount of time in seconds passed since the last + * log, when this amount is non-zero. The format is a '+' character followed by + * the number of seconds: + * + * +1 trace: log message + * + * @param enabled Whether to enable tracing with timestamps. + * @return Previous timestamp setting. + */ +FLECS_API +bool ecs_log_enable_timedelta( + bool enabled); + /** Get last logged error code. * Calling this operation resets the error code. * @@ -8382,6 +11891,11 @@ bool ecs_log_enable_colors( FLECS_API int ecs_log_last_error(void); +FLECS_API +void ecs_log_start_capture(bool capture_try); + +FLECS_API +char* ecs_log_stop_capture(void); //////////////////////////////////////////////////////////////////////////////// //// Error codes @@ -8398,11 +11912,12 @@ int ecs_log_last_error(void); #define ECS_MISSING_OS_API (9) #define ECS_OPERATION_FAILED (10) #define ECS_INVALID_CONVERSION (11) -#define ECS_ID_IN_USE (12) +#define ECS_CYCLE_DETECTED (13) +#define ECS_LEAK_DETECTED (14) +#define ECS_DOUBLE_FREE (15) #define ECS_INCONSISTENT_NAME (20) #define ECS_NAME_IN_USE (21) -#define ECS_NOT_A_COMPONENT (22) #define ECS_INVALID_COMPONENT_SIZE (23) #define ECS_INVALID_COMPONENT_ALIGNMENT (24) #define ECS_COMPONENT_NOT_REGISTERED (25) @@ -8412,15 +11927,13 @@ int ecs_log_last_error(void); #define ECS_MISSING_SYMBOL (29) #define ECS_ALREADY_IN_USE (30) -#define ECS_COLUMN_ACCESS_VIOLATION (40) +#define ECS_ACCESS_VIOLATION (40) #define ECS_COLUMN_INDEX_OUT_OF_RANGE (41) #define ECS_COLUMN_IS_NOT_SHARED (42) #define ECS_COLUMN_IS_SHARED (43) #define ECS_COLUMN_TYPE_MISMATCH (45) -#define ECS_TYPE_INVALID_CASE (62) - -#define ECS_INVALID_WHILE_ITERATING (70) +#define ECS_INVALID_WHILE_READONLY (70) #define ECS_LOCKED_STORAGE (71) #define ECS_INVALID_FROM_WORKER (72) @@ -8445,15 +11958,33 @@ int ecs_log_last_error(void); } #endif +/** @} */ + #endif // FLECS_LOG_H +/* Handle addon dependencies that need declarations to be visible in header */ +#ifdef FLECS_STATS +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif +#ifndef FLECS_TIMER +#define FLECS_TIMER +#endif +#endif + +#ifdef FLECS_REST +#ifndef FLECS_HTTP +#define FLECS_HTTP +#endif +#endif + #ifdef FLECS_APP #ifdef FLECS_NO_APP #error "FLECS_NO_APP failed: APP is required by other addons" #endif /** - * @file app.h + * @file addons/app.h * @brief App addon. * * The app addon is a wrapper around the application's main loop. Its main @@ -8475,41 +12006,52 @@ int ecs_log_last_error(void); extern "C" { #endif +/** + * @defgroup c_addons_app App + * @ingroup c_addons + * Optional addon for running the main application loop. + * + * @{ + */ + /** Callback type for init action. */ typedef int(*ecs_app_init_action_t)( ecs_world_t *world); -/** Used with ecs_app_run. */ +/** Used with ecs_app_run(). */ typedef struct ecs_app_desc_t { - FLECS_FLOAT target_fps; /* Target FPS. */ - FLECS_FLOAT delta_time; /* Frame time increment (0 for measured values) */ - int32_t threads; /* Number of threads. */ - bool enable_rest; /* Allows HTTP clients to access ECS data */ - - ecs_app_init_action_t init; /* If set, function is ran before starting the + ecs_ftime_t target_fps; /**< Target FPS. */ + ecs_ftime_t delta_time; /**< Frame time increment (0 for measured values) */ + int32_t threads; /**< Number of threads. */ + int32_t frames; /**< Number of frames to run (0 for infinite) */ + bool enable_rest; /**< Enables ECS access over HTTP, necessary for explorer */ + bool enable_stats; /**< Periodically collect statistics */ + uint16_t port; /**< HTTP port used by REST API */ + + ecs_app_init_action_t init; /**< If set, function is ran before starting the * main loop. */ - void *ctx; /* Reserved for custom run/frame actions */ + void *ctx; /**< Reserved for custom run/frame actions */ } ecs_app_desc_t; /** Callback type for run action. */ typedef int(*ecs_app_run_action_t)( - ecs_world_t *world, + ecs_world_t *world, ecs_app_desc_t *desc); /** Callback type for frame action. */ typedef int(*ecs_app_frame_action_t)( - ecs_world_t *world, + ecs_world_t *world, const ecs_app_desc_t *desc); /** Run application. * This will run the application with the parameters specified in desc. After - * the application quits (ecs_quit is called) the world will be cleaned up. - * + * the application quits (ecs_quit() is called) the world will be cleaned up. + * * If a custom run action is set, it will be invoked by this operation. The * default run action calls the frame action in a loop until it returns a * non-zero value. - * + * * @param world The world. * @param desc Application parameters. */ @@ -8520,11 +12062,11 @@ int ecs_app_run( /** Default frame callback. * This operation will run a single frame. By default this operation will invoke - * ecs_progress directly, unless a custom frame action is set. - * + * ecs_progress() directly, unless a custom frame action is set. + * * @param world The world. - * @param desc The desc struct passed to ecs_app_run. - * @return value returned by ecs_progress + * @param desc The desc struct passed to ecs_app_run(). + * @return value returned by ecs_progress() */ FLECS_API int ecs_app_run_frame( @@ -8532,8 +12074,8 @@ int ecs_app_run_frame( const ecs_app_desc_t *desc); /** Set custom run action. - * See ecs_app_run. - * + * See ecs_app_run(). + * * @param callback The run action. */ FLECS_API @@ -8541,14 +12083,16 @@ int ecs_app_set_run_action( ecs_app_run_action_t callback); /** Set custom frame action. - * See ecs_app_run_frame. - * + * See ecs_app_run_frame(). + * * @param callback The frame action. */ FLECS_API int ecs_app_set_frame_action( ecs_app_frame_action_t callback); +/** @} */ + #ifdef __cplusplus } #endif @@ -8558,141 +12102,435 @@ int ecs_app_set_frame_action( #endif // FLECS_APP #endif -#ifdef FLECS_REST -#ifdef FLECS_NO_REST -#error "FLECS_NO_REST failed: REST is required by other addons" + +#ifdef FLECS_HTTP +#ifdef FLECS_NO_HTTP +#error "FLECS_NO_HTTP failed: HTTP is required by other addons" #endif /** - * @file rest.h - * @brief REST API addon. + * @file addons/http.h + * @brief HTTP addon. * - * A small REST API that uses the HTTP server and JSON serializer to provide - * access to application data for remote applications. - * - * A description of the API can be found in docs/RestApi.md + * Minimalistic HTTP server that can receive and reply to simple HTTP requests. + * The main goal of this addon is to enable remotely connecting to a running + * Flecs application (for example, with a web-based UI) and request/visualize + * data from the ECS world. + * + * Each server instance creates a single thread used for receiving requests. + * Receiving requests are enqueued and handled when the application calls + * ecs_http_server_dequeue(). This increases latency of request handling vs. + * responding directly in the receive thread, but is better suited for + * retrieving data from ECS applications, as requests can be processed by an ECS + * system without having to lock the world. + * + * This server is intended to be used in a development environment. */ -#ifdef FLECS_REST +#ifdef FLECS_HTTP -/* Used for the HTTP server */ -#ifndef FLECS_HTTP -#define FLECS_HTTP -#endif +/** + * @defgroup c_addons_http Http + * @ingroup c_addons + * Simple HTTP server used for serving up REST API. + * + * @{ + */ -/* Used for building the JSON replies */ -#ifndef FLECS_JSON -#define FLECS_JSON +#if !defined(FLECS_OS_API_IMPL) && !defined(FLECS_NO_OS_API_IMPL) +#define FLECS_OS_API_IMPL #endif -/* Query engine used */ -#ifndef FLECS_RULES -#define FLECS_RULES -#endif +#ifndef FLECS_HTTP_H +#define FLECS_HTTP_H -/* For the REST system */ -#ifndef FLECS_PIPELINE -#define FLECS_PIPELINE -#endif +/** Maximum number of headers in request. */ +#define ECS_HTTP_HEADER_COUNT_MAX (32) -#ifndef FLECS_REST_H -#define FLECS_REST_H +/** Maximum number of query parameters in request. */ +#define ECS_HTTP_QUERY_PARAM_COUNT_MAX (32) #ifdef __cplusplus extern "C" { #endif -#define ECS_REST_DEFAULT_PORT (27750) - -/* Component that instantiates the REST API */ -FLECS_API extern const ecs_entity_t ecs_id(EcsRest); +/** HTTP server. */ +typedef struct ecs_http_server_t ecs_http_server_t; +/** A connection manages communication with the remote host. */ typedef struct { - uint16_t port; /* Port of server (optional, default = 27750) */ - char *ipaddr; /* Interface address (optional, default = 0.0.0.0) */ - void *impl; -} EcsRest; - -/* Module import */ -FLECS_API -void FlecsRestImport( - ecs_world_t *world); - -#ifdef __cplusplus -} -#endif - -#endif - -#endif + uint64_t id; + ecs_http_server_t *server; -#endif -#ifdef FLECS_TIMER -#ifdef FLECS_NO_TIMER -#error "FLECS_NO_TIMER failed: TIMER is required by other addons" -#endif -/** - * @file timer.h - * @brief Timer module. - * - * Timers can be used to trigger actions at periodic or one-shot intervals. They - * are typically used together with systems and pipelines. - */ + char host[128]; + char port[16]; +} ecs_http_connection_t; -#ifdef FLECS_TIMER +/** Helper type used for headers & URL query parameters. */ +typedef struct { + const char *key; + const char *value; +} ecs_http_key_value_t; -#ifndef FLECS_MODULE -#define FLECS_MODULE -#endif +/** Supported request methods. */ +typedef enum { + EcsHttpGet, + EcsHttpPost, + EcsHttpPut, + EcsHttpDelete, + EcsHttpOptions, + EcsHttpMethodUnsupported +} ecs_http_method_t; -#ifndef FLECS_PIPELINE -#define FLECS_PIPELINE -#endif +/** An HTTP request. */ +typedef struct { + uint64_t id; -#ifndef FLECS_TIMER_H -#define FLECS_TIMER_H + ecs_http_method_t method; + char *path; + char *body; + ecs_http_key_value_t headers[ECS_HTTP_HEADER_COUNT_MAX]; + ecs_http_key_value_t params[ECS_HTTP_HEADER_COUNT_MAX]; + int32_t header_count; + int32_t param_count; -#ifdef __cplusplus -extern "C" { -#endif + ecs_http_connection_t *conn; +} ecs_http_request_t; +/** An HTTP reply. */ +typedef struct { + int code; /**< default = 200 */ + ecs_strbuf_t body; /**< default = "" */ + const char* status; /**< default = OK */ + const char* content_type; /**< default = application/json */ + ecs_strbuf_t headers; /**< default = "" */ +} ecs_http_reply_t; -//////////////////////////////////////////////////////////////////////////////// -//// Components -//////////////////////////////////////////////////////////////////////////////// +#define ECS_HTTP_REPLY_INIT \ + (ecs_http_reply_t){200, ECS_STRBUF_INIT, "OK", "application/json", ECS_STRBUF_INIT} -/** Component used for one shot/interval timer functionality */ -typedef struct EcsTimer { - FLECS_FLOAT timeout; /* Timer timeout period */ - FLECS_FLOAT time; /* Incrementing time value */ - int32_t fired_count; /* Number of times ticked */ - bool active; /* Is the timer active or not */ - bool single_shot; /* Is this a single shot timer */ -} EcsTimer; +/* Global HTTP statistics. */ +extern int64_t ecs_http_request_received_count; /**< Total number of HTTP requests received. */ +extern int64_t ecs_http_request_invalid_count; /**< Total number of invalid HTTP requests. */ +extern int64_t ecs_http_request_handled_ok_count; /**< Total number of successful HTTP requests. */ +extern int64_t ecs_http_request_handled_error_count; /**< Total number of HTTP requests with errors. */ +extern int64_t ecs_http_request_not_handled_count; /**< Total number of HTTP requests with an unknown endpoint. */ +extern int64_t ecs_http_request_preflight_count; /**< Total number of preflight HTTP requests received. */ +extern int64_t ecs_http_send_ok_count; /**< Total number of HTTP replies successfully sent. */ +extern int64_t ecs_http_send_error_count; /**< Total number of HTTP replies that failed to send. */ +extern int64_t ecs_http_busy_count; /**< Total number of HTTP busy replies. */ -/* Apply a rate filter to a tick source */ -typedef struct EcsRateFilter { - ecs_entity_t src; /* Source of the rate filter */ - int32_t rate; /* Rate of the rate filter */ - int32_t tick_count; /* Number of times the rate filter ticked */ - FLECS_FLOAT time_elapsed; /* Time elapsed since last tick */ -} EcsRateFilter; +/** Request callback. + * Invoked for each valid request. The function should populate the reply and + * return true. When the function returns false, the server will reply with a + * 404 (Not found) code. */ +typedef bool (*ecs_http_reply_action_t)( + const ecs_http_request_t* request, + ecs_http_reply_t *reply, + void *ctx); +/** Used with ecs_http_server_init(). */ +typedef struct { + ecs_http_reply_action_t callback; /**< Function called for each request */ + void *ctx; /**< Passed to callback (optional) */ + uint16_t port; /**< HTTP port */ + const char *ipaddr; /**< Interface to listen on (optional) */ + int32_t send_queue_wait_ms; /**< Send queue wait time when empty */ + double cache_timeout; /**< Cache invalidation timeout (0 disables caching) */ + double cache_purge_timeout; /**< Cache purge timeout (for purging cache entries) */ +} ecs_http_server_desc_t; -//////////////////////////////////////////////////////////////////////////////// -//// Timer API -//////////////////////////////////////////////////////////////////////////////// +/** Create server. + * Use ecs_http_server_start() to start receiving requests. + * + * @param desc Server configuration parameters. + * @return The new server, or NULL if creation failed. + */ +FLECS_API +ecs_http_server_t* ecs_http_server_init( + const ecs_http_server_desc_t *desc); -/** Set timer timeout. - * This operation executes any systems associated with the timer after the - * specified timeout value. If the entity contains an existing timer, the - * timeout value will be reset. The timer can be started and stopped with - * ecs_start_timer and ecs_stop_timer. +/** Destroy server. + * This operation will stop the server if it was still running. + * + * @param server The server to destroy. + */ +FLECS_API +void ecs_http_server_fini( + ecs_http_server_t* server); + +/** Start server. + * After this operation the server will be able to accept requests. + * + * @param server The server to start. + * @return Zero if successful, non-zero if failed. + */ +FLECS_API +int ecs_http_server_start( + ecs_http_server_t* server); + +/** Process server requests. + * This operation invokes the reply callback for each received request. No new + * requests will be enqueued while processing requests. + * + * @param server The server for which to process requests. + */ +FLECS_API +void ecs_http_server_dequeue( + ecs_http_server_t* server, + ecs_ftime_t delta_time); + +/** Stop server. + * After this operation no new requests can be received. + * + * @param server The server. + */ +FLECS_API +void ecs_http_server_stop( + ecs_http_server_t* server); + +/** Emulate a request. + * The request string must be a valid HTTP request. A minimal example: + * + * GET /entity/flecs/core/World?label=true HTTP/1.1 + * + * @param srv The server. + * @param req The request. + * @param len The length of the request (optional). + * @return The reply. + */ +FLECS_API +int ecs_http_server_http_request( + ecs_http_server_t* srv, + const char *req, + ecs_size_t len, + ecs_http_reply_t *reply_out); + +/** Convenience wrapper around ecs_http_server_http_request(). */ +FLECS_API +int ecs_http_server_request( + ecs_http_server_t* srv, + const char *method, + const char *req, + const char *body, + ecs_http_reply_t *reply_out); + +/** Get context provided in ecs_http_server_desc_t */ +FLECS_API +void* ecs_http_server_ctx( + ecs_http_server_t* srv); + +/** Find header in request. + * + * @param req The request. + * @param name name of the header to find + * @return The header value, or NULL if not found. +*/ +FLECS_API +const char* ecs_http_get_header( + const ecs_http_request_t* req, + const char* name); + +/** Find query parameter in request. + * + * @param req The request. + * @param name The parameter name. + * @return The decoded parameter value, or NULL if not found. + */ +FLECS_API +const char* ecs_http_get_param( + const ecs_http_request_t* req, + const char* name); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif // FLECS_HTTP_H + +#endif // FLECS_HTTP + +#endif + +#ifdef FLECS_REST +#ifdef FLECS_NO_REST +#error "FLECS_NO_REST failed: REST is required by other addons" +#endif +/** + * @file addons/rest.h + * @brief REST API addon. + * + * A small REST API that uses the HTTP server and JSON serializer to provide + * access to application data for remote applications. + * + * A description of the API can be found in docs/FlecsRemoteApi.md + */ + +#ifdef FLECS_REST + +/** + * @defgroup c_addons_rest Rest + * @ingroup c_addons + * REST API for querying and mutating entities. + * + * @{ + */ + +/* Used for the HTTP server */ +#ifndef FLECS_HTTP +#define FLECS_HTTP +#endif + +/* Used for building the JSON replies */ +#ifndef FLECS_JSON +#define FLECS_JSON +#endif + +/* For the REST system */ +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif + +#ifndef FLECS_REST_H +#define FLECS_REST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define ECS_REST_DEFAULT_PORT (27750) + +/** Component that instantiates the REST API. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsRest); + +/** Private REST data. */ +typedef struct { + ecs_world_t *world; + ecs_http_server_t *srv; + int32_t rc; + ecs_map_t cmd_captures; + double last_time; +} ecs_rest_ctx_t; + +/** Component that creates a REST API server when instantiated. */ +typedef struct { + uint16_t port; /**< Port of server (optional, default = 27750) */ + char *ipaddr; /**< Interface address (optional, default = 0.0.0.0) */ + ecs_rest_ctx_t *impl; +} EcsRest; + +/** Create HTTP server for REST API. + * This allows for the creation of a REST server that can be managed by the + * application without using Flecs systems. + * + * @param world The world. + * @param desc The HTTP server descriptor. + * @return The HTTP server, or NULL if failed. + */ +FLECS_API +ecs_http_server_t* ecs_rest_server_init( + ecs_world_t *world, + const ecs_http_server_desc_t *desc); + +/** Cleanup REST HTTP server. + * The server must have been created with ecs_rest_server_init(). + */ +FLECS_API +void ecs_rest_server_fini( + ecs_http_server_t *srv); + +/** Rest module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsRest) + * @endcode + * + * @param world The world. + */ +FLECS_API +void FlecsRestImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_TIMER +#ifdef FLECS_NO_TIMER +#error "FLECS_NO_TIMER failed: TIMER is required by other addons" +#endif +/** + * @file addons/timer.h + * @brief Timer module. + * + * Timers can be used to trigger actions at periodic or one-shot intervals. They + * are typically used together with systems and pipelines. + */ + +#ifdef FLECS_TIMER + +/** + * @defgroup c_addons_timer Timer + * @ingroup c_addons + * Run systems at a time interval. + * + * @{ + */ + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif + +#ifndef FLECS_TIMER_H +#define FLECS_TIMER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Component used for one shot/interval timer functionality */ +typedef struct EcsTimer { + ecs_ftime_t timeout; /**< Timer timeout period */ + ecs_ftime_t time; /**< Incrementing time value */ + ecs_ftime_t overshoot; /**< Used to correct returned interval time */ + int32_t fired_count; /**< Number of times ticked */ + bool active; /**< Is the timer active or not */ + bool single_shot; /**< Is this a single shot timer */ +} EcsTimer; + +/** Apply a rate filter to a tick source */ +typedef struct EcsRateFilter { + ecs_entity_t src; /**< Source of the rate filter */ + int32_t rate; /**< Rate of the rate filter */ + int32_t tick_count; /**< Number of times the rate filter ticked */ + ecs_ftime_t time_elapsed; /**< Time elapsed since last tick */ +} EcsRateFilter; + + +/** Set timer timeout. + * This operation executes any systems associated with the timer after the + * specified timeout value. If the entity contains an existing timer, the + * timeout value will be reset. The timer can be started and stopped with + * ecs_start_timer() and ecs_stop_timer(). * * The timer is synchronous, and is incremented each frame by delta_time. * - * The tick_source entity will be be a tick source after this operation. Tick + * The tick_source entity will be a tick source after this operation. Tick * sources can be read by getting the EcsTickSource component. If the tick - * source ticked this frame, the 'tick' member will be true. When the tick + * source ticked this frame, the 'tick' member will be true. When the tick * source is a system, the system will tick when the timer ticks. * * @param world The world. @@ -8704,21 +12542,21 @@ FLECS_API ecs_entity_t ecs_set_timeout( ecs_world_t *world, ecs_entity_t tick_source, - FLECS_FLOAT timeout); + ecs_ftime_t timeout); /** Get current timeout value for the specified timer. - * This operation returns the value set by ecs_set_timeout. If no timer is + * This operation returns the value set by ecs_set_timeout(). If no timer is * active for this entity, the operation returns 0. * * After the timeout expires the EcsTimer component is removed from the entity. - * This means that if ecs_get_timeout is invoked after the timer is expired, the + * This means that if ecs_get_timeout() is invoked after the timer is expired, the * operation will return 0. * * The timer is synchronous, and is incremented each frame by delta_time. * - * The tick_source entity will be be a tick source after this operation. Tick + * The tick_source entity will be a tick source after this operation. Tick * sources can be read by getting the EcsTickSource component. If the tick - * source ticked this frame, the 'tick' member will be true. When the tick + * source ticked this frame, the 'tick' member will be true. When the tick * source is a system, the system will tick when the timer ticks. * * @param world The world. @@ -8726,21 +12564,21 @@ ecs_entity_t ecs_set_timeout( * @return The current timeout value, or 0 if no timer is active. */ FLECS_API -FLECS_FLOAT ecs_get_timeout( +ecs_ftime_t ecs_get_timeout( const ecs_world_t *world, ecs_entity_t tick_source); /** Set timer interval. - * This operation will continously invoke systems associated with the timer + * This operation will continuously invoke systems associated with the timer * after the interval period expires. If the entity contains an existing timer, * the interval value will be reset. * * The timer is synchronous, and is incremented each frame by delta_time. * - * The tick_source entity will be be a tick source after this operation. Tick + * The tick_source entity will be a tick source after this operation. Tick * sources can be read by getting the EcsTickSource component. If the tick - * source ticked this frame, the 'tick' member will be true. When the tick - * source is a system, the system will tick when the timer ticks. + * source ticked this frame, the 'tick' member will be true. When the tick + * source is a system, the system will tick when the timer ticks. * * @param world The world. * @param tick_source The timer for which to set the interval (0 to create one). @@ -8751,10 +12589,10 @@ FLECS_API ecs_entity_t ecs_set_interval( ecs_world_t *world, ecs_entity_t tick_source, - FLECS_FLOAT interval); + ecs_ftime_t interval); /** Get current interval value for the specified timer. - * This operation returns the value set by ecs_set_interval. If the entity is + * This operation returns the value set by ecs_set_interval(). If the entity is * not a timer, the operation will return 0. * * @param world The world. @@ -8762,15 +12600,12 @@ ecs_entity_t ecs_set_interval( * @return The current interval value, or 0 if no timer is active. */ FLECS_API -FLECS_FLOAT ecs_get_interval( +ecs_ftime_t ecs_get_interval( const ecs_world_t *world, ecs_entity_t tick_source); /** Start timer. - * This operation resets the timer and starts it with the specified timeout. The - * entity must have the EcsTimer component (added by ecs_set_timeout and - * ecs_set_interval). If the entity does not have the EcsTimer component this - * operation will assert. + * This operation resets the timer and starts it with the specified timeout. * * @param world The world. * @param tick_source The timer to start. @@ -8781,8 +12616,7 @@ void ecs_start_timer( ecs_entity_t tick_source); /** Stop timer - * This operation stops a timer from triggering. The entity must have the - * EcsTimer component or this operation will assert. + * This operation stops a timer from triggering. * * @param world The world. * @param tick_source The timer to stop. @@ -8792,6 +12626,27 @@ void ecs_stop_timer( ecs_world_t *world, ecs_entity_t tick_source); +/** Reset time value of timer to 0. + * This operation resets the timer value to 0. + * + * @param world The world. + * @param tick_source The timer to reset. + */ +FLECS_API +void ecs_reset_timer( + ecs_world_t *world, + ecs_entity_t tick_source); + +/** Enable randomizing initial time value of timers. + * Initializes timers with a random time value, which can improve scheduling as + * systems/timers for the same interval don't all happen on the same tick. + * + * @param world The world. + */ +FLECS_API +void ecs_randomize_timers( + ecs_world_t *world); + /** Set rate filter. * This operation initializes a rate filter. Rate filters sample tick sources * and tick at a configurable multiple. A rate filter is a tick source itself, @@ -8801,20 +12656,20 @@ void ecs_stop_timer( * with interval timers alone. For example, if timer A has interval 2.0 and * timer B has interval 4.0, it is not guaranteed that B will tick at exactly * twice the multiple of A. This is partly due to the indeterministic nature of - * timers, and partly due to floating point rounding errors. + * timers, and partly due to floating point rounding errors. * - * Rate filters can be combined with timers (or other rate filters) to ensure + * Rate filters can be combined with timers (or other rate filters) to ensure * that a system ticks at an exact multiple of a tick source (which can be * another system). If a rate filter is created with a rate of 1 it will tick * at the exact same time as its source. * * If no tick source is provided, the rate filter will use the frame tick as - * source, which corresponds with the number of times ecs_progress is called. + * source, which corresponds with the number of times ecs_progress() is called. * - * The tick_source entity will be be a tick source after this operation. Tick + * The tick_source entity will be a tick source after this operation. Tick * sources can be read by getting the EcsTickSource component. If the tick - * source ticked this frame, the 'tick' member will be true. When the tick - * source is a system, the system will tick when the timer ticks. + * source ticked this frame, the 'tick' member will be true. When the tick + * source is a system, the system will tick when the timer ticks. * * @param world The world. * @param tick_source The rate filter entity (0 to create one). @@ -8833,7 +12688,7 @@ ecs_entity_t ecs_set_rate( * Systems can be their own tick source, which can be any of the tick sources * (one shot timers, interval times and rate filters). However, in some cases it * is must be guaranteed that different systems tick on the exact same frame. - * + * * This cannot be guaranteed by giving two systems the same interval/rate filter * as it is possible that one system is (for example) disabled, which would * cause the systems to go out of sync. To provide these guarantees, systems @@ -8844,13 +12699,13 @@ ecs_entity_t ecs_set_rate( * source, including another system. If the provided entity is not a tick source * the system will not be ran. * - * To disassociate a tick source from a system, use 0 for the tick_source + * To disassociate a tick source from a system, use 0 for the tick_source * parameter. * * @param world The world. * @param system The system to associate with the timer. * @param tick_source The tick source to associate with the system. - */ + */ FLECS_API void ecs_set_tick_source( ecs_world_t *world, @@ -8862,6 +12717,14 @@ void ecs_set_tick_source( //// Module //////////////////////////////////////////////////////////////////////////////// +/** Timer module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsTimer) + * @endcode + * + * @param world The world. + */ FLECS_API void FlecsTimerImport( ecs_world_t *world); @@ -8872,15 +12735,18 @@ void FlecsTimerImport( #endif +/** @} */ + #endif #endif + #ifdef FLECS_PIPELINE #ifdef FLECS_NO_PIPELINE #error "FLECS_NO_PIPELINE failed: PIPELINE is required by other addons" #endif /** - * @file pipeline.h + * @file addons/pipeline.h * @brief Pipeline module. * * The pipeline module provides support for running systems automatically and @@ -8888,14 +12754,22 @@ void FlecsTimerImport( * systems. When ran, a pipeline will query for all systems that have the tags * that belong to a pipeline, and run them. * - * The module defines a number of builtin tags (EcsPreUpdate, EcsOnUpdate, - * EcsPostUpdate etc.) that are registered with the builtin pipeline. The - * builtin pipeline is ran by default when calling ecs_progress(). An - * application can set a custom pipeline with the ecs_set_pipeline function. + * The module defines a number of builtin tags (EcsPreUpdate, EcsOnUpdate, + * EcsPostUpdate etc.) that are registered with the builtin pipeline. The + * builtin pipeline is ran by default when calling ecs_progress(). An + * application can set a custom pipeline with the ecs_set_pipeline() function. */ #ifdef FLECS_PIPELINE +/** + * @defgroup c_addons_pipeline Pipeline + * @ingroup c_addons + * Pipelines order and schedule systems for execution. + * + * @{ + */ + #ifndef FLECS_MODULE #define FLECS_MODULE #endif @@ -8916,25 +12790,91 @@ extern "C" { #endif #ifndef FLECS_LEGACY -#define ECS_PIPELINE_DEFINE(world, id, ...)\ - id = ecs_type_init(world, &(ecs_type_desc_t){\ - .entity = {\ - .name = #id,\ - .add = {EcsPipeline}\ - },\ - .ids_expr = #__VA_ARGS__\ - });\ - ecs_id(id) = id; +/** Convenience macro to create a predeclared pipeline. + * Usage: + * @code + * ECS_ENTITY_DECLARE(MyPipeline); + * ECS_PIPELINE_DEFINE(world, MyPipeline, Update || Physics || Render) + * @endcode + */ +#define ECS_PIPELINE_DEFINE(world, id_, ...) \ + { \ + ecs_pipeline_desc_t desc = {0}; \ + ecs_entity_desc_t edesc = {0}; \ + edesc.id = id_;\ + edesc.name = #id_;\ + desc.entity = ecs_entity_init(world, &edesc);\ + desc.query.expr = #__VA_ARGS__; \ + id_ = ecs_pipeline_init(world, &desc); \ + ecs_id(id_) = id_;\ + } \ + ecs_assert(id_ != 0, ECS_INVALID_PARAMETER, "failed to create pipeline"); + +/** Convenience macro to create a pipeline. + * Usage: + * @code + * ECS_PIPELINE(world, MyPipeline, Update || Physics || Render) + * @endcode + * + */ #define ECS_PIPELINE(world, id, ...) \ - ecs_entity_t ecs_id(id), ECS_PIPELINE_DEFINE(world, id, __VA_ARGS__);\ + ecs_entity_t id = 0, ecs_id(id) = 0; ECS_PIPELINE_DEFINE(world, id, __VA_ARGS__);\ (void)id;\ - (void)ecs_id(id) - + (void)ecs_id(id); + +/** Convenience macro to create a pipeline. + * See ecs_pipeline_init(). + */ +#define ecs_pipeline(world, ...)\ + ecs_pipeline_init(world, &(ecs_pipeline_desc_t) __VA_ARGS__ ) + #endif +/** Pipeline descriptor, used with ecs_pipeline_init(). */ +typedef struct ecs_pipeline_desc_t { + /** Existing entity to associate with pipeline (optional). */ + ecs_entity_t entity; + + /** The pipeline query. + * Pipelines are queries that are matched with system entities. Pipeline + * queries are the same as regular queries, which means the same query rules + * apply. A common mistake is to try a pipeline that matches systems in a + * list of phases by specifying all the phases, like: + * OnUpdate, OnPhysics, OnRender + * + * That however creates a query that matches entities with OnUpdate _and_ + * OnPhysics _and_ OnRender tags, which is likely undesired. Instead, a + * query could use the or operator match a system that has one of the + * specified phases: + * OnUpdate || OnPhysics || OnRender + * + * This will return the correct set of systems, but they likely won't be in + * the correct order. To make sure systems are returned in the correct order + * two query ordering features can be used: + * - group_by + * - order_by + * + * Take a look at the system manual for a more detailed explanation of + * how query features can be applied to pipelines, and how the builtin + * pipeline query works. + */ + ecs_query_desc_t query; +} ecs_pipeline_desc_t; + +/** Create a custom pipeline. + * + * @param world The world. + * @param desc The pipeline descriptor. + * @return The pipeline, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_pipeline_init( + ecs_world_t *world, + const ecs_pipeline_desc_t *desc); + /** Set a custom pipeline. - * This operation sets the pipeline to run when ecs_progress is invoked. + * This operation sets the pipeline to run when ecs_progress() is invoked. * * @param world The world. * @param pipeline The pipeline to set. @@ -8942,7 +12882,7 @@ extern "C" { FLECS_API void ecs_set_pipeline( ecs_world_t *world, - ecs_entity_t pipeline); + ecs_entity_t pipeline); /** Get the current pipeline. * This operation gets the current pipeline. @@ -8952,7 +12892,7 @@ void ecs_set_pipeline( */ FLECS_API ecs_entity_t ecs_get_pipeline( - const ecs_world_t *world); + const ecs_world_t *world); /** Progress a world. * This operation progresses the world by running all systems that are both @@ -8963,19 +12903,19 @@ ecs_entity_t ecs_get_pipeline( * update entity values proportional to the elapsed time since their last * invocation. * - * When an application passes 0 to delta_time, ecs_progress will automatically + * When an application passes 0 to delta_time, ecs_progress() will automatically * measure the time passed since the last frame. If an application does not uses * time management, it should pass a non-zero value for delta_time (1.0 is * recommended). That way, no time will be wasted measuring the time. * * @param world The world to progress. * @param delta_time The time passed since the last frame. - * @return false if ecs_quit has been called, true otherwise. + * @return false if ecs_quit() has been called, true otherwise. */ FLECS_API bool ecs_progress( ecs_world_t *world, - FLECS_FLOAT delta_time); + ecs_ftime_t delta_time); /** Set time scale. * Increase or decrease simulation speed by the provided multiplier. @@ -8983,10 +12923,10 @@ bool ecs_progress( * @param world The world. * @param scale The scale to apply (default = 1). */ -FLECS_API +FLECS_API void ecs_set_time_scale( ecs_world_t *world, - FLECS_FLOAT scale); + ecs_ftime_t scale); /** Reset world clock. * Reset the clock that keeps track of the total time passed in the simulation. @@ -9003,37 +12943,21 @@ void ecs_reset_clock( * pipeline manages staging and, if necessary, synchronization between threads. * * If 0 is provided for the pipeline id, the default pipeline will be ran (this - * is either the builtin pipeline or the pipeline set with set_pipeline()). + * is either the builtin pipeline or the pipeline set with set_pipeline()). * * When using progress() this operation will be invoked automatically for the - * default pipeline (either the builtin pipeline or the pipeline set with + * default pipeline (either the builtin pipeline or the pipeline set with * set_pipeline()). An application may run additional pipelines. * - * Note: calling this function from an application currently only works in - * single threaded applications with a single stage. - * * @param world The world. * @param pipeline The pipeline to run. + * @param delta_time The delta_time to pass to systems. */ -FLECS_API +FLECS_API void ecs_run_pipeline( ecs_world_t *world, ecs_entity_t pipeline, - FLECS_FLOAT delta_time); - -/** Deactivate systems that are not matched with tables. - * By default Flecs deactivates systems that are not matched with any tables. - * However, once a system has been matched with a table it remains activated, to - * prevent systems from continuously becoming active and inactive. - * - * To re-deactivate systems, an application can invoke this function, which will - * deactivate all systems that are not matched with any tables. - * - * @param world The world. - */ -FLECS_API -void ecs_deactivate_systems( - ecs_world_t *world); + ecs_ftime_t delta_time); //////////////////////////////////////////////////////////////////////////////// @@ -9044,16 +12968,60 @@ void ecs_deactivate_systems( * Setting this value to a value higher than 1 will start as many threads and * will cause systems to evenly distribute matched entities across threads. The * operation may be called multiple times to reconfigure the number of threads - * used, but never while running a system / pipeline. */ + * used, but never while running a system / pipeline. + * Calling ecs_set_threads() will also end the use of task threads setup with + * ecs_set_task_threads() and vice-versa. + * + * @param world The world. + * @param threads The number of threads to create. + */ FLECS_API void ecs_set_threads( ecs_world_t *world, int32_t threads); +/** Set number of worker task threads. + * ecs_set_task_threads() is similar to ecs_set_threads(), except threads are treated + * as short-lived tasks and will be created and joined around each update of the world. + * Creation and joining of these tasks will use the os_api_t tasks APIs rather than the + * the standard thread API functions, although they may be the same if desired. + * This function is useful for multithreading world updates using an external + * asynchronous job system rather than long running threads by providing the APIs + * to create tasks for your job system and then wait on their conclusion. + * The operation may be called multiple times to reconfigure the number of task threads + * used, but never while running a system / pipeline. + * Calling ecs_set_task_threads() will also end the use of threads setup with + * ecs_set_threads() and vice-versa + * + * @param world The world. + * @param task_threads The number of task threads to create. + */ +FLECS_API +void ecs_set_task_threads( + ecs_world_t *world, + int32_t task_threads); + +/** Returns true if task thread use have been requested. + * + * @param world The world. + * @result Whether the world is using task threads. + */ +FLECS_API +bool ecs_using_task_threads( + ecs_world_t *world); + //////////////////////////////////////////////////////////////////////////////// //// Module //////////////////////////////////////////////////////////////////////////////// +/** Pipeline module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsPipeline) + * @endcode + * + * @param world The world. + */ FLECS_API void FlecsPipelineImport( ecs_world_t *world); @@ -9064,15 +13032,18 @@ void FlecsPipelineImport( #endif +/** @} */ + #endif #endif + #ifdef FLECS_SYSTEM #ifdef FLECS_NO_SYSTEM #error "FLECS_NO_SYSTEM failed: SYSTEM is required by other addons" #endif /** - * @file system.h + * @file addons/system.h * @brief System module. * * The system module allows for creating and running systems. A system is a @@ -9082,6 +13053,14 @@ void FlecsPipelineImport( #ifdef FLECS_SYSTEM +/** + * @defgroup c_addons_system System + * @ingroup c_addons + * Systems are a query + function that can be ran manually or by a pipeline. + * + * @{ + */ + #ifndef FLECS_MODULE #define FLECS_MODULE #endif @@ -9093,146 +13072,216 @@ void FlecsPipelineImport( extern "C" { #endif - -//////////////////////////////////////////////////////////////////////////////// -//// Components -//////////////////////////////////////////////////////////////////////////////// - -/* Component used to provide a tick source to systems */ +/** Component used to provide a tick source to systems */ typedef struct EcsTickSource { - bool tick; /* True if providing tick */ - FLECS_FLOAT time_elapsed; /* Time elapsed since last tick */ + bool tick; /**< True if providing tick */ + ecs_ftime_t time_elapsed; /**< Time elapsed since last tick */ } EcsTickSource; -//////////////////////////////////////////////////////////////////////////////// -//// Systems API -//////////////////////////////////////////////////////////////////////////////// - -/** System status change callback */ -typedef enum ecs_system_status_t { - EcsSystemStatusNone = 0, - EcsSystemEnabled, - EcsSystemDisabled, - EcsSystemActivated, - EcsSystemDeactivated -} ecs_system_status_t; - -/** System status action. - * The status action is invoked whenever a system is enabled or disabled. Note - * that a system may be enabled but may not actually match any entities. In this - * case the system is enabled but not _active_. - * - * In addition to communicating the enabled / disabled status, the action also - * communicates changes in the activation status of the system. A system becomes - * active when it has one or more matching entities, and becomes inactive when - * it no longer matches any entities. - * - * A system switches between enabled and disabled when an application invokes the - * ecs_enable operation with a state different from the state of the system, for - * example the system is disabled, and ecs_enable is invoked with enabled: true. - * - * @param world The world. - * @param system The system for which the action has changed. - * @param status The status that triggered the callback. - * @param ctx Context passed to ecs_system_desc_t as status_ctx. - */ -typedef void (*ecs_system_status_action_t)( - ecs_world_t *world, - ecs_entity_t system, - ecs_system_status_t status, - void *ctx); - -/* Use with ecs_system_init */ +/** Use with ecs_system_init() to create or update a system. */ typedef struct ecs_system_desc_t { int32_t _canary; - /* System entity creation parameters */ - ecs_entity_desc_t entity; + /** Existing entity to associate with system (optional) */ + ecs_entity_t entity; - /* System query parameters */ + /** System query parameters */ ecs_query_desc_t query; - /* Callback that is invoked when a system is ran. When left to NULL, the - * default system runner is used, which calls the "callback" action for each - * result returned from the system's query. - * - * It should not be assumed that the input iterator can always be iterated - * with ecs_query_next. When a system is multithreaded and/or paged, the - * iterator can be either a worker or paged iterator. Future use cases may - * introduce additional inputs for a system, such as rules and filters. The - * correct function to use for iteration is ecs_iter_next. - * - * An implementation can test whether the iterator is a query iterator by - * testing whether the it->next value is equal to ecs_query_next. */ - ecs_run_action_t run; + /** Optional pipeline phase for the system to run in. When set, it will be + * added to the system both as a tag and as a (DependsOn, phase) pair. */ + ecs_entity_t phase; - /* Callback that is ran for each result returned by the system's query. This + /** Callback that is ran for each result returned by the system's query. This * means that this callback can be invoked multiple times per system per * frame, typically once for each matching table. */ ecs_iter_action_t callback; - /* System status callback, invoked when system status changes */ - ecs_system_status_action_t status_callback; - - /* Associate with entity. */ - ecs_entity_t self; + /** Callback that is invoked when a system is ran. + * When left to NULL, the default system runner is used, which calls the + * "callback" action for each result returned from the system's query. + * + * It should not be assumed that the input iterator can always be iterated + * with ecs_query_next(). When a system is multithreaded and/or paged, the + * iterator can be either a worker or paged iterator. The correct function + * to use for iteration is ecs_iter_next(). + * + * An implementation can test whether the iterator is a query iterator by + * testing whether the it->next value is equal to ecs_query_next(). */ + ecs_run_action_t run; - /* Context to be passed to callback (as ecs_iter_t::param) */ + /** Context to be passed to callback (as ecs_iter_t::param) */ void *ctx; - /* Context to be passed to system status callback */ - void *status_ctx; + /** Callback to free ctx. */ + ecs_ctx_free_t ctx_free; - /* Binding context, for when system is implemented in other language */ - void *binding_ctx; + /** Context associated with callback (for language bindings). */ + void *callback_ctx; - /* Functions that are invoked during system cleanup to free context data. - * When set, functions are called unconditionally, even when the ctx - * pointers are NULL. */ - ecs_ctx_free_t ctx_free; - ecs_ctx_free_t status_ctx_free; - ecs_ctx_free_t binding_ctx_free; + /** Callback to free callback ctx. */ + ecs_ctx_free_t callback_ctx_free; + + /** Context associated with run (for language bindings). */ + void *run_ctx; - /* Interval in seconds at which the system should run */ - FLECS_FLOAT interval; + /** Callback to free run ctx. */ + ecs_ctx_free_t run_ctx_free; + + /** Interval in seconds at which the system should run */ + ecs_ftime_t interval; - /* Rate at which the system should run */ + /** Rate at which the system should run */ int32_t rate; - /* External tick soutce that determines when system ticks */ + /** External tick source that determines when system ticks */ ecs_entity_t tick_source; - /* If true, system will be ran on multiple threads */ + /** If true, system will be ran on multiple threads */ bool multi_threaded; - /* If true, system will have access to actuall world. Cannot be true at the + /** If true, system will have access to the actual world. Cannot be true at the * same time as multi_threaded. */ - bool no_staging; + bool immediate; } ecs_system_desc_t; -/* Create a system */ +/** Create a system */ FLECS_API ecs_entity_t ecs_system_init( ecs_world_t *world, const ecs_system_desc_t *desc); +/** System type, get with ecs_system_get() */ +typedef struct ecs_system_t { + ecs_header_t hdr; + + /** See ecs_system_desc_t */ + ecs_run_action_t run; + + /** See ecs_system_desc_t */ + ecs_iter_action_t action; + + /** System query */ + ecs_query_t *query; + + /** Tick source associated with system */ + ecs_entity_t tick_source; + + /** Is system multithreaded */ + bool multi_threaded; + + /** Is system ran in immediate mode */ + bool immediate; + + /** Cached system name (for perf tracing) */ + const char *name; + + /** Userdata for system */ + void *ctx; + + /** Callback language binding context */ + void *callback_ctx; + + /** Run language binding context */ + void *run_ctx; + + /** Callback to free ctx. */ + ecs_ctx_free_t ctx_free; + + /** Callback to free callback ctx. */ + ecs_ctx_free_t callback_ctx_free; + + /** Callback to free run ctx. */ + ecs_ctx_free_t run_ctx_free; + + /** Time spent on running system */ + ecs_ftime_t time_spent; + + /** Time passed since last invocation */ + ecs_ftime_t time_passed; + + /** Last frame for which the system was considered */ + int64_t last_frame; + + /* Mixins */ + flecs_poly_dtor_t dtor; +} ecs_system_t; + +/** Get system object. + * Returns the system object. Can be used to access various information about + * the system, like the query and context. + * + * @param world The world. + * @param system The system. + * @return The system object. + */ +FLECS_API +const ecs_system_t* ecs_system_get( + const ecs_world_t *world, + ecs_entity_t system); + #ifndef FLECS_LEGACY -#define ECS_SYSTEM_DEFINE(world, id, kind, ...) \ + +/** Forward declare a system. */ +#define ECS_SYSTEM_DECLARE(id) ecs_entity_t ecs_id(id) + +/** Define a forward declared system. + * + * Example: + * + * @code + * ECS_SYSTEM_DEFINE(world, Move, EcsOnUpdate, Position, Velocity); + * @endcode + */ +#define ECS_SYSTEM_DEFINE(world, id_, phase_, ...) \ { \ ecs_system_desc_t desc = {0}; \ - desc.entity.name = #id; \ - desc.entity.add[0] = kind; \ - desc.query.filter.expr = #__VA_ARGS__; \ - desc.callback = id; \ - ecs_id(id) = ecs_system_init(world, &desc); \ + ecs_entity_desc_t edesc = {0}; \ + edesc.id = ecs_id(id_);\ + edesc.name = #id_;\ + desc.entity = ecs_entity_init(world, &edesc);\ + desc.query.expr = #__VA_ARGS__; \ + desc.phase = phase_; \ + desc.callback = id_; \ + ecs_id(id_) = ecs_system_init(world, &desc); \ } \ - ecs_assert(ecs_id(id) != 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, "failed to create system %s", #id_) -#define ECS_SYSTEM(world, id, kind, ...) \ - ecs_entity_t ecs_id(id); ECS_SYSTEM_DEFINE(world, id, kind, __VA_ARGS__);\ +/** Declare & define a system. + * + * Example: + * + * @code + * ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + * @endcode + */ +#define ECS_SYSTEM(world, id, phase, ...) \ + ecs_entity_t ecs_id(id) = 0; ECS_SYSTEM_DEFINE(world, id, phase, __VA_ARGS__);\ ecs_entity_t id = ecs_id(id);\ (void)ecs_id(id);\ - (void)id; + (void)id + +/** Shorthand for creating a system with ecs_system_init(). + * + * Example: + * + * @code + * ecs_system(world, { + * .entity = ecs_entity(world, { + * .name = "MyEntity", + * .add = ecs_ids( ecs_dependson(EcsOnUpdate) ) + * }), + * .query.terms = { + * { ecs_id(Position) }, + * { ecs_id(Velocity) } + * }, + * .callback = Move + * }); + * @endcode + */ +#define ecs_system(world, ...)\ + ecs_system_init(world, &(ecs_system_desc_t) __VA_ARGS__ ) + #endif /** Run a specific system manually. @@ -9240,7 +13289,7 @@ ecs_entity_t ecs_system_init( * invoke logic on a set of entities, as manual systems are only matched to * tables at creation time or after creation time, when a new table is created. * - * Manual systems are useful to evaluate lists of prematched entities at + * Manual systems are useful to evaluate lists of pre-matched entities at * application defined times. Because none of the matching logic is evaluated * before the system is invoked, manual systems are much more efficient than * manually obtaining a list of entities and retrieving their components. @@ -9252,7 +13301,7 @@ ecs_entity_t ecs_system_init( * Any system may interrupt execution by setting the interrupted_by member in * the ecs_iter_t value. This is particularly useful for manual systems, where * the value of interrupted_by is returned by this operation. This, in - * cominbation with the param argument lets applications use manual systems + * combination with the param argument lets applications use manual systems * to lookup entities: once the entity has been found its handle is passed to * interrupted_by, which is then subsequently returned. * @@ -9266,11 +13315,11 @@ FLECS_API ecs_entity_t ecs_run( ecs_world_t *world, ecs_entity_t system, - FLECS_FLOAT delta_time, + ecs_ftime_t delta_time, void *param); -/** Same as ecs_run, but subdivides entities across number of provided stages. - * +/** Same as ecs_run(), but subdivides entities across number of provided stages. + * * @param world The world. * @param system The system to run. * @param stage_current The id of the current stage. @@ -9285,84 +13334,17 @@ ecs_entity_t ecs_run_worker( ecs_entity_t system, int32_t stage_current, int32_t stage_count, - FLECS_FLOAT delta_time, + ecs_ftime_t delta_time, void *param); -/** Run system with offset/limit and type filter. - * This operation is the same as ecs_run, but filters the entities that will be - * iterated by the system. - * - * Entities can be filtered in two ways. Offset and limit control the range of - * entities that is iterated over. The range is applied to all entities matched - * with the system, thus may cover multiple archetypes. - * - * The type filter controls which entity types the system will evaluate. Only - * types that contain all components in the type filter will be iterated over. A - * type filter is only evaluated once per table, which makes filtering cheap if - * the number of entities is large and the number of tables is small, but not as - * cheap as filtering in the system signature. +/** System module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsSystem) + * @endcode * * @param world The world. - * @param system The system to invoke. - * @param delta_time The time passed since the last system invocation. - * @param param A user-defined parameter to pass to the system. - * @return handle to last evaluated entity if system was interrupted. - */ -FLECS_API -ecs_entity_t ecs_run_w_filter( - ecs_world_t *world, - ecs_entity_t system, - FLECS_FLOAT delta_time, - int32_t offset, - int32_t limit, - void *param); - -/** Get the query object for a system. - * Systems use queries under the hood. This enables an application to get access - * to the underlying query object of a system. This can be useful when, for - * example, an application needs to enable sorting for a system. - * - * @param world The world. - * @param system The system from which to obtain the query. - * @return The query. - */ -FLECS_API -ecs_query_t* ecs_system_get_query( - const ecs_world_t *world, - ecs_entity_t system); - -/** Get system context. - * This operation returns the context pointer set for the system. If - * the provided entity is not a system, the function will return NULL. - * - * @param world The world. - * @param system The system from which to obtain the context. - * @return The context. - */ -FLECS_API -void* ecs_get_system_ctx( - const ecs_world_t *world, - ecs_entity_t system); - -/** Get system binding context. - * The binding context is a context typically used to attach any language - * binding specific data that is needed when invoking a callback that is - * implemented in another language. - * - * @param world The world. - * @param system The system from which to obtain the context. - * @return The context. */ -FLECS_API -void* ecs_get_system_binding_ctx( - const ecs_world_t *world, - ecs_entity_t system); - - -//////////////////////////////////////////////////////////////////////////////// -//// Module -//////////////////////////////////////////////////////////////////////////////// - FLECS_API void FlecsSystemImport( ecs_world_t *world); @@ -9373,449 +13355,763 @@ void FlecsSystemImport( #endif +/** @} */ + #endif #endif -#ifdef FLECS_COREDOC -#ifdef FLECS_NO_COREDOC -#error "FLECS_NO_COREDOC failed: COREDOC is required by other addons" + +#ifdef FLECS_STATS +#ifdef FLECS_NO_STATS +#error "FLECS_NO_STATS failed: STATS is required by other addons" #endif /** - * @file coredoc.h - * @brief Core doc module. + * @file addons/stats.h + * @brief Statistics addon. * - * The core doc module imports documentation and reflection data for core - * components, tags and systems. + * The stats addon tracks high resolution statistics for the world, systems and + * pipelines. The addon can be used as an API where an application calls + * functions to obtain statistics directly and as a module where statistics are + * automatically tracked. The latter is required for statistics tracking in the + * explorer. + * + * When the addon is imported as module, statistics are tracked for each frame, + * second, minute, hour, day and week with 60 datapoints per tier. */ -#ifdef FLECS_COREDOC +#ifdef FLECS_STATS -#ifndef FLECS_DOC -#define FLECS_DOC -#endif +/** + * @defgroup c_addons_stats Stats + * @ingroup c_addons + * Collection of statistics for world, queries, systems and pipelines. + * + * @{ + */ -#ifndef FLECS_META -#define FLECS_META +#ifndef FLECS_STATS_H +#define FLECS_STATS_H + +#ifndef FLECS_MODULE +#define FLECS_MODULE #endif -#ifndef FLECS_COREDOC_H -#define FLECS_COREDOC_H +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif #ifdef __cplusplus extern "C" { #endif -/* Module import */ +#define ECS_STAT_WINDOW (60) -FLECS_API -void FlecsCoreDocImport( - ecs_world_t *world); +/** Simple value that indicates current state */ +typedef struct ecs_gauge_t { + ecs_float_t avg[ECS_STAT_WINDOW]; + ecs_float_t min[ECS_STAT_WINDOW]; + ecs_float_t max[ECS_STAT_WINDOW]; +} ecs_gauge_t; -#ifdef __cplusplus -} -#endif +/** Monotonically increasing counter */ +typedef struct ecs_counter_t { + ecs_gauge_t rate; /**< Keep track of deltas too */ + double value[ECS_STAT_WINDOW]; +} ecs_counter_t; -#endif +/** Make all metrics the same size, so we can iterate over fields */ +typedef union ecs_metric_t { + ecs_gauge_t gauge; + ecs_counter_t counter; +} ecs_metric_t; -#endif +typedef struct ecs_world_stats_t { + int64_t first_; + + /* Entities */ + struct { + ecs_metric_t count; /**< Number of entities */ + ecs_metric_t not_alive_count; /**< Number of not alive (recyclable) entity ids */ + } entities; + + /* Component ids */ + struct { + ecs_metric_t tag_count; /**< Number of tag ids (ids without data) */ + ecs_metric_t component_count; /**< Number of components ids (ids with data) */ + ecs_metric_t pair_count; /**< Number of pair ids */ + ecs_metric_t type_count; /**< Number of registered types */ + ecs_metric_t create_count; /**< Number of times id has been created */ + ecs_metric_t delete_count; /**< Number of times id has been deleted */ + } components; -#endif -#ifdef FLECS_DOC -#ifdef FLECS_NO_DOC -#error "FLECS_NO_DOC failed: DOC is required by other addons" -#endif -/** - * @file doc.h - * @brief Doc module. - * - * The doc module allows for documenting entities (and thus components, systems) - * by adding brief and/or detailed descriptions as components. Documentation - * added with the doc module can be retrieved at runtime, and can be used by - * tooling such as UIs or documentation frameworks. - */ + /* Tables */ + struct { + ecs_metric_t count; /**< Number of tables */ + ecs_metric_t empty_count; /**< Number of empty tables */ + ecs_metric_t create_count; /**< Number of times table has been created */ + ecs_metric_t delete_count; /**< Number of times table has been deleted */ + } tables; -#ifdef FLECS_DOC + /* Queries & events */ + struct { + ecs_metric_t query_count; /**< Number of queries */ + ecs_metric_t observer_count; /**< Number of observers */ + ecs_metric_t system_count; /**< Number of systems */ + } queries; + + /* Commands */ + struct { + ecs_metric_t add_count; + ecs_metric_t remove_count; + ecs_metric_t delete_count; + ecs_metric_t clear_count; + ecs_metric_t set_count; + ecs_metric_t ensure_count; + ecs_metric_t modified_count; + ecs_metric_t other_count; + ecs_metric_t discard_count; + ecs_metric_t batched_entity_count; + ecs_metric_t batched_count; + } commands; -#ifndef FLECS_DOC_H -#define FLECS_DOC_H + /* Frame data */ + struct { + ecs_metric_t frame_count; /**< Number of frames processed. */ + ecs_metric_t merge_count; /**< Number of merges executed. */ + ecs_metric_t rematch_count; /**< Number of query rematches */ + ecs_metric_t pipeline_build_count; /**< Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ + ecs_metric_t systems_ran; /**< Number of systems ran. */ + ecs_metric_t observers_ran; /**< Number of times an observer was invoked. */ + ecs_metric_t event_emit_count; /**< Number of events emitted */ + } frame; -#ifndef FLECS_MODULE -#define FLECS_MODULE -#endif + /* Timing */ + struct { + ecs_metric_t world_time_raw; /**< Actual time passed since simulation start (first time progress() is called) */ + ecs_metric_t world_time; /**< Simulation time passed since simulation start. Takes into account time scaling */ + ecs_metric_t frame_time; /**< Time spent processing a frame. Smaller than world_time_total when load is not 100% */ + ecs_metric_t system_time; /**< Time spent on running systems. */ + ecs_metric_t emit_time; /**< Time spent on notifying observers. */ + ecs_metric_t merge_time; /**< Time spent on merging commands. */ + ecs_metric_t rematch_time; /**< Time spent on rematching. */ + ecs_metric_t fps; /**< Frames per second. */ + ecs_metric_t delta_time; /**< Delta_time. */ + } performance; + + struct { + /* Memory allocation data */ + ecs_metric_t alloc_count; /**< Allocs per frame */ + ecs_metric_t realloc_count; /**< Reallocs per frame */ + ecs_metric_t free_count; /**< Frees per frame */ + ecs_metric_t outstanding_alloc_count; /**< Difference between allocs & frees */ + + /* Memory allocator data */ + ecs_metric_t block_alloc_count; /**< Block allocations per frame */ + ecs_metric_t block_free_count; /**< Block frees per frame */ + ecs_metric_t block_outstanding_alloc_count; /**< Difference between allocs & frees */ + ecs_metric_t stack_alloc_count; /**< Page allocations per frame */ + ecs_metric_t stack_free_count; /**< Page frees per frame */ + ecs_metric_t stack_outstanding_alloc_count; /**< Difference between allocs & frees */ + } memory; + + /* HTTP statistics */ + struct { + ecs_metric_t request_received_count; + ecs_metric_t request_invalid_count; + ecs_metric_t request_handled_ok_count; + ecs_metric_t request_handled_error_count; + ecs_metric_t request_not_handled_count; + ecs_metric_t request_preflight_count; + ecs_metric_t send_ok_count; + ecs_metric_t send_error_count; + ecs_metric_t busy_count; + } http; + + int64_t last_; + + /** Current position in ring buffer */ + int32_t t; +} ecs_world_stats_t; -#ifdef __cplusplus -extern "C" { -#endif +/** Statistics for a single query (use ecs_query_cache_stats_get) */ +typedef struct ecs_query_stats_t { + int64_t first_; + ecs_metric_t result_count; /**< Number of query results */ + ecs_metric_t matched_table_count; /**< Number of matched tables */ + ecs_metric_t matched_entity_count; /**< Number of matched entities */ + int64_t last_; -FLECS_API extern const ecs_entity_t ecs_id(EcsDocDescription); -FLECS_API extern const ecs_entity_t EcsDocBrief; -FLECS_API extern const ecs_entity_t EcsDocDetail; -FLECS_API extern const ecs_entity_t EcsDocLink; + /** Current position in ringbuffer */ + int32_t t; +} ecs_query_stats_t; -typedef struct EcsDocDescription { - const char *value; -} EcsDocDescription; +/** Statistics for a single system (use ecs_system_stats_get()) */ +typedef struct ecs_system_stats_t { + int64_t first_; + ecs_metric_t time_spent; /**< Time spent processing a system */ + int64_t last_; -/** Add human-readable name to entity. - * Contrary to entity names, human readable names do not have to be unique and - * can contain special characters used in the query language like '*'. - * - * @param world The world. - * @param entity The entity to which to add the name. - * @param name The name to add. - */ -FLECS_API -void ecs_doc_set_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name); + bool task; /**< Is system a task */ -/** Add brief description to entity. - * - * @param world The world. - * @param entity The entity to which to add the description. - * @param description The description to add. - */ -FLECS_API -void ecs_doc_set_brief( - ecs_world_t *world, - ecs_entity_t entity, - const char *description); + ecs_query_stats_t query; +} ecs_system_stats_t; -/** Add detailed description to entity. - * +/** Statistics for sync point */ +typedef struct ecs_sync_stats_t { + int64_t first_; + ecs_metric_t time_spent; + ecs_metric_t commands_enqueued; + int64_t last_; + + int32_t system_count; + bool multi_threaded; + bool immediate; +} ecs_sync_stats_t; + +/** Statistics for all systems in a pipeline. */ +typedef struct ecs_pipeline_stats_t { + /* Allow for initializing struct with {0} */ + int8_t canary_; + + /** Vector with system ids of all systems in the pipeline. The systems are + * stored in the order they are executed. Merges are represented by a 0. */ + ecs_vec_t systems; + + /** Vector with sync point stats */ + ecs_vec_t sync_points; + + /** Current position in ring buffer */ + int32_t t; + + int32_t system_count; /**< Number of systems in pipeline */ + int32_t active_system_count; /**< Number of active systems in pipeline */ + int32_t rebuild_count; /**< Number of times pipeline has rebuilt */ +} ecs_pipeline_stats_t; + +/** Get world statistics. + * * @param world The world. - * @param entity The entity to which to add the description. - * @param description The description to add. + * @param stats Out parameter for statistics. */ FLECS_API -void ecs_doc_set_detail( - ecs_world_t *world, - ecs_entity_t entity, - const char *description); +void ecs_world_stats_get( + const ecs_world_t *world, + ecs_world_stats_t *stats); -/** Add link to external documentation to entity. - * - * @param world The world. - * @param entity The entity to which to add the link. - * @param link The link to add. - */ +/** Reduce source measurement window into single destination measurement. */ FLECS_API -void ecs_doc_set_link( - ecs_world_t *world, - ecs_entity_t entity, - const char *link); +void ecs_world_stats_reduce( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src); -/** Get human readable name from entity. - * If entity does not have an explicit human readable name, this operation will - * return the entity name. - * - * To test if an entity has a human readable name, use: - * ecs_has_pair(world, e, ecs_id(EcsDescription), EcsName); - * Or in C++: - * e.has(flecs::Name); - * - * @param world The world. - * @param entity The entity from which to get the name. - * @return The name. - */ +/** Reduce last measurement into previous measurement, restore old value. */ FLECS_API -const char* ecs_doc_get_name( +void ecs_world_stats_reduce_last( + ecs_world_stats_t *stats, + const ecs_world_stats_t *old, + int32_t count); + +/** Repeat last measurement. */ +FLECS_API +void ecs_world_stats_repeat_last( + ecs_world_stats_t *stats); + +/** Copy last measurement from source to destination. */ +FLECS_API +void ecs_world_stats_copy_last( + ecs_world_stats_t *dst, + const ecs_world_stats_t *src); + +FLECS_API +void ecs_world_stats_log( const ecs_world_t *world, - ecs_entity_t entity); + const ecs_world_stats_t *stats); -/** Get brief description from entity. - * +/** Get query statistics. + * Obtain statistics for the provided query. + * * @param world The world. - * @param entity The entity from which to get the description. - * @return The description. + * @param query The query. + * @param stats Out parameter for statistics. */ FLECS_API -const char* ecs_doc_get_brief( +void ecs_query_stats_get( const ecs_world_t *world, - ecs_entity_t entity); + const ecs_query_t *query, + ecs_query_stats_t *stats); -/** Get detailed description from entity. - * +/** Reduce source measurement window into single destination measurement. */ +FLECS_API +void ecs_query_cache_stats_reduce( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src); + +/** Reduce last measurement into previous measurement, restore old value. */ +FLECS_API +void ecs_query_cache_stats_reduce_last( + ecs_query_stats_t *stats, + const ecs_query_stats_t *old, + int32_t count); + +/** Repeat last measurement. */ +FLECS_API +void ecs_query_cache_stats_repeat_last( + ecs_query_stats_t *stats); + +/** Copy last measurement from source to destination. */ +FLECS_API +void ecs_query_cache_stats_copy_last( + ecs_query_stats_t *dst, + const ecs_query_stats_t *src); + +/** Get system statistics. + * Obtain statistics for the provided system. + * * @param world The world. - * @param entity The entity from which to get the description. - * @return The description. + * @param system The system. + * @param stats Out parameter for statistics. + * @return true if success, false if not a system. */ FLECS_API -const char* ecs_doc_get_detail( +bool ecs_system_stats_get( const ecs_world_t *world, - ecs_entity_t entity); + ecs_entity_t system, + ecs_system_stats_t *stats); -/** Get link to external documentation from entity. - * +/** Reduce source measurement window into single destination measurement */ +FLECS_API +void ecs_system_stats_reduce( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src); + +/** Reduce last measurement into previous measurement, restore old value. */ +FLECS_API +void ecs_system_stats_reduce_last( + ecs_system_stats_t *stats, + const ecs_system_stats_t *old, + int32_t count); + +/** Repeat last measurement. */ +FLECS_API +void ecs_system_stats_repeat_last( + ecs_system_stats_t *stats); + +/** Copy last measurement from source to destination. */ +FLECS_API +void ecs_system_stats_copy_last( + ecs_system_stats_t *dst, + const ecs_system_stats_t *src); + +/** Get pipeline statistics. + * Obtain statistics for the provided pipeline. + * * @param world The world. - * @param entity The entity from which to get the link. - * @return The link. + * @param pipeline The pipeline. + * @param stats Out parameter for statistics. + * @return true if success, false if not a pipeline. */ FLECS_API -const char* ecs_doc_get_link( - const ecs_world_t *world, - ecs_entity_t entity); +bool ecs_pipeline_stats_get( + ecs_world_t *world, + ecs_entity_t pipeline, + ecs_pipeline_stats_t *stats); -/* Module import */ +/** Free pipeline stats. + * + * @param stats The stats to free. + */ FLECS_API -void FlecsDocImport( - ecs_world_t *world); +void ecs_pipeline_stats_fini( + ecs_pipeline_stats_t *stats); -#ifdef __cplusplus -} -#endif +/** Reduce source measurement window into single destination measurement */ +FLECS_API +void ecs_pipeline_stats_reduce( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src); -#endif +/** Reduce last measurement into previous measurement, restore old value. */ +FLECS_API +void ecs_pipeline_stats_reduce_last( + ecs_pipeline_stats_t *stats, + const ecs_pipeline_stats_t *old, + int32_t count); -#endif +/** Repeat last measurement. */ +FLECS_API +void ecs_pipeline_stats_repeat_last( + ecs_pipeline_stats_t *stats); -#endif -#ifdef FLECS_JSON -#ifdef FLECS_NO_JSON -#error "FLECS_NO_JSON failed: JSON is required by other addons" -#endif -/** - * @file json.h - * @brief JSON parser addon. +/** Copy last measurement to destination. + * This operation copies the last measurement into the destination. It does not + * modify the cursor. * - * Parse expression strings into component values. Entity identifiers, - * enumerations and bitmasks are encoded as strings. - * - * See docs/JsonFormat.md for a description of the JSON format. + * @param dst The metrics. + * @param src The metrics to copy. */ +FLECS_API +void ecs_pipeline_stats_copy_last( + ecs_pipeline_stats_t *dst, + const ecs_pipeline_stats_t *src); -#ifdef FLECS_JSON +/** Reduce all measurements from a window into a single measurement. */ +FLECS_API +void ecs_metric_reduce( + ecs_metric_t *dst, + const ecs_metric_t *src, + int32_t t_dst, + int32_t t_src); -#ifndef FLECS_EXPR -#define FLECS_EXPR -#endif +/** Reduce last measurement into previous measurement */ +FLECS_API +void ecs_metric_reduce_last( + ecs_metric_t *m, + int32_t t, + int32_t count); -#ifndef FLECS_JSON_H -#define FLECS_JSON_H +/** Copy measurement */ +FLECS_API +void ecs_metric_copy( + ecs_metric_t *m, + int32_t dst, + int32_t src); + +FLECS_API extern ECS_COMPONENT_DECLARE(FlecsStats); /**< Flecs stats module. */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsWorldStats); /**< Component id for EcsWorldStats. */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsWorldSummary); /**< Component id for EcsWorldSummary. */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsSystemStats); /**< Component id for EcsSystemStats. */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsPipelineStats); /**< Component id for EcsPipelineStats. */ + +/* Memory statistics components */ +FLECS_API extern ECS_COMPONENT_DECLARE(ecs_entities_memory_t); /**< Component id for ecs_entities_memory_t. */ +FLECS_API extern ECS_COMPONENT_DECLARE(ecs_component_index_memory_t); /**< Component id for ecs_component_index_memory_t. */ +FLECS_API extern ECS_COMPONENT_DECLARE(ecs_query_memory_t); /**< Component id for ecs_query_memory_t. */ +FLECS_API extern ECS_COMPONENT_DECLARE(ecs_component_memory_t); /**< Component id for ecs_component_memory_t. */ +FLECS_API extern ECS_COMPONENT_DECLARE(ecs_table_memory_t); /**< Component id for ecs_table_memory_t. */ +FLECS_API extern ECS_COMPONENT_DECLARE(ecs_misc_memory_t); /**< Component id for ecs_misc_memory_t. */ +FLECS_API extern ECS_COMPONENT_DECLARE(ecs_table_histogram_t); /**< Component id for ecs_table_histogram_t. */ +FLECS_API extern ECS_COMPONENT_DECLARE(ecs_allocator_memory_t); /**< Component id for ecs_allocator_memory_t. */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsWorldMemory); /**< Component id for EcsWorldMemory. */ + +FLECS_API extern ecs_entity_t EcsPeriod1s; /**< Tag used for metrics collected in last second. */ +FLECS_API extern ecs_entity_t EcsPeriod1m; /**< Tag used for metrics collected in last minute. */ +FLECS_API extern ecs_entity_t EcsPeriod1h; /**< Tag used for metrics collected in last hour. */ +FLECS_API extern ecs_entity_t EcsPeriod1d; /**< Tag used for metrics collected in last day. */ +FLECS_API extern ecs_entity_t EcsPeriod1w; /**< Tag used for metrics collected in last week. */ + +/** Common data for statistics. */ +typedef struct { + ecs_ftime_t elapsed; + int32_t reduce_count; +} EcsStatsHeader; -#ifdef __cplusplus -extern "C" { -#endif +/** Component that stores world statistics. */ +typedef struct { + EcsStatsHeader hdr; + ecs_world_stats_t *stats; +} EcsWorldStats; + +/** Component that stores system statistics. */ +typedef struct { + EcsStatsHeader hdr; + ecs_map_t stats; +} EcsSystemStats; -/** Used with ecs_parse_json. */ -typedef struct ecs_parse_json_desc_t { - const char *name; /* Name of expression (used for logging) */ - const char *expr; /* Full expression (used for logging) */ -} ecs_parse_json_desc_t; +/** Component that stores pipeline statistics. */ +typedef struct { + EcsStatsHeader hdr; + ecs_map_t stats; +} EcsPipelineStats; -/** Parse JSON string into value. - * This operation parses a JSON expression into the provided pointer. The - * memory pointed to must be large enough to contain a value of the used type. +/** Component that stores a summary of world statistics. */ +typedef struct { + /* Time */ + double target_fps; /**< Target FPS */ + double time_scale; /**< Simulation time scale */ + double fps; /**< FPS */ + + /* Totals */ + double frame_time_total; /**< Total time spent processing a frame */ + double system_time_total; /**< Total time spent in systems */ + double merge_time_total; /**< Total time spent in merges */ + + int64_t entity_count; + int64_t table_count; + int64_t frame_count; /**< Number of frames processed */ + int64_t command_count; /**< Number of commands processed */ + int64_t merge_count; /**< Number of merges executed */ + + int64_t systems_ran_total; + int64_t observers_ran_total; + int64_t queries_ran_total; + + int32_t tag_count; /**< Number of tag (no data) ids in the world */ + int32_t component_count; /**< Number of component (data) ids in the world */ + int32_t pair_count; /**< Number of pair ids in the world */ + + /* Per frame */ + double frame_time_frame; /**< Time spent processing a frame */ + double system_time_frame; /**< Time spent in systems */ + double merge_time_frame; /**< Time spent in merges */ + + int64_t merge_count_frame; + int64_t systems_ran_frame; + int64_t observers_ran_frame; + int64_t queries_ran_frame; + int64_t command_count_frame; /**< Number of commands processed in last frame */ + + double simulation_time; /**< Time spent in simulation */ + uint32_t uptime; /**< Time since world was created */ + + /* Build info */ + ecs_build_info_t build_info; /**< Build info */ +} EcsWorldSummary; + +/** Entity memory. */ +typedef struct { + int32_t alive_count; /** Number of alive entities. */ + int32_t not_alive_count; /** Number of not alive entities. */ + ecs_size_t bytes_entity_index; /** Bytes used by entity index. */ + ecs_size_t bytes_names; /** Bytes used by names, symbols, aliases. */ + ecs_size_t bytes_doc_strings; /** Bytes used by doc strings. */ +} ecs_entities_memory_t; + +/* Component memory. */ +typedef struct { + int32_t instances; /** Total number of component instances. */ + ecs_size_t bytes_table_components; /** Bytes used by table columns. */ + ecs_size_t bytes_table_components_unused; /** Unused bytes in table columns. */ + ecs_size_t bytes_toggle_bitsets; /** Bytes used in bitsets (toggled components). */ + ecs_size_t bytes_sparse_components; /** Bytes used in component sparse sets. */ +} ecs_component_memory_t; + +/** Component index memory. */ +typedef struct { + int32_t count; /** Number of component records. */ + ecs_size_t bytes_component_record; /** Bytes used by ecs_component_record_t struct. */ + ecs_size_t bytes_table_cache; /** Bytes used by table cache. */ + ecs_size_t bytes_name_index; /** Bytes used by name index. */ + ecs_size_t bytes_ordered_children; /** Bytes used by ordered children vector. */ + ecs_size_t bytes_children_table_map; /** Bytes used by map for non-fragmenting ChildOf table lookups. */ + ecs_size_t bytes_reachable_cache; /** Bytes used by reachable cache. */ +} ecs_component_index_memory_t; + +/** Query memory. */ +typedef struct { + int32_t count; /** Number of queries. */ + int32_t cached_count; /** Number of queries with caches. */ + ecs_size_t bytes_query; /** Bytes used by ecs_query_impl_t struct. */ + ecs_size_t bytes_cache; /** Bytes used by query cache. */ + ecs_size_t bytes_group_by; /** Bytes used by query cache groups (excludes cache elements). */ + ecs_size_t bytes_order_by; /** Bytes used by table_slices. */ + ecs_size_t bytes_plan; /** Bytes used by query plan. */ + ecs_size_t bytes_terms; /** Bytes used by terms array. */ + ecs_size_t bytes_misc; /** Bytes used by remaining misc arrays. */ +} ecs_query_memory_t; + +/** Table memory histogram constants */ +#define ECS_TABLE_MEMORY_HISTOGRAM_BUCKET_COUNT 14 +#define ECS_TABLE_MEMORY_HISTOGRAM_MAX_COUNT (1 << ECS_TABLE_MEMORY_HISTOGRAM_BUCKET_COUNT) + +/** Table memory */ +typedef struct { + int32_t count; /** Total number of tables. */ + int32_t empty_count; /** Number of empty tables. */ + int32_t column_count; /** Number of table columns. */ + ecs_size_t bytes_table; /** Bytes used by ecs_table_t struct. */ + ecs_size_t bytes_type; /** Bytes used by type, columns and table records. */ + ecs_size_t bytes_entities; /** Bytes used by entity vectors. */ + ecs_size_t bytes_overrides; /** Bytes used by table overrides. */ + ecs_size_t bytes_column_map; /** Bytes used by column map. */ + ecs_size_t bytes_component_map; /** Bytes used by component map. */ + ecs_size_t bytes_dirty_state; /** Bytes used by dirty state. */ + ecs_size_t bytes_edges; /** Bytes used by table graph edges. */ +} ecs_table_memory_t; + +/** Table size histogram */ +typedef struct { + int32_t entity_counts[ECS_TABLE_MEMORY_HISTOGRAM_BUCKET_COUNT]; +} ecs_table_histogram_t; + +/** Misc memory */ +typedef struct { + ecs_size_t bytes_world; /** Memory used by world and stages */ + ecs_size_t bytes_observers; /** Memory used by observers. */ + ecs_size_t bytes_systems; /** Memory used by systems (excluding system queries). */ + ecs_size_t bytes_pipelines; /** Memory used by pipelines (excluding pipeline queries). */ + ecs_size_t bytes_table_lookup; /** Bytes used for table lookup data structures. */ + ecs_size_t bytes_component_record_lookup; /** Bytes used for component record lookup data structures. */ + ecs_size_t bytes_locked_components; /** Locked component map. */ + ecs_size_t bytes_type_info; /** Bytes used for storing type information. */ + ecs_size_t bytes_commands; /** Command queue */ + ecs_size_t bytes_rematch_monitor; /** Memory used by monitor used to track rematches */ + ecs_size_t bytes_component_ids; /** Memory used for mapping global to world-local component ids. */ + ecs_size_t bytes_reflection; /** Memory used for component reflection not tracked elsewhere. */ + ecs_size_t bytes_tree_spawner; /** Memory used for tree (prefab) spawners. */ + ecs_size_t bytes_prefab_child_indices; /** Memory used by map that stores indices for ordered prefab children */ + ecs_size_t bytes_stats; /** Memory used for statistics tracking not tracked elsewhere. */ + ecs_size_t bytes_rest; /** Memory used by REST HTTP server */ +} ecs_misc_memory_t; + +/** Allocator memory. + * Returns memory that's allocated by allocators but not in use. */ +typedef struct { + ecs_size_t bytes_graph_edge; /** Graph edge allocator. */ + ecs_size_t bytes_component_record; /** Component record allocator. */ + ecs_size_t bytes_pair_record; /** Pair record allocator. */ + ecs_size_t bytes_table_diff; /** Table diff allocator. */ + ecs_size_t bytes_sparse_chunk; /** Sparse chunk allocator. */ + ecs_size_t bytes_allocator; /** Generic allocator. */ + ecs_size_t bytes_stack_allocator; /** Stack allocator. */ + ecs_size_t bytes_cmd_entry_chunk; /** Command batching entry chunk allocator. */ + ecs_size_t bytes_query_impl; /** Query struct allocator. */ + ecs_size_t bytes_query_cache; /** Query cache struct allocator. */ + ecs_size_t bytes_misc; /** Miscalleneous allocators */ +} ecs_allocator_memory_t; + +/** Component with memory statistics. */ +typedef struct { + ecs_entities_memory_t entities; + ecs_component_memory_t components; + ecs_component_index_memory_t component_index; + ecs_query_memory_t queries; + ecs_table_memory_t tables; + ecs_table_histogram_t table_histogram; + ecs_misc_memory_t misc; + ecs_allocator_memory_t allocators; + double collection_time; /** Time spent collecting statistics. */ +} EcsWorldMemory; + +/** Memory statistics getters. */ +/** Get memory usage statistics for the entity index. * * @param world The world. - * @param ptr The pointer to the expression to parse. - * @param type The type of the expression to parse. - * @param data_out Pointer to the memory to write to. - * @param desc Configuration parameters for deserializer. - * @return Pointer to the character after the last one read, or NULL if failed. + * @return Memory statistics for the entity index. */ FLECS_API -const char* ecs_parse_json( - const ecs_world_t *world, - const char *ptr, - ecs_entity_t type, - void *data_out, - const ecs_parse_json_desc_t *desc); +ecs_entities_memory_t ecs_entity_memory_get( + const ecs_world_t *world); -/** Serialize value into JSON string. - * This operation serializes a value of the provided type to a JSON string. The - * memory pointed to must be large enough to contain a value of the used type. +/** Get memory usage statistics for single component record. * - * If count is 0, the function will serialize a single value, not wrapped in - * array brackets. If count is >= 1, the operation will serialize values to a - * a comma-separated list inside of array brackets. + * @param cr The component record. + * @param result Memory statistics for component record (out). + */ +void ecs_component_record_memory_get( + const ecs_component_record_t *cr, + ecs_component_index_memory_t *result); + +/** Get memory usage statistics for the component index. * * @param world The world. - * @param type The type of the value to serialize. - * @param data The value to serialize. - * @param count The number of elements to serialize. - * @return String with JSON expression, or NULL if failed. + * @return Memory statistics for the component index. */ FLECS_API -char* ecs_array_to_json( - const ecs_world_t *world, - ecs_entity_t type, - const void *data, - int32_t count); +ecs_component_index_memory_t ecs_component_index_memory_get( + const ecs_world_t *world); -/** Serialize value into JSON string buffer. - * Same as ecs_array_to_json_buf, but serializes to an ecs_strbuf_t instance. +/** Get memory usage statistics for single query. * - * @param world The world. - * @param type The type of the value to serialize. - * @param data The value to serialize. - * @param count The number of elements to serialize. - * @param buf_out The strbuf to append the string to. - * @return Zero if success, non-zero if failed. + * @param query The query. + * @param result Memory statistics for query (out). */ FLECS_API -int ecs_array_to_json_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *data, - int32_t count, - ecs_strbuf_t *buf_out); +void ecs_query_memory_get( + const ecs_query_t *query, + ecs_query_memory_t *result); -/** Serialize value into JSON string. - * Same as ecs_array_to_json, with count = 0. +/** Get memory usage statistics for queries. * * @param world The world. - * @param type The type of the value to serialize. - * @param data The value to serialize. - * @return String with JSON expression, or NULL if failed. + * @return Memory statistics for queries. */ FLECS_API -char* ecs_ptr_to_json( - const ecs_world_t *world, - ecs_entity_t type, - const void *data); +ecs_query_memory_t ecs_queries_memory_get( + const ecs_world_t *world); -/** Serialize value into JSON string buffer. - * Same as ecs_ptr_to_json, but serializes to an ecs_strbuf_t instance. +/** Get component memory for table. + * + * @param table The table. + * @param result The memory used by components stored in this table (out). + */ +void ecs_table_component_memory_get( + const ecs_table_t *table, + ecs_component_memory_t *result); + +/** Get memory usage statistics for components. * * @param world The world. - * @param type The type of the value to serialize. - * @param data The value to serialize. - * @param buf_out The strbuf to append the string to. - * @return Zero if success, non-zero if failed. + * @return Memory statistics for components. */ FLECS_API -int ecs_ptr_to_json_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *data, - ecs_strbuf_t *buf_out); +ecs_component_memory_t ecs_component_memory_get( + const ecs_world_t *world); -/** Serialize type info to JSON. - * This serializes type information to JSON, and can be used to store/transmit - * the structure of a (component) value. - * - * If the provided type does not have reflection data, "0" will be returned. - * +/** Get memory usage statistics for single table. + * + * @param table The table. + * @param result Memory statistics for table (out). + */ +void ecs_table_memory_get( + const ecs_table_t *table, + ecs_table_memory_t *result); + +/** Get memory usage statistics for tables. + * * @param world The world. - * @param type The type to serialize to JSON. - * @return A JSON string with the serialized type info, or NULL if failed. + * @return Memory statistics for tables. */ FLECS_API -char* ecs_type_info_to_json( - const ecs_world_t *world, - ecs_entity_t type); +ecs_table_memory_t ecs_tables_memory_get( + const ecs_world_t *world); -/** Serialize type info into JSON string buffer. - * Same as ecs_type_info_to_json, but serializes to an ecs_strbuf_t instance. +/** Get number of tables by number of entities in the table. * * @param world The world. - * @param type The type to serialize. - * @param buf_out The strbuf to append the string to. - * @return Zero if success, non-zero if failed. + * @return Number of tables by number of entities in the table. */ FLECS_API -int ecs_type_info_to_json_buf( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *buf_out); - -/** Used with ecs_iter_to_json. */ -typedef struct ecs_entity_to_json_desc_t { - bool serialize_path; /* Serialize full pathname */ - bool serialize_meta_ids; /* Serialize 'meta' ids (Name, ChildOf, etc) */ - bool serialize_label; /* Serialize doc name */ - bool serialize_brief; /* Serialize brief doc description */ - bool serialize_link; /* Serialize doc link (URL) */ - bool serialize_id_labels; /* Serialize labels of (component) ids */ - bool serialize_base; /* Serialize base components */ - bool serialize_private; /* Serialize private components */ - bool serialize_hidden; /* Serialize ids hidden by override */ - bool serialize_values; /* Serialize component values */ - bool serialize_type_info; /* Serialize type info (requires serialize_values) */ -} ecs_entity_to_json_desc_t; - -#define ECS_ENTITY_TO_JSON_INIT (ecs_entity_to_json_desc_t) {\ - true, false, false, false, false, false, true, false, false, false, false } +ecs_table_histogram_t ecs_table_histogram_get( + const ecs_world_t *world); -/** Serialize entity into JSON string. - * This creates a JSON object with the entity's (path) name, which components - * and tags the entity has, and the component values. - * - * The operation may fail if the entity contains components with invalid values. +/** Get memory usage statistics for commands. * * @param world The world. - * @param entity The entity to serialize to JSON. - * @return A JSON string with the serialized entity data, or NULL if failed. + * @return Memory statistics for commands. */ FLECS_API -char* ecs_entity_to_json( - const ecs_world_t *world, - ecs_entity_t entity, - const ecs_entity_to_json_desc_t *desc); +ecs_misc_memory_t ecs_misc_memory_get( + const ecs_world_t *world); -/** Serialize entity into JSON string buffer. - * Same as ecs_entity_to_json, but serializes to an ecs_strbuf_t instance. +/** Get memory usage statistics for allocators. * * @param world The world. - * @param entity The entity to serialize. - * @param buf_out The strbuf to append the string to. - * @return Zero if success, non-zero if failed. + * @return Memory statistics for allocators. */ FLECS_API -int ecs_entity_to_json_buf( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_strbuf_t *buf_out, - const ecs_entity_to_json_desc_t *desc); - -/** Used with ecs_iter_to_json. */ -typedef struct ecs_iter_to_json_desc_t { - bool serialize_term_ids; /* Include term (query) component ids */ - bool serialize_ids; /* Include actual (matched) component ids */ - bool serialize_subjects; /* Include subjects */ - bool serialize_variables; /* Include variables */ - bool serialize_is_set; /* Include is_set (for optional terms) */ - bool serialize_values; /* Include component values */ - bool serialize_entities; /* Include entities (for This terms) */ - bool serialize_entity_labels; /* Include doc name for entities */ - bool serialize_variable_labels; /* Include doc name for variables */ - bool measure_eval_duration; /* Include evaluation duration */ - bool serialize_type_info; /* Include type information */ -} ecs_iter_to_json_desc_t; - -#define ECS_ITER_TO_JSON_INIT (ecs_iter_to_json_desc_t) {\ - true, true, true, true, true, true, true, false, false, false, false } +ecs_allocator_memory_t ecs_allocator_memory_get( + const ecs_world_t *world); -/** Serialize iterator into JSON string. - * This operation will iterate the contents of the iterator and serialize them - * to JSON. The function acccepts iterators from any source. +/** Get total memory used by world. * * @param world The world. - * @param iter The iterator to serialize to JSON. - * @return A JSON string with the serialized iterator data, or NULL if failed. */ FLECS_API -char* ecs_iter_to_json( - const ecs_world_t *world, - ecs_iter_t *iter, - const ecs_iter_to_json_desc_t *desc); +ecs_size_t ecs_memory_get( + const ecs_world_t *world); -/** Serialize iterator into JSON string buffer. - * Same as ecs_iter_to_json, but serializes to an ecs_strbuf_t instance. + +/** Stats module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsStats) + * @endcode * * @param world The world. - * @param iter The iterator to serialize. - * @param buf_out The strbuf to append the string to. - * @return Zero if success, non-zero if failed. */ FLECS_API -int ecs_iter_to_json_buf( - const ecs_world_t *world, - ecs_iter_t *iter, - ecs_strbuf_t *buf_out, - const ecs_iter_to_json_desc_t *desc); +void FlecsStatsImport( + ecs_world_t *world); #ifdef __cplusplus } @@ -9823,186 +14119,191 @@ int ecs_iter_to_json_buf( #endif -#endif +/** @} */ #endif -#if defined(FLECS_EXPR) || defined(FLECS_META_C) -#define FLECS_META + #endif -#ifdef FLECS_UNITS -#ifdef FLECS_NO_UNITS -#error "FLECS_NO_UNITS failed: UNITS is required by other addons" + +#ifdef FLECS_METRICS +#ifdef FLECS_NO_METRICS +#error "FLECS_NO_METRICS failed: METRICS is required by other addons" #endif /** - * @file units.h - * @brief Units module. + * @file addons/metrics.h + * @brief Metrics module. * - * Builtin standard units. The units addon is not imported by default, even if - * the addon is included in the build. To import the module, do: - * - * In C: - * ECS_IMPORT(world, FlecsUnits); - * - * In C++: - * world.import(); - * - * As a result this module behaves just like an application-defined module, - * which means that the ids generated for the entities inside the module are not - * fixed, and depend on the order in which the module is imported. + * The metrics module extracts metrics from components and makes them available + * through a unified component interface. */ -#ifdef FLECS_UNITS +#ifdef FLECS_METRICS -#ifndef FLECS_MODULE -#define FLECS_MODULE -#endif +/** + * @defgroup c_addons_metrics Metrics + * @ingroup c_addons + * Collect user-defined metrics from ECS data. + * + * @{ + */ + +#ifndef FLECS_METRICS_H +#define FLECS_METRICS_H #ifndef FLECS_META #define FLECS_META #endif -#ifndef FLECS_UNITS_H -#define FLECS_UNITS_H +#ifndef FLECS_UNITS +#define FLECS_UNITS +#endif + +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE +#endif #ifdef __cplusplus extern "C" { #endif -//////////////////////////////////////////////////////////////////////////////// -//// Unit prefixes -//////////////////////////////////////////////////////////////////////////////// +/** Flecs metrics module. */ +FLECS_API extern ECS_COMPONENT_DECLARE(FlecsMetrics); -FLECS_API extern ECS_DECLARE(EcsUnitPrefixes); /* Parent scope for prefixes */ - -FLECS_API extern ECS_DECLARE(EcsYocto); -FLECS_API extern ECS_DECLARE(EcsZepto); -FLECS_API extern ECS_DECLARE(EcsAtto); -FLECS_API extern ECS_DECLARE(EcsFemto); -FLECS_API extern ECS_DECLARE(EcsPico); -FLECS_API extern ECS_DECLARE(EcsNano); -FLECS_API extern ECS_DECLARE(EcsMicro); -FLECS_API extern ECS_DECLARE(EcsMilli); -FLECS_API extern ECS_DECLARE(EcsCenti); -FLECS_API extern ECS_DECLARE(EcsDeci); -FLECS_API extern ECS_DECLARE(EcsDeca); -FLECS_API extern ECS_DECLARE(EcsHecto); -FLECS_API extern ECS_DECLARE(EcsKilo); -FLECS_API extern ECS_DECLARE(EcsMega); -FLECS_API extern ECS_DECLARE(EcsGiga); -FLECS_API extern ECS_DECLARE(EcsTera); -FLECS_API extern ECS_DECLARE(EcsPeta); -FLECS_API extern ECS_DECLARE(EcsExa); -FLECS_API extern ECS_DECLARE(EcsZetta); -FLECS_API extern ECS_DECLARE(EcsYotta); - -FLECS_API extern ECS_DECLARE(EcsKibi); -FLECS_API extern ECS_DECLARE(EcsMebi); -FLECS_API extern ECS_DECLARE(EcsGibi); -FLECS_API extern ECS_DECLARE(EcsTebi); -FLECS_API extern ECS_DECLARE(EcsPebi); -FLECS_API extern ECS_DECLARE(EcsExbi); -FLECS_API extern ECS_DECLARE(EcsZebi); -FLECS_API extern ECS_DECLARE(EcsYobi); +/** Tag added to metrics, and used as first element of metric kind pair. */ +FLECS_API extern ECS_TAG_DECLARE(EcsMetric); +/** Metric that has monotonically increasing value. */ +FLECS_API extern ECS_TAG_DECLARE(EcsCounter); -//////////////////////////////////////////////////////////////////////////////// -//// Units & quantities -//////////////////////////////////////////////////////////////////////////////// +/** Counter metric that is auto-incremented by source value. */ +FLECS_API extern ECS_TAG_DECLARE(EcsCounterIncrement); -FLECS_API extern ECS_DECLARE(EcsDuration); -FLECS_API extern ECS_DECLARE(EcsPicoSeconds); -FLECS_API extern ECS_DECLARE(EcsNanoSeconds); -FLECS_API extern ECS_DECLARE(EcsMicroSeconds); -FLECS_API extern ECS_DECLARE(EcsMilliSeconds); -FLECS_API extern ECS_DECLARE(EcsSeconds); -FLECS_API extern ECS_DECLARE(EcsMinutes); -FLECS_API extern ECS_DECLARE(EcsHours); -FLECS_API extern ECS_DECLARE(EcsDays); - -FLECS_API extern ECS_DECLARE(EcsTime); -FLECS_API extern ECS_DECLARE(EcsDate); - -FLECS_API extern ECS_DECLARE(EcsMass); -FLECS_API extern ECS_DECLARE(EcsGrams); -FLECS_API extern ECS_DECLARE(EcsKiloGrams); - -FLECS_API extern ECS_DECLARE(EcsElectricCurrent); -FLECS_API extern ECS_DECLARE(EcsAmpere); - -FLECS_API extern ECS_DECLARE(EcsAmount); -FLECS_API extern ECS_DECLARE(EcsMole); - -FLECS_API extern ECS_DECLARE(EcsLuminousIntensity); -FLECS_API extern ECS_DECLARE(EcsCandela); - -FLECS_API extern ECS_DECLARE(EcsForce); -FLECS_API extern ECS_DECLARE(EcsNewton); - -FLECS_API extern ECS_DECLARE(EcsLength); -FLECS_API extern ECS_DECLARE(EcsMeters); -FLECS_API extern ECS_DECLARE(EcsPicoMeters); -FLECS_API extern ECS_DECLARE(EcsNanoMeters); -FLECS_API extern ECS_DECLARE(EcsMicroMeters); -FLECS_API extern ECS_DECLARE(EcsMilliMeters); -FLECS_API extern ECS_DECLARE(EcsCentiMeters); -FLECS_API extern ECS_DECLARE(EcsKiloMeters); -FLECS_API extern ECS_DECLARE(EcsMiles); - -FLECS_API extern ECS_DECLARE(EcsPressure); -FLECS_API extern ECS_DECLARE(EcsPascal); -FLECS_API extern ECS_DECLARE(EcsBar); - -FLECS_API extern ECS_DECLARE(EcsSpeed); -FLECS_API extern ECS_DECLARE(EcsMetersPerSecond); -FLECS_API extern ECS_DECLARE(EcsKiloMetersPerSecond); -FLECS_API extern ECS_DECLARE(EcsKiloMetersPerHour); -FLECS_API extern ECS_DECLARE(EcsMilesPerHour); - -FLECS_API extern ECS_DECLARE(EcsTemperature); -FLECS_API extern ECS_DECLARE(EcsKelvin); -FLECS_API extern ECS_DECLARE(EcsCelsius); -FLECS_API extern ECS_DECLARE(EcsFahrenheit); - -FLECS_API extern ECS_DECLARE(EcsAcceleration); - -FLECS_API extern ECS_DECLARE(EcsData); -FLECS_API extern ECS_DECLARE(EcsBits); -FLECS_API extern ECS_DECLARE(EcsKiloBits); -FLECS_API extern ECS_DECLARE(EcsMegaBits); -FLECS_API extern ECS_DECLARE(EcsGigaBits); -FLECS_API extern ECS_DECLARE(EcsBytes); -FLECS_API extern ECS_DECLARE(EcsKiloBytes); -FLECS_API extern ECS_DECLARE(EcsMegaBytes); -FLECS_API extern ECS_DECLARE(EcsGigaBytes); -FLECS_API extern ECS_DECLARE(EcsKibiBytes); -FLECS_API extern ECS_DECLARE(EcsMebiBytes); -FLECS_API extern ECS_DECLARE(EcsGibiBytes); - -FLECS_API extern ECS_DECLARE(EcsDataRate); -FLECS_API extern ECS_DECLARE(EcsBitsPerSecond); -FLECS_API extern ECS_DECLARE(EcsKiloBitsPerSecond); -FLECS_API extern ECS_DECLARE(EcsMegaBitsPerSecond); -FLECS_API extern ECS_DECLARE(EcsGigaBitsPerSecond); -FLECS_API extern ECS_DECLARE(EcsBytesPerSecond); -FLECS_API extern ECS_DECLARE(EcsKiloBytesPerSecond); -FLECS_API extern ECS_DECLARE(EcsMegaBytesPerSecond); -FLECS_API extern ECS_DECLARE(EcsGigaBytesPerSecond); - -FLECS_API extern ECS_DECLARE(EcsPercentage); - -FLECS_API extern ECS_DECLARE(EcsAngle); -FLECS_API extern ECS_DECLARE(EcsRadians); -FLECS_API extern ECS_DECLARE(EcsDegrees); - -FLECS_API extern ECS_DECLARE(EcsBel); -FLECS_API extern ECS_DECLARE(EcsDeciBel); +/** Counter metric that counts the number of entities with an id. */ +FLECS_API extern ECS_TAG_DECLARE(EcsCounterId); -//////////////////////////////////////////////////////////////////////////////// -//// Module -//////////////////////////////////////////////////////////////////////////////// +/** Metric that represents current value. */ +FLECS_API extern ECS_TAG_DECLARE(EcsGauge); + +/** Tag added to metric instances. */ +FLECS_API extern ECS_TAG_DECLARE(EcsMetricInstance); + +/** Component with metric instance value. */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsMetricValue); + +/** Component with entity source of metric instance. */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsMetricSource); + +/** Component that stores metric value. */ +typedef struct EcsMetricValue { + double value; +} EcsMetricValue; + +/** Component that stores metric source. */ +typedef struct EcsMetricSource { + ecs_entity_t entity; +} EcsMetricSource; + +/** Used with ecs_metric_init to create metric. */ +typedef struct ecs_metric_desc_t { + int32_t _canary; + + /** Entity associated with metric */ + ecs_entity_t entity; + + /** Entity associated with member that stores metric value. Must not be set + * at the same time as id. Cannot be combined with EcsCounterId. */ + ecs_entity_t member; + + /* Member dot expression. Can be used instead of member and supports nested + * members. Must be set together with id and should not be set at the same + * time as member. */ + const char *dotmember; + + /** Tracks whether entities have the specified component id. Must not be set + * at the same time as member. */ + ecs_id_t id; + + /** If id is a (R, *) wildcard and relationship R has the OneOf property, + * setting this value to true will track individual targets. + * If the kind is EcsCountId and the id is a (R, *) wildcard, this value + * will create a metric per target. */ + bool targets; + + /** Must be EcsGauge, EcsCounter, EcsCounterIncrement or EcsCounterId */ + ecs_entity_t kind; + + /** Description of metric. Will only be set if FLECS_DOC addon is enabled */ + const char *brief; +} ecs_metric_desc_t; + +/** Create a new metric. + * Metrics are entities that store values measured from a range of different + * properties in the ECS storage. Metrics provide a single unified interface to + * discovering and reading these values, which can be useful for monitoring + * utilities, or for debugging. + * + * Examples of properties that can be measured by metrics are: + * - Component member values + * - How long an entity has had a specific component + * - How long an entity has had a specific target for a relationship + * - How many entities have a specific component + * + * Metrics can either be created as a "gauge" or "counter". A gauge is a metric + * that represents the value of something at a specific point in time, for + * example "velocity". A counter metric represents a value that is monotonically + * increasing, for example "miles driven". + * + * There are three different kinds of counter metric kinds: + * - EcsCounter + * When combined with a member, this will store the actual value of the member + * in the metric. This is useful for values that are already counters, such as + * a MilesDriven component. + * This kind creates a metric per entity that has the member/id. + * + * - EcsCounterIncrement + * When combined with a member, this will increment the value of the metric by + * the value of the member * delta_time. This is useful for values that are + * not counters, such as a Velocity component. + * This kind creates a metric per entity that has the member. + * + * - EcsCounterId + * This metric kind will count the number of entities with a specific + * (component) id. This kind creates a single metric instance for regular ids, + * and a metric instance per target for wildcard ids when targets is set. + * + * @param world The world. + * @param desc Metric description. + * @return The metric entity. + */ +FLECS_API +ecs_entity_t ecs_metric_init( + ecs_world_t *world, + const ecs_metric_desc_t *desc); + +/** Shorthand for creating a metric with ecs_metric_init(). + * + * Example: + * + * @code + * ecs_metric(world, { + * .member = ecs_lookup(world, "Position.x") + * .kind = EcsGauge + * }); + * @endcode + */ +#define ecs_metric(world, ...)\ + ecs_metric_init(world, &(ecs_metric_desc_t) __VA_ARGS__ ) +/** Metrics module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsMetrics) + * @endcode + * + * @param world The world. + */ FLECS_API -void FlecsUnitsImport( +void FlecsMetricsImport( ecs_world_t *world); #ifdef __cplusplus @@ -10011,881 +14312,1109 @@ void FlecsUnitsImport( #endif +/** @} */ + #endif #endif -#ifdef FLECS_META -#ifdef FLECS_NO_META -#error "FLECS_NO_META failed: META is required by other addons" + +#ifdef FLECS_ALERTS +#ifdef FLECS_NO_ALERTS +#error "FLECS_NO_ALERTS failed: ALERTS is required by other addons" #endif /** - * @file meta.h - * @brief Meta addon. - * - * The meta addon enables reflecting on component data. Types are stored as - * entities, with components that store the reflection data. A type has at least - * two components: - * - * - EcsComponent: core component, contains size & alignment - * - EcsMetaType: component that indicates what kind of type the entity is - * - * Additionally the type may have an additional component that contains the - * reflection data for the type. For example, structs have these components: - * - * - EcsComponent - * - EcsMetaType - * - EcsStruct - * - * Structs can be populated by adding child entities with the EcsMember - * component. Adding a child with a Member component to an entity will - * automatically add the EcsStruct component to the parent. - * - * Enums/bitmasks can be populated by adding child entities with the Constant - * tag. By default constants are automatically assigned values when they are - * added to the enum/bitmask. The parent entity must have the EcsEnum or - * EcsBitmask component before adding the constants. - * - * To create enum constants with a manual value, set (Constant, i32) to the - * desired value. To create bitmask constants with a manual value, set - * (Constant, u32) to the desired value. Constants with manual values should not - * conflict with other constants. - * - * The _init APIs are convenience wrappers around creating the entities and - * components for the types. - * - * When a type is created it automatically receives the EcsComponent and - * EcsMetaType components. The former means that the resulting type can be - * used as a regular component: - * - * // Create Position type - * ecs_entity_t pos = ecs_struct_init(world, &(ecs_struct_desc_t) { - * .entity.name = "Position", - * .members = { - * {"x", ecs_id(ecs_f32_t)}, - * {"y", ecs_id(ecs_f32_t)} - * } - * }); + * @file addons/alerts.h + * @brief Alerts module. * - * // Create entity with Position component - * ecs_entity_t e = ecs_new_w_id(world, pos); - * - * Type entities do not have to be named. + * The alerts module enables applications to register alerts for when certain + * conditions are met. Alerts are registered as queries, and automatically + * become active when entities match the alert query. */ -#ifdef FLECS_META +#ifdef FLECS_ALERTS + +/** + * @defgroup c_addons_alerts Alerts + * @ingroup c_addons + * Create alerts from monitoring queries. + * + * @{ + */ -#include +#ifndef FLECS_ALERTS_H +#define FLECS_ALERTS_H -#ifndef FLECS_MODULE -#define FLECS_MODULE +#ifndef FLECS_PIPELINE +#define FLECS_PIPELINE #endif -#ifndef FLECS_META_H -#define FLECS_META_H - #ifdef __cplusplus extern "C" { #endif -#define ECS_MEMBER_DESC_CACHE_SIZE (32) +#define ECS_ALERT_MAX_SEVERITY_FILTERS (4) + +/** Module id. */ +FLECS_API extern ECS_COMPONENT_DECLARE(FlecsAlerts); + +/* Module components */ + +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlert); /**< Component added to alert, and used as first element of alert severity pair. */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlertInstance); /**< Component added to alert instance. */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlertsActive); /**< Component added to alert source which tracks how many active alerts there are. */ +FLECS_API extern ECS_COMPONENT_DECLARE(EcsAlertTimeout); /**< Component added to alert which tracks how long an alert has been inactive. */ + +/* Alert severity tags */ +FLECS_API extern ECS_TAG_DECLARE(EcsAlertInfo); /**< Info alert severity. */ +FLECS_API extern ECS_TAG_DECLARE(EcsAlertWarning); /**< Warning alert severity. */ +FLECS_API extern ECS_TAG_DECLARE(EcsAlertError); /**< Error alert severity. */ +FLECS_API extern ECS_TAG_DECLARE(EcsAlertCritical); /**< Critical alert severity. */ + +/** Component added to alert instance. */ +typedef struct EcsAlertInstance { + char *message; /**< Generated alert message */ +} EcsAlertInstance; + +/** Map with active alerts for entity. */ +typedef struct EcsAlertsActive { + int32_t info_count; /**< Number of alerts for source with info severity */ + int32_t warning_count; /**< Number of alerts for source with warning severity */ + int32_t error_count; /**< Number of alerts for source with error severity */ + ecs_map_t alerts; +} EcsAlertsActive; + +/** Alert severity filter. + * A severity filter can adjust the severity of an alert based on whether an + * entity in the alert query has a specific component. For example, a filter + * could check if an entity has the "Production" tag, and increase the default + * severity of an alert from Warning to Error. + */ +typedef struct ecs_alert_severity_filter_t { + ecs_entity_t severity; /* Severity kind */ + ecs_id_t with; /* Component to match */ + const char *var; /* Variable to match component on. Do not include the + * '$' character. Leave to NULL for $this. */ + int32_t _var_index; /* Index of variable in filter (do not set) */ +} ecs_alert_severity_filter_t; + +/** Alert descriptor, used with ecs_alert_init(). */ +typedef struct ecs_alert_desc_t { + int32_t _canary; -/** Primitive type definitions. - * These typedefs allow the builtin primitives to be used as regular components: - * ecs_set(world, e, ecs_i32_t, {10}); - * - * Or a more useful example (create an enum constant with a manual value): - * ecs_set_pair_object(world, e, EcsConstant, ecs_i32_t, {10}); - */ -typedef bool ecs_bool_t; -typedef char ecs_char_t; -typedef unsigned char ecs_byte_t; -typedef uint8_t ecs_u8_t; -typedef uint16_t ecs_u16_t; -typedef uint32_t ecs_u32_t; -typedef uint64_t ecs_u64_t; -typedef uintptr_t ecs_uptr_t; -typedef int8_t ecs_i8_t; -typedef int16_t ecs_i16_t; -typedef int32_t ecs_i32_t; -typedef int64_t ecs_i64_t; -typedef intptr_t ecs_iptr_t; -typedef float ecs_f32_t; -typedef double ecs_f64_t; -typedef char* ecs_string_t; - -/** Meta module component ids */ -FLECS_API extern const ecs_entity_t ecs_id(EcsMetaType); -FLECS_API extern const ecs_entity_t ecs_id(EcsMetaTypeSerialized); -FLECS_API extern const ecs_entity_t ecs_id(EcsPrimitive); -FLECS_API extern const ecs_entity_t ecs_id(EcsEnum); -FLECS_API extern const ecs_entity_t ecs_id(EcsBitmask); -FLECS_API extern const ecs_entity_t ecs_id(EcsMember); -FLECS_API extern const ecs_entity_t ecs_id(EcsStruct); -FLECS_API extern const ecs_entity_t ecs_id(EcsArray); -FLECS_API extern const ecs_entity_t ecs_id(EcsVector); -FLECS_API extern const ecs_entity_t ecs_id(EcsUnit); -FLECS_API extern const ecs_entity_t ecs_id(EcsUnitPrefix); -FLECS_API extern const ecs_entity_t EcsConstant; -FLECS_API extern const ecs_entity_t EcsQuantity; - -/** Primitive type component ids */ -FLECS_API extern const ecs_entity_t ecs_id(ecs_bool_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_char_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_byte_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_u8_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_u16_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_u32_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_u64_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_uptr_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_i8_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_i16_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_i32_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_i64_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_iptr_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_f32_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_f64_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_string_t); -FLECS_API extern const ecs_entity_t ecs_id(ecs_entity_t); - -/** Type kinds supported by reflection type system */ -typedef enum ecs_type_kind_t { - EcsPrimitiveType, - EcsBitmaskType, - EcsEnumType, - EcsStructType, - EcsArrayType, - EcsVectorType, - EcsTypeKindLast = EcsVectorType -} ecs_type_kind_t; + /** Entity associated with alert */ + ecs_entity_t entity; -/** Component that is automatically added to every type with the right kind. */ -typedef struct EcsMetaType { - ecs_type_kind_t kind; - bool existing; /* Did the type exist or is it populated from reflection */ - bool partial; /* Is the reflection data a partial type description */ - ecs_size_t size; /* Computed size */ - ecs_size_t alignment; /* Computed alignment */ -} EcsMetaType; + /** Alert query. An alert will be created for each entity that matches the + * specified query. The query must have at least one term that uses the + * $this variable (default). */ + ecs_query_desc_t query; -typedef enum ecs_primitive_kind_t { - EcsBool = 1, - EcsChar, - EcsByte, - EcsU8, - EcsU16, - EcsU32, - EcsU64, - EcsI8, - EcsI16, - EcsI32, - EcsI64, - EcsF32, - EcsF64, - EcsUPtr, - EcsIPtr, - EcsString, - EcsEntity, - EcsPrimitiveKindLast = EcsEntity -} ecs_primitive_kind_t; + /** Template for alert message. This string is used to generate the alert + * message and may refer to variables in the query result. The format for + * the template expressions is as specified by ecs_script_string_interpolate(). + * + * Examples: + * + * "$this has Position but not Velocity" + * "$this has a parent entity $parent without Position" + */ + const char *message; -typedef struct EcsPrimitive { - ecs_primitive_kind_t kind; -} EcsPrimitive; + /** User friendly name. Will only be set if FLECS_DOC addon is enabled. */ + const char *doc_name; -typedef struct EcsMember { - ecs_entity_t type; - int32_t count; - ecs_entity_t unit; - int32_t offset; -} EcsMember; + /** Description of alert. Will only be set if FLECS_DOC addon is enabled */ + const char *brief; -/* Element type of members vector in EcsStruct */ -typedef struct ecs_member_t { - /* Must be set when used with ecs_struct_desc_t */ - const char *name; - ecs_entity_t type; + /** Metric kind. Must be EcsAlertInfo, EcsAlertWarning, EcsAlertError or + * EcsAlertCritical. Defaults to EcsAlertError. */ + ecs_entity_t severity; - /* May be set when used with ecs_struct_desc_t */ - int32_t count; - int32_t offset; + /** Severity filters can be used to assign different severities to the same + * alert. This prevents having to create multiple alerts, and allows + * entities to transition between severities without resetting the + * alert duration (optional). */ + ecs_alert_severity_filter_t severity_filters[ECS_ALERT_MAX_SEVERITY_FILTERS]; - /* May be set when used with ecs_struct_desc_t, will be auto-populated if - * type entity is also a unit */ - ecs_entity_t unit; + /** The retain period specifies how long an alert must be inactive before it + * is cleared. This makes it easier to track noisy alerts. While an alert is + * inactive its duration won't increase. + * When the retain period is 0, the alert will clear immediately after it no + * longer matches the alert query. */ + ecs_ftime_t retain_period; - /* Should not be set by ecs_struct_desc_t */ - ecs_size_t size; + /** Alert when member value is out of range. Uses the warning/error ranges + * assigned to the member in the MemberRanges component (optional). */ ecs_entity_t member; -} ecs_member_t; - -typedef struct EcsStruct { - /* Populated from child entities with Member component */ - ecs_vector_t *members; /* vector */ -} EcsStruct; - -typedef struct ecs_enum_constant_t { - /* Must be set when used with ecs_enum_desc_t */ - const char *name; - - /* May be set when used with ecs_enum_desc_t */ - int32_t value; - - /* Should not be set by ecs_enum_desc_t */ - ecs_entity_t constant; -} ecs_enum_constant_t; - -typedef struct EcsEnum { - /* Populated from child entities with Constant component */ - ecs_map_t *constants; /* map */ -} EcsEnum; - -typedef struct ecs_bitmask_constant_t { - /* Must be set when used with ecs_bitmask_desc_t */ - const char *name; - - /* May be set when used with ecs_bitmask_desc_t */ - ecs_flags32_t value; - /* Should not be set by ecs_bitmask_desc_t */ - ecs_entity_t constant; -} ecs_bitmask_constant_t; + /** (Component) id of member to monitor. If left to 0 this will be set to + * the parent entity of the member (optional). */ + ecs_id_t id; -typedef struct EcsBitmask { - /* Populated from child entities with Constant component */ - ecs_map_t *constants; /* map */ -} EcsBitmask; + /** Variable from which to fetch the member (optional). When left to NULL + * 'id' will be obtained from $this. */ + const char *var; +} ecs_alert_desc_t; -typedef struct EcsArray { - ecs_entity_t type; - int32_t count; -} EcsArray; +/** Create a new alert. + * An alert is a query that is evaluated periodically and creates alert + * instances for each entity that matches the query. Alerts can be used to + * automate detection of errors in an application. + * + * Alerts are automatically cleared when a query is no longer true for an alert + * instance. At most one alert instance will be created per matched entity. + * + * Alert instances have three components: + * - AlertInstance: contains the alert message for the instance + * - MetricSource: contains the entity that triggered the alert + * - MetricValue: contains how long the alert has been active + * + * Alerts reuse components from the metrics addon so that alert instances can be + * tracked and discovered as metrics. Just like metrics, alert instances are + * created as children of the alert. + * + * When an entity has active alerts, it will have the EcsAlertsActive component + * which contains a map with active alerts for the entity. This component + * will be automatically removed once all alerts are cleared for the entity. + * + * @param world The world. + * @param desc Alert description. + * @return The alert entity. + */ +FLECS_API +ecs_entity_t ecs_alert_init( + ecs_world_t *world, + const ecs_alert_desc_t *desc); -typedef struct EcsVector { - ecs_entity_t type; -} EcsVector; +/** Create a new alert. + * @see ecs_alert_init() + */ +#define ecs_alert(world, ...)\ + ecs_alert_init(world, &(ecs_alert_desc_t)__VA_ARGS__) +/** Return number of active alerts for entity. + * When a valid alert entity is specified for the alert parameter, the operation + * will return whether the specified alert is active for the entity. When no + * alert is specified, the operation will return the total number of active + * alerts for the entity. + * + * @param world The world. + * @param entity The entity. + * @param alert The alert to test for (optional). + * @return The number of active alerts for the entity. + */ +FLECS_API +int32_t ecs_get_alert_count( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert); -/** Units */ +/** Return alert instance for specified alert. + * This operation returns the alert instance for the specified alert. If the + * alert is not active for the entity, the operation will return 0. + * + * @param world The world. + * @param entity The entity. + * @param alert The alert to test for. + * @return The alert instance for the specified alert. + */ +FLECS_API +ecs_entity_t ecs_get_alert( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t alert); -/* Helper type to describe translation between two units. Note that this - * is not intended as a generic approach to unit conversions (e.g. from celsius - * to fahrenheit) but to translate between units that derive from the same base - * (e.g. meters to kilometers). +/** Alert module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsAlerts) + * @endcode * - * Note that power is applied to the factor. When describing a translation of - * 1000, either use {factor = 1000, power = 1} or {factor = 1, power = 3}. */ -typedef struct ecs_unit_translation_t { - int32_t factor; /* Factor to apply (e.g. "1000", "1000000", "1024") */ - int32_t power; /* Power to apply to factor (e.g. "1", "3", "-9") */ -} ecs_unit_translation_t; - -typedef struct EcsUnit { - char *symbol; - ecs_entity_t prefix; /* Order of magnitude prefix relative to derived */ - ecs_entity_t base; /* Base unit (e.g. "meters") */ - ecs_entity_t over; /* Over unit (e.g. "per second") */ - ecs_unit_translation_t translation; /* Translation for derived unit */ -} EcsUnit; + * @param world The world. + */ +FLECS_API +void FlecsAlertsImport( + ecs_world_t *world); -typedef struct EcsUnitPrefix { - char *symbol; /* Symbol of prefix (e.g. "K", "M", "Ki") */ - ecs_unit_translation_t translation; /* Translation of prefix */ -} EcsUnitPrefix; +#ifdef __cplusplus +} +#endif +#endif -/** Serializer utilities */ +/** @} */ -typedef enum ecs_meta_type_op_kind_t { - EcsOpArray, - EcsOpVector, - EcsOpPush, - EcsOpPop, +#endif - EcsOpScope, /* Marks last constant that can open/close a scope */ +#endif - EcsOpEnum, - EcsOpBitmask, +#ifdef FLECS_JSON +#ifdef FLECS_NO_JSON +#error "FLECS_NO_JSON failed: JSON is required by other addons" +#endif +/** + * @file addons/json.h + * @brief JSON parser addon. + * + * Parse expression strings into component values. Entity identifiers, + * enumerations and bitmasks are encoded as strings. + * + * See docs/FlecsRemoteApi.md for a description of the JSON format. + */ - EcsOpPrimitive, /* Marks first constant that's a primitive */ +#ifdef FLECS_JSON - EcsOpBool, - EcsOpChar, - EcsOpByte, - EcsOpU8, - EcsOpU16, - EcsOpU32, - EcsOpU64, - EcsOpI8, - EcsOpI16, - EcsOpI32, - EcsOpI64, - EcsOpF32, - EcsOpF64, - EcsOpUPtr, - EcsOpIPtr, - EcsOpString, - EcsOpEntity -} ecs_meta_type_op_kind_t; - -typedef struct ecs_meta_type_op_t { - ecs_meta_type_op_kind_t kind; - ecs_size_t offset; /* Offset of current field */ - int32_t count; - const char *name; /* Name of value (only used for struct members) */ - int32_t op_count; /* Number of operations until next field or end */ - ecs_size_t size; /* Size of type of operation */ - ecs_entity_t type; - ecs_entity_t unit; - ecs_hashmap_t *members; /* string -> member index (structs only) */ -} ecs_meta_type_op_t; +#ifndef FLECS_META +#define FLECS_META +#endif -typedef struct EcsMetaTypeSerialized { - ecs_vector_t* ops; /* vector */ -} EcsMetaTypeSerialized; +#ifndef FLECS_DOC +#define FLECS_DOC +#endif +#ifndef FLECS_QUERY_DSL +#define FLECS_QUERY_DSL /* For parsing component id expressions */ +#endif -/** Deserializer utilities */ +#ifndef FLECS_JSON_H +#define FLECS_JSON_H -#define ECS_META_MAX_SCOPE_DEPTH (32) /* >32 levels of nesting is not sane */ +/** + * @defgroup c_addons_json Json + * @ingroup c_addons + * Functions for serializing to/from JSON. + * + * @{ + */ -typedef struct ecs_meta_scope_t { - ecs_entity_t type; /* The type being iterated */ - ecs_meta_type_op_t *ops; /* The type operations (see ecs_meta_type_op_t) */ - int32_t op_count; /* Number of operations in ops array to process */ - int32_t op_cur; /* Current operation */ - int32_t elem_cur; /* Current element (for collections) */ - void *ptr; /* Pointer to the value being iterated */ - - const EcsComponent *comp; /* Pointer to component, in case size/alignment is needed */ - ecs_vector_t **vector; /* Current vector, in case a vector is iterated */ - bool is_collection; /* Is the scope iterating elements? */ - bool is_inline_array; /* Is the scope iterating an inline array? */ -} ecs_meta_scope_t; +#ifdef __cplusplus +extern "C" { +#endif -/** Type that enables iterating/populating a value using reflection data */ -typedef struct ecs_meta_cursor_t { - const ecs_world_t *world; - ecs_meta_scope_t scope[ECS_META_MAX_SCOPE_DEPTH]; - int32_t depth; - bool valid; - bool is_primitive_scope; /* If in root scope, this allows for a push for primitive types */ +/** Used with ecs_ptr_from_json(), ecs_entity_from_json(). */ +typedef struct ecs_from_json_desc_t { + const char *name; /**< Name of expression (used for logging) */ + const char *expr; /**< Full expression (used for logging) */ - /* Custom entity lookup action for overriding default ecs_lookup_fullpath */ - ecs_entity_t (*lookup_action)(const ecs_world_t*, const char*, void*); + /** Callback that allows for specifying a custom lookup function. The + * default behavior uses ecs_lookup() */ + ecs_entity_t (*lookup_action)( + ecs_world_t*, + const char *value, + void *ctx); void *lookup_ctx; -} ecs_meta_cursor_t; + /** Require components to be registered with reflection data. When not + * in strict mode, values for components without reflection are ignored. */ + bool strict; +} ecs_from_json_desc_t; + +/** Parse JSON string into value. + * This operation parses a JSON expression into the provided pointer. The + * memory pointed to must be large enough to contain a value of the used type. + * + * @param world The world. + * @param type The type of the expression to parse. + * @param ptr Pointer to the memory to write to. + * @param json The JSON expression to parse. + * @param desc Configuration parameters for deserializer. + * @return Pointer to the character after the last one read, or NULL if failed. + */ FLECS_API -ecs_meta_cursor_t ecs_meta_cursor( +const char* ecs_ptr_from_json( const ecs_world_t *world, ecs_entity_t type, - void *ptr); + void *ptr, + const char *json, + const ecs_from_json_desc_t *desc); -/** Get pointer to current field */ +/** Parse JSON object with multiple component values into entity. The format + * is the same as the one outputted by ecs_entity_to_json(), but at the moment + * only supports the "ids" and "values" member. + * + * @param world The world. + * @param entity The entity to serialize to. + * @param json The JSON expression to parse (see entity in JSON format manual). + * @param desc Configuration parameters for deserializer. + * @return Pointer to the character after the last one read, or NULL if failed. + */ FLECS_API -void* ecs_meta_get_ptr( - ecs_meta_cursor_t *cursor); +const char* ecs_entity_from_json( + ecs_world_t *world, + ecs_entity_t entity, + const char *json, + const ecs_from_json_desc_t *desc); -/** Move cursor to next field */ +/** Parse JSON object with multiple entities into the world. The format is the + * same as the one outputted by ecs_world_to_json(). + * + * @param world The world. + * @param json The JSON expression to parse (see iterator in JSON format manual). + * @param desc Deserialization parameters. + * @return Last deserialized character, NULL if failed. + */ FLECS_API -int ecs_meta_next( - ecs_meta_cursor_t *cursor); +const char* ecs_world_from_json( + ecs_world_t *world, + const char *json, + const ecs_from_json_desc_t *desc); -/** Move cursor to a field */ +/** Same as ecs_world_from_json(), but loads JSON from file. + * + * @param world The world. + * @param filename The file from which to load the JSON. + * @param desc Deserialization parameters. + * @return Last deserialized character, NULL if failed. + */ FLECS_API -int ecs_meta_move( - ecs_meta_cursor_t *cursor, - int32_t pos); +const char* ecs_world_from_json_file( + ecs_world_t *world, + const char *filename, + const ecs_from_json_desc_t *desc); -/** Move cursor to member */ +/** Serialize array into JSON string. + * This operation serializes a value of the provided type to a JSON string. The + * memory pointed to must be large enough to contain a value of the used type. + * + * If count is 0, the function will serialize a single value, not wrapped in + * array brackets. If count is >= 1, the operation will serialize values to a + * a comma-separated list inside of array brackets. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param count The number of elements to serialize. + * @return String with JSON expression, or NULL if failed. + */ FLECS_API -int ecs_meta_member( - ecs_meta_cursor_t *cursor, - const char *name); +char* ecs_array_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void *data, + int32_t count); -/** Push a scope (required/only valid for structs & collections) */ +/** Serialize array into JSON string buffer. + * Same as ecs_array_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param count The number of elements to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ FLECS_API -int ecs_meta_push( - ecs_meta_cursor_t *cursor); +int ecs_array_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *data, + int32_t count, + ecs_strbuf_t *buf_out); -/** Pop a struct or collection scope (must follow a push) */ +/** Serialize value into JSON string. + * Same as ecs_array_to_json(), with count = 0. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @return String with JSON expression, or NULL if failed. + */ FLECS_API -int ecs_meta_pop( - ecs_meta_cursor_t *cursor); +char* ecs_ptr_to_json( + const ecs_world_t *world, + ecs_entity_t type, + const void *data); -/** Is the current scope a collection? */ +/** Serialize value into JSON string buffer. + * Same as ecs_ptr_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ FLECS_API -bool ecs_meta_is_collection( - const ecs_meta_cursor_t *cursor); +int ecs_ptr_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + const void *data, + ecs_strbuf_t *buf_out); -/** Get type of current element. */ +/** Serialize type info to JSON. + * This serializes type information to JSON, and can be used to store/transmit + * the structure of a (component) value. + * + * If the provided type does not have reflection data, "0" will be returned. + * + * @param world The world. + * @param type The type to serialize to JSON. + * @return A JSON string with the serialized type info, or NULL if failed. + */ FLECS_API -ecs_entity_t ecs_meta_get_type( - const ecs_meta_cursor_t *cursor); +char* ecs_type_info_to_json( + const ecs_world_t *world, + ecs_entity_t type); -/** Get unit of current element. */ +/** Serialize type info into JSON string buffer. + * Same as ecs_type_info_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param type The type to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ FLECS_API -ecs_entity_t ecs_meta_get_unit( - const ecs_meta_cursor_t *cursor); +int ecs_type_info_to_json_buf( + const ecs_world_t *world, + ecs_entity_t type, + ecs_strbuf_t *buf_out); -/** Get member name of current member */ -FLECS_API -const char* ecs_meta_get_member( - const ecs_meta_cursor_t *cursor); +/** Used with ecs_iter_to_json(). */ +typedef struct ecs_entity_to_json_desc_t { + bool serialize_entity_id; /**< Serialize entity id */ + bool serialize_doc; /**< Serialize doc attributes */ + bool serialize_full_paths; /**< Serialize full paths for tags, components and pairs */ + bool serialize_inherited; /**< Serialize base components */ + bool serialize_values; /**< Serialize component values */ + bool serialize_builtin; /**< Serialize builtin data as components (e.g. "name", "parent") */ + bool serialize_type_info; /**< Serialize type info (requires serialize_values) */ + bool serialize_alerts; /**< Serialize active alerts for entity */ + ecs_entity_t serialize_refs; /**< Serialize references (incoming edges) for relationship */ + bool serialize_matches; /**< Serialize which queries entity matches with */ + /** Callback for if the component should be serialized */ + bool (*component_filter) + (const ecs_world_t *, ecs_entity_t); +} ecs_entity_to_json_desc_t; -/** The set functions assign the field with the specified value. If the value - * does not have the same type as the field, it will be cased to the field type. - * If no valid conversion is available, the operation will fail. */ +/** Utility used to initialize JSON entity serializer. */ +#ifndef __cplusplus +#define ECS_ENTITY_TO_JSON_INIT (ecs_entity_to_json_desc_t){\ + .serialize_entity_id = false, \ + .serialize_doc = false, \ + .serialize_full_paths = true, \ + .serialize_inherited = false, \ + .serialize_values = true, \ + .serialize_builtin = false, \ + .serialize_type_info = false, \ + .serialize_alerts = false, \ + .serialize_refs = 0, \ + .serialize_matches = false, \ + .component_filter = NULL, \ +} +#else +#define ECS_ENTITY_TO_JSON_INIT {\ + false, \ + false, \ + true, \ + false, \ + true, \ + false, \ + false, \ + false, \ + 0, \ + false, \ + nullptr, \ +} +#endif -/** Set field with boolean value */ +/** Serialize entity into JSON string. + * This creates a JSON object with the entity's (path) name, which components + * and tags the entity has, and the component values. + * + * The operation may fail if the entity contains components with invalid values. + * + * @param world The world. + * @param entity The entity to serialize to JSON. + * @return A JSON string with the serialized entity data, or NULL if failed. + */ FLECS_API -int ecs_meta_set_bool( - ecs_meta_cursor_t *cursor, - bool value); +char* ecs_entity_to_json( + ecs_world_t *world, + ecs_entity_t entity, + const ecs_entity_to_json_desc_t *desc); -/** Set field with char value */ +/** Serialize entity into JSON string buffer. + * Same as ecs_entity_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world. + * @param entity The entity to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ FLECS_API -int ecs_meta_set_char( - ecs_meta_cursor_t *cursor, - char value); +int ecs_entity_to_json_buf( + ecs_world_t *world, + ecs_entity_t entity, + ecs_strbuf_t *buf_out, + const ecs_entity_to_json_desc_t *desc); -/** Set field with int value */ -FLECS_API -int ecs_meta_set_int( - ecs_meta_cursor_t *cursor, - int64_t value); +/** Used with ecs_iter_to_json(). */ +typedef struct ecs_iter_to_json_desc_t { + bool serialize_entity_ids; /**< Serialize entity ids */ + bool serialize_values; /**< Serialize component values */ + bool serialize_builtin; /**< Serialize builtin data as components (e.g. "name", "parent") */ + bool serialize_doc; /**< Serialize doc attributes */ + bool serialize_full_paths; /**< Serialize full paths for tags, components and pairs */ + bool serialize_fields; /**< Serialize field data */ + bool serialize_inherited; /**< Serialize inherited components */ + bool serialize_table; /**< Serialize entire table vs. matched components */ + bool serialize_type_info; /**< Serialize type information */ + bool serialize_field_info; /**< Serialize metadata for fields returned by query */ + bool serialize_query_info; /**< Serialize query terms */ + bool serialize_query_plan; /**< Serialize query plan */ + bool serialize_query_profile; /**< Profile query performance */ + bool dont_serialize_results; /**< If true, query won't be evaluated */ + bool serialize_alerts; /**< Serialize active alerts for entity */ + ecs_entity_t serialize_refs; /**< Serialize references (incoming edges) for relationship */ + bool serialize_matches; /**< Serialize which queries entity matches with */ + bool serialize_parents_before_children; /** If query matches both children and parent, serialize parent before children */ + + /** Callback for if the component should be serialized */ + bool (*component_filter) + (const ecs_world_t *, ecs_entity_t); + ecs_poly_t *query; /**< Query object (required for serialize_query_[plan|profile]). */ +} ecs_iter_to_json_desc_t; -/** Set field with uint value */ -FLECS_API -int ecs_meta_set_uint( - ecs_meta_cursor_t *cursor, - uint64_t value); +/** Utility used to initialize JSON iterator serializer. */ +#ifndef __cplusplus +#define ECS_ITER_TO_JSON_INIT (ecs_iter_to_json_desc_t){\ + .serialize_entity_ids = false, \ + .serialize_values = true, \ + .serialize_builtin = false, \ + .serialize_doc = false, \ + .serialize_full_paths = true, \ + .serialize_fields = true, \ + .serialize_inherited = false, \ + .serialize_table = false, \ + .serialize_type_info = false, \ + .serialize_field_info = false, \ + .serialize_query_info = false, \ + .serialize_query_plan = false, \ + .serialize_query_profile = false, \ + .dont_serialize_results = false, \ + .serialize_alerts = false, \ + .serialize_refs = false, \ + .serialize_matches = false, \ + .serialize_parents_before_children = false,\ + .component_filter = NULL, \ + .query = NULL \ +} +#else +#define ECS_ITER_TO_JSON_INIT {\ + false, \ + true, \ + false, \ + false, \ + true, \ + true, \ + false, \ + false, \ + false, \ + false, \ + false, \ + false, \ + false, \ + false, \ + false, \ + false, \ + false, \ + false, \ + nullptr, \ + nullptr \ +} +#endif -/** Set field with float value */ +/** Serialize iterator into JSON string. + * This operation will iterate the contents of the iterator and serialize them + * to JSON. The function accepts iterators from any source. + * + * @param iter The iterator to serialize to JSON. + * @return A JSON string with the serialized iterator data, or NULL if failed. + */ FLECS_API -int ecs_meta_set_float( - ecs_meta_cursor_t *cursor, - double value); +char* ecs_iter_to_json( + ecs_iter_t *iter, + const ecs_iter_to_json_desc_t *desc); -/** Set field with string value */ +/** Serialize iterator into JSON string buffer. + * Same as ecs_iter_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param iter The iterator to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ FLECS_API -int ecs_meta_set_string( - ecs_meta_cursor_t *cursor, - const char *value); +int ecs_iter_to_json_buf( + ecs_iter_t *iter, + ecs_strbuf_t *buf_out, + const ecs_iter_to_json_desc_t *desc); -/** Set field with string literal value (has enclosing "") */ -FLECS_API -int ecs_meta_set_string_literal( - ecs_meta_cursor_t *cursor, - const char *value); +/** Used with ecs_iter_to_json(). */ +typedef struct ecs_world_to_json_desc_t { + bool serialize_builtin; /**< Exclude flecs modules & contents */ + bool serialize_modules; /**< Exclude modules & contents */ +} ecs_world_to_json_desc_t; -/** Set field with entity value */ +/** Serialize world into JSON string. + * This operation iterates the contents of the world to JSON. The operation is + * equivalent to the following code: + * + * @code + * ecs_query_t *f = ecs_query(world, { + * .terms = {{ .id = EcsAny }} + * }); + * + * ecs_iter_t it = ecs_query_init(world, &f); + * ecs_iter_to_json_desc_t desc = { .serialize_table = true }; + * ecs_iter_to_json(iter, &desc); + * @endcode + * + * @param world The world to serialize. + * @return A JSON string with the serialized iterator data, or NULL if failed. + */ FLECS_API -int ecs_meta_set_entity( - ecs_meta_cursor_t *cursor, - ecs_entity_t value); +char* ecs_world_to_json( + ecs_world_t *world, + const ecs_world_to_json_desc_t *desc); -/** Set field with null value */ +/** Serialize world into JSON string buffer. + * Same as ecs_world_to_json(), but serializes to an ecs_strbuf_t instance. + * + * @param world The world to serialize. + * @param buf_out The strbuf to append the string to. + * @return Zero if success, non-zero if failed. + */ FLECS_API -int ecs_meta_set_null( - ecs_meta_cursor_t *cursor); +int ecs_world_to_json_buf( + ecs_world_t *world, + ecs_strbuf_t *buf_out, + const ecs_world_to_json_desc_t *desc); +#ifdef __cplusplus +} +#endif -/** Functions for getting members. */ +#endif -/** Get field value as boolean. */ -FLECS_API -bool ecs_meta_get_bool( - const ecs_meta_cursor_t *cursor); +/** @} */ -/** Get field value as char. */ -FLECS_API -char ecs_meta_get_char( - const ecs_meta_cursor_t *cursor); +#endif -/** Get field value as signed integer. */ -FLECS_API -int64_t ecs_meta_get_int( - const ecs_meta_cursor_t *cursor); +#endif -/** Get field value as unsigned integer. */ -FLECS_API -uint64_t ecs_meta_get_uint( - const ecs_meta_cursor_t *cursor); +#ifdef FLECS_UNITS +#ifdef FLECS_NO_UNITS +#error "FLECS_NO_UNITS failed: UNITS is required by other addons" +#endif +/** + * @file addons/units.h + * @brief Units module. + * + * Builtin standard units. The units addon is not imported by default, even if + * the addon is included in the build. To import the module, do: + * + * In C: + * + * @code + * ECS_IMPORT(world, FlecsUnits); + * @endcode + * + * In C++: + * + * @code + * world.import(); + * @endcode + * + * As a result this module behaves just like an application-defined module, + * which means that the ids generated for the entities inside the module are not + * fixed, and depend on the order in which the module is imported. + */ -/** Get field value as float. */ -FLECS_API -double ecs_meta_get_float( - const ecs_meta_cursor_t *cursor); +#ifdef FLECS_UNITS -/** Get field value as string. - * This operation does not perform conversions. If the field is not a string, - * this operation will fail. +/** + * @defgroup c_addons_units Units. + * @ingroup c_addons + * Common unit annotations for reflection framework. + * + * @{ */ -FLECS_API -const char* ecs_meta_get_string( - const ecs_meta_cursor_t *cursor); -/** Get field value as entity. - * This operation does not perform conversions. */ -FLECS_API -ecs_entity_t ecs_meta_get_entity( - const ecs_meta_cursor_t *cursor); +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif +#ifndef FLECS_META +#define FLECS_META +#endif -/** API functions for creating meta types */ +#ifndef FLECS_UNITS_H +#define FLECS_UNITS_H -/** Used with ecs_primitive_init. */ -typedef struct ecs_primitive_desc_t { - ecs_entity_desc_t entity; - ecs_primitive_kind_t kind; -} ecs_primitive_desc_t; +#ifdef __cplusplus +extern "C" { +#endif -/** Create a new primitive type */ -FLECS_API -ecs_entity_t ecs_primitive_init( - ecs_world_t *world, - const ecs_primitive_desc_t *desc); +/** + * @defgroup c_addons_units_prefixes Prefixes + * @ingroup c_addons_units + * Prefixes to indicate unit count (e.g. Kilo, Mega) + * + * @{ + */ -/** Used with ecs_enum_init. */ -typedef struct ecs_enum_desc_t { - ecs_entity_desc_t entity; - ecs_enum_constant_t constants[ECS_MEMBER_DESC_CACHE_SIZE]; -} ecs_enum_desc_t; +FLECS_API extern ecs_entity_t EcsUnitPrefixes; /**< Parent scope for prefixes. */ + +FLECS_API extern ecs_entity_t EcsYocto; /**< Yocto unit prefix. */ +FLECS_API extern ecs_entity_t EcsZepto; /**< Zepto unit prefix. */ +FLECS_API extern ecs_entity_t EcsAtto; /**< Atto unit prefix. */ +FLECS_API extern ecs_entity_t EcsFemto; /**< Femto unit prefix. */ +FLECS_API extern ecs_entity_t EcsPico; /**< Pico unit prefix. */ +FLECS_API extern ecs_entity_t EcsNano; /**< Nano unit prefix. */ +FLECS_API extern ecs_entity_t EcsMicro; /**< Micro unit prefix. */ +FLECS_API extern ecs_entity_t EcsMilli; /**< Milli unit prefix. */ +FLECS_API extern ecs_entity_t EcsCenti; /**< Centi unit prefix. */ +FLECS_API extern ecs_entity_t EcsDeci; /**< Deci unit prefix. */ +FLECS_API extern ecs_entity_t EcsDeca; /**< Deca unit prefix. */ +FLECS_API extern ecs_entity_t EcsHecto; /**< Hecto unit prefix. */ +FLECS_API extern ecs_entity_t EcsKilo; /**< Kilo unit prefix. */ +FLECS_API extern ecs_entity_t EcsMega; /**< Mega unit prefix. */ +FLECS_API extern ecs_entity_t EcsGiga; /**< Giga unit prefix. */ +FLECS_API extern ecs_entity_t EcsTera; /**< Tera unit prefix. */ +FLECS_API extern ecs_entity_t EcsPeta; /**< Peta unit prefix. */ +FLECS_API extern ecs_entity_t EcsExa; /**< Exa unit prefix. */ +FLECS_API extern ecs_entity_t EcsZetta; /**< Zetta unit prefix. */ +FLECS_API extern ecs_entity_t EcsYotta; /**< Yotta unit prefix. */ + +FLECS_API extern ecs_entity_t EcsKibi; /**< Kibi unit prefix. */ +FLECS_API extern ecs_entity_t EcsMebi; /**< Mebi unit prefix. */ +FLECS_API extern ecs_entity_t EcsGibi; /**< Gibi unit prefix. */ +FLECS_API extern ecs_entity_t EcsTebi; /**< Tebi unit prefix. */ +FLECS_API extern ecs_entity_t EcsPebi; /**< Pebi unit prefix. */ +FLECS_API extern ecs_entity_t EcsExbi; /**< Exbi unit prefix. */ +FLECS_API extern ecs_entity_t EcsZebi; /**< Zebi unit prefix. */ +FLECS_API extern ecs_entity_t EcsYobi; /**< Yobi unit prefix. */ -/** Create a new enum type */ -FLECS_API -ecs_entity_t ecs_enum_init( - ecs_world_t *world, - const ecs_enum_desc_t *desc); +/** @} */ +/** + * @defgroup c_addons_units_duration Duration + * @ingroup c_addons_units + * @{ + */ -/** Used with ecs_bitmask_init. */ -typedef struct ecs_bitmask_desc_t { - ecs_entity_desc_t entity; - ecs_bitmask_constant_t constants[ECS_MEMBER_DESC_CACHE_SIZE]; -} ecs_bitmask_desc_t; +FLECS_API extern ecs_entity_t EcsDuration; /**< Duration quantity. */ +FLECS_API extern ecs_entity_t EcsPicoSeconds; /**< PicoSeconds duration unit. */ +FLECS_API extern ecs_entity_t EcsNanoSeconds; /**< NanoSeconds duration unit. */ +FLECS_API extern ecs_entity_t EcsMicroSeconds; /**< MicroSeconds duration unit. */ +FLECS_API extern ecs_entity_t EcsMilliSeconds; /**< MilliSeconds duration unit. */ +FLECS_API extern ecs_entity_t EcsSeconds; /**< Seconds duration unit. */ +FLECS_API extern ecs_entity_t EcsMinutes; /**< Minutes duration unit. */ +FLECS_API extern ecs_entity_t EcsHours; /**< Hours duration unit. */ +FLECS_API extern ecs_entity_t EcsDays; /**< Days duration unit. */ -/** Create a new bitmask type */ -FLECS_API -ecs_entity_t ecs_bitmask_init( - ecs_world_t *world, - const ecs_bitmask_desc_t *desc); +/** @} */ +/** + * @defgroup c_addons_units_time Time + * @ingroup c_addons_units + * @{ + */ -/** Used with ecs_array_init. */ -typedef struct ecs_array_desc_t { - ecs_entity_desc_t entity; - ecs_entity_t type; - int32_t count; -} ecs_array_desc_t; +FLECS_API extern ecs_entity_t EcsTime; /**< Time quantity. */ +FLECS_API extern ecs_entity_t EcsDate; /**< Date unit. */ -/** Create a new array type */ -FLECS_API -ecs_entity_t ecs_array_init( - ecs_world_t *world, - const ecs_array_desc_t *desc); +/** @} */ +/** + * @defgroup c_addons_units_mass Mass + * @ingroup c_addons_units + * @{ + */ -/** Used with ecs_vector_init. */ -typedef struct ecs_vector_desc_t { - ecs_entity_desc_t entity; - ecs_entity_t type; -} ecs_vector_desc_t; +FLECS_API extern ecs_entity_t EcsMass; /**< Mass quantity. */ +FLECS_API extern ecs_entity_t EcsGrams; /**< Grams unit. */ +FLECS_API extern ecs_entity_t EcsKiloGrams; /**< KiloGrams unit. */ -/** Create a new vector type */ -FLECS_API -ecs_entity_t ecs_vector_init( - ecs_world_t *world, - const ecs_vector_desc_t *desc); +/** @} */ +/** + * @defgroup c_addons_units_electric_Current Electric Current + * @ingroup c_addons_units + * @{ + */ -/** Used with ecs_struct_init. */ -typedef struct ecs_struct_desc_t { - ecs_entity_desc_t entity; - ecs_member_t members[ECS_MEMBER_DESC_CACHE_SIZE]; -} ecs_struct_desc_t; +FLECS_API extern ecs_entity_t EcsElectricCurrent; /**< ElectricCurrent quantity. */ +FLECS_API extern ecs_entity_t EcsAmpere; /**< Ampere unit. */ -/** Create a new struct type */ -FLECS_API -ecs_entity_t ecs_struct_init( - ecs_world_t *world, - const ecs_struct_desc_t *desc); +/** @} */ -/** Used with ecs_unit_init. */ -typedef struct ecs_unit_desc_t { - ecs_entity_desc_t entity; - - /* Unit symbol, e.g. "m", "%", "g". (optional) */ - const char *symbol; +/** + * @defgroup c_addons_units_amount Amount + * @ingroup c_addons_units + * @{ + */ - /* Unit quantity, e.g. distance, percentage, weight. (optional) */ - ecs_entity_t quantity; +FLECS_API extern ecs_entity_t EcsAmount; /**< Amount quantity. */ +FLECS_API extern ecs_entity_t EcsMole; /**< Mole unit. */ - /* Base unit, e.g. "meters" (optional) */ - ecs_entity_t base; +/** @} */ - /* Over unit, e.g. "per second" (optional) */ - ecs_entity_t over; +/** + * @defgroup c_addons_units_luminous_intensity Luminous Intensity + * @ingroup c_addons_units + * @{ + */ - /* Translation to apply to derived unit (optional) */ - ecs_unit_translation_t translation; +FLECS_API extern ecs_entity_t EcsLuminousIntensity; /**< LuminousIntensity quantity. */ +FLECS_API extern ecs_entity_t EcsCandela; /**< Candela unit. */ - /* Prefix indicating order of magnitude relative to the derived unit. If set - * together with "translation", the values must match. If translation is not - * set, setting prefix will autopopulate it. - * Additionally, setting the prefix will enforce that the symbol (if set) - * is consistent with the prefix symbol + symbol of the derived unit. If the - * symbol is not set, it will be auto populated. */ - ecs_entity_t prefix; -} ecs_unit_desc_t; +/** @} */ -/** Create a new unit */ -FLECS_API -ecs_entity_t ecs_unit_init( - ecs_world_t *world, - const ecs_unit_desc_t *desc); +/** + * @defgroup c_addons_units_force Force + * @ingroup c_addons_units + * @{ + */ -/** Used with ecs_unit_prefix_init. */ -typedef struct ecs_unit_prefix_desc_t { - ecs_entity_desc_t entity; - - /* Unit symbol, e.g. "m", "%", "g". (optional) */ - const char *symbol; +FLECS_API extern ecs_entity_t EcsForce; /**< Force quantity. */ +FLECS_API extern ecs_entity_t EcsNewton; /**< Newton unit. */ - /* Translation to apply to derived unit (optional) */ - ecs_unit_translation_t translation; -} ecs_unit_prefix_desc_t; +/** @} */ -/** Create a new unit prefix */ -FLECS_API -ecs_entity_t ecs_unit_prefix_init( - ecs_world_t *world, - const ecs_unit_prefix_desc_t *desc); +/** + * @defgroup c_addons_units_length Length + * @ingroup c_addons_units + * @{ + */ -/** Create a new quantity */ -FLECS_API -ecs_entity_t ecs_quantity_init( - ecs_world_t *world, - const ecs_entity_desc_t *desc); +FLECS_API extern ecs_entity_t EcsLength; /**< Length quantity. */ +FLECS_API extern ecs_entity_t EcsMeters; /**< Meters unit. */ +FLECS_API extern ecs_entity_t EcsPicoMeters; /**< PicoMeters unit. */ +FLECS_API extern ecs_entity_t EcsNanoMeters; /**< NanoMeters unit. */ +FLECS_API extern ecs_entity_t EcsMicroMeters; /**< MicroMeters unit. */ +FLECS_API extern ecs_entity_t EcsMilliMeters; /**< MilliMeters unit. */ +FLECS_API extern ecs_entity_t EcsCentiMeters; /**< CentiMeters unit. */ +FLECS_API extern ecs_entity_t EcsKiloMeters; /**< KiloMeters unit. */ +FLECS_API extern ecs_entity_t EcsMiles; /**< Miles unit. */ +FLECS_API extern ecs_entity_t EcsPixels; /**< Pixels unit. */ -/* Module import */ -FLECS_API -void FlecsMetaImport( - ecs_world_t *world); +/** @} */ -#ifdef __cplusplus -} -#endif +/** + * @defgroup c_addons_units_pressure Pressure + * @ingroup c_addons_units + * @{ + */ -#endif +FLECS_API extern ecs_entity_t EcsPressure; /**< Pressure quantity. */ +FLECS_API extern ecs_entity_t EcsPascal; /**< Pascal unit. */ +FLECS_API extern ecs_entity_t EcsBar; /**< Bar unit. */ -#endif +/** @} */ -#endif -#ifdef FLECS_EXPR -#ifdef FLECS_NO_EXPR -#error "FLECS_NO_EXPR failed: EXPR is required by other addons" -#endif /** - * @file expr.h - * @brief Flecs expression parser addon. - * - * Parse expression strings into component values. The notation is similar to - * JSON but with a smaller footprint, native support for (large) integer types, - * character types, enumerations, bitmasks and entity identifiers. - * - * Examples: - * - * Member names: - * {x: 10, y: 20} - * - * No member names (uses member ordering): - * {10, 20} - * - * Enum values: - * {color: Red} - * - * Bitmask values: - * {toppings: Lettuce|Tomato} - * - * Collections: - * {points: [10, 20, 30]} - * - * Nested objects: - * {start: {x: 10, y: 20}, stop: {x: 30, y: 40}} - * + * @defgroup c_addons_units_speed Speed + * @ingroup c_addons_units + * @{ */ -#ifdef FLECS_EXPR +FLECS_API extern ecs_entity_t EcsSpeed; /**< Speed quantity. */ +FLECS_API extern ecs_entity_t EcsMetersPerSecond; /**< MetersPerSecond unit. */ +FLECS_API extern ecs_entity_t EcsKiloMetersPerSecond; /**< KiloMetersPerSecond unit. */ +FLECS_API extern ecs_entity_t EcsKiloMetersPerHour; /**< KiloMetersPerHour unit. */ +FLECS_API extern ecs_entity_t EcsMilesPerHour; /**< MilesPerHour unit. */ -#ifndef FLECS_META -#define FLECS_META -#endif +/** @} */ -#ifndef FLECS_PARSER -#define FLECS_PARSER -#endif +/** + * @defgroup c_addons_units_temperature Temperature + * @ingroup c_addons_units + * @{ + */ -#ifndef FLECS_EXPR_H -#define FLECS_EXPR_H +FLECS_API extern ecs_entity_t EcsTemperature; /**< Temperature quantity. */ +FLECS_API extern ecs_entity_t EcsKelvin; /**< Kelvin unit. */ +FLECS_API extern ecs_entity_t EcsCelsius; /**< Celsius unit. */ +FLECS_API extern ecs_entity_t EcsFahrenheit; /**< Fahrenheit unit. */ -#ifdef __cplusplus -extern "C" { -#endif +/** @} */ -/** Write an escaped character. - * Write a character to an output string, insert escape character if necessary. - * - * @param out The string to write the character to. - * @param in The input character. - * @param delimiter The delimiiter used (for example '"') - * @return Pointer to the character after the last one written. +/** + * @defgroup c_addons_units_data Data + * @ingroup c_addons_units + * @{ */ -FLECS_API -char* ecs_chresc( - char *out, - char in, - char delimiter); -/** Parse an escaped character. - * Parse a character with a potential escape sequence. - * - * @param in Pointer to character in input string. - * @param out Output string. - * @return Pointer to the character after the last one read. - */ -const char* ecs_chrparse( - const char *in, - char *out); +FLECS_API extern ecs_entity_t EcsData; /**< Data quantity. */ +FLECS_API extern ecs_entity_t EcsBits; /**< Bits unit. */ +FLECS_API extern ecs_entity_t EcsKiloBits; /**< KiloBits unit. */ +FLECS_API extern ecs_entity_t EcsMegaBits; /**< MegaBits unit. */ +FLECS_API extern ecs_entity_t EcsGigaBits; /**< GigaBits unit. */ +FLECS_API extern ecs_entity_t EcsBytes; /**< Bytes unit. */ +FLECS_API extern ecs_entity_t EcsKiloBytes; /**< KiloBytes unit. */ +FLECS_API extern ecs_entity_t EcsMegaBytes; /**< MegaBytes unit. */ +FLECS_API extern ecs_entity_t EcsGigaBytes; /**< GigaBytes unit. */ +FLECS_API extern ecs_entity_t EcsKibiBytes; /**< KibiBytes unit. */ +FLECS_API extern ecs_entity_t EcsMebiBytes; /**< MebiBytes unit. */ +FLECS_API extern ecs_entity_t EcsGibiBytes; /**< GibiBytes unit. */ -/** Write an escaped string. - * Write an input string to an output string, escape characters where necessary. - * To determine the size of the output string, call the operation with a NULL - * argument for 'out', and use the returned size to allocate a string that is - * large enough. - * - * @param out Pointer to output string (msut be). - * @param size Maximum number of characters written to output. - * @param delimiter The delimiter used (for example '"'). - * @param in The input string. - * @return The number of characters that (would) have been written. +/** @} */ + +/** + * @defgroup c_addons_units_datarate Data Rate + * @ingroup c_addons_units + * @{ */ -FLECS_API -ecs_size_t ecs_stresc( - char *out, - ecs_size_t size, - char delimiter, - const char *in); -/** Return escaped string. - * Return escaped version of input string. Same as ecs_stresc, but returns an - * allocated string of the right size. - * - * @param delimiter The delimiter used (for example '"'). - * @param in The input string. - * @return Escaped string. +FLECS_API extern ecs_entity_t EcsDataRate; /**< DataRate quantity. */ +FLECS_API extern ecs_entity_t EcsBitsPerSecond; /**< BitsPerSecond unit. */ +FLECS_API extern ecs_entity_t EcsKiloBitsPerSecond; /**< KiloBitsPerSecond unit. */ +FLECS_API extern ecs_entity_t EcsMegaBitsPerSecond; /**< MegaBitsPerSecond unit. */ +FLECS_API extern ecs_entity_t EcsGigaBitsPerSecond; /**< GigaBitsPerSecond unit. */ +FLECS_API extern ecs_entity_t EcsBytesPerSecond; /**< BytesPerSecond unit. */ +FLECS_API extern ecs_entity_t EcsKiloBytesPerSecond; /**< KiloBytesPerSecond unit. */ +FLECS_API extern ecs_entity_t EcsMegaBytesPerSecond; /**< MegaBytesPerSecond unit. */ +FLECS_API extern ecs_entity_t EcsGigaBytesPerSecond; /**< GigaBytesPerSecond unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_duration Duration + * @ingroup c_addons_units + * @{ */ -FLECS_API -char* ecs_astresc( - char delimiter, - const char *in); -/** Used with ecs_parse_expr. */ -typedef struct ecs_parse_expr_desc_t { - const char *name; - const char *expr; - ecs_entity_t (*lookup_action)( - const ecs_world_t*, - const char *value, - void *ctx); - void *lookup_ctx; -} ecs_parse_expr_desc_t; +FLECS_API extern ecs_entity_t EcsAngle; /**< Angle quantity. */ +FLECS_API extern ecs_entity_t EcsRadians; /**< Radians unit. */ +FLECS_API extern ecs_entity_t EcsDegrees; /**< Degrees unit. */ -/** Parse expression into value. - * This operation parses a flecs expression into the provided pointer. The - * memory pointed to must be large enough to contain a value of the used type. - * - * @param world The world. - * @param ptr The pointer to the expression to parse. - * @param type The type of the expression to parse. - * @param data_out Pointer to the memory to write to. - * @param desc Configuration parameters for deserializer. - * @return Pointer to the character after the last one read, or NULL if failed. +/** @} */ + +/** + * @defgroup c_addons_units_angle Angle + * @ingroup c_addons_units + * @{ */ -FLECS_API -const char* ecs_parse_expr( - const ecs_world_t *world, - const char *ptr, - ecs_entity_t type, - void *data_out, - const ecs_parse_expr_desc_t *desc); -/** Serialize value into expression string. - * This operation serializes a value of the provided type to a string. The - * memory pointed to must be large enough to contain a value of the used type. - * - * @param world The world. - * @param type The type of the value to serialize. - * @param data The value to serialize. - * @return String with expression, or NULL if failed. +FLECS_API extern ecs_entity_t EcsFrequency; /**< Frequency quantity. */ +FLECS_API extern ecs_entity_t EcsHertz; /**< Hertz unit. */ +FLECS_API extern ecs_entity_t EcsKiloHertz; /**< KiloHertz unit. */ +FLECS_API extern ecs_entity_t EcsMegaHertz; /**< MegaHertz unit. */ +FLECS_API extern ecs_entity_t EcsGigaHertz; /**< GigaHertz unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_uri Uri + * @ingroup c_addons_units + * @{ */ -FLECS_API -char* ecs_ptr_to_expr( - const ecs_world_t *world, - ecs_entity_t type, - const void *data); -/** Serialize value into string buffer. - * Same as ecs_ptr_to_expr, but serializes to an ecs_strbuf_t instance. +FLECS_API extern ecs_entity_t EcsUri; /**< URI quantity. */ +FLECS_API extern ecs_entity_t EcsUriHyperlink; /**< UriHyperlink unit. */ +FLECS_API extern ecs_entity_t EcsUriImage; /**< UriImage unit. */ +FLECS_API extern ecs_entity_t EcsUriFile; /**< UriFile unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_color Color + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ecs_entity_t EcsColor; /**< Color quantity. */ +FLECS_API extern ecs_entity_t EcsColorRgb; /**< ColorRgb unit. */ +FLECS_API extern ecs_entity_t EcsColorHsl; /**< ColorHsl unit. */ +FLECS_API extern ecs_entity_t EcsColorCss; /**< ColorCss unit. */ + +/** @} */ + + +FLECS_API extern ecs_entity_t EcsAcceleration; /**< Acceleration unit. */ +FLECS_API extern ecs_entity_t EcsPercentage; /**< Percentage unit. */ +FLECS_API extern ecs_entity_t EcsBel; /**< Bel unit. */ +FLECS_API extern ecs_entity_t EcsDeciBel; /**< DeciBel unit. */ + +//////////////////////////////////////////////////////////////////////////////// +//// Module +//////////////////////////////////////////////////////////////////////////////// + +/** Units module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsUnits) + * @endcode * * @param world The world. - * @param type The type of the value to serialize. - * @param data The value to serialize. - * @param buf The strbuf to append the string to. - * @return Zero if success, non-zero if failed. */ FLECS_API -int ecs_ptr_to_expr_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *data, - ecs_strbuf_t *buf); +void FlecsUnitsImport( + ecs_world_t *world); -/** Serialize primitive value into string buffer. - * Serializes a primitive value to an ecs_strbuf_t instance. This operation can - * be reused by other serializers to avoid having to write boilerplate code that - * serializes primitive values to a string. - * - * @param world The world. - * @param kind The kind of primitive value. - * @param data The value ot serialize - * @param buf The strbuf to append the string to. - * @return Zero if success, non-zero if failed. +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_SCRIPT_MATH +#ifdef FLECS_NO_SCRIPT_MATH +#error "FLECS_NO_SCRIPT_MATH failed: SCRIPT_MATH is required by other addons" +#endif +/** + * @file addons/script_math.h + * @brief Math functions for flecs script. + */ + +#ifdef FLECS_SCRIPT_MATH + +#ifndef FLECS_SCRIPT +#define FLECS_SCRIPT +#endif + +/** + * @defgroup c_addons_script_math Script Math + * @ingroup c_addons + * Math functions for flecs script. + * @{ */ + +#ifndef FLECS_SCRIPT_MATH_H +#define FLECS_SCRIPT_MATH_H + +#ifdef __cplusplus +extern "C" { +#endif + FLECS_API -int ecs_primitive_to_expr_buf( - const ecs_world_t *world, - ecs_primitive_kind_t kind, - const void *data, - ecs_strbuf_t *buf); +extern ECS_COMPONENT_DECLARE(EcsScriptRng); -/** Parse expression token. - * Expression tokens can contain more characters (such as '|') than tokens - * parsed by the query (term) parser. +/* Randon number generator */ +typedef struct { + uint64_t seed; + void *impl; +} EcsScriptRng; + +/** Script math import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsScriptMath) + * @endcode * - * @param name The name of the expression (used for debug logs). - * @param expr The full expression (used for debug logs). - * @param ptr The pointer to the expression to parse. - * @param token The buffer to write to (must have size ECS_MAX_TOKEN_SIZE) - * @return Pointer to the character after the last one read, or NULL if failed. + * @param world The world. */ FLECS_API -const char *ecs_parse_expr_token( - const char *name, - const char *expr, - const char *ptr, - char *token); +void FlecsScriptMathImport( + ecs_world_t *world); #ifdef __cplusplus } @@ -10893,783 +15422,945 @@ const char *ecs_parse_expr_token( #endif +/** @} */ + +#endif + +#endif + +#ifdef FLECS_PARSER +#ifdef FLECS_NO_PARSER +#error "FLECS_NO_PARSER failed: PARSER is required by other addons" +#endif #endif +#ifdef FLECS_QUERY_DSL +#ifdef FLECS_NO_QUERY_DSL +#error "FLECS_NO_QUERY_DSL failed: QUERY_DSL is required by other addons" +#endif +#ifndef FLECS_PARSER +#define FLECS_PARSER #endif -#ifdef FLECS_META_C -#ifdef FLECS_NO_META_C -#error "FLECS_NO_META_C failed: META_C is required by other addons" +#endif + +#ifdef FLECS_SCRIPT +#ifdef FLECS_NO_SCRIPT +#error "FLECS_NO_SCRIPT failed: SCRIPT is required by other addons" #endif /** - * @file meta_c.h - * @brief Utility macro's for populating reflection data in C. + * @file addons/script.h + * @brief Flecs script module. + * + * For script, see examples/script. */ -#ifdef FLECS_META_C +#ifdef FLECS_SCRIPT + +/** + * @defgroup c_addons_script Flecs script + * @ingroup c_addons + * DSL for loading scenes, assets and configuration. + * + * @{ + */ #ifndef FLECS_META #define FLECS_META #endif +#ifndef FLECS_DOC +#define FLECS_DOC +#endif + #ifndef FLECS_PARSER #define FLECS_PARSER #endif -#ifndef FLECS_META_C_H -#define FLECS_META_C_H +#ifndef FLECS_SCRIPT_H +#define FLECS_SCRIPT_H #ifdef __cplusplus extern "C" { #endif -/* Public API */ +#define FLECS_SCRIPT_FUNCTION_ARGS_MAX (16) -/* Macro that controls behavior of API. Usually set in module header. When the - * macro is not defined, it defaults to IMPL. */ +/* Must be the same as EcsPrimitiveKindLast */ +#define FLECS_SCRIPT_VECTOR_FUNCTION_COUNT (18) -/* Define variables used by reflection utilities. This should only be defined - * by the module itself, not by the code importing the module */ -/* #define ECS_META_IMPL IMPL */ +FLECS_API +extern ECS_COMPONENT_DECLARE(EcsScript); -/* Don't define variables used by reflection utilities but still declare the - * variable for the component id. This enables the reflection utilities to be - * used for global component variables, even if no reflection is used. */ -/* #define ECS_META_IMPL DECLARE */ +FLECS_API +extern ECS_DECLARE(EcsScriptTemplate); -/* Don't define variables used by reflection utilities. This generates an extern - * variable for the component identifier. */ -/* #define ECS_META_IMPL EXTERN */ +FLECS_API +extern ECS_COMPONENT_DECLARE(EcsScriptConstVar); -/** Declare component with descriptor */ -#define ECS_META_COMPONENT(world, name)\ - ECS_COMPONENT_DEFINE(world, name);\ - ecs_meta_from_desc(world, ecs_id(name),\ - FLECS__##name##_kind, FLECS__##name##_desc) - -/** ECS_STRUCT(name, body) */ -#define ECS_STRUCT(name, ...)\ - ECS_STRUCT_TYPE(name, __VA_ARGS__);\ - ECS_META_IMPL_CALL(ECS_STRUCT_, ECS_META_IMPL, name, #__VA_ARGS__) - -/** ECS_ENUM(name, body) */ -#define ECS_ENUM(name, ...)\ - ECS_ENUM_TYPE(name, __VA_ARGS__);\ - ECS_META_IMPL_CALL(ECS_ENUM_, ECS_META_IMPL, name, #__VA_ARGS__) - -/** ECS_BITMASK(name, body) */ -#define ECS_BITMASK(name, ...)\ - ECS_ENUM_TYPE(name, __VA_ARGS__);\ - ECS_META_IMPL_CALL(ECS_BITMASK_, ECS_META_IMPL, name, #__VA_ARGS__) - -/** Macro used to mark part of type for which no reflection data is created */ -#define ECS_PRIVATE +FLECS_API +extern ECS_COMPONENT_DECLARE(EcsScriptFunction); -/** Populate meta information from type descriptor. */ FLECS_API -int ecs_meta_from_desc( - ecs_world_t *world, - ecs_entity_t component, - ecs_type_kind_t kind, - const char *desc); +extern ECS_COMPONENT_DECLARE(EcsScriptMethod); +FLECS_API +extern ECS_DECLARE(EcsScriptVectorType); -/* Private API */ +/* Script template. */ +typedef struct ecs_script_template_t ecs_script_template_t; -/* Utilities to switch beteen IMPL, DECLARE and EXTERN variants */ -#define ECS_META_IMPL_CALL_INNER(base, impl, name, type_desc)\ - base ## impl(name, type_desc) +/** Script variable. */ +typedef struct ecs_script_var_t { + const char *name; + ecs_value_t value; + const ecs_type_info_t *type_info; + int32_t sp; + bool is_const; +} ecs_script_var_t; -#define ECS_META_IMPL_CALL(base, impl, name, type_desc)\ - ECS_META_IMPL_CALL_INNER(base, impl, name, type_desc) +/** Script variable scope. */ +typedef struct ecs_script_vars_t { + struct ecs_script_vars_t *parent; + int32_t sp; -/* ECS_STRUCT implementation */ -#define ECS_STRUCT_TYPE(name, ...)\ - typedef struct __VA_ARGS__ name + ecs_hashmap_t var_index; + ecs_vec_t vars; -#define ECS_STRUCT_ECS_META_IMPL ECS_STRUCT_IMPL + const ecs_world_t *world; + struct ecs_stack_t *stack; + ecs_stack_cursor_t *cursor; + ecs_allocator_t *allocator; +} ecs_script_vars_t; + +/** Script object. */ +typedef struct ecs_script_t { + ecs_world_t *world; + const char *name; + const char *code; +} ecs_script_t; + +/* Runtime for executing scripts */ +typedef struct ecs_script_runtime_t ecs_script_runtime_t; + +/** Script component. + * This component is added to the entities of managed scripts and templates. + */ +typedef struct EcsScript { + char *filename; + char *code; + char *error; /* Set if script evaluation had errors */ + ecs_script_t *script; + ecs_script_template_t *template_; /* Only set for template scripts */ +} EcsScript; + +/** Script function context. */ +typedef struct ecs_function_ctx_t { + ecs_world_t *world; + ecs_entity_t function; + void *ctx; +} ecs_function_ctx_t; + +/** Script function callback. */ +typedef void(*ecs_function_callback_t)( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result); + +/** Script vector function callback. */ +typedef void(*ecs_vector_function_callback_t)( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result, + int32_t elem_count); -#define ECS_STRUCT_IMPL(name, type_desc)\ - FLECS_META_C_EXPORT extern ECS_COMPONENT_DECLARE(name);\ - static const char *FLECS__##name##_desc = type_desc;\ - static ecs_type_kind_t FLECS__##name##_kind = EcsStructType;\ - FLECS_META_C_EXPORT ECS_COMPONENT_DECLARE(name) = 0 +/** Function argument type. */ +typedef struct ecs_script_parameter_t { + const char *name; + ecs_entity_t type; +} ecs_script_parameter_t; -#define ECS_STRUCT_DECLARE(name, type_desc)\ - FLECS_META_C_EXPORT extern ECS_COMPONENT_DECLARE(name);\ - FLECS_META_C_EXPORT ECS_COMPONENT_DECLARE(name) = 0 +/** Const component. + * This component describes a const variable that can be used from scripts. + */ +typedef struct EcsScriptConstVar { + ecs_value_t value; + const ecs_type_info_t *type_info; +} EcsScriptConstVar; -#define ECS_STRUCT_EXTERN(name, type_desc)\ - FLECS_META_C_IMPORT extern ECS_COMPONENT_DECLARE(name) +struct ecs_script_function_t { + ecs_entity_t return_type; + ecs_vec_t params; /* vec */ + ecs_function_callback_t callback; + ecs_vector_function_callback_t vector_callbacks[FLECS_SCRIPT_VECTOR_FUNCTION_COUNT]; + void *ctx; +}; +/** Function component. + * This component describes a function that can be called from a script. + */ +typedef struct ecs_script_function_t EcsScriptFunction; -/* ECS_ENUM implementation */ -#define ECS_ENUM_TYPE(name, ...)\ - typedef enum __VA_ARGS__ name +/** Method component. + * This component describes a method that can be called from a script. Methods + * are functions that can be called on instances of a type. A method entity is + * stored in the scope of the type it belongs to. + */ +typedef struct ecs_script_function_t EcsScriptMethod; -#define ECS_ENUM_ECS_META_IMPL ECS_ENUM_IMPL +/* Parsing & running scripts */ -#define ECS_ENUM_IMPL(name, type_desc)\ - FLECS_META_C_EXPORT extern ECS_COMPONENT_DECLARE(name);\ - static const char *FLECS__##name##_desc = type_desc;\ - static ecs_type_kind_t FLECS__##name##_kind = EcsEnumType;\ - FLECS_META_C_EXPORT ECS_COMPONENT_DECLARE(name) = 0 +/** Used with ecs_script_parse() and ecs_script_eval() */ +typedef struct ecs_script_eval_desc_t { + ecs_script_vars_t *vars; /**< Variables used by script */ + ecs_script_runtime_t *runtime; /**< Reusable runtime (optional) */ +} ecs_script_eval_desc_t; -#define ECS_ENUM_DECLARE(name, type_desc)\ - FLECS_META_C_EXPORT extern ECS_COMPONENT_DECLARE(name);\ - FLECS_META_C_EXPORT ECS_COMPONENT_DECLARE(name) = 0 +/** Used to capture error output from script evaluation. */ +typedef struct ecs_script_eval_result_t { + char *error; +} ecs_script_eval_result_t; -#define ECS_ENUM_EXTERN(name, type_desc)\ - FLECS_META_C_IMPORT extern ECS_COMPONENT_DECLARE(name) +/** Parse script. + * This operation parses a script and returns a script object upon success. To + * run the script, call ecs_script_eval(). + * + * If the script uses outside variables, an ecs_script_vars_t object must be + * provided in the vars member of the desc object that defines all variables + * with the correct types. + * + * When the result parameter is not NULL, the script will capture errors and + * return them in the output struct. If result.error is set, it must be freed + * by the application. + * + * @param world The world. + * @param name Name of the script (typically a file/module name). + * @param code The script code. + * @param desc Parameters for script runtime. + * @param result Output of script evaluation. + * @return Script object if success, NULL if failed. +*/ +FLECS_API +ecs_script_t* ecs_script_parse( + ecs_world_t *world, + const char *name, + const char *code, + const ecs_script_eval_desc_t *desc, + ecs_script_eval_result_t *result); +/** Evaluate script. + * This operation evaluates (runs) a parsed script. + * + * If variables were provided to ecs_script_parse(), an application may pass + * a different ecs_script_vars_t object to ecs_script_eval(), as long as the + * object has all referenced variables and they are of the same type. + * + * When the result parameter is not NULL, the script will capture errors and + * return them in the output struct. If result.error is set, it must be freed + * by the application. + * + * @param script The script. + * @param desc Parameters for script runtime. + * @return Zero if success, non-zero if failed. +*/ +FLECS_API +int ecs_script_eval( + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc, + ecs_script_eval_result_t *result); -/* ECS_BITMASK implementation */ -#define ECS_BITMASK_TYPE(name, ...)\ - typedef enum __VA_ARGS__ name +/** Free script. + * This operation frees a script object. + * + * Templates created by the script rely upon resources in the script object, + * and for that reason keep the script alive until all templates created by the + * script are deleted. + * + * @param script The script. + */ +FLECS_API +void ecs_script_free( + ecs_script_t *script); -#define ECS_BITMASK_ECS_META_IMPL ECS_BITMASK_IMPL +/** Parse script. + * This parses a script and instantiates the entities in the world. + * This operation is the equivalent to doing: + * + * @code + * ecs_script_t *script = ecs_script_parse(world, name, code); + * ecs_script_eval(script); + * ecs_script_free(script); + * @endcode + * + * @param world The world. + * @param name The script name (typically the file). + * @param code The script. + * @return Zero if success, non-zero otherwise. + */ +FLECS_API +int ecs_script_run( + ecs_world_t *world, + const char *name, + const char *code, + ecs_script_eval_result_t *result); -#define ECS_BITMASK_IMPL(name, type_desc)\ - FLECS_META_C_EXPORT extern ECS_COMPONENT_DECLARE(name);\ - static const char *FLECS__##name##_desc = type_desc;\ - static ecs_type_kind_t FLECS__##name##_kind = EcsBitmaskType;\ - FLECS_META_C_EXPORT ECS_COMPONENT_DECLARE(name) = 0 +/** Parse script file. + * This parses a script file and instantiates the entities in the world. This + * operation is equivalent to loading the file contents and passing it to + * ecs_script_run(). + * + * @param world The world. + * @param filename The script file name. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_script_run_file( + ecs_world_t *world, + const char *filename); -#define ECS_BITMASK_DECLARE(name, type_desc)\ - FLECS_META_C_EXPORT extern ECS_COMPONENT_DECLARE(name);\ - FLECS_META_C_EXPORT ECS_COMPONENT_DECLARE(name) = 0 +/** Create runtime for script. + * A script runtime is a container for any data created during script + * evaluation. By default calling ecs_script_run() or ecs_script_eval() will + * create a runtime on the spot. A runtime can be created in advance and reused + * across multiple script evaluations to improve performance. + * + * When scripts are evaluated on multiple threads, each thread should have its + * own script runtime. + * + * A script runtime must be deleted with ecs_script_runtime_free(). + * + * @return A new script runtime. + */ +FLECS_API +ecs_script_runtime_t* ecs_script_runtime_new(void); -#define ECS_BITMASK_EXTERN(name, type_desc)\ - FLECS_META_C_IMPORT extern ECS_COMPONENT_DECLARE(name) +/** Free script runtime. + * This operation frees a script runtime created by ecs_script_runtime_new(). + * + * @param runtime The runtime to free. + */ +FLECS_API +void ecs_script_runtime_free( + ecs_script_runtime_t *runtime); +/** Convert script AST to string. + * This operation converts the script abstract syntax tree to a string, which + * can be used to debug a script. + * + * @param script The script. + * @param buf The buffer to write to. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_script_ast_to_buf( + ecs_script_t *script, + ecs_strbuf_t *buf, + bool colors); -/* Symbol export utility macro's */ -#if defined(ECS_TARGET_WINDOWS) -#define FLECS_META_C_EXPORT __declspec(dllexport) -#define FLECS_META_C_IMPORT __declspec(dllimport) -#else -#define FLECS_META_C_EXPORT __attribute__((__visibility__("default"))) -#define FLECS_META_C_IMPORT -#endif +/** Convert script AST to string. + * This operation converts the script abstract syntax tree to a string, which + * can be used to debug a script. + * + * @param script The script. + * @return The string if success, NULL if failed. + */ +FLECS_API +char* ecs_script_ast_to_str( + ecs_script_t *script, + bool colors); -#ifdef __cplusplus -} -#endif -#endif // FLECS_META_C_H +/* Managed scripts (script associated with entity that outlives the function) */ -#endif // FLECS_META_C +/** Used with ecs_script_init(). */ +typedef struct ecs_script_desc_t { + ecs_entity_t entity; /**< Set to customize entity handle associated with script */ + const char *filename; /**< Set to load script from file */ + const char *code; /**< Set to parse script from string */ +} ecs_script_desc_t; -#endif -#ifdef FLECS_PLECS -#ifdef FLECS_NO_PLECS -#error "FLECS_NO_PLECS failed: PLECS is required by other addons" -#endif -/** - * @file pecs.h - * @brief Plecs addon. - * - * Plecs is a small data definition language for instantiating entities that - * reuses the existing flecs query parser. The following examples illustrate - * how a plecs snippet translates to regular flecs operations: +/** Load managed script. + * A managed script tracks which entities it creates, and keeps those entities + * synchronized when the contents of the script are updated. When the script is + * updated, entities that are no longer in the new version will be deleted. * - * Plecs: - * Entity - * C code: - * ecs_entity_t Entity = ecs_set_name(world, 0, "Entity"); + * This feature is experimental. * - * Plecs: - * Position(Entity) - * C code: - * ecs_entity_t Position = ecs_set_name(world, 0, "Position"); - * ecs_entity_t Entity = ecs_set_name(world, 0, "Entity"); - * ecs_add_id(world, Entity, Position); - * - * Plecs: - * Likes(Entity, Apples) - * C code: - * ecs_entity_t Likes = ecs_set_name(world, 0, "Likes"); - * ecs_entity_t Apples = ecs_set_name(world, 0, "Apples"); - * ecs_entity_t Entity = ecs_set_name(world, 0, "Entity"); - * ecs_add_pair(world, Entity, Likes, Apples); - * - * A plecs string may contain multiple statements, separated by a newline: - * Likes(Entity, Apples) - * Likes(Entity, Pears) - * Likes(Entity, Bananas) + * @param world The world. + * @param desc Script descriptor. */ +FLECS_API +ecs_entity_t ecs_script_init( + ecs_world_t *world, + const ecs_script_desc_t *desc); -#ifdef FLECS_PLECS - -#ifndef FLECS_PARSER -#define FLECS_PARSER -#endif - -#ifndef FLECS_PLECS_H -#define FLECS_PLECS_H - -#ifdef __cplusplus -extern "C" { -#endif +#define ecs_script(world, ...)\ + ecs_script_init(world, &(ecs_script_desc_t) __VA_ARGS__) -/** Parse plecs string. - * This parses a plecs string and instantiates the entities in the world. +/** Update script with new code. * * @param world The world. - * @param name The script name (typically the file). - * @param str The plecs string. - * @return Zero if success, non-zero otherwise. + * @param script The script entity. + * @param instance An template instance (optional). + * @param code The script code. */ FLECS_API -int ecs_plecs_from_str( +int ecs_script_update( ecs_world_t *world, - const char *name, - const char *str); + ecs_entity_t script, + ecs_entity_t instance, + const char *code); -/** Parse plecs file. - * This parses a plecs file and instantiates the entities in the world. This - * operation is equivalent to loading the file contents and passing it to - * ecs_plecs_from_str. +/** Clear all entities associated with script. * * @param world The world. - * @param filename The plecs file name. - * @return Zero if success, non-zero otherwise. + * @param script The script entity. + * @param instance The script instance. */ FLECS_API -int ecs_plecs_from_file( +void ecs_script_clear( ecs_world_t *world, - const char *filename); - -#ifdef __cplusplus -} -#endif - -#endif - -#endif - -#endif -#ifdef FLECS_RULES -#ifdef FLECS_NO_RULES -#error "FLECS_NO_RULES failed: RULES is required by other addons" -#endif + ecs_entity_t script, + ecs_entity_t instance); -/** - * @file rules.h - * @brief Rule query engine addon. - * - * Rules are advanced queries that in addition to the capabilities of regular - * queries and filters have the folllowing features: - * - * - query for all components of an entity (vs. all entities for a component) - * - query for all relationship pairs of an entity - * - support for query variables that are resolved at evaluation time - * - automatic traversal of transitive relationships - */ -#ifdef FLECS_RULES - -#ifndef FLECS_RULES_H -#define FLECS_RULES_H - -#ifdef __cplusplus -extern "C" { -#endif +/* Script variables */ -/** Create a rule. - * A rule accepts the same descriptor as a filter, but has the additional - * ability to use query variables. - * - * Query variables can be used to constrain wildcards across multiple terms to - * the same entity. Regular ECS queries do this in a limited form, as querying - * for Position, Velocity only returns entities that have both components. - * - * Query variables expand this to constrain entities that are resolved while the - * query is being matched. Consider a query for all entities and the mission - * they are on: - * (Mission, *) - * - * If an entity is on multiple missions, the wildcard will match it multiple - * times. Now say we want to only list combat missions. Naively we could try: - * (Mission, *), CombatMission(*) - * - * But this doesn't work, as term 1 returns entities with missions, and term 2 - * returns all combat missions for all entities. Query variables make it - * possible to apply CombatMission to the found mission: - * (Mission, $M), CombatMission($M) - * - * By using the same variable ('M') we ensure that CombatMission is applied to - * the mission found in the current result. - * - * Variables can be used in each part of the term (predicate, subject, object). - * This is a valid query: - * Likes($X, $Y), Likes($Y, $X) - * - * This is also a valid query: - * _Component, Serializable(_Component) +/** Create new variable scope. + * Create root variable scope. A variable scope contains one or more variables. + * Scopes can be nested, which allows variables in different scopes to have the + * same name. Variables from parent scopes will be shadowed by variables in + * child scopes with the same name. * - * In the query expression syntax, variables are prefixed with a $. When using - * the descriptor, specify the variable kind: - * desc.terms[0].obj = { .name = "X", .var = EcsVarIsVariable } + * Use the `ecs_script_vars_push()` and `ecs_script_vars_pop()` functions to + * push and pop variable scopes. * - * Different terms with the same variable name are automatically correlated by - * the query engine. - * - * A rule needs to be explicitly deleted with ecs_rule_fini. + * When a variable contains allocated resources (e.g. a string), its resources + * will be freed when `ecs_script_vars_pop()` is called on the scope, the + * ecs_script_vars_t::type_info field is initialized for the variable, and + * `ecs_type_info_t::hooks::dtor` is set. * * @param world The world. - * @param desc The descriptor (see ecs_filter_desc_t) - * @return The rule. */ FLECS_API -ecs_rule_t* ecs_rule_init( - ecs_world_t *world, - const ecs_filter_desc_t *desc); +ecs_script_vars_t* ecs_script_vars_init( + ecs_world_t *world); -/** Delete a rule. - * - * @param rule The rule. +/** Free variable scope. + * Free root variable scope. The provided scope should not have a parent. This + * operation calls `ecs_script_vars_pop()` on the scope. + * + * @param vars The variable scope. */ FLECS_API -void ecs_rule_fini( - ecs_rule_t *rule); +void ecs_script_vars_fini( + ecs_script_vars_t *vars); -/** Obtain filter from rule. - * This operation returns the filter with which the rule was created. +/** Push new variable scope. + * + * Scopes created with ecs_script_vars_push() must be cleaned up with + * ecs_script_vars_pop(). * - * @param rule The rule. - * @return The filter. + * If the stack and allocator arguments are left to NULL, their values will be + * copied from the parent. + * + * @param parent The parent scope (provide NULL for root scope). + * @return The new variable scope. */ FLECS_API -const ecs_filter_t* ecs_rule_get_filter( - const ecs_rule_t *rule); +ecs_script_vars_t* ecs_script_vars_push( + ecs_script_vars_t *parent); -/** Return number of variables in rule. - * - * @param rule The rule. - * @return The number of variables/ +/** Pop variable scope. + * This frees up the resources for a variable scope. The scope must be at the + * top of a vars stack. Calling ecs_script_vars_pop() on a scope that is not the + * last scope causes undefined behavior. + * + * @param vars The scope to free. + * @return The parent scope. */ FLECS_API -int32_t ecs_rule_var_count( - const ecs_rule_t *rule); +ecs_script_vars_t* ecs_script_vars_pop( + ecs_script_vars_t *vars); -/** Find variable index. - * This operation looks up the index of a variable in the rule. This index can - * be used in operations like ecs_iter_set_var and ecs_iter_get_var. +/** Declare a variable. + * This operation declares a new variable in the current scope. If a variable + * with the specified name already exists, the operation will fail. * - * @param rule The rule. + * This operation does not allocate storage for the variable. This is done to + * allow for variables that point to existing storage, which prevents having + * to copy existing values to a variable scope. + * + * @param vars The variable scope. * @param name The variable name. - * @return The variable index. + * @return The new variable, or NULL if the operation failed. */ FLECS_API -int32_t ecs_rule_find_var( - const ecs_rule_t *rule, - const char *name); +ecs_script_var_t* ecs_script_vars_declare( + ecs_script_vars_t *vars, + const char *name); -/** Get variable name. - * This operation returns the variable name for an index. +/** Define a variable. + * This operation calls `ecs_script_vars_declare()` and allocates storage for + * the variable. If the type has a ctor, it will be called on the new storage. * - * @param rule The rule. - * @param var_id The variable index. + * The scope's stack allocator will be used to allocate the storage. After + * `ecs_script_vars_pop()` is called on the scope, the variable storage will no + * longer be valid. + * + * The operation will fail if the type argument is not a type. + * + * @param vars The variable scope. + * @param name The variable name. + * @param type The variable type. + * @return The new variable, or NULL if the operation failed. */ FLECS_API -const char* ecs_rule_var_name( - const ecs_rule_t *rule, - int32_t var_id); +ecs_script_var_t* ecs_script_vars_define_id( + ecs_script_vars_t *vars, + const char *name, + ecs_entity_t type); -/** Test if variable is an entity. - * Internally the rule engine has entity variables and table variables. When - * iterating through rule variables (by using ecs_rule_variable_count) only - * the values for entity variables are accessible. This operation enables an - * appliction to check if a variable is an entity variable. +#define ecs_script_vars_define(vars, name, type)\ + ecs_script_vars_define_id(vars, name, ecs_id(type)) + +/** Lookup a variable. + * This operation looks up a variable in the current scope. If the variable + * can't be found in the current scope, the operation will recursively search + * the parent scopes. * - * @param rule The rule. - * @param var_id The variable id. + * @param vars The variable scope. + * @param name The variable name. + * @return The variable, or NULL if one with the provided name does not exist. */ FLECS_API -bool ecs_rule_var_is_entity( - const ecs_rule_t *rule, - int32_t var_id); +ecs_script_var_t* ecs_script_vars_lookup( + const ecs_script_vars_t *vars, + const char *name); -/** Iterate a rule. - * Note that rule iterators may allocate memory, and that unless the iterator - * is iterated until completion, it may still hold resources. When stopping - * iteration before ecs_rule_next has returned false, use ecs_iter_fini to - * cleanup any remaining resources. +/** Lookup a variable by stack pointer. + * This operation provides a faster way to lookup variables that are always + * declared in the same order in a ecs_script_vars_t scope. * - * @param world The world. - * @param rule The rule. - * @return An iterator. + * The stack pointer of a variable can be obtained from the ecs_script_var_t + * type. The provided frame offset must be valid for the provided variable + * stack. If the frame offset is not valid, this operation will panic. + * + * @param vars The variable scope. + * @param sp The stack pointer to the variable. + * @return The variable. */ FLECS_API -ecs_iter_t ecs_rule_iter( - const ecs_world_t *world, - const ecs_rule_t *rule); +ecs_script_var_t* ecs_script_vars_from_sp( + const ecs_script_vars_t *vars, + int32_t sp); -/** Progress rule iterator. +/** Print variables. + * This operation prints all variables in the vars scope and parent scopes.asm * - * @param it The iterator. + * @param vars The variable scope. */ FLECS_API -bool ecs_rule_next( - ecs_iter_t *it); +void ecs_script_vars_print( + const ecs_script_vars_t *vars); -/** Progress instanced iterator. - * Should not be called unless you know what you're doing :-) +/** Preallocate space for variables. + * This operation preallocates space for the specified number of variables. This + * is a performance optimization only, and is not necessary before declaring + * variables in a scope. * - * @param it The iterator. + * @param vars The variable scope. + * @param count The number of variables to preallocate space for. */ FLECS_API -bool ecs_rule_next_instanced( - ecs_iter_t *it); +void ecs_script_vars_set_size( + ecs_script_vars_t *vars, + int32_t count); -/** Convert rule to a string. - * This will convert the rule program to a string which can aid in debugging - * the behavior of a rule. - * - * The returned string must be freed with ecs_os_free. - * - * @param rule The rule. - * @return The string +/** Convert iterator to vars + * This operation converts an iterator to a variable array. This allows for + * using iterator results in expressions. The operation only converts a + * single result at a time, and does not progress the iterator. + * + * Iterator fields with data will be made available as variables with as name + * the field index (e.g. "$1"). The operation does not check if reflection data + * is registered for a field type. If no reflection data is registered for the + * type, using the field variable in expressions will fail. + * + * Field variables will only contain single elements, even if the iterator + * returns component arrays. The offset parameter can be used to specify which + * element in the component arrays to return. The offset parameter must be + * smaller than it->count. + * + * The operation will create a variable for query variables that contain a + * single entity. + * + * The operation will attempt to use existing variables. If a variable does not + * yet exist, the operation will create it. If an existing variable exists with + * a mismatching type, the operation will fail. + * + * Accessing variables after progressing the iterator or after the iterator is + * destroyed will result in undefined behavior. + * + * If vars contains a variable that is not present in the iterator, the variable + * will not be modified. + * + * @param it The iterator to convert to variables. + * @param vars The variables to write to. + * @param offset The offset to the current element. */ FLECS_API -char* ecs_rule_str( - ecs_rule_t *rule); - +void ecs_script_vars_from_iter( + const ecs_iter_t *it, + ecs_script_vars_t *vars, + int offset); -#ifdef __cplusplus -} -#endif -#endif // FLECS_RULES_H +/* Standalone expression evaluation */ -#endif // FLECS_RULES +/** Used with ecs_expr_run(). */ +typedef struct ecs_expr_eval_desc_t { + const char *name; /**< Script name */ + const char *expr; /**< Full expression string */ + const ecs_script_vars_t *vars; /**< Variables accessible in expression */ + ecs_entity_t type; /**< Type of parsed value (optional) */ + ecs_entity_t (*lookup_action)( /**< Function for resolving entity identifiers */ + const ecs_world_t*, + const char *value, + void *ctx); + void *lookup_ctx; /**< Context passed to lookup function */ -#endif -#ifdef FLECS_SNAPSHOT -#ifdef FLECS_NO_SNAPSHOT -#error "FLECS_NO_SNAPSHOT failed: SNAPSHOT is required by other addons" -#endif -/** - * @file snapshot.h - * @brief Snapshot addon. - * - * A snapshot records the state of a world in a way so that it can be restored - * later. Snapshots work with POD components and non-POD components, provided - * that the appropriate lifecycle actions are registered for non-POD components. - * - * A snapshot is tightly coupled to a world. It is not possible to restore a - * snapshot from world A into world B. - */ + /** Disable constant folding (slower evaluation, faster parsing) */ + bool disable_folding; -#ifdef FLECS_SNAPSHOT + /** This option instructs the expression runtime to lookup variables by + * stack pointer instead of by name, which improves performance. Only enable + * when provided variables are always declared in the same order. */ + bool disable_dynamic_variable_binding; -#ifndef FLECS_SNAPSHOT_H -#define FLECS_SNAPSHOT_H + /** Allow for unresolved identifiers when parsing. Useful when entities can + * be created in between parsing & evaluating. */ + bool allow_unresolved_identifiers; -#ifdef __cplusplus -extern "C" { -#endif + ecs_script_runtime_t *runtime; /**< Reusable runtime (optional) */ -/** A snapshot stores the state of a world in a particular point in time. */ -typedef struct ecs_snapshot_t ecs_snapshot_t; + void *script_visitor; /**< For internal usage */ +} ecs_expr_eval_desc_t; -/** Create a snapshot. - * This operation makes a copy of all component in the world that matches the - * specified filter. +/** Run expression. + * This operation runs an expression and stores the result in the provided + * value. If the value contains a type that is different from the type of the + * expression, the expression will be cast to the value. * - * @param world The world to snapshot. - * @return The snapshot. - */ -FLECS_API -ecs_snapshot_t* ecs_snapshot_take( - ecs_world_t *world); - -/** Create a filtered snapshot. - * This operation is the same as ecs_snapshot_take, but accepts an iterator so - * an application can control what is stored by the snapshot. + * If the provided value for value.ptr is NULL, the value must be freed with + * ecs_value_free() afterwards. * - * @param iter An iterator to the data to be stored by the snapshot. - * @return The snapshot. + * @param world The world. + * @param ptr The pointer to the expression to parse. + * @param value The value containing type & pointer to write to. + * @param desc Configuration parameters for the parser. + * @return Pointer to the character after the last one read, or NULL if failed. */ FLECS_API -ecs_snapshot_t* ecs_snapshot_take_w_iter( - ecs_iter_t *iter); +const char* ecs_expr_run( + ecs_world_t *world, + const char *ptr, + ecs_value_t *value, + const ecs_expr_eval_desc_t *desc); -/** Restore a snapshot. - * This operation restores the world to the state it was in when the specified - * snapshot was taken. A snapshot can only be used once for restoring, as its - * data replaces the data that is currently in the world. - * This operation also resets the last issued entity handle, so any calls to - * ecs_new may return entity ids that have been issued before restoring the - * snapshot. - * - * The world in which the snapshot is restored must be the same as the world in - * which the snapshot is taken. - * - * @param world The world to restore the snapshot to. - * @param snapshot The snapshot to restore. +/** Parse expression. + * This operation parses an expression and returns an object that can be + * evaluated multiple times with ecs_expr_eval(). + * + * @param world The world. + * @param expr The expression string. + * @param desc Configuration parameters for the parser. + * @return A script object if parsing is successful, NULL if parsing failed. */ FLECS_API -void ecs_snapshot_restore( +ecs_script_t* ecs_expr_parse( ecs_world_t *world, - ecs_snapshot_t *snapshot); - -/** Obtain iterator to snapshot data. - * - * @param snapshot The snapshot to iterate over. - * @return Iterator to snapshot data. */ -FLECS_API -ecs_iter_t ecs_snapshot_iter( - ecs_snapshot_t *snapshot); + const char *expr, + const ecs_expr_eval_desc_t *desc); -/** Progress snapshot iterator. +/** Evaluate expression. + * This operation evaluates an expression parsed with ecs_expr_parse() + * and stores the result in the provided value. If the value contains a type + * that is different from the type of the expression, the expression will be + * cast to the value. * - * @param iter The snapshot iterator. - * @return True if more data is available, otherwise false. + * If the provided value for value.ptr is NULL, the value must be freed with + * ecs_value_free() afterwards. + * + * @param script The script containing the expression. + * @param value The value in which to store the expression result. + * @param desc Configuration parameters for the parser. + * @return Zero if successful, non-zero if failed. */ FLECS_API -bool ecs_snapshot_next( - ecs_iter_t *iter); +int ecs_expr_eval( + const ecs_script_t *script, + ecs_value_t *value, + const ecs_expr_eval_desc_t *desc); -/** Free snapshot resources. - * This frees resources associated with a snapshot without restoring it. +/** Evaluate interpolated expressions in string. + * This operation evaluates expressions in a string, and replaces them with + * their evaluated result. Supported expression formats are: + * - $variable_name + * - {expression} * - * @param snapshot The snapshot to free. + * The $, { and } characters can be escaped with a backslash (\). + * + * @param world The world. + * @param str The string to evaluate. + * @param vars The variables to use for evaluation. */ FLECS_API -void ecs_snapshot_free( - ecs_snapshot_t *snapshot); - -#ifdef __cplusplus -} -#endif +char* ecs_script_string_interpolate( + ecs_world_t *world, + const char *str, + const ecs_script_vars_t *vars); -#endif -#endif +/* Global const variables */ -#endif -#ifdef FLECS_STATS -#ifdef FLECS_NO_STATS -#error "FLECS_NO_STATS failed: STATS is required by other addons" -#endif -/** - * @file stats.h - * @brief Statistics addon. - * - * The statistics addon enables an application to obtain detailed metrics about - * the storage, systems and operations of a world. - */ +/** Used with ecs_const_var_init */ +typedef struct ecs_const_var_desc_t { + /* Variable name. */ + const char *name; -#ifdef FLECS_STATS + /* Variable parent (namespace). */ + ecs_entity_t parent; -#ifndef FLECS_STATS_H -#define FLECS_STATS_H + /* Variable type. */ + ecs_entity_t type; -#ifdef __cplusplus -extern "C" { -#endif + /* Pointer to value of variable. The value will be copied to an internal + * storage and does not need to be kept alive. */ + void *value; +} ecs_const_var_desc_t; -#define ECS_STAT_WINDOW (60) +/** Create a const variable that can be accessed by scripts. + * + * @param world The world. + * @param desc Const var parameters. + * @return The const var, or 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_const_var_init( + ecs_world_t *world, + ecs_const_var_desc_t *desc); -/** Simple value that indicates current state */ -typedef struct ecs_gauge_t { - float avg[ECS_STAT_WINDOW]; - float min[ECS_STAT_WINDOW]; - float max[ECS_STAT_WINDOW]; -} ecs_gauge_t; +#define ecs_const_var(world, ...)\ + ecs_const_var_init(world, &(ecs_const_var_desc_t)__VA_ARGS__) -/* Monotonically increasing counter */ -typedef struct ecs_counter_t { - ecs_gauge_t rate; /* Keep track of deltas too */ - float value[ECS_STAT_WINDOW]; -} ecs_counter_t; -typedef struct ecs_world_stats_t { - /* Allows struct to be initialized with {0} */ - int32_t dummy_; +/** Returns value for a const variable. + * This returns the value for a const variable that is created either with + * ecs_const_var_init, or in a script with "export const v: ...". + * + * @param world The world. + * @param var The variable associated with the entity. + */ +FLECS_API +ecs_value_t ecs_const_var_get( + const ecs_world_t *world, + ecs_entity_t var); - ecs_gauge_t entity_count; /* Number of entities */ - ecs_gauge_t entity_not_alive_count; /* Number of not alive (recyclable) entity ids */ +/* Functions */ - /* Components and ids */ - ecs_gauge_t id_count; /* Number of ids (excluding wildcards) */ - ecs_gauge_t tag_id_count; /* Number of tag ids (ids without data) */ - ecs_gauge_t component_id_count; /* Number of components ids (ids with data) */ - ecs_gauge_t pair_id_count; /* Number of pair ids */ - ecs_gauge_t wildcard_id_count; /* Number of wildcard ids */ - ecs_gauge_t component_count; /* Number of components (non-zero sized types) */ - ecs_counter_t id_create_count; /* Number of times id has been created */ - ecs_counter_t id_delete_count; /* Number of times id has been deleted */ +typedef struct ecs_vector_fn_callbacks_t { + ecs_vector_function_callback_t i8; + ecs_vector_function_callback_t i32; +} ecs_vector_fn_callbacks_t; - /* Tables */ - ecs_gauge_t table_count; /* Number of tables */ - ecs_gauge_t empty_table_count; /* Number of empty tables */ - ecs_gauge_t singleton_table_count; /* Number of singleton tables. Singleton tables are tables with just a single entity that contains itself */ - ecs_gauge_t tag_table_count; /* Number of tables with only tags */ - ecs_gauge_t trivial_table_count; /* Number of tables with only trivial components */ - ecs_gauge_t table_record_count; /* Number of table cache records */ - ecs_gauge_t table_storage_count; /* Number of table storages */ - ecs_counter_t table_create_count; /* Number of times table has been created */ - ecs_counter_t table_delete_count; /* Number of times table has been deleted */ +/** Used with ecs_function_init and ecs_method_init */ +typedef struct ecs_function_desc_t { + /** Function name. */ + const char *name; + + /** Parent of function. For methods the parent is the type for which the + * method will be registered. */ + ecs_entity_t parent; - /* Queries & events */ - ecs_gauge_t query_count; /* Number of queries */ - ecs_gauge_t trigger_count; /* Number of triggers */ - ecs_gauge_t observer_count; /* Number of observers */ - ecs_gauge_t system_count; /* Number of systems */ - - /* Deferred operations */ - ecs_counter_t new_count; - ecs_counter_t bulk_new_count; - ecs_counter_t delete_count; - ecs_counter_t clear_count; - ecs_counter_t add_count; - ecs_counter_t remove_count; - ecs_counter_t set_count; - ecs_counter_t discard_count; + /** Function parameters. */ + ecs_script_parameter_t params[FLECS_SCRIPT_FUNCTION_ARGS_MAX]; - /* Timing */ - ecs_counter_t world_time_total_raw; /* Actual time passed since simulation start (first time progress() is called) */ - ecs_counter_t world_time_total; /* Simulation time passed since simulation start. Takes into account time scaling */ - ecs_counter_t frame_time_total; /* Time spent processing a frame. Smaller than world_time_total when load is not 100% */ - ecs_counter_t system_time_total; /* Time spent on processing systems. */ - ecs_counter_t merge_time_total; /* Time spent on merging deferred actions. */ - ecs_gauge_t fps; /* Frames per second. */ - ecs_gauge_t delta_time; /* Delta_time. */ - - /* Frame data */ - ecs_counter_t frame_count_total; /* Number of frames processed. */ - ecs_counter_t merge_count_total; /* Number of merges executed. */ - ecs_counter_t pipeline_build_count_total; /* Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ - ecs_counter_t systems_ran_frame; /* Number of systems ran in the last frame. */ + /** Function return type. */ + ecs_entity_t return_type; - /** Current position in ringbuffer */ - int32_t t; -} ecs_world_stats_t; + /** Function implementation. */ + ecs_function_callback_t callback; -/* Statistics for a single query (use ecs_get_query_stats) */ -typedef struct ecs_query_stats_t { - ecs_gauge_t matched_table_count; /* Number of matched non-empty tables. This is the number of tables - * iterated over when evaluating a query. */ + /** Vector function implementations. + * Set these callbacks if a function has one or more arguments of type + * flecs.script vector, and optionally a return type of flecs.script.vector. + * + * The flecs.script.vector type allows a function to be called with types + * that meet the following constraints: + * - The same type is provided for all arguments of type flecs.script.vector + * - The provided type has one or members of the same type + * - The member type must be a primitive type + * - The vector_callbacks array has an implementation for the primitive type. + * + * This allows for statements like: + * @code + * const a = Rgb: {100, 150, 250} + * const b = Rgb: {10, 10, 10} + * const r = lerp(a, b, 0.1) + * @endcode + * + * which would otherwise have to be written out as: + * + * @code + * const r = Rgb: { + * lerp(a.r, b.r, 0.1), + * lerp(a.g, b.g, 0.1), + * lerp(a.b, b.b, 0.1) + * } + * @endcode + * + * To register vector functions, do: + * + * @code + * ecs_function(world, { + * .name = "lerp", + * .return_type = EcsScriptVectorType, + * .params = { + * { .name = "a", .type = EcsScriptVectorType }, + * { .name = "b", .type = EcsScriptVectorType }, + * { .name = "t", .type = ecs_id(ecs_f64_t) } + * }, + * .vector_callbacks = { + * [EcsF32] = flecs_lerp32, + * [EcsF64] = flecs_lerp64 + * } + * }); + * @endcode + * + */ + ecs_vector_function_callback_t vector_callbacks[FLECS_SCRIPT_VECTOR_FUNCTION_COUNT]; - ecs_gauge_t matched_empty_table_count; /* Number of matched empty tables. Empty tables are not iterated over when - * evaluating a query. */ - - ecs_gauge_t matched_entity_count; /* Number of matched entities across all tables */ + /** Context passed to function implementation. */ + void *ctx; +} ecs_function_desc_t; - /** Current position in ringbuffer */ - int32_t t; -} ecs_query_stats_t; +/** Create new function. + * This operation creates a new function that can be called from a script. + * + * @param world The world. + * @param desc Function init parameters. + * @return The function, or 0 if failed. +*/ +FLECS_API +ecs_entity_t ecs_function_init( + ecs_world_t *world, + const ecs_function_desc_t *desc); -/** Statistics for a single system (use ecs_get_system_stats) */ -typedef struct ecs_system_stats_t { - ecs_query_stats_t query_stats; - ecs_counter_t time_spent; /* Time spent processing a system */ - ecs_counter_t invoke_count; /* Number of times system is invoked */ - ecs_gauge_t active; /* Whether system is active (is matched with >0 entities) */ - ecs_gauge_t enabled; /* Whether system is enabled */ -} ecs_system_stats_t; +#define ecs_function(world, ...)\ + ecs_function_init(world, &(ecs_function_desc_t)__VA_ARGS__) -/** Statistics for all systems in a pipeline. */ -typedef struct ecs_pipeline_stats_t { - /** Vector with system ids of all systems in the pipeline. The systems are - * stored in the order they are executed. Merges are represented by a 0. */ - ecs_vector_t *systems; +/** Create new method. + * This operation creates a new method that can be called from a script. A + * method is like a function, except that it can be called on every instance of + * a type. + * + * Methods automatically receive the instance on which the method is invoked as + * first argument. + * + * @param world Method The world. + * @param desc Method init parameters. + * @return The function, or 0 if failed. +*/ +FLECS_API +ecs_entity_t ecs_method_init( + ecs_world_t *world, + const ecs_function_desc_t *desc); - /** Map with system statistics. For each system in the systems vector, an - * entry in the map exists of type ecs_system_stats_t. */ - ecs_map_t *system_stats; +#define ecs_method(world, ...)\ + ecs_method_init(world, &(ecs_function_desc_t)__VA_ARGS__) - int32_t system_count; /* Number of systems in pipeline */ - int32_t active_system_count; /* Number of active systems in pipeline */ - int32_t rebuild_count; /* Number of times pipeline has rebuilt */ -} ecs_pipeline_stats_t; -/** Get world statistics. - * Obtain statistics for the provided world. This operation loops several times - * over the tables in the world, and can impact application performance. +/* Value serialization */ + +/** Serialize value into expression string. + * This operation serializes a value of the provided type to a string. The + * memory pointed to must be large enough to contain a value of the used type. * * @param world The world. - * @param stats Out parameter for statistics. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @return String with expression, or NULL if failed. */ -FLECS_API -void ecs_get_world_stats( +FLECS_API +char* ecs_ptr_to_expr( const ecs_world_t *world, - ecs_world_stats_t *stats); + ecs_entity_t type, + const void *data); -/** Print world statistics. - * Print statistics obtained by ecs_get_world_statistics and in the - * ecs_world_info_t struct. - * +/** Serialize value into expression buffer. + * Same as ecs_ptr_to_expr(), but serializes to an ecs_strbuf_t instance. + * * @param world The world. - * @param stats The statistics to print. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param buf The strbuf to append the string to. + * @return Zero if success, non-zero if failed. */ -FLECS_API -void ecs_dump_world_stats( +FLECS_API +int ecs_ptr_to_expr_buf( const ecs_world_t *world, - const ecs_world_stats_t *stats); + ecs_entity_t type, + const void *data, + ecs_strbuf_t *buf); -/** Get query statistics. - * Obtain statistics for the provided query. +/** Similar as ecs_ptr_to_expr(), but serializes values to string. + * Whereas the output of ecs_ptr_to_expr() is a valid expression, the output of + * ecs_ptr_to_str() is a string representation of the value. In most cases the + * output of the two operations is the same, but there are some differences: + * - Strings are not quoted * * @param world The world. - * @param query The query. - * @param stats Out parameter for statistics. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @return String with result, or NULL if failed. */ -FLECS_API -void ecs_get_query_stats( +FLECS_API +char* ecs_ptr_to_str( const ecs_world_t *world, - const ecs_query_t *query, - ecs_query_stats_t *stats); + ecs_entity_t type, + const void *data); -#ifdef FLECS_SYSTEM -/** Get system statistics. - * Obtain statistics for the provided system. +/** Serialize value into string buffer. + * Same as ecs_ptr_to_str(), but serializes to an ecs_strbuf_t instance. * * @param world The world. - * @param system The system. - * @param stats Out parameter for statistics. - * @return true if success, false if not a system. + * @param type The type of the value to serialize. + * @param data The value to serialize. + * @param buf The strbuf to append the string to. + * @return Zero if success, non-zero if failed. */ -FLECS_API -bool ecs_get_system_stats( +FLECS_API +int ecs_ptr_to_str_buf( const ecs_world_t *world, - ecs_entity_t system, - ecs_system_stats_t *stats); -#endif + ecs_entity_t type, + const void *data, + ecs_strbuf_t *buf); -#ifdef FLECS_PIPELINE -/** Get pipeline statistics. - * Obtain statistics for the provided pipeline. - * - * @param world The world. - * @param pipeline The pipeline. - * @param stats Out parameter for statistics. - * @return true if success, false if not a pipeline. - */ -FLECS_API -bool ecs_get_pipeline_stats( - ecs_world_t *world, - ecs_entity_t pipeline, - ecs_pipeline_stats_t *stats); +typedef struct ecs_expr_node_t ecs_expr_node_t; -/** Free pipeline stats. +/** Script module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsScript) + * @endcode * - * @param stats The stats to free. + * @param world The world. */ FLECS_API -void ecs_pipeline_stats_fini( - ecs_pipeline_stats_t *stats); - -#endif - -FLECS_API -void ecs_gauge_reduce( - ecs_gauge_t *dst, - int32_t t_dst, - ecs_gauge_t *src, - int32_t t_src); +void FlecsScriptImport( + ecs_world_t *world); #ifdef __cplusplus } @@ -11677,7117 +16368,14994 @@ void ecs_gauge_reduce( #endif +/** @} */ + #endif #endif -#ifdef FLECS_PARSER -#ifdef FLECS_NO_PARSER -#error "FLECS_NO_PARSER failed: PARSER is required by other addons" + +#ifdef FLECS_DOC +#ifdef FLECS_NO_DOC +#error "FLECS_NO_DOC failed: DOC is required by other addons" #endif /** - * @file parser.h - * @brief Parser addon. + * @file addons/doc.h + * @brief Doc module. * - * The parser addon parses string expressions into lists of terms, and can be - * used to construct filters, queries and types. + * The doc module allows for documenting entities (and thus components, systems) + * by adding brief and/or detailed descriptions as components. Documentation + * added with the doc module can be retrieved at runtime, and can be used by + * tooling such as UIs or documentation frameworks. */ -#ifdef FLECS_PARSER +#ifdef FLECS_DOC -#ifndef FLECS_PARSER_H -#define FLECS_PARSER_H +#ifndef FLECS_DOC_H +#define FLECS_DOC_H + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif #ifdef __cplusplus extern "C" { #endif -/** Skip whitespace characters. - * This function skips whitespace characters. Does not skip newlines. - * - * @param ptr Pointer to (potential) whitespaces to skip. - * @return Pointer to the next non-whitespace character. +/** + * @defgroup c_addons_doc Doc + * @ingroup c_addons + * Utilities for documenting entities, components and systems. + * + * @{ */ -FLECS_API -const char* ecs_parse_whitespace( - const char *ptr); -/** Skip whitespace and newline characters. - * This function skips whitespace characters. - * - * @param ptr Pointer to (potential) whitespaces to skip. - * @return Pointer to the next non-whitespace character. +FLECS_API extern const ecs_entity_t ecs_id(EcsDocDescription); /**< Component id for EcsDocDescription. */ + +/** Tag for adding a UUID to entities. + * Added to an entity as (EcsDocDescription, EcsUuid) by ecs_doc_set_uuid(). */ -FLECS_API -const char* ecs_parse_eol_and_whitespace( - const char *ptr); +FLECS_API extern const ecs_entity_t EcsDocUuid; -/** Parse digit. - * This function will parse until the first non-digit character is found. The - * provided expression must contain at least one digit character. - * - * @param ptr The expression to parse. - * @param token The output buffer. - * @return Pointer to the first non-digit character. +/** Tag for adding brief descriptions to entities. + * Added to an entity as (EcsDocDescription, EcsBrief) by ecs_doc_set_brief(). */ -FLECS_API -const char* ecs_parse_digit( - const char *ptr, - char *token); +FLECS_API extern const ecs_entity_t EcsDocBrief; -/** Skip whitespaces and comments. - * This function skips whitespace characters and comments (single line, //). - * - * @param ptr pointer to (potential) whitespaces/comments to skip. - * @return pointer to the next non-whitespace character. +/** Tag for adding detailed descriptions to entities. + * Added to an entity as (EcsDocDescription, EcsDocDetail) by ecs_doc_set_detail(). */ -FLECS_API -const char* ecs_parse_fluff( - const char *ptr, - char **last_comment); +FLECS_API extern const ecs_entity_t EcsDocDetail; -/** Parse a single token. - * This function can be used as simple tokenizer by other parsers. - * - * @param name of program (used for logging). - * @param expr pointer to token to parse. - * @param ptr pointer to first character to parse. - * @param token_out Parsed token (buffer should be ECS_MAX_TOKEN_SIZE large) - * @return Pointer to the next token, or NULL if error occurred. +/** Tag for adding a link to entities. + * Added to an entity as (EcsDocDescription, EcsDocLink) by ecs_doc_set_link(). */ -FLECS_API -const char* ecs_parse_token( - const char *name, - const char *expr, - const char *ptr, - char *token_out); +FLECS_API extern const ecs_entity_t EcsDocLink; -/** Parse term in expression. - * This operation parses a single term in an expression and returns a pointer - * to the next term expression. - * - * If the returned pointer points to the 0-terminator, the expression is fully - * parsed. The function would typically be called in a while loop: +/** Tag for adding a color to entities. + * Added to an entity as (EcsDocDescription, EcsDocColor) by ecs_doc_set_link(). + */ +FLECS_API extern const ecs_entity_t EcsDocColor; + +/** Component that stores description. + * Used as pair together with the following tags to store entity documentation: + * - EcsName + * - EcsDocBrief + * - EcsDocDetail + * - EcsDocLink + * - EcsDocColor + */ +typedef struct EcsDocDescription { + char *value; +} EcsDocDescription; + +/** Add UUID to entity. + * Associate entity with an (external) UUID. * - * const char *ptr = expr; - * while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))) { } + * @param world The world. + * @param entity The entity to which to add the UUID. + * @param uuid The UUID to add. * - * The operation does not attempt to find entity ids from the names in the - * expression. Use the ecs_term_resolve_ids function to resolve the identifiers - * in the parsed term. + * @see ecs_doc_get_uuid() + * @see flecs::doc::set_uuid() + * @see flecs::entity_builder::set_doc_uuid() + */ +FLECS_API +void ecs_doc_set_uuid( + ecs_world_t *world, + ecs_entity_t entity, + const char *uuid); + +/** Add human-readable name to entity. + * Contrary to entity names, human readable names do not have to be unique and + * can contain special characters used in the query language like '*'. * - * The returned term will in most cases contain allocated resources, which - * should freed (or used) by the application. To free the resources for a term, - * use the ecs_term_free function. + * @param world The world. + * @param entity The entity to which to add the name. + * @param name The name to add. * - * The parser accepts expressions in the legacy string format. + * @see ecs_doc_get_name() + * @see flecs::doc::set_name() + * @see flecs::entity_builder::set_doc_name() + */ +FLECS_API +void ecs_doc_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name); + +/** Add brief description to entity. * * @param world The world. - * @param name The name of the expression (optional, improves error logs) - * @param expr The expression to parse (optional, improves error logs) - * @param ptr The pointer to the current term (must be in expr). - * @param term_out Out parameter for the term. - * @return pointer to next term if successful, NULL if failed. + * @param entity The entity to which to add the description. + * @param description The description to add. + * + * @see ecs_doc_get_brief() + * @see flecs::doc::set_brief() + * @see flecs::entity_builder::set_doc_brief() */ FLECS_API -char* ecs_parse_term( - const ecs_world_t *world, - const char *name, - const char *expr, - const char *ptr, - ecs_term_t *term_out); - -#ifdef __cplusplus -} -#endif // __cplusplus - -#endif // FLECS_PARSER_H - -#endif // FLECS_PARSER +void ecs_doc_set_brief( + ecs_world_t *world, + ecs_entity_t entity, + const char *description); -#endif -#ifdef FLECS_HTTP -#ifdef FLECS_NO_HTTP -#error "FLECS_NO_HTTP failed: HTTP is required by other addons" -#endif -/** - * @file http.h - * @brief HTTP addon. - * - * Minimalistic HTTP server that can receive and reply to simple HTTP requests. - * The main goal of this addon is to enable remotely connecting to a running - * Flecs application (for example, with a web-based UI) and request/visualize - * data from the ECS world. - * - * Each server instance creates a single thread used for receiving requests. - * Receiving requests are enqueued and handled when the application calls - * ecs_http_server_dequeue. This increases latency of request handling vs. - * responding directly in the receive thread, but is better suited for - * retrieving data from ECS applications, as requests can be processed by an ECS - * system without having to lock the world. - * - * This server is intended to be used in a development environment. +/** Add detailed description to entity. + * + * @param world The world. + * @param entity The entity to which to add the description. + * @param description The description to add. + * + * @see ecs_doc_get_detail() + * @see flecs::doc::set_detail() + * @see flecs::entity_builder::set_doc_detail() */ +FLECS_API +void ecs_doc_set_detail( + ecs_world_t *world, + ecs_entity_t entity, + const char *description); -#ifdef FLECS_HTTP - -#if !defined(FLECS_OS_API_IMPL) && !defined(FLECS_NO_OS_API_IMPL) -#define FLECS_OS_API_IMPL -#endif - -#ifndef FLECS_HTTP_H -#define FLECS_HTTP_H - -/* Maximum number of headers in request */ -#define ECS_HTTP_HEADER_COUNT_MAX (32) - -/* Maximum number of query parameters in request */ -#define ECS_HTTP_QUERY_PARAM_COUNT_MAX (32) - -#ifdef __cplusplus -extern "C" { -#endif - -/** HTTP server */ -typedef struct ecs_http_server_t ecs_http_server_t; - -/** A connection manages communication with the remote host */ -typedef struct { - uint64_t id; - ecs_http_server_t *server; - - char host[128]; - char port[16]; -} ecs_http_connection_t; - -/** Helper type used for headers & URL query parameters */ -typedef struct { - const char *key; - const char *value; -} ecs_http_key_value_t; - -/** Supported request methods */ -typedef enum { - EcsHttpGet, - EcsHttpPost, - EcsHttpPut, - EcsHttpDelete, - EcsHttpOptions, - EcsHttpMethodUnsupported -} ecs_http_method_t; - -/** A request */ -typedef struct { - uint64_t id; - - ecs_http_method_t method; - char *path; - char *body; - ecs_http_key_value_t headers[ECS_HTTP_HEADER_COUNT_MAX]; - ecs_http_key_value_t params[ECS_HTTP_HEADER_COUNT_MAX]; - int32_t header_count; - int32_t param_count; - - ecs_http_connection_t *conn; -} ecs_http_request_t; - -/** A reply */ -typedef struct { - int code; /* default = 200 */ - ecs_strbuf_t body; /* default = "" */ - const char* status; /* default = OK */ - const char* content_type; /* default = application/json */ - ecs_strbuf_t headers; /* default = "" */ -} ecs_http_reply_t; - -#define ECS_HTTP_REPLY_INIT \ - (ecs_http_reply_t){200, ECS_STRBUF_INIT, "OK", "application/json", ECS_STRBUF_INIT} - -/** Request callback. - * Invoked for each valid request. The function should populate the reply and - * return true. When the function returns false, the server will reply with a - * 404 (Not found) code. */ -typedef bool (*ecs_http_reply_action_t)( - const ecs_http_request_t* request, - ecs_http_reply_t *reply, - void *ctx); +/** Add link to external documentation to entity. + * + * @param world The world. + * @param entity The entity to which to add the link. + * @param link The link to add. + * + * @see ecs_doc_get_link() + * @see flecs::doc::set_link() + * @see flecs::entity_builder::set_doc_link() + */ +FLECS_API +void ecs_doc_set_link( + ecs_world_t *world, + ecs_entity_t entity, + const char *link); -/** Used with ecs_http_server_init. */ -typedef struct { - ecs_http_reply_action_t callback; /* Function called for each request */ - void *ctx; /* Passed to callback (optional) */ - uint16_t port; /* HTTP port */ - const char *ipaddr; /* Interface to listen on (optional) */ -} ecs_http_server_desc_t; +/** Add color to entity. + * UIs can use color as hint to improve visualizing entities. + * + * @param world The world. + * @param entity The entity to which to add the link. + * @param color The color to add. + * + * @see ecs_doc_get_color() + * @see flecs::doc::set_color() + * @see flecs::entity_builder::set_doc_color() + */ +FLECS_API +void ecs_doc_set_color( + ecs_world_t *world, + ecs_entity_t entity, + const char *color); -/** Create server. - * Use ecs_http_server_start to start receiving requests. - * - * @param desc Server configuration parameters. - * @return The new server, or NULL if creation failed. +/** Get UUID from entity. + * @param world The world. + * @param entity The entity from which to get the UUID. + * @return The UUID. + * + * @see ecs_doc_set_uuid() + * @see flecs::doc::get_uuid() + * @see flecs::entity_view::get_doc_uuid() */ FLECS_API -ecs_http_server_t* ecs_http_server_init( - const ecs_http_server_desc_t *desc); +const char* ecs_doc_get_uuid( + const ecs_world_t *world, + ecs_entity_t entity); -/** Destroy server. - * This operation will stop the server if it was still running. - * - * @param server The server to destroy. +/** Get human readable name from entity. + * If entity does not have an explicit human readable name, this operation will + * return the entity name. + * + * To test if an entity has a human readable name, use: + * + * @code + * ecs_has_pair(world, e, ecs_id(EcsDocDescription), EcsName); + * @endcode + * + * Or in C++: + * + * @code + * e.has(flecs::Name); + * @endcode + * + * @param world The world. + * @param entity The entity from which to get the name. + * @return The name. + * + * @see ecs_doc_set_name() + * @see flecs::doc::get_name() + * @see flecs::entity_view::get_doc_name() */ FLECS_API -void ecs_http_server_fini( - ecs_http_server_t* server); +const char* ecs_doc_get_name( + const ecs_world_t *world, + ecs_entity_t entity); -/** Start server. - * After this operation the server will be able to accept requests. - * - * @param server The server to start. - * @return Zero if successful, non-zero if failed. +/** Get brief description from entity. + * + * @param world The world. + * @param entity The entity from which to get the description. + * @return The description. + * + * @see ecs_doc_set_brief() + * @see flecs::doc::get_brief() + * @see flecs::entity_view::get_doc_brief() */ FLECS_API -int ecs_http_server_start( - ecs_http_server_t* server); +const char* ecs_doc_get_brief( + const ecs_world_t *world, + ecs_entity_t entity); -/** Process server requests. - * This operation invokes the reply callback for each received request. No new - * requests will be enqueued while processing requests. - * - * @param server The server for which to process requests. +/** Get detailed description from entity. + * + * @param world The world. + * @param entity The entity from which to get the description. + * @return The description. + * + * @see ecs_doc_set_detail() + * @see flecs::doc::get_detail() + * @see flecs::entity_view::get_doc_detail() */ FLECS_API -void ecs_http_server_dequeue( - ecs_http_server_t* server, - float delta_time); +const char* ecs_doc_get_detail( + const ecs_world_t *world, + ecs_entity_t entity); -/** Stop server. - * After this operation no new requests can be received. - * - * @param server The server. +/** Get link to external documentation from entity. + * + * @param world The world. + * @param entity The entity from which to get the link. + * @return The link. + * + * @see ecs_doc_set_link() + * @see flecs::doc::get_link() + * @see flecs::entity_view::get_doc_link() */ FLECS_API -void ecs_http_server_stop( - ecs_http_server_t* server); +const char* ecs_doc_get_link( + const ecs_world_t *world, + ecs_entity_t entity); -/** Find header in request. - * - * @param req The request. - * @param name name of the header to find - * @return The header value, or NULL if not found. -*/ +/** Get color from entity. + * + * @param world The world. + * @param entity The entity from which to get the color. + * @return The color. + * + * @see ecs_doc_set_color() + * @see flecs::doc::get_color() + * @see flecs::entity_view::get_doc_color() + */ FLECS_API -const char* ecs_http_get_header( - const ecs_http_request_t* req, - const char* name); +const char* ecs_doc_get_color( + const ecs_world_t *world, + ecs_entity_t entity); -/** Find query parameter in request. +/** Doc module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsDoc) + * @endcode * - * @param req The request. - * @param name The parameter name. - * @return The decoded parameter value, or NULL if not found. + * @param world The world. */ FLECS_API -const char* ecs_http_get_param( - const ecs_http_request_t* req, - const char* name); +void FlecsDocImport( + ecs_world_t *world); + +/** @} */ #ifdef __cplusplus } #endif -#endif // FLECS_HTTP_H +#endif -#endif // FLECS_HTTP +#endif + +#endif + +#ifdef FLECS_META +#ifdef FLECS_NO_META +#error "FLECS_NO_META failed: META is required by other addons" +#endif +/** + * @file addons/meta.h + * @brief Meta addon. + * + * The meta addon enables reflecting on component data. Types are stored as + * entities, with components that store the reflection data. A type has at least + * two components: + * + * - EcsComponent: core component, contains size & alignment + * - EcsType: component that indicates what kind of type the entity is + * + * Additionally the type may have an additional component that contains the + * reflection data for the type. For example, structs have these components: + * + * - EcsComponent + * - EcsType + * - EcsStruct + * + * Structs can be populated by adding child entities with the EcsMember + * component. Adding a child with a Member component to an entity will + * automatically add the EcsStruct component to the parent. + * + * Enums/bitmasks can be populated by adding child entities with the Constant + * tag. By default constants are automatically assigned values when they are + * added to the enum/bitmask. The parent entity must have the EcsEnum or + * EcsBitmask component before adding the constants. + * + * To create enum constants with a manual value, set (Constant, i32) to the + * desired value. To create bitmask constants with a manual value, set + * (Constant, u32) to the desired value. Constants with manual values should not + * conflict with other constants. + * + * The _init APIs are convenience wrappers around creating the entities and + * components for the types. + * + * When a type is created it automatically receives the EcsComponent and + * EcsType components. The former means that the resulting type can be + * used as a regular component: + * + * @code + * // Create Position type + * ecs_entity_t pos = ecs_struct_init(world, &(ecs_struct_desc_t){ + * .entity.name = "Position", + * .members = { + * {"x", ecs_id(ecs_f32_t)}, + * {"y", ecs_id(ecs_f32_t)} + * } + * }); + * + * // Create entity with Position component + * ecs_entity_t e = ecs_new_w_id(world, pos); + * @endcode + * + * Type entities do not have to be named. + */ + +#ifdef FLECS_META + +/** + * @defgroup c_addons_meta Meta + * @ingroup c_addons + * Flecs reflection framework. + * + * @{ + */ + +#ifndef FLECS_MODULE +#define FLECS_MODULE +#endif + +#ifndef FLECS_META_H +#define FLECS_META_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Max number of constants/members that can be specified in desc structs. */ +#define ECS_MEMBER_DESC_CACHE_SIZE (32) + +/** Primitive type definitions. + * These typedefs allow the builtin primitives to be used as regular components: + * + * @code + * ecs_set(world, e, ecs_i32_t, {10}); + * @endcode + * + * Or a more useful example (create an enum constant with a manual value): + * + * @code + * ecs_set_pair_second(world, e, EcsConstant, ecs_i32_t, {10}); + * @endcode + */ + +typedef bool ecs_bool_t; /**< Builtin bool type */ +typedef char ecs_char_t; /**< Builtin char type */ +typedef unsigned char ecs_byte_t; /**< Builtin ecs_byte type */ +typedef uint8_t ecs_u8_t; /**< Builtin u8 type */ +typedef uint16_t ecs_u16_t; /**< Builtin u16 type */ +typedef uint32_t ecs_u32_t; /**< Builtin u32 type */ +typedef uint64_t ecs_u64_t; /**< Builtin u64 type */ +typedef uintptr_t ecs_uptr_t; /**< Builtin uptr type */ +typedef int8_t ecs_i8_t; /**< Builtin i8 type */ +typedef int16_t ecs_i16_t; /**< Builtin i16 type */ +typedef int32_t ecs_i32_t; /**< Builtin i32 type */ +typedef int64_t ecs_i64_t; /**< Builtin i64 type */ +typedef intptr_t ecs_iptr_t; /**< Builtin iptr type */ +typedef float ecs_f32_t; /**< Builtin f32 type */ +typedef double ecs_f64_t; /**< Builtin f64 type */ +typedef char* ecs_string_t; /**< Builtin string type */ + +/* Meta module component ids */ +FLECS_API extern const ecs_entity_t ecs_id(EcsType); /**< Id for component added to all types with reflection data. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsTypeSerializer); /**< Id for component that stores a type specific serializer. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsPrimitive); /**< Id for component that stores reflection data for a primitive type. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsEnum); /**< Id for component that stores reflection data for an enum type. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsBitmask); /**< Id for component that stores reflection data for a bitmask type. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsConstants); /**< Id for component that stores reflection data for a constants. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsMember); /**< Id for component that stores reflection data for struct members. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsMemberRanges); /**< Id for component that stores min/max ranges for member values. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsStruct); /**< Id for component that stores reflection data for a struct type. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsArray); /**< Id for component that stores reflection data for an array type. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsVector); /**< Id for component that stores reflection data for a vector type. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsOpaque); /**< Id for component that stores reflection data for an opaque type. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsUnit); /**< Id for component that stores unit data. */ +FLECS_API extern const ecs_entity_t ecs_id(EcsUnitPrefix); /**< Id for component that stores unit prefix data. */ +FLECS_API extern const ecs_entity_t EcsQuantity; /**< Tag added to unit quantities. */ + +/* Primitive type component ids */ + +FLECS_API extern const ecs_entity_t ecs_id(ecs_bool_t); /**< Builtin boolean type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_char_t); /**< Builtin char type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_byte_t); /**< Builtin byte type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_u8_t); /**< Builtin 8 bit unsigned int type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_u16_t); /**< Builtin 16 bit unsigned int type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_u32_t); /**< Builtin 32 bit unsigned int type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_u64_t); /**< Builtin 64 bit unsigned int type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_uptr_t); /**< Builtin pointer sized unsigned int type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_i8_t); /**< Builtin 8 bit signed int type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_i16_t); /**< Builtin 16 bit signed int type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_i32_t); /**< Builtin 32 bit signed int type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_i64_t); /**< Builtin 64 bit signed int type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_iptr_t); /**< Builtin pointer sized signed int type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_f32_t); /**< Builtin 32 bit floating point type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_f64_t); /**< Builtin 64 bit floating point type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_string_t); /**< Builtin string type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_entity_t); /**< Builtin entity type. */ +FLECS_API extern const ecs_entity_t ecs_id(ecs_id_t); /**< Builtin (component) id type. */ + +/** Type kinds supported by meta addon */ +typedef enum ecs_type_kind_t { + EcsPrimitiveType, + EcsBitmaskType, + EcsEnumType, + EcsStructType, + EcsArrayType, + EcsVectorType, + EcsOpaqueType, + EcsTypeKindLast = EcsOpaqueType +} ecs_type_kind_t; + +/** Component that is automatically added to every type with the right kind. */ +typedef struct EcsType { + ecs_type_kind_t kind; /**< Type kind. */ + bool existing; /**< Did the type exist or is it populated from reflection */ + bool partial; /**< Is the reflection data a partial type description */ +} EcsType; + +/** Primitive type kinds supported by meta addon */ +typedef enum ecs_primitive_kind_t { + EcsBool = 1, + EcsChar, + EcsByte, + EcsU8, + EcsU16, + EcsU32, + EcsU64, + EcsI8, + EcsI16, + EcsI32, + EcsI64, + EcsF32, + EcsF64, + EcsUPtr, + EcsIPtr, + EcsString, + EcsEntity, + EcsId, + EcsPrimitiveKindLast = EcsId +} ecs_primitive_kind_t; + +/** Component added to primitive types */ +typedef struct EcsPrimitive { + ecs_primitive_kind_t kind; /**< Primitive type kind. */ +} EcsPrimitive; + +/** Component added to member entities */ +typedef struct EcsMember { + ecs_entity_t type; /**< Member type. */ + int32_t count; /**< Number of elements for inline arrays. Leave to 0 for non-array members. */ + ecs_entity_t unit; /**< Member unit. */ + int32_t offset; /**< Member offset. */ + bool use_offset; /**< If offset should be explicitly used. */ +} EcsMember; + +/** Type expressing a range for a member value */ +typedef struct ecs_member_value_range_t { + double min; /**< Min member value. */ + double max; /**< Max member value. */ +} ecs_member_value_range_t; + +/** Component added to member entities to express valid value ranges */ +typedef struct EcsMemberRanges { + ecs_member_value_range_t value; /**< Member value range. */ + ecs_member_value_range_t warning; /**< Member value warning range. */ + ecs_member_value_range_t error; /**< Member value error range. */ +} EcsMemberRanges; + +/** Element type of members vector in EcsStruct */ +typedef struct ecs_member_t { + /** Must be set when used with ecs_struct_desc_t */ + const char *name; + + /** Member type. */ + ecs_entity_t type; + + /** Element count (for inline arrays). May be set when used with + * ecs_struct_desc_t. Leave to 0 for non-array members. */ + int32_t count; + + /** May be set when used with ecs_struct_desc_t. Member offset. */ + int32_t offset; + + /** May be set when used with ecs_struct_desc_t, will be auto-populated if + * type entity is also a unit */ + ecs_entity_t unit; + + /** Set to true to prevent automatic offset computation. This option should + * be used when members are registered out of order or where calculation of + * member offsets doesn't match C type offsets. */ + bool use_offset; + + /** Numerical range that specifies which values member can assume. This + * range may be used by UI elements such as a progress bar or slider. The + * value of a member should not exceed this range. */ + ecs_member_value_range_t range; + + /** Numerical range outside of which the value represents an error. This + * range may be used by UI elements to style a value. */ + ecs_member_value_range_t error_range; + + /** Numerical range outside of which the value represents an warning. This + * range may be used by UI elements to style a value. */ + ecs_member_value_range_t warning_range; + + /** Should not be set by ecs_struct_desc_t */ + ecs_size_t size; + + /** Should not be set by ecs_struct_desc_t */ + ecs_entity_t member; +} ecs_member_t; + +/** Component added to struct type entities */ +typedef struct EcsStruct { + /** Populated from child entities with Member component */ + ecs_vec_t members; /* vector */ +} EcsStruct; + +/** Type that describes an enum constant */ +typedef struct ecs_enum_constant_t { + /** Must be set when used with ecs_enum_desc_t */ + const char *name; + + /** May be set when used with ecs_enum_desc_t */ + int64_t value; + + /** For when the underlying type is unsigned */ + uint64_t value_unsigned; + + /** Should not be set by ecs_enum_desc_t */ + ecs_entity_t constant; +} ecs_enum_constant_t; + +/** Component added to enum type entities */ +typedef struct EcsEnum { + ecs_entity_t underlying_type; +} EcsEnum; + +/** Type that describes an bitmask constant */ +typedef struct ecs_bitmask_constant_t { + /** Must be set when used with ecs_bitmask_desc_t */ + const char *name; + + /** May be set when used with ecs_bitmask_desc_t */ + ecs_flags64_t value; + + /** Keep layout the same with ecs_enum_constant_t */ + int64_t _unused; + + /** Should not be set by ecs_bitmask_desc_t */ + ecs_entity_t constant; +} ecs_bitmask_constant_t; + +/** Component added to bitmask type entities */ +typedef struct EcsBitmask { + int32_t dummy_; +} EcsBitmask; + +/** Component with datastructures for looking up enum/bitmask constants. */ +typedef struct EcsConstants { + /** Populated from child entities with Constant component */ + ecs_map_t *constants; /**< map */ + + /** Stores the constants in registration order */ + ecs_vec_t ordered_constants; /**< vector */ +} EcsConstants; + +/** Component added to array type entities */ +typedef struct EcsArray { + ecs_entity_t type; /**< Element type */ + int32_t count; /**< Number of elements */ +} EcsArray; + +/** Component added to vector type entities */ +typedef struct EcsVector { + ecs_entity_t type; /**< Element type */ +} EcsVector; + + +/* Opaque type support */ + +#if !defined(__cplusplus) || !defined(FLECS_CPP) + +/** Serializer interface */ +typedef struct ecs_serializer_t { + /* Serialize value */ + int (*value)( + const struct ecs_serializer_t *ser, /**< Serializer */ + ecs_entity_t type, /**< Type of the value to serialize */ + const void *value); /**< Pointer to the value to serialize */ + + /* Serialize member */ + int (*member)( + const struct ecs_serializer_t *ser, /**< Serializer */ + const char *member); /**< Member name */ + + const ecs_world_t *world; /**< The world. */ + void *ctx; /**< Serializer context. */ +} ecs_serializer_t; + +#elif defined(__cplusplus) + +} /* extern "C" { */ + +/** Serializer interface (same layout as C, but with convenience methods) */ +typedef struct ecs_serializer_t { + /* Serialize value */ + int (*value_)( + const struct ecs_serializer_t *ser, + ecs_entity_t type, + const void *value); + + /* Serialize member */ + int (*member_)( + const struct ecs_serializer_t *ser, + const char *name); + + /* Serialize value */ + int value(ecs_entity_t type, const void *value) const; + + /* Serialize value */ + template + int value(const T& value) const; + + /* Serialize member */ + int member(const char *name) const; + + const ecs_world_t *world; + void *ctx; +} ecs_serializer_t; + +extern "C" { +#endif + +/** Callback invoked serializing an opaque type. */ +typedef int (*ecs_meta_serialize_t)( + const ecs_serializer_t *ser, + const void *src); /**< Pointer to value to serialize */ + + +/** Callback invoked to serialize an opaque struct member */ +typedef int (*ecs_meta_serialize_member_t)( + const ecs_serializer_t *ser, + const void *src, /**< Pointer to value to serialize */ + const char* name); /**< Name of member to serialize */ + +/** Callback invoked to serialize an opaque vector/array element */ +typedef int (*ecs_meta_serialize_element_t)( + const ecs_serializer_t *ser, + const void *src, /**< Pointer to value to serialize */ + size_t elem); /**< Element index to serialize */ + +/** Opaque type reflection data. + * An opaque type is a type with an unknown layout that can be mapped to a type + * known to the reflection framework. See the opaque type reflection examples. + */ +typedef struct EcsOpaque { + ecs_entity_t as_type; /**< Type that describes the serialized output */ + ecs_meta_serialize_t serialize; /**< Serialize action */ + ecs_meta_serialize_member_t serialize_member; /**< Serialize member action */ + ecs_meta_serialize_element_t serialize_element; /**< Serialize element action */ + + /* Deserializer interface + * Only override the callbacks that are valid for the opaque type. If a + * deserializer attempts to assign a value type that is not supported by the + * interface, a conversion error is thrown. + */ + + /** Assign bool value */ + void (*assign_bool)( + void *dst, + bool value); + + /** Assign char value */ + void (*assign_char)( + void *dst, + char value); + + /** Assign int value */ + void (*assign_int)( + void *dst, + int64_t value); + + /** Assign unsigned int value */ + void (*assign_uint)( + void *dst, + uint64_t value); + + /** Assign float value */ + void (*assign_float)( + void *dst, + double value); + + /** Assign string value */ + void (*assign_string)( + void *dst, + const char *value); + + /** Assign entity value */ + void (*assign_entity)( + void *dst, + ecs_world_t *world, + ecs_entity_t entity); + + /** Assign (component) id value */ + void (*assign_id)( + void *dst, + ecs_world_t *world, + ecs_id_t id); + + /** Assign null value */ + void (*assign_null)( + void *dst); + + /** Clear collection elements */ + void (*clear)( + void *dst); + + /** Ensure & get collection element */ + void* (*ensure_element)( + void *dst, + size_t elem); + + /** Ensure & get element */ + void* (*ensure_member)( + void *dst, + const char *member); + + /** Return number of elements */ + size_t (*count)( + const void *dst); + + /** Resize to number of elements */ + void (*resize)( + void *dst, + size_t count); +} EcsOpaque; + + +/* Units */ + +/** Helper type to describe translation between two units. Note that this + * is not intended as a generic approach to unit conversions (e.g. from celsius + * to fahrenheit) but to translate between units that derive from the same base + * (e.g. meters to kilometers). + * + * Note that power is applied to the factor. When describing a translation of + * 1000, either use {factor = 1000, power = 1} or {factor = 1, power = 3}. */ +typedef struct ecs_unit_translation_t { + int32_t factor; /**< Factor to apply (e.g. "1000", "1000000", "1024") */ + int32_t power; /**< Power to apply to factor (e.g. "1", "3", "-9") */ +} ecs_unit_translation_t; + +/** Component that stores unit data. */ +typedef struct EcsUnit { + char *symbol; /**< Unit symbol. */ + ecs_entity_t prefix; /**< Order of magnitude prefix relative to derived */ + ecs_entity_t base; /**< Base unit (e.g. "meters") */ + ecs_entity_t over; /**< Over unit (e.g. "per second") */ + ecs_unit_translation_t translation; /**< Translation for derived unit */ +} EcsUnit; + +/** Component that stores unit prefix data. */ +typedef struct EcsUnitPrefix { + char *symbol; /**< Symbol of prefix (e.g. "K", "M", "Ki") */ + ecs_unit_translation_t translation; /**< Translation of prefix */ +} EcsUnitPrefix; + + +/* Serializer utilities */ + +/** Serializer instruction opcodes. + * The meta type serializer works by generating a flattened array with + * instructions that tells a serializer what kind of fields can be found in a + * type at which offsets. +*/ +typedef enum ecs_meta_op_kind_t { + EcsOpPushStruct, /**< Push struct. */ + EcsOpPushArray, /**< Push array. */ + EcsOpPushVector, /**< Push vector. */ + EcsOpPop, /**< Pop scope. */ + + EcsOpOpaqueStruct, /**< Opaque struct. */ + EcsOpOpaqueArray, /**< Opaque array. */ + EcsOpOpaqueVector, /**< Opaque vector. */ + EcsOpForward, /**< Forward to type. Allows for recursive types. */ + + EcsOpScope, /**< Marks last constant that can open/close a scope */ + + EcsOpOpaqueValue, /**< Opaque value. */ + EcsOpEnum, + EcsOpBitmask, + + EcsOpPrimitive, /**< Marks first constant that's a primitive */ + + EcsOpBool, + EcsOpChar, + EcsOpByte, + EcsOpU8, + EcsOpU16, + EcsOpU32, + EcsOpU64, + EcsOpI8, + EcsOpI16, + EcsOpI32, + EcsOpI64, + EcsOpF32, + EcsOpF64, + EcsOpUPtr, + EcsOpIPtr, + EcsOpString, + EcsOpEntity, + EcsOpId, + EcsMetaTypeOpKindLast = EcsOpId +} ecs_meta_op_kind_t; + +/** Meta type serializer instruction data. */ +typedef struct ecs_meta_op_t { + ecs_meta_op_kind_t kind; /**< Instruction opcode. */ + ecs_meta_op_kind_t underlying_kind; /**< Underlying type kind (for enums). */ + ecs_size_t offset; /**< Offset of current field. */ + const char *name; /**< Name of value (only used for struct members) */ + ecs_size_t elem_size; /**< Element size (for PushArray/PushVector) and element count (for PopArray) */ + int16_t op_count; /**< Number of operations until next field or end */ + int16_t member_index; /**< Index of member in struct */ + ecs_entity_t type; /**< Type entity */ + const ecs_type_info_t *type_info; /**< Type info */ + union { + ecs_hashmap_t *members; /**< string -> member index (structs) */ + ecs_map_t *constants; /**< (u)int -> constant entity (enums/bitmasks) */ + ecs_meta_serialize_t opaque; /**< Serialize callback for opaque types */ + } is; +} ecs_meta_op_t; + +/** Component that stores the type serializer. + * Added to all types with reflection data. */ +typedef struct EcsTypeSerializer { + ecs_type_kind_t kind; /**< Quick access to type kind (same as EcsType) */ + ecs_vec_t ops; /**< vector */ +} EcsTypeSerializer; + + +/* Deserializer utilities */ + +/** Maximum level of type nesting. + * >32 levels of nesting is not sane. + */ +#define ECS_META_MAX_SCOPE_DEPTH (32) + +/** Type with information about currently serialized scope. */ +typedef struct ecs_meta_scope_t { + ecs_entity_t type; /**< The type being iterated */ + ecs_meta_op_t *ops; /**< The type operations (see ecs_meta_op_t) */ + int16_t ops_count; /**< Number of elements in ops */ + int16_t ops_cur; /**< Current element in ops */ + int16_t prev_depth; /**< Depth to restore, in case dotmember was used */ + void *ptr; /**< Pointer to ops[0] */ + const EcsOpaque *opaque; /**< Opaque type interface */ + ecs_hashmap_t *members; /**< string -> member index */ + bool is_collection; /**< Is the scope iterating elements? */ + bool is_empty_scope; /**< Was scope populated (for vectors) */ + bool is_moved_scope; /**< Was scope moved in (with ecs_meta_elem, for vectors) */ + int32_t elem, elem_count; /**< Set for collections */ +} ecs_meta_scope_t; + +/** Type that enables iterating/populating a value using reflection data. */ +typedef struct ecs_meta_cursor_t { + const ecs_world_t *world; /**< The world. */ + ecs_meta_scope_t scope[ECS_META_MAX_SCOPE_DEPTH]; /**< Cursor scope stack. */ + int16_t depth; /**< Current scope depth. */ + bool valid; /**< Does the cursor point to a valid field. */ + bool is_primitive_scope; /**< If in root scope, this allows for a push for primitive types */ + + /** Custom entity lookup action for overriding default ecs_lookup */ + ecs_entity_t (*lookup_action)(ecs_world_t*, const char*, void*); + void *lookup_ctx; /**< Context for lookup_action */ +} ecs_meta_cursor_t; + +/** Convert serializer to string. */ +FLECS_API +char* ecs_meta_serializer_to_str( + ecs_world_t *world, + ecs_entity_t type); + +/** Create meta cursor. + * A meta cursor allows for walking over, reading and writing a value without + * having to know its type at compile time. + * + * When a value is assigned through the cursor API, it will get converted to + * the actual value of the underlying type. This allows the underlying type to + * change without having to update the serialized data. For example, an integer + * field can be set by a string, a floating point can be set as integer etc. + * + * @param world The world. + * @param type The type of the value. + * @param ptr Pointer to the value. + * @return A meta cursor for the value. + */ +FLECS_API +ecs_meta_cursor_t ecs_meta_cursor( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr); + +/** Get pointer to current field. + * + * @param cursor The cursor. + * @return A pointer to the current field. + */ +FLECS_API +void* ecs_meta_get_ptr( + ecs_meta_cursor_t *cursor); + +/** Move cursor to next field. + * + * @param cursor The cursor. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_next( + ecs_meta_cursor_t *cursor); + +/** Move cursor to a field. + * + * @param cursor The cursor. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_elem( + ecs_meta_cursor_t *cursor, + int32_t elem); + +/** Move cursor to member. + * + * @param cursor The cursor. + * @param name The name of the member. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_member( + ecs_meta_cursor_t *cursor, + const char *name); + +/** Same as ecs_meta_member() but doesn't throw an error. + * + * @param cursor The cursor. + * @param name The name of the member. + * @return Zero if success, non-zero if failed. + * @see ecs_meta_member() + */ +FLECS_API +int ecs_meta_try_member( + ecs_meta_cursor_t *cursor, + const char *name); + +/** Move cursor to member. + * Same as ecs_meta_member(), but with support for "foo.bar" syntax. + * + * @param cursor The cursor. + * @param name The name of the member. + * @return Zero if success, non-zero if failed. + * @see ecs_meta_member() + */ +FLECS_API +int ecs_meta_dotmember( + ecs_meta_cursor_t *cursor, + const char *name); + +/** Same as ecs_meta_dotmember() but doesn't throw an error. + * + * @param cursor The cursor. + * @param name The name of the member. + * @return Zero if success, non-zero if failed. + * @see ecs_meta_dotmember() + */ +FLECS_API +int ecs_meta_try_dotmember( + ecs_meta_cursor_t *cursor, + const char *name); + +/** Push a scope (required/only valid for structs & collections). + * + * @param cursor The cursor. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_push( + ecs_meta_cursor_t *cursor); + +/** Pop a struct or collection scope (must follow a push). + * + * @param cursor The cursor. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_pop( + ecs_meta_cursor_t *cursor); + +/** Is the current scope a collection?. + * + * @param cursor The cursor. + * @return True if current scope is a collection, false if not. + */ +FLECS_API +bool ecs_meta_is_collection( + const ecs_meta_cursor_t *cursor); + +/** Get type of current field. + * + * @param cursor The cursor. + * @return The type of the current field. + */ +FLECS_API +ecs_entity_t ecs_meta_get_type( + const ecs_meta_cursor_t *cursor); + +/** Get unit of current field. + * + * @param cursor The cursor. + * @return The unit of the current field. + */ +FLECS_API +ecs_entity_t ecs_meta_get_unit( + const ecs_meta_cursor_t *cursor); + +/** Get member name of current field. + * + * @param cursor The cursor. + * @return The member name of the current field. + */ +FLECS_API +const char* ecs_meta_get_member( + const ecs_meta_cursor_t *cursor); + +/** Get member entity of current field. + * + * @param cursor The cursor. + * @return The member entity of the current field. + */ +FLECS_API +ecs_entity_t ecs_meta_get_member_id( + const ecs_meta_cursor_t *cursor); + +/* The set functions assign the field with the specified value. If the value + * does not have the same type as the field, it will be cased to the field type. + * If no valid conversion is available, the operation will fail. */ + +/** Set field with boolean value. + * + * @param cursor The cursor. + * @param value The value to set. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_bool( + ecs_meta_cursor_t *cursor, + bool value); + +/** Set field with char value. + * + * @param cursor The cursor. + * @param value The value to set. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_char( + ecs_meta_cursor_t *cursor, + char value); + +/** Set field with int value. + * + * @param cursor The cursor. + * @param value The value to set. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_int( + ecs_meta_cursor_t *cursor, + int64_t value); + +/** Set field with uint value. + * + * @param cursor The cursor. + * @param value The value to set. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_uint( + ecs_meta_cursor_t *cursor, + uint64_t value); + +/** Set field with float value. + * + * @param cursor The cursor. + * @param value The value to set. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_float( + ecs_meta_cursor_t *cursor, + double value); + +/** Set field with string value. + * + * @param cursor The cursor. + * @param value The value to set. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_string( + ecs_meta_cursor_t *cursor, + const char *value); + +/** Set field with string literal value (has enclosing ""). + * + * @param cursor The cursor. + * @param value The value to set. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_string_literal( + ecs_meta_cursor_t *cursor, + const char *value); + +/** Set field with entity value. + * + * @param cursor The cursor. + * @param value The value to set. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_entity( + ecs_meta_cursor_t *cursor, + ecs_entity_t value); + +/** Set field with (component) id value. + * + * @param cursor The cursor. + * @param value The value to set. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_id( + ecs_meta_cursor_t *cursor, + ecs_id_t value); + +/** Set field with null value. + * + * @param cursor The cursor. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_null( + ecs_meta_cursor_t *cursor); + +/** Set field with dynamic value. + * + * @param cursor The cursor. + * @param value The value to set. + * @return Zero if success, non-zero if failed. + */ +FLECS_API +int ecs_meta_set_value( + ecs_meta_cursor_t *cursor, + const ecs_value_t *value); + +/* Functions for getting members. */ + +/** Get field value as boolean. + * + * @param cursor The cursor. + * @return The value of the current field. + */ +FLECS_API +bool ecs_meta_get_bool( + const ecs_meta_cursor_t *cursor); + +/** Get field value as char. + * + * @param cursor The cursor. + * @return The value of the current field. + */ +FLECS_API +char ecs_meta_get_char( + const ecs_meta_cursor_t *cursor); + +/** Get field value as signed integer. + * + * @param cursor The cursor. + * @return The value of the current field. + */ +FLECS_API +int64_t ecs_meta_get_int( + const ecs_meta_cursor_t *cursor); + +/** Get field value as unsigned integer. + * + * @param cursor The cursor. + * @return The value of the current field. + */ +FLECS_API +uint64_t ecs_meta_get_uint( + const ecs_meta_cursor_t *cursor); + +/** Get field value as float. + * + * @param cursor The cursor. + * @return The value of the current field. + */ +FLECS_API +double ecs_meta_get_float( + const ecs_meta_cursor_t *cursor); + +/** Get field value as string. + * This operation does not perform conversions. If the field is not a string, + * this operation will fail. + * + * @param cursor The cursor. + * @return The value of the current field. + */ +FLECS_API +const char* ecs_meta_get_string( + const ecs_meta_cursor_t *cursor); + +/** Get field value as entity. + * This operation does not perform conversions. + * + * @param cursor The cursor. + * @return The value of the current field. + */ +FLECS_API +ecs_entity_t ecs_meta_get_entity( + const ecs_meta_cursor_t *cursor); + +/** Get field value as (component) id. + * This operation can convert from an entity. + * + * @param cursor The cursor. + * @return The value of the current field. + */ +FLECS_API +ecs_id_t ecs_meta_get_id( + const ecs_meta_cursor_t *cursor); + +/** Convert pointer of primitive kind to float. + * + * @param type_kind The primitive type kind of the value. + * @param ptr Pointer to a value of a primitive type. + * @return The value in floating point format. + */ +FLECS_API +double ecs_meta_ptr_to_float( + ecs_primitive_kind_t type_kind, + const void *ptr); + +/** Get element count for array/vector operations. + * The operation must either be EcsOpPushArray or EcsOpPushVector. If the + * operation is EcsOpPushArray, the provided pointer may be NULL. + * + * @param op The serializer operation. + * @param ptr Pointer to the array/vector value. + * @return The number of elements. + */ +FLECS_API +ecs_size_t ecs_meta_op_get_elem_count( + const ecs_meta_op_t *op, + const void *ptr); + +/* API functions for creating meta types */ + +/** Used with ecs_primitive_init(). */ +typedef struct ecs_primitive_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional). */ + ecs_primitive_kind_t kind; /**< Primitive type kind. */ +} ecs_primitive_desc_t; + +/** Create a new primitive type. + * + * @param world The world. + * @param desc The type descriptor. + * @return The new type, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_primitive_init( + ecs_world_t *world, + const ecs_primitive_desc_t *desc); + + +/** Used with ecs_enum_init(). */ +typedef struct ecs_enum_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional). */ + ecs_enum_constant_t constants[ECS_MEMBER_DESC_CACHE_SIZE]; /**< Enum constants. */ + ecs_entity_t underlying_type; +} ecs_enum_desc_t; + +/** Create a new enum type. + * + * @param world The world. + * @param desc The type descriptor. + * @return The new type, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_enum_init( + ecs_world_t *world, + const ecs_enum_desc_t *desc); + + +/** Used with ecs_bitmask_init(). */ +typedef struct ecs_bitmask_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional). */ + ecs_bitmask_constant_t constants[ECS_MEMBER_DESC_CACHE_SIZE]; /**< Bitmask constants. */ +} ecs_bitmask_desc_t; + +/** Create a new bitmask type. + * + * @param world The world. + * @param desc The type descriptor. + * @return The new type, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_bitmask_init( + ecs_world_t *world, + const ecs_bitmask_desc_t *desc); + + +/** Used with ecs_array_init(). */ +typedef struct ecs_array_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional). */ + ecs_entity_t type; /**< Element type. */ + int32_t count; /**< Number of elements. */ +} ecs_array_desc_t; + +/** Create a new array type. + * + * @param world The world. + * @param desc The type descriptor. + * @return The new type, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_array_init( + ecs_world_t *world, + const ecs_array_desc_t *desc); + + +/** Used with ecs_vector_init(). */ +typedef struct ecs_vector_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional). */ + ecs_entity_t type; /**< Element type. */ +} ecs_vector_desc_t; + +/** Create a new vector type. + * + * @param world The world. + * @param desc The type descriptor. + * @return The new type, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_vector_init( + ecs_world_t *world, + const ecs_vector_desc_t *desc); + + +/** Used with ecs_struct_init(). */ +typedef struct ecs_struct_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional). */ + ecs_member_t members[ECS_MEMBER_DESC_CACHE_SIZE]; /**< Struct members. */ + bool create_member_entities; /**< Create entities for members. */ +} ecs_struct_desc_t; + +/** Create a new struct type. + * + * @param world The world. + * @param desc The type descriptor. + * @return The new type, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_struct_init( + ecs_world_t *world, + const ecs_struct_desc_t *desc); + +/** Add member to struct. + * This operation adds a member to a struct type. If the provided entity is not + * a struct type, this operation will add the Struct component. + * + * @param world The world. + * @param type The struct type. + * @param member The member data. + */ +FLECS_API +int ecs_struct_add_member( + ecs_world_t *world, + ecs_entity_t type, + const ecs_member_t *member); + +/** Get member by name from struct. + * + * @param world The world. + * @param type The struct type. + * @param name The member name. + * @return The member if found, or NULL if no member with the provided name exists. + */ +FLECS_API +ecs_member_t* ecs_struct_get_member( + ecs_world_t *world, + ecs_entity_t type, + const char *name); + +/** Get member by index from struct. + * + * @param world The world. + * @param type The struct type. + * @param i The member index. + * @return The member if found, or NULL if index is larger than the number of members for the struct. + */ +FLECS_API +ecs_member_t* ecs_struct_get_nth_member( + ecs_world_t *world, + ecs_entity_t type, + int32_t i); + +/** Used with ecs_opaque_init(). */ +typedef struct ecs_opaque_desc_t { + ecs_entity_t entity; /**< Existing entity to use for type (optional). */ + EcsOpaque type; /**< Type that the opaque type maps to. */ +} ecs_opaque_desc_t; + +/** Create a new opaque type. + * Opaque types are types of which the layout doesn't match what can be modelled + * with the primitives of the meta framework, but which have a structure + * that can be described with meta primitives. Typical examples are STL types + * such as std::string or std::vector, types with a nontrivial layout, and types + * that only expose getter/setter methods. + * + * An opaque type is a combination of a serialization function, and a handle to + * a meta type which describes the structure of the serialized output. For + * example, an opaque type for std::string would have a serializer function that + * accesses .c_str(), and with type ecs_string_t. + * + * The serializer callback accepts a serializer object and a pointer to the + * value of the opaque type to be serialized. The serializer has two methods: + * + * - value, which serializes a value (such as .c_str()) + * - member, which specifies a member to be serialized (in the case of a struct) + * + * @param world The world. + * @param desc The type descriptor. + * @return The new type, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_opaque_init( + ecs_world_t *world, + const ecs_opaque_desc_t *desc); + + +/** Used with ecs_unit_init(). */ +typedef struct ecs_unit_desc_t { + /** Existing entity to associate with unit (optional). */ + ecs_entity_t entity; + + /** Unit symbol, e.g. "m", "%", "g". (optional). */ + const char *symbol; + + /** Unit quantity, e.g. distance, percentage, weight. (optional). */ + ecs_entity_t quantity; + + /** Base unit, e.g. "meters" (optional). */ + ecs_entity_t base; + + /** Over unit, e.g. "per second" (optional). */ + ecs_entity_t over; + + /** Translation to apply to derived unit (optional). */ + ecs_unit_translation_t translation; + + /** Prefix indicating order of magnitude relative to the derived unit. If set + * together with "translation", the values must match. If translation is not + * set, setting prefix will auto-populate it. + * Additionally, setting the prefix will enforce that the symbol (if set) + * is consistent with the prefix symbol + symbol of the derived unit. If the + * symbol is not set, it will be auto populated. */ + ecs_entity_t prefix; +} ecs_unit_desc_t; + +/** Create a new unit. + * + * @param world The world. + * @param desc The unit descriptor. + * @return The new unit, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_unit_init( + ecs_world_t *world, + const ecs_unit_desc_t *desc); + + +/** Used with ecs_unit_prefix_init(). */ +typedef struct ecs_unit_prefix_desc_t { + /** Existing entity to associate with unit prefix (optional). */ + ecs_entity_t entity; + + /** Unit symbol, e.g. "m", "%", "g". (optional). */ + const char *symbol; + + /** Translation to apply to derived unit (optional). */ + ecs_unit_translation_t translation; +} ecs_unit_prefix_desc_t; + +/** Create a new unit prefix. + * + * @param world The world. + * @param desc The type descriptor. + * @return The new unit prefix, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_unit_prefix_init( + ecs_world_t *world, + const ecs_unit_prefix_desc_t *desc); + + +/** Create a new quantity. + * + * @param world The world. + * @param desc The quantity descriptor. + * @return The new quantity, 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_quantity_init( + ecs_world_t *world, + const ecs_entity_desc_t *desc); + +/* Convenience macros */ + +/** Create a primitive type. */ +#define ecs_primitive(world, ...)\ + ecs_primitive_init(world, &(ecs_primitive_desc_t) __VA_ARGS__ ) + +/** Create an enum type. */ +#define ecs_enum(world, ...)\ + ecs_enum_init(world, &(ecs_enum_desc_t) __VA_ARGS__ ) + +/** Create a bitmask type. */ +#define ecs_bitmask(world, ...)\ + ecs_bitmask_init(world, &(ecs_bitmask_desc_t) __VA_ARGS__ ) + +/** Create an array type. */ +#define ecs_array(world, ...)\ + ecs_array_init(world, &(ecs_array_desc_t) __VA_ARGS__ ) + +/** Create a vector type. */ +#define ecs_vector(world, ...)\ + ecs_vector_init(world, &(ecs_vector_desc_t) __VA_ARGS__ ) + +/** Create an opaque type. */ +#define ecs_opaque(world, ...)\ + ecs_opaque_init(world, &(ecs_opaque_desc_t) __VA_ARGS__ ) + +/** Create a struct type. */ +#define ecs_struct(world, ...)\ + ecs_struct_init(world, &(ecs_struct_desc_t) __VA_ARGS__ ) + +/** Create a unit. */ +#define ecs_unit(world, ...)\ + ecs_unit_init(world, &(ecs_unit_desc_t) __VA_ARGS__ ) + +/** Create a unit prefix. */ +#define ecs_unit_prefix(world, ...)\ + ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t) __VA_ARGS__ ) + +/** Create a unit quantity. */ +#define ecs_quantity(world, ...)\ + ecs_quantity_init(world, &(ecs_entity_desc_t) __VA_ARGS__ ) + + +/** Meta module import function. + * Usage: + * @code + * ECS_IMPORT(world, FlecsMeta) + * @endcode + * + * @param world The world. + */ +FLECS_API +void FlecsMetaImport( + ecs_world_t *world); + +#ifdef __cplusplus +} +#endif + +/** + * @file addons/meta_c.h + * @brief Utility macros for populating reflection data in C. + */ + +#ifdef FLECS_META + +/** + * @defgroup c_addons_meta_c Meta Utilities + * @ingroup c_addons + * Macro utilities to automatically insert reflection data. + * + * @{ + */ + +#ifndef FLECS_META_C_H +#define FLECS_META_C_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Macro that controls behavior of API. Usually set in module header. When the + * macro is not defined, it defaults to IMPL. */ + +/* Define variables used by reflection utilities. This should only be defined + * by the module itself, not by the code importing the module */ +/* #define ECS_META_IMPL IMPL */ + +/* Don't define variables used by reflection utilities but still declare the + * variable for the component id. This enables the reflection utilities to be + * used for global component variables, even if no reflection is used. */ +/* #define ECS_META_IMPL DECLARE */ + +/* Don't define variables used by reflection utilities. This generates an extern + * variable for the component identifier. */ +/* #define ECS_META_IMPL EXTERN */ + +/** Declare component with descriptor. */ +#define ECS_META_COMPONENT(world, name)\ + ECS_COMPONENT_DEFINE(world, name);\ + ecs_meta_from_desc(world, ecs_id(name),\ + FLECS__##name##_kind, FLECS__##name##_desc) + +/** ECS_STRUCT(name, body). */ +#define ECS_STRUCT(name, ...)\ + ECS_META_IMPL_CALL(ECS_STRUCT_, ECS_META_IMPL, name, #__VA_ARGS__);\ + ECS_STRUCT_TYPE(name, __VA_ARGS__) + +/** ECS_ENUM(name, body). */ +#define ECS_ENUM(name, ...)\ + ECS_META_IMPL_CALL(ECS_ENUM_, ECS_META_IMPL, name, #__VA_ARGS__);\ + ECS_ENUM_TYPE(name, __VA_ARGS__) + +/** ECS_BITMASK(name, body). */ +#define ECS_BITMASK(name, ...)\ + ECS_META_IMPL_CALL(ECS_BITMASK_, ECS_META_IMPL, name, #__VA_ARGS__);\ + ECS_ENUM_TYPE(name, __VA_ARGS__) + +/** Macro used to mark part of type for which no reflection data is created. */ +#define ECS_PRIVATE + +/** Populate meta information from type descriptor. */ +FLECS_API +int ecs_meta_from_desc( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_kind_t kind, + const char *desc); + + +/** \cond + * Private utilities to switch between meta IMPL, DECLARE and EXTERN variants. + */ + +#define ECS_META_IMPL_CALL_INNER(base, impl, name, type_desc)\ + base ## impl(name, type_desc) + +#define ECS_META_IMPL_CALL(base, impl, name, type_desc)\ + ECS_META_IMPL_CALL_INNER(base, impl, name, type_desc) + +/* ECS_STRUCT implementation */ +#define ECS_STRUCT_TYPE(name, ...)\ + typedef struct name __VA_ARGS__ name + +#define ECS_STRUCT_ECS_META_IMPL ECS_STRUCT_IMPL + +#define ECS_STRUCT_IMPL(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + static const char *FLECS__##name##_desc = type_desc;\ + static ecs_type_kind_t FLECS__##name##_kind = EcsStructType;\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_STRUCT_DECLARE(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_STRUCT_EXTERN(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name) + + +/* ECS_ENUM implementation */ +#define ECS_ENUM_TYPE(name, ...)\ + typedef enum name __VA_ARGS__ name + +#define ECS_ENUM_ECS_META_IMPL ECS_ENUM_IMPL + +#define ECS_ENUM_IMPL(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + static const char *FLECS__##name##_desc = type_desc;\ + static ecs_type_kind_t FLECS__##name##_kind = EcsEnumType;\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_ENUM_DECLARE(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_ENUM_EXTERN(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name) + + +/* ECS_BITMASK implementation */ +#define ECS_BITMASK_TYPE(name, ...)\ + typedef enum name __VA_ARGS__ name + +#define ECS_BITMASK_ECS_META_IMPL ECS_BITMASK_IMPL + +#define ECS_BITMASK_IMPL(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + static const char *FLECS__##name##_desc = type_desc;\ + static ecs_type_kind_t FLECS__##name##_kind = EcsBitmaskType;\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_BITMASK_DECLARE(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name);\ + ECS_COMPONENT_DECLARE(name) = 0 + +#define ECS_BITMASK_EXTERN(name, type_desc)\ + extern ECS_COMPONENT_DECLARE(name) + +/** \endcond */ + +#ifdef __cplusplus +} +#endif + +#endif // FLECS_META_H + +/** @} */ + +#endif // FLECS_META + + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_OS_API_IMPL +#ifdef FLECS_NO_OS_API_IMPL +#error "FLECS_NO_OS_API_IMPL failed: OS_API_IMPL is required by other addons" +#endif +/** + * @file addons/os_api_impl.h + * @brief Default OS API implementation. + */ + +#ifdef FLECS_OS_API_IMPL + +/** + * @defgroup c_addons_os_api_impl OS API Implementation + * @ingroup c_addons + * Default implementation for OS API interface. + * + * @{ + */ + +#ifndef FLECS_OS_API_IMPL_H +#define FLECS_OS_API_IMPL_H + +#ifdef __cplusplus +extern "C" { +#endif + +FLECS_API +void ecs_set_os_api_impl(void); + +#ifdef __cplusplus +} +#endif + +#endif // FLECS_OS_API_IMPL_H + +/** @} */ + +#endif // FLECS_OS_API_IMPL + +#endif + +#ifdef FLECS_MODULE +#ifdef FLECS_NO_MODULE +#error "FLECS_NO_MODULE failed: MODULE is required by other addons" +#endif +/** + * @file addons/module.h + * @brief Module addon. + * + * The module addon allows for creating and importing modules. Flecs modules + * enable applications to organize components and systems into reusable units of + * code that can easily be across projects. + */ + +#ifdef FLECS_MODULE + +/** + * @defgroup c_addons_module Module + * @ingroup c_addons + * Modules organize components, systems and more in reusable units of code. + * + * @{ + */ + +#ifndef FLECS_MODULE_H +#define FLECS_MODULE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** Import a module. + * This operation will load a modules and store the public module handles in the + * handles_out out parameter. The module name will be used to verify if the + * module was already loaded, in which case it won't be reimported. The name + * will be translated from PascalCase to an entity path (pascal.case) before the + * lookup occurs. + * + * Module contents will be stored as children of the module entity. This + * prevents modules from accidentally defining conflicting identifiers. This is + * enforced by setting the scope before and after loading the module to the + * module entity id. + * + * A more convenient way to import a module is by using the ECS_IMPORT macro. + * + * @param world The world. + * @param module The module import function. + * @param module_name The name of the module. + * @return The module entity. + */ +FLECS_API +ecs_entity_t ecs_import( + ecs_world_t *world, + ecs_module_action_t module, + const char *module_name); + +/** Same as ecs_import(), but with name to scope conversion. + * PascalCase names are automatically converted to scoped names. + * + * @param world The world. + * @param module The module import function. + * @param module_name_c The name of the module. + * @return The module entity. + */ +FLECS_API +ecs_entity_t ecs_import_c( + ecs_world_t *world, + ecs_module_action_t module, + const char *module_name_c); + +/** Import a module from a library. + * Similar to ecs_import(), except that this operation will attempt to load the + * module from a dynamic library. + * + * A library may contain multiple modules, which is why both a library name and + * a module name need to be provided. If only a library name is provided, the + * library name will be reused for the module name. + * + * The library will be looked up using a canonical name, which is in the same + * form as a module, like `flecs.components.transform`. To transform this + * identifier to a platform specific library name, the operation relies on the + * module_to_dl callback of the os_api which the application has to override if + * the default does not yield the correct library name. + * + * @param world The world. + * @param library_name The name of the library to load. + * @param module_name The name of the module to load. + */ +FLECS_API +ecs_entity_t ecs_import_from_library( + ecs_world_t *world, + const char *library_name, + const char *module_name); + +/** Register a new module. */ +FLECS_API +ecs_entity_t ecs_module_init( + ecs_world_t *world, + const char *c_name, + const ecs_component_desc_t *desc); + +/** Define module. */ +#define ECS_MODULE_DEFINE(world, id)\ + {\ + ecs_component_desc_t desc = {0};\ + desc.entity = ecs_id(id);\ + ecs_id(id) = ecs_module_init(world, #id, &desc);\ + ecs_set_scope(world, ecs_id(id));\ + } + +/** Create a module. */ +#define ECS_MODULE(world, id)\ + ecs_entity_t ecs_id(id) = 0; ECS_MODULE_DEFINE(world, id)\ + (void)ecs_id(id) + +/** Wrapper around ecs_import(). + * This macro provides a convenient way to load a module with the world. It can + * be used like this: + * + * @code + * ECS_IMPORT(world, FlecsSystemsPhysics); + * @endcode + */ +#define ECS_IMPORT(world, id) ecs_import_c(world, id##Import, #id) + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ + +#endif + +#endif + +#ifdef FLECS_CPP +#ifdef FLECS_NO_CPP +#error "FLECS_NO_CPP failed: CPP is required by other addons" +#endif +/** + * @file addons/flecs_cpp.h + * @brief C++ utility functions + * + * This header contains utility functions that are accessible from both C and + * C++ code. These functions are not part of the public API and are not meant + * to be used directly by applications. + */ + +#ifdef FLECS_CPP + +#ifndef FLECS_CPP_H +#define FLECS_CPP_H + +#ifdef __cplusplus +extern "C" { +#endif + +// The functions in this file can be used from C or C++, but these macros are only relevant to C++. +#ifdef __cplusplus + +#if defined(__clang__) +#define ECS_FUNC_NAME_FRONT(type, name) ((sizeof(#type) + sizeof(" flecs::_::() [T = ") + sizeof(#name)) - 3u) +#define ECS_FUNC_NAME_BACK (sizeof("]") - 1u) +#define ECS_FUNC_NAME __PRETTY_FUNCTION__ +#elif defined(__GNUC__) +#define ECS_FUNC_NAME_FRONT(type, name) ((sizeof(#type) + sizeof(" flecs::_::() [with T = ") + sizeof(#name)) - 3u) +#define ECS_FUNC_NAME_BACK (sizeof("]") - 1u) +#define ECS_FUNC_NAME __PRETTY_FUNCTION__ +#elif defined(_WIN32) +#define ECS_FUNC_NAME_FRONT(type, name) ((sizeof(#type) + sizeof(" __cdecl flecs::_::<") + sizeof(#name)) - 3u) +#define ECS_FUNC_NAME_BACK (sizeof(">(void)") - 1u) +#define ECS_FUNC_NAME __FUNCSIG__ +#else +#error "implicit component registration not supported" +#endif + +#define ECS_FUNC_TYPE_LEN(type, name, str)\ + (flecs::string::length(str) - (ECS_FUNC_NAME_FRONT(type, name) + ECS_FUNC_NAME_BACK)) + +#endif + +FLECS_API +char* ecs_cpp_get_type_name( + char *type_name, + const char *func_name, + size_t len, + size_t front_len); + +FLECS_API +char* ecs_cpp_get_symbol_name( + char *symbol_name, + const char *type_name, + size_t len); + +FLECS_API +char* ecs_cpp_get_constant_name( + char *constant_name, + const char *func_name, + size_t len, + size_t back_len); + +FLECS_API +const char* ecs_cpp_trim_module( + ecs_world_t *world, + const char *type_name); + +FLECS_API +ecs_entity_t ecs_cpp_component_register( + ecs_world_t *world, + ecs_entity_t id, + int32_t ids_index, + const char *name, + const char *cpp_name, + const char *cpp_symbol, + size_t size, + size_t alignment, + bool is_component, + bool explicit_registration, + bool *registered_out, + bool *existing_out); + +FLECS_API +void ecs_cpp_enum_init( + ecs_world_t *world, + ecs_entity_t id, + ecs_entity_t underlying_type); + +FLECS_API +ecs_entity_t ecs_cpp_enum_constant_register( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t id, + const char *name, + void *value, + ecs_entity_t value_type, + size_t value_size); + +typedef struct ecs_cpp_get_mut_t { + ecs_world_t *world; + ecs_stage_t *stage; + void *ptr; + bool call_modified; +} ecs_cpp_get_mut_t; + +FLECS_API +FLECS_ALWAYS_INLINE ecs_cpp_get_mut_t ecs_cpp_set( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component, + const void *new_ptr, + size_t size); + +FLECS_API +FLECS_ALWAYS_INLINE ecs_cpp_get_mut_t ecs_cpp_assign( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t component, + const void *new_ptr, + size_t size); + +FLECS_API +FLECS_ALWAYS_INLINE ecs_entity_t ecs_cpp_new( + ecs_world_t *world, + ecs_entity_t parent, + const char *name, + const char *sep, + const char *root_sep); + +#ifdef FLECS_META +FLECS_API +ecs_member_t* ecs_cpp_last_member( + const ecs_world_t *world, + ecs_entity_t type); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // FLECS_CPP_H + +#endif // FLECS_CPP + + +#ifdef __cplusplus +/** + * @file addons/cpp/flecs.hpp + * @brief Flecs C++17 API. + */ + +#pragma once + +// STL includes +#include + +/** + * @defgroup cpp C++ API + * @{ + */ + +namespace flecs +{ + +struct world; +struct world_async_stage; +struct iter; +struct entity_view; +struct entity; +struct type; +struct table; +struct table_range; +struct untyped_component; + +template +struct component; + +struct untyped_ref; +template +struct ref; + +namespace _ +{ +template +struct type; + +template +struct each_delegate; + +} // namespace _ +} // namespace flecs + +// Types imported from C API +/** + * @file addons/cpp/c_types.hpp + * @brief Aliases for types/constants from C API + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_globals API Types & Globals + * @ingroup cpp_core + * Types & constants bridged from C API. + * + * @{ + */ + +using world_t = ecs_world_t; +using world_info_t = ecs_world_info_t; +using id_t = ecs_id_t; +using entity_t = ecs_entity_t; +using type_t = ecs_type_t; +using table_t = ecs_table_t; +using term_t = ecs_term_t; +using query_t = ecs_query_t; +using query_group_info_t = ecs_query_group_info_t; +using observer_t = ecs_observer_t; +using iter_t = ecs_iter_t; +using ref_t = ecs_ref_t; +using table_record_t = ecs_table_record_t; +using table_records_t = ecs_table_records_t; +using component_record_t = ecs_component_record_t; +using type_info_t = ecs_type_info_t; +using type_hooks_t = ecs_type_hooks_t; +using flags32_t = ecs_flags32_t; +using flags64_t = ecs_flags64_t; + +enum inout_kind_t { + InOutDefault = EcsInOutDefault, + InOutNone = EcsInOutNone, + InOutFilter = EcsInOutFilter, + InOut = EcsInOut, + In = EcsIn, + Out = EcsOut +}; + +enum oper_kind_t { + And = EcsAnd, + Or = EcsOr, + Not = EcsNot, + Optional = EcsOptional, + AndFrom = EcsAndFrom, + OrFrom = EcsOrFrom, + NotFrom = EcsNotFrom +}; + +enum query_cache_kind_t { + QueryCacheDefault = EcsQueryCacheDefault, + QueryCacheAuto = EcsQueryCacheAuto, + QueryCacheAll = EcsQueryCacheAll, + QueryCacheNone = EcsQueryCacheNone +}; + +/** Id bit flags */ +static const flecs::entity_t PAIR = ECS_PAIR; +static const flecs::entity_t AUTO_OVERRIDE = ECS_AUTO_OVERRIDE; +static const flecs::entity_t TOGGLE = ECS_TOGGLE; + +//////////////////////////////////////////////////////////////////////////////// +//// Builtin components and tags +//////////////////////////////////////////////////////////////////////////////// + +/* Builtin components */ +using Component = EcsComponent; +using Identifier = EcsIdentifier; +using Poly = EcsPoly; +using DefaultChildComponent = EcsDefaultChildComponent; +using Parent = EcsParent; + +/* Builtin tags */ +static const flecs::entity_t Query = EcsQuery; +static const flecs::entity_t Observer = EcsObserver; +static const flecs::entity_t Module = EcsModule; +static const flecs::entity_t Prefab = EcsPrefab; +static const flecs::entity_t Disabled = EcsDisabled; +static const flecs::entity_t Empty = EcsEmpty; +static const flecs::entity_t Monitor = EcsMonitor; +static const flecs::entity_t System = EcsSystem; +static const flecs::entity_t Pipeline = ecs_id(EcsPipeline); +static const flecs::entity_t Phase = EcsPhase; +static const flecs::entity_t Constant = EcsConstant; +static const flecs::entity_t ParentDepth = EcsParentDepth; + +/* Builtin event tags */ +static const flecs::entity_t OnAdd = EcsOnAdd; +static const flecs::entity_t OnRemove = EcsOnRemove; +static const flecs::entity_t OnSet = EcsOnSet; +static const flecs::entity_t OnTableCreate = EcsOnTableCreate; +static const flecs::entity_t OnTableDelete = EcsOnTableDelete; + +/* Builtin term flags */ +static const uint64_t Self = EcsSelf; +static const uint64_t Up = EcsUp; +static const uint64_t Trav = EcsTrav; +static const uint64_t Cascade = EcsCascade; +static const uint64_t Desc = EcsDesc; +static const uint64_t IsVariable = EcsIsVariable; +static const uint64_t IsEntity = EcsIsEntity; +static const uint64_t IsName = EcsIsName; +static const uint64_t TraverseFlags = EcsTraverseFlags; +static const uint64_t TermRefFlags = EcsTermRefFlags; + +/* Builtin entity ids */ +static const flecs::entity_t Flecs = EcsFlecs; +static const flecs::entity_t FlecsCore = EcsFlecsCore; +static const flecs::entity_t World = EcsWorld; + +/* Component traits */ +static const flecs::entity_t Wildcard = EcsWildcard; +static const flecs::entity_t Any = EcsAny; +static const flecs::entity_t This = EcsThis; +static const flecs::entity_t Transitive = EcsTransitive; +static const flecs::entity_t Reflexive = EcsReflexive; +static const flecs::entity_t Final = EcsFinal; +static const flecs::entity_t Inheritable = EcsInheritable; +static const flecs::entity_t PairIsTag = EcsPairIsTag; +static const flecs::entity_t Exclusive = EcsExclusive; +static const flecs::entity_t Acyclic = EcsAcyclic; +static const flecs::entity_t Traversable = EcsTraversable; +static const flecs::entity_t Symmetric = EcsSymmetric; +static const flecs::entity_t With = EcsWith; +static const flecs::entity_t OneOf = EcsOneOf; +static const flecs::entity_t Trait = EcsTrait; +static const flecs::entity_t Relationship = EcsRelationship; +static const flecs::entity_t Target = EcsTarget; +static const flecs::entity_t CanToggle = EcsCanToggle; + +/* OnInstantiate trait */ +static const flecs::entity_t OnInstantiate = EcsOnInstantiate; +static const flecs::entity_t Override = EcsOverride; +static const flecs::entity_t Inherit = EcsInherit; +static const flecs::entity_t DontInherit = EcsDontInherit; + +/* OnDelete/OnDeleteTarget traits */ +static const flecs::entity_t OnDelete = EcsOnDelete; +static const flecs::entity_t OnDeleteTarget = EcsOnDeleteTarget; +static const flecs::entity_t Remove = EcsRemove; +static const flecs::entity_t Delete = EcsDelete; +static const flecs::entity_t Panic = EcsPanic; + +/* Builtin relationships */ +static const flecs::entity_t IsA = EcsIsA; +static const flecs::entity_t ChildOf = EcsChildOf; +static const flecs::entity_t DependsOn = EcsDependsOn; +static const flecs::entity_t SlotOf = EcsSlotOf; + +/* Misc */ +static const flecs::entity_t OrderedChildren = EcsOrderedChildren; +static const flecs::entity_t Singleton = EcsSingleton; + +/* Builtin identifiers */ +static const flecs::entity_t Name = EcsName; +static const flecs::entity_t Symbol = EcsSymbol; + +/* Storage */ +static const flecs::entity_t Sparse = EcsSparse; +static const flecs::entity_t DontFragment = EcsDontFragment; + +/* Builtin predicates for comparing entity ids in queries. */ +static const flecs::entity_t PredEq = EcsPredEq; +static const flecs::entity_t PredMatch = EcsPredMatch; +static const flecs::entity_t PredLookup = EcsPredLookup; + +/* Builtin marker entities for query scopes */ +static const flecs::entity_t ScopeOpen = EcsScopeOpen; +static const flecs::entity_t ScopeClose = EcsScopeClose; + +/** @} */ + +} + + +// C++ utilities +/** + * @file addons/cpp/utils/utils.hpp + * @brief Flecs STL (FTL?) + * + * Flecs STL (FTL?) + * Minimalistic utilities that allow for STL like functionality without having + * to depend on the actual STL. + */ + +// Macros so that C++ new calls can allocate using ecs_os_api memory allocation functions +// Rationale: +// - Using macros here instead of a templated function bc clients might override ecs_os_malloc +// to contain extra debug info like source tracking location. Using a template function +// in that scenario would collapse all source location into said function vs. the +// actual call site +// - FLECS_PLACEMENT_NEW(): exists to remove any naked new calls/make it easy to identify any regressions +// by grepping for new/delete + +#define FLECS_PLACEMENT_NEW(_ptr, _type) ::new(flecs::_::placement_new_tag, _ptr) _type +#define FLECS_NEW(_type) FLECS_PLACEMENT_NEW(ecs_os_malloc(sizeof(_type)), _type) +#define FLECS_DELETE(_ptr) \ + do { \ + if (_ptr) { \ + flecs::_::destruct_obj(_ptr); \ + ecs_os_free(_ptr); \ + } \ + } while (false) + +/* Faster (compile time) alternatives to std::move / std::forward. From: + * https://www.foonathan.net/2020/09/move-forward/ + */ + +#define FLECS_MOV(...) \ + static_cast&&>(__VA_ARGS__) + +#define FLECS_FWD(...) \ + static_cast(__VA_ARGS__) + +namespace flecs +{ + +namespace _ +{ + +// Dummy Placement new tag to disambiguate from any other operator new overrides +struct placement_new_tag_t{}; +constexpr placement_new_tag_t placement_new_tag{}; +template inline void destruct_obj(Ty* _ptr) { _ptr->~Ty(); } +template inline void free_obj(void* _ptr) { + if (_ptr) { + destruct_obj(static_cast(_ptr)); + ecs_os_free(_ptr); + } +} + +} // namespace _ + +} // namespace flecs + +// Allows overriding flecs_static_assert, which is useful when testing +#ifndef flecs_static_assert +#define flecs_static_assert(cond, str) static_assert(cond, str) +#endif + +inline void* operator new(size_t, flecs::_::placement_new_tag_t, void* _ptr) noexcept { return _ptr; } +inline void operator delete(void*, flecs::_::placement_new_tag_t, void*) noexcept { } + +namespace flecs +{ + +// faster (compile time) alternative to std::conditional +template struct condition; + +template <> struct condition { + template using type = F; +}; + +template <> struct condition { + template using type = T; +}; + +using std::conditional_t; +using std::decay_t; +using std::enable_if_t; +using std::remove_pointer_t; +using std::remove_reference_t; +using std::underlying_type_t; +using std::is_base_of; +using std::is_empty; +using std::is_const; +using std::is_pointer; +using std::is_reference; +using std::is_volatile; +using std::is_same; +using std::is_enum; +using std::is_trivially_constructible; +using std::is_trivially_move_assignable; +using std::is_trivially_copy_assignable; +using std::is_trivially_copy_constructible; +using std::is_trivially_move_constructible; +using std::is_trivially_copyable; +using std::is_move_assignable; +using std::is_move_constructible; +using std::is_copy_constructible; +using std::is_trivially_destructible; +using std::is_empty_v; +using std::is_const_v; +using std::is_pointer_v; +using std::is_reference_v; +using std::is_volatile_v; +using std::is_same_v; +using std::is_enum_v; +using std::is_base_of_v; +using std::is_trivially_constructible_v; +using std::is_trivially_destructible_v; +using std::is_trivially_copyable_v; +using std::is_trivially_move_constructible_v; +using std::is_trivially_copy_constructible_v; +using std::is_trivially_move_assignable_v; +using std::is_trivially_copy_assignable_v; +using std::is_default_constructible_v; +using std::is_move_constructible_v; +using std::is_copy_constructible_v; +using std::is_move_assignable_v; +using std::is_copy_assignable_v; +using std::is_destructible_v; + +// Determine constness even if T is a pointer type +template +using is_const_p = is_const< remove_pointer_t >; + +// Apply cv modifiers from source type to destination type +// (from: https://stackoverflow.com/questions/52559336/add-const-to-type-if-template-arg-is-const) +template +using transcribe_const_t = conditional_t::value, Dst const, Dst>; + +template +using transcribe_volatile_t = conditional_t::value, Dst volatile, Dst>; + +template +using transcribe_cv_t = transcribe_const_t< Src, transcribe_volatile_t< Src, Dst> >; + +template +using transcribe_pointer_t = conditional_t::value, Dst*, Dst>; + +template +using transcribe_cvp_t = transcribe_cv_t< Src, transcribe_pointer_t< Src, Dst> >; + + +// More convenience templates. The if_*_t templates use int as default type +// instead of void. This enables writing code that's a bit less cluttered when +// the templates are used in a template declaration: +// +// enable_if_t* = nullptr +// vs: +// if_t = 0 + +template +using if_t = enable_if_t; + +template +using if_not_t = enable_if_t; + +namespace _ +{ + +// Utility to prevent static assert from immediately triggering +template +struct always_false { + static const bool value = false; +}; + +} // namespace _ + +} // namespace flecs + +/** + * @file addons/cpp/utils/array.hpp + * @brief Array class. + * + * Array class. Simple std::array like utility that is mostly there to aid + * template code where template expansion would lead to an array with size 0. + */ + +namespace flecs { + +template +struct array_iterator +{ + explicit array_iterator(T* value, int index) { + value_ = value; + index_ = index; + } + + bool operator!=(array_iterator const& other) const + { + return index_ != other.index_; + } + + T & operator*() const + { + return value_[index_]; + } + + array_iterator& operator++() + { + ++index_; + return *this; + } + +private: + T* value_; + int index_; +}; + +template +struct array final { }; + +template +struct array > final { + array() {}; + + array(const T (&elems)[Size]) { + int i = 0; + for (auto it = this->begin(); it != this->end(); ++ it) { + *it = elems[i ++]; + } + } + + T& operator[](int index) { + return array_[index]; + } + + T& operator[](size_t index) { + return array_[index]; + } + + array_iterator begin() { + return array_iterator(array_, 0); + } + + array_iterator end() { + return array_iterator(array_, Size); + } + + size_t size() { + return Size; + } + + T* ptr() { + return array_; + } + + template + void each(const Func& func) { + for (auto& elem : *this) { + func(elem); + } + } + +private: + T array_[Size]; +}; + +template +array to_array(const T (&elems)[Size]) { + return array(elems); +} + +// Specialized class for zero-sized array +template +struct array> final { + array() {}; + array(const T* (&elems)) { (void)elems; } + T operator[](size_t index) { ecs_os_abort(); (void)index; return T(); } + array_iterator begin() { return array_iterator(nullptr, 0); } + array_iterator end() { return array_iterator(nullptr, 0); } + + size_t size() { + return 0; + } + + T* ptr() { + return NULL; + } +}; + +} + +/** + * @file addons/cpp/utils/string.hpp + * @brief String utility that doesn't implicitly allocate memory. + */ + +namespace flecs { + +struct string_view; + +// This removes dependencies on std::string (and therefore STL) and allows the +// API to return allocated strings without incurring additional allocations when +// wrapping in an std::string. +struct string { + explicit string() + : str_(nullptr) + , const_str_("") + , length_(0) { } + + explicit string(char *str) + : str_(str) + , const_str_(str ? str : "") + , length_(str ? ecs_os_strlen(str) : 0) { } + + ~string() { + // If flecs is included in a binary but is not used, it is possible that + // the OS API is not initialized. Calling ecs_os_free in that case could + // crash the application during exit. However, if a string has been set + // flecs has been used, and OS API should have been initialized. + if (str_) { + ecs_os_free(str_); + } + } + + string(string&& str) noexcept { + ecs_os_free(str_); + str_ = str.str_; + const_str_ = str.const_str_; + length_ = str.length_; + str.str_ = nullptr; + } + + operator const char*() const { + return const_str_; + } + + string& operator=(string&& str) noexcept { + ecs_os_free(str_); + str_ = str.str_; + const_str_ = str.const_str_; + length_ = str.length_; + str.str_ = nullptr; + return *this; + } + + // Ban implicit copies/allocations + string& operator=(const string& str) = delete; + string(const string& str) = delete; + + bool operator==(const flecs::string& str) const { + if (str.const_str_ == const_str_) { + return true; + } + + if (!const_str_ || !str.const_str_) { + return false; + } + + if (str.length_ != length_) { + return false; + } + + return ecs_os_strcmp(str, const_str_) == 0; + } + + bool operator!=(const flecs::string& str) const { + return !(*this == str); + } + + bool operator==(const char *str) const { + if (const_str_ == str) { + return true; + } + + if (!const_str_ || !str) { + return false; + } + + return ecs_os_strcmp(str, const_str_) == 0; + } + + bool operator!=(const char *str) const { + return !(*this == str); + } + + const char* c_str() const { + return const_str_; + } + + std::size_t length() const { + return static_cast(length_); + } + + template + static constexpr size_t length( char const (&)[N] ) { + return N - 1; + } + + std::size_t size() const { + return length(); + } + + void clear() { + ecs_os_free(str_); + str_ = nullptr; + const_str_ = nullptr; + } + + bool contains(const char *substr) { + if (const_str_) { + return strstr(const_str_, substr) != nullptr; + } else { + return false; + } + } + +protected: + // Must be constructed through string_view. This allows for using the string + // class for both owned and non-owned strings, which can reduce allocations + // when code conditionally should store a literal or an owned string. + // Making this constructor private forces the code to explicitly create a + // string_view which emphasizes that the string won't be freed by the class. + string(const char *str) + : str_(nullptr) + , const_str_(str ? str : "") + , length_(str ? ecs_os_strlen(str) : 0) { } + + char *str_ = nullptr; + const char *const_str_; + ecs_size_t length_; +}; + +// For consistency, the API returns a string_view where it could have returned +// a const char*, so an application won't have to think about whether to call +// c_str() or not. The string_view is a thin wrapper around a string that forces +// the API to indicate explicitly when a string is owned or not. +struct string_view : string { + explicit string_view(const char *str) + : string(str) { } +}; + +} + +/** + * @file addons/cpp/utils/enum.hpp + * @brief Compile time enum reflection utilities. + * + * Discover at compile time valid enumeration constants for an enumeration type + * and their names. This is used to automatically register enum constants. + */ + +#include + +// 126, so that FLECS_ENUM_MAX_COUNT is 127 which is the largest value +// representable by an int8_t. +#define FLECS_ENUM_MAX(T) _::to_constant::value +#define FLECS_ENUM_MAX_COUNT (FLECS_ENUM_MAX(int) + 1) + +// Flag to turn off enum reflection +#ifdef FLECS_CPP_NO_ENUM_REFLECTION +#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 0 +#endif + +// Test if we're using a compiler that supports the required features +#ifndef FLECS_CPP_ENUM_REFLECTION_SUPPORT +#if !defined(__clang__) && defined(__GNUC__) +#if __GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 5) +#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 1 +#else +#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 0 +#endif +#else +#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 1 +#endif +#endif + +#if defined(__clang__) && __clang_major__ >= 16 +// https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 +#define flecs_enum_cast(T, v) __builtin_bit_cast(T, v) +#elif defined(__GNUC__) && __GNUC__ > 10 +#define flecs_enum_cast(T, v) __builtin_bit_cast(T, v) +#else +#define flecs_enum_cast(T, v) static_cast(v) +#endif + +namespace flecs { + +/** Int to enum */ +namespace _ { +template Value> +struct to_constant { + static constexpr E value = flecs_enum_cast(E, Value); +}; + +template Value> +constexpr E to_constant::value; +} + +/** Convenience type with enum reflection data */ +template +struct enum_data; + +template +static enum_data enum_type(flecs::world_t *world); + +template +struct enum_last { + static constexpr E value = FLECS_ENUM_MAX(E); +}; + +/* Utility macro to override enum_last trait */ +#define FLECS_ENUM_LAST(T, Last)\ + namespace flecs {\ + template<>\ + struct enum_last {\ + static constexpr T value = Last;\ + };\ + } + +namespace _ { + +#if INTPTR_MAX == INT64_MAX + #ifdef ECS_TARGET_MSVC + #if _MSC_VER >= 1929 + #define ECS_SIZE_T_STR "unsigned __int64" + #else + #define ECS_SIZE_T_STR "unsigned int" + #endif + #elif defined(__clang__) + #define ECS_SIZE_T_STR "size_t" + #else + #ifdef ECS_TARGET_WINDOWS + #define ECS_SIZE_T_STR "constexpr size_t; size_t = long long unsigned int" + #else + #define ECS_SIZE_T_STR "constexpr size_t; size_t = long unsigned int" + #endif + #endif +#else + #ifdef ECS_TARGET_MSVC + #if _MSC_VER >= 1929 + #define ECS_SIZE_T_STR "unsigned __int32" + #else + #define ECS_SIZE_T_STR "unsigned int" + #endif + #elif defined(__clang__) + #define ECS_SIZE_T_STR "size_t" + #else + #ifdef ECS_TARGET_WINDOWS + #define ECS_SIZE_T_STR "constexpr size_t; size_t = unsigned int" + #else + #define ECS_SIZE_T_STR "constexpr size_t; size_t = unsigned int" + #endif + #endif +#endif + +template +constexpr size_t enum_type_len() { + return ECS_FUNC_TYPE_LEN(, enum_type_len, ECS_FUNC_NAME) + - (sizeof(ECS_SIZE_T_STR) - 1u); +} + +/** Test if value is valid for enumeration. + * This function leverages that when a valid value is provided, + * __PRETTY_FUNCTION__ contains the enumeration name, whereas if a value is + * invalid, the string contains a number or a negative (-) symbol. */ +#if defined(ECS_TARGET_CLANG) +#if ECS_CLANG_VERSION < 13 +template +constexpr bool enum_constant_is_valid() { + return !(( + (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + + enum_type_len() + 6 /* ', C = ' */] >= '0') && + (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + + enum_type_len() + 6 /* ', C = ' */] <= '9')) || + (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + + enum_type_len() + 6 /* ', C = ' */] == '-')); +} +#else +template +constexpr bool enum_constant_is_valid() { + return (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + + enum_type_len() + 6 /* ', E C = ' */] != '('); +} +#endif +#elif defined(ECS_TARGET_GNU) +template +constexpr bool enum_constant_is_valid() { + return (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(constexpr bool, enum_constant_is_valid) + + enum_type_len() + 8 /* ', E C = ' */] != '('); +} +#else +/* Use different trick on MSVC, since it uses hexadecimal representation for + * invalid enum constants. We can leverage that msvc inserts a C-style cast + * into the name, and the location of its first character ('(') is known. */ +template +constexpr bool enum_constant_is_valid() { + return ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + + enum_type_len() + 1] != '('; +} +#endif + +/* Without this wrapper __builtin_bit_cast doesn't work */ +template C> +constexpr bool enum_constant_is_valid_wrap() { + return enum_constant_is_valid(); +} + +template +struct enum_is_valid { + static constexpr bool value = enum_constant_is_valid(); +}; + +/** Extract name of constant from string */ +template +static const char* enum_constant_to_name() { + static const size_t len = ECS_FUNC_TYPE_LEN( + const char*, enum_constant_to_name, ECS_FUNC_NAME); + static char result[len + 1] = {}; + return ecs_cpp_get_constant_name( + result, ECS_FUNC_NAME, string::length(ECS_FUNC_NAME), + ECS_FUNC_NAME_BACK); +} + +/** + * @brief Provides utilities for enum reflection. + * + * This struct provides static functions for enum reflection, including + * conversion between enum values and their underlying integral types, and + * iteration over enum values. + * + * @tparam E The enum type. + * @tparam Handler The handler for enum reflection operations. + */ +template +struct enum_reflection { + using U = underlying_type_t; + + /** + * @brief Iterates over the range [Low, High] of enum values between Low and + * High. + * + * Recursively divide and conquers the search space to reduce the + * template-depth. Once recursive division is complete, calls + * Handle::handle_constant in ascending order, passing the values + * computed up the chain. + * + * @tparam Low The lower bound of the search range, inclusive. + * @tparam High The upper bound of the search range, inclusive. + * @tparam Args Additional arguments to be passed through to Handler::handle_constant + * @param last_value The last value processed in the iteration. + * @param args Additional arguments to be passed through to Handler::handle_constant + * @return constexpr U The result of the iteration. + */ + template + static constexpr U each_enum_range(U last_value, Args&... args) { + return High - Low <= 1 + ? High == Low + ? Handler::template handle_constant(last_value, args...) + : Handler::template handle_constant( + Handler::template handle_constant(last_value, args...), + args...) + : each_enum_range<(Low + High) / 2 + 1, High>( + each_enum_range(last_value, args...), + args... + ); + } + + /** + * @brief Iterates over the mask range (Low, High] of enum values between + * Low and High. + * + * Recursively iterates the search space, looking for enums defined as + * multiple-of-2 bitmasks. Each iteration, shifts bit to the right until it + * hits Low, then calls Handler::handle_constant for each bitmask in + * ascending order. + * + * @tparam Low The lower bound of the search range, not inclusive + * @tparam High The upper bound of the search range, inclusive. + * @tparam Args Additional arguments to be passed through to Handler::handle_constant + * @param last_value The last value processed in the iteration. + * @param args Additional arguments to be passed through to Handler::handle_constant + * @return constexpr U The result of the iteration. + */ + template + static constexpr U each_mask_range(U last_value, Args&... args) { + // If Low shares any bits with Current Flag, or if High is less + // than/equal to Low (and High isn't negative because max-flag signed) + return (Low & High) || (High <= Low && High != high_bit) + ? last_value + : Handler::template handle_constant( + each_mask_range> 1) & ~high_bit)>(last_value, args...), + args... + ); + } + + /** + * @brief Handles enum iteration for gathering reflection data. + * + * Iterates over all enum values up to a specified maximum value + * (each_enum_range<0, Value>), then iterates the rest of the possible bitmasks + * (each_mask_range). + * + * @tparam Value The maximum enum value to iterate up to. + * @tparam Args Additional arguments to be passed through to Handler::handle_constant + * @param args Additional arguments to be passed through to Handler::handle_constant + * @return constexpr U The result of the iteration. + */ + template (FLECS_ENUM_MAX(E)), typename... Args> + static constexpr U each_enum(Args&... args) { + return each_mask_range( + each_enum_range<0, Value>(0, args...), args...); + } + /* to avoid warnings with bit manipulation, calculate the high bit with an + unsigned type of the same size: */ + using UU = typename std::make_unsigned::type; + static const U high_bit = + static_cast(static_cast(1) << (sizeof(UU) * 8 - 1)); +}; + +/** Enumeration constant data */ +template +struct enum_constant { + int32_t index; // Global index used to obtain world local entity id + T value; + T offset; + const char *name; +}; + +/** Class that scans an enum for constants, extracts names & creates entities */ +template +struct enum_type { +private: + using This = enum_type; + using U = underlying_type_t; + + /** + * @brief Handler struct for generating compile-time count of enum constants. + */ + struct reflection_count { + template + static constexpr U handle_constant(U last_value) { + if constexpr (enum_constant_is_valid_wrap()) { + return 1 + last_value; + } else { + return last_value; + } + } + }; + + /** + * @brief Helper struct for filling enum_type's static `enum_data_impl` + * member with reflection data. + * + * Because reflection occurs in-order, we can use current value/last value + * to determine continuity, and use that as a lookup heuristic later on. + */ + struct reflection_init { + template + static U handle_constant(U last_value, This& me) { + if constexpr (enum_constant_is_valid_wrap()) { + // Constant is valid, so fill reflection data. + auto v = Value; + const char *name = enum_constant_to_name(); + + ++me.max; // Increment cursor as we build constants array. + + // If the enum was previously contiguous, and continues to be + // through the current value... + if (me.has_contiguous && static_cast(me.max) == v && me.contiguous_until == v) { + ++me.contiguous_until; + } + + // else, if the enum was never contiguous and hasn't been set as not + // contiguous... + else if (!me.contiguous_until && me.has_contiguous) { + me.has_contiguous = false; + } + + ecs_assert(!(last_value > 0 && + v < std::numeric_limits::min() + last_value), + ECS_UNSUPPORTED, + "Signed integer enums causes integer overflow when recording " + "offset from high positive to low negative. Consider using " + "unsigned integers as underlying type."); + + me.constants[me.max].value = v; + me.constants[me.max].offset = v - last_value; + me.constants[me.max].name = name; + if (!me.constants[me.max].index) { + me.constants[me.max].index = + flecs_component_ids_index_get(); + } + + return v; + } else { + // Search for constant failed. Pass last valid value through. + return last_value; + } + } + }; +public: + + enum_type() { + // Initialize/reset reflection data values to default state. + min = 0; + max = -1; + has_contiguous = true; + contiguous_until = 0; + +#if FLECS_CPP_ENUM_REFLECTION_SUPPORT + enum_reflection:: + template each_enum< static_cast(enum_last::value) >(*this); +#endif + } + + static enum_type& get() { + static _::enum_type instance; + return instance; + } + + flecs::entity_t entity(E value) const { + int index = index_by_value(value); + if (index >= 0) { + return constants[index].id; + } + return 0; + } + + void register_for_world(flecs::world_t *world, flecs::entity_t id) { +#if !FLECS_CPP_ENUM_REFLECTION_SUPPORT + ecs_abort(ECS_UNSUPPORTED, "enum reflection requires gcc 7.5 or higher") +#endif + + ecs_log_push(); + ecs_cpp_enum_init(world, id, type::id(world)); + + for (U v = 0; v < static_cast(max + 1); v ++) { + if (constants[v].index) { + flecs::entity_t constant = ecs_cpp_enum_constant_register(world, + type::id(world), 0, constants[v].name, &constants[v].value, + type::id(world), sizeof(U)); + + flecs_component_ids_set(world, constants[v].index, constant); + } + } + + ecs_log_pop(); + } + + int min; + int max; + + // If enum constants start not-sparse, contiguous_until will be the index of + // the first sparse value, or end of the constants array + U contiguous_until; + + // Compile-time generated count of enum constants. + static constexpr unsigned int constants_size = + enum_reflection:: + template each_enum< static_cast(enum_last::value) >(); + + // Constants array is sized to the number of found-constants, or 1 + // to avoid 0-sized array. + #ifdef FLECS_CPP_ENUM_REFLECTION + enum_constant constants[constants_size? constants_size: 1] = {}; + bool has_contiguous; + #else + // If we're not using enum reflection, we cannot statically determine the + // upper bound of the enum, so use 128. + enum_constant constants[128] = {}; + bool has_contiguous = true; // Assume contiguous ids + #endif +}; + +template +inline static void init_enum(flecs::world_t *world, flecs::entity_t id) { + (void)world; (void)id; + if constexpr (is_enum_v) { + _::enum_type::get().register_for_world(world, id); + } +} + +} // namespace _ + +/** Enumeration type data wrapper with world pointer */ +template +struct enum_data { + using U = underlying_type_t; + + enum_data(flecs::world_t *world, _::enum_type& impl) + : world_(world) + , impl_(impl) { } + + /** + * @brief Checks if a given integral value is a valid enum value. + * + * @param value The integral value. + * @return true If the value is a valid enum value. + * @return false If the value is not a valid enum value. + */ + bool is_valid(U value) { + int index = index_by_value(value); + if (index < 0) { + return false; + } + return impl_.constants[index].index != 0; + } + + /** + * @brief Checks if a given enum value is valid. + * + * @param value The enum value. + * @return true If the value is valid. + * @return false If the value is not valid. + */ + bool is_valid(E value) { + return is_valid(static_cast(value)); + } + + /** + * @brief Finds the index into the constants array for a value, if one exists + * + * @param value The enum value. + * @return int The index of the enum value. + */ + int index_by_value(U value) const { + if (impl_.max < 0) { + return -1; + } + + // Check if value is in contiguous lookup section + if (impl_.has_contiguous && value < impl_.contiguous_until && value >= 0) { + return static_cast(value); + } + U accumulator = impl_.contiguous_until? impl_.contiguous_until - 1: 0; + for (int i = static_cast(impl_.contiguous_until); i <= impl_.max; ++i) { + accumulator += impl_.constants[i].offset; + if (accumulator == value) { + return i; + } + } + return -1; + } + + /** + * @brief Finds the index into the constants array for an enum value, if one exists + * + * @param value The enum value. + * @return int The index of the enum value. + */ + int index_by_value(E value) const { + return index_by_value(static_cast(value)); + } + + int first() const { + return impl_.min; + } + + int last() const { + return impl_.max; + } + + int next(int cur) const { + return cur + 1; + } + + flecs::entity entity() const; + flecs::entity entity(U value) const; + flecs::entity entity(E value) const; + + /** + * @brief Manually register constant for enum. + * + * If automatic enum reflection is not supported, provide method for + * manually registering constant. + */ + #ifdef FLECS_CPP_NO_ENUM_REFLECTION + void register_constant(flecs::world_t *world, U v, flecs::entity_t e) { + if (v < 128) { + if (!impl_.constants[v].index) { + impl_.constants[v].index = flecs_component_ids_index_get(); + } + + flecs_component_ids_set(world, impl_.constants[v].index, e); + + impl_.max ++; + + if (impl_.contiguous_until <= v) { + impl_.contiguous_until = v + 1; + } + } + } + #endif + + flecs::world_t *world_; + _::enum_type& impl_; +}; + +/** Convenience function for getting enum reflection data */ +template +enum_data enum_type(flecs::world_t *world) { + _::type::id(world); // Ensure enum is registered + auto& ref = _::enum_type::get(); + return enum_data(world, ref); +} + +} // namespace flecs + +/** + * @file addons/cpp/utils/stringstream.hpp + * @brief Wrapper around ecs_strbuf_t that provides a simple stringstream like API. + */ + +namespace flecs { + +struct stringstream { + explicit stringstream() + : buf_({}) { } + + ~stringstream() { + ecs_strbuf_reset(&buf_); + } + + stringstream(stringstream&& str) noexcept { + ecs_strbuf_reset(&buf_); + buf_ = str.buf_; + str.buf_ = {}; + } + + stringstream& operator=(stringstream&& str) noexcept { + ecs_strbuf_reset(&buf_); + buf_ = str.buf_; + str.buf_ = {}; + return *this; + } + + // Ban implicit copies/allocations + stringstream& operator=(const stringstream& str) = delete; + stringstream(const stringstream& str) = delete; + + stringstream& operator<<(const char* str) { + ecs_strbuf_appendstr(&buf_, str); + return *this; + } + + flecs::string str() { + return flecs::string(ecs_strbuf_get(&buf_)); + } + +private: + ecs_strbuf_t buf_; +}; + +} + +/** + * @file addons/cpp/utils/function_traits.hpp + * @brief Compile time utilities to inspect properties of functions. + * + * Code from: https://stackoverflow.com/questions/27024238/c-template-mechanism-to-get-the-number-of-function-arguments-which-would-work + */ + +namespace flecs { +namespace _ { + +template +struct arg_list { }; + +// Base type that contains the traits +template +struct function_traits_defs +{ + static constexpr bool is_callable = true; + static constexpr size_t arity = sizeof...(Args); + using return_type = ReturnType; + using args = arg_list; +}; + +// Primary template for function_traits_impl +template +struct function_traits_impl { + static constexpr bool is_callable = false; +}; + +// Template specializations for the different kinds of function types (whew) +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +template +struct function_traits_impl + : function_traits_defs {}; + +// Primary template for function_traits_no_cv. If T is not a function, the +// compiler will attempt to instantiate this template and fail, because its base +// is undefined. +template +struct function_traits_no_cv + : function_traits_impl {}; + +// Specialized template for function types +template +struct function_traits_no_cv + : function_traits_impl {}; + +// Front facing template that decays T before ripping it apart. +template +struct function_traits + : function_traits_no_cv< decay_t > {}; + +} // _ + + +template +struct is_callable { + static constexpr bool value = _::function_traits::is_callable; +}; + +template +struct arity { + static constexpr int value = _::function_traits::arity; +}; + +template +using return_type_t = typename _::function_traits::return_type; + +template +using arg_list_t = typename _::function_traits::args; + +// First arg +template +struct first_arg_impl; + +template +struct first_arg_impl > { + using type = T; +}; + +template +struct first_arg { + using type = typename first_arg_impl>::type; +}; + +template +using first_arg_t = typename first_arg::type; + +// Last arg +template +struct second_arg_impl; + +template +struct second_arg_impl > { + using type = T; +}; + +template +struct second_arg { + using type = typename second_arg_impl>::type; +}; + +template +using second_arg_t = typename second_arg::type; + +} // flecs + + +namespace flecs { +namespace _ { + +// Trick to obtain typename from type, as described here +// https://blog.molecular-matters.com/2015/12/11/getting-the-type-of-a-template-argument-as-string-without-rtti/ +// +// The code from the link has been modified to work with more types, and across +// multiple compilers. The resulting string should be the same on all platforms +// for all compilers. +// + +#if defined(__GNUC__) || defined(_WIN32) +template +inline const char* type_name() { + static const size_t len = ECS_FUNC_TYPE_LEN(const char*, type_name, ECS_FUNC_NAME); + static char result[len + 1] = {}; + static const size_t front_len = ECS_FUNC_NAME_FRONT(const char*, type_name); + static const char* cppTypeName = ecs_cpp_get_type_name(result, ECS_FUNC_NAME, len, front_len); + return cppTypeName; +} +#else +#error "implicit component registration not supported" +#endif + +} +} + + +// Mixin forward declarations +/** + * @file addons/cpp/mixins/id/decl.hpp + * @brief Id class. + */ + +#pragma once + +namespace flecs { + +struct id; +struct entity; + +/** + * @defgroup cpp_ids Ids + * @ingroup cpp_core + * Class for working with entity, component, tag and pair ids. + * + * @{ + */ + +/** Class that wraps around a flecs::id_t. + * A flecs id is an identifier that can be added to entities. Ids can be: + * - entities (including components, tags) + * - pair ids + * - entities with id flags set (like flecs::AUTO_OVERRIDE, flecs::TOGGLE) + */ +struct id { + id() + : world_(nullptr) + , id_(0) { } + + explicit id(flecs::id_t value) + : world_(nullptr) + , id_(value) { } + + explicit id(flecs::world_t *world, flecs::id_t value = 0) + : world_(world) + , id_(value) { } + + explicit id(flecs::world_t *world, flecs::id_t first, flecs::id_t second) + : world_(world) + , id_(ecs_pair(first, second)) { } + + explicit id(flecs::world_t *world, const char *expr) + : world_(world) + , id_(ecs_id_from_str(world, expr)) { } + + explicit id(flecs::id_t first, flecs::id_t second) + : world_(nullptr) + , id_(ecs_pair(first, second)) { } + + explicit id(const flecs::id& first, const flecs::id& second) + : world_(first.world_) + , id_(ecs_pair(first.id_, second.id_)) { } + + /** Test if id is pair (has first, second) */ + bool is_pair() const { + return (id_ & ECS_ID_FLAGS_MASK) == flecs::PAIR; + } + + /** Test if id is a wildcard */ + bool is_wildcard() const { + return ecs_id_is_wildcard(id_); + } + + /** Test if id is entity */ + bool is_entity() const { + return !(id_ & ECS_ID_FLAGS_MASK); + } + + /** Return id as entity (only allowed when id is valid entity) */ + flecs::entity entity() const; + + /** Return id with role added */ + flecs::entity add_flags(flecs::id_t flags) const; + + /** Return id with role removed */ + flecs::entity remove_flags(flecs::id_t flags) const; + + /** Return id without role */ + flecs::entity remove_flags() const; + + /** Return id without role */ + flecs::entity remove_generation() const; + + /** Return component type of id */ + flecs::entity type_id() const; + + /** Test if id has specified role */ + bool has_flags(flecs::id_t flags) const { + return ((id_ & flags) == flags); + } + + /** Test if id has any role */ + bool has_flags() const { + return (id_ & ECS_ID_FLAGS_MASK) != 0; + } + + /** Return id flags set on id */ + flecs::entity flags() const; + + /** Test if id has specified first */ + bool has_relation(flecs::id_t first) const { + if (!is_pair()) { + return false; + } + return ECS_PAIR_FIRST(id_) == first; + } + + /** Get first element from a pair. + * If the id is not a pair, this operation will fail. When the id has a + * world, the operation will ensure that the returned id has the correct + * generation count. */ + flecs::entity first() const; + + /** Get second element from a pair. + * If the id is not a pair, this operation will fail. When the id has a + * world, the operation will ensure that the returned id has the correct + * generation count. */ + flecs::entity second() const; + + /* Convert id to string */ + flecs::string str() const { + return flecs::string(ecs_id_str(world_, id_)); + } + + /** Convert role of id to string. */ + flecs::string flags_str() const { + return flecs::string_view( ecs_id_flag_str(id_ & ECS_ID_FLAGS_MASK)); + } + + /** Return flecs::id_t value */ + flecs::id_t raw_id() const { + return id_; + } + + operator flecs::id_t() const { + return id_; + } + + flecs::world world() const; + +protected: + /* World is optional, but guarantees that entity identifiers extracted from + * the id are valid */ + flecs::world_t *world_; + flecs::id_t id_; +}; + +/** @} */ + +} + +/** + * @file addons/cpp/mixins/term/decl.hpp + * @brief Term declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @ingroup cpp_core_queries + * + * @{ + */ + +struct term; +struct term_builder; + +/** @} */ + +} + +/** + * @file addons/cpp/mixins/query/decl.hpp + * @brief Query declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_core_queries Queries + * @ingroup cpp_core + * + * @{ + */ + +struct query_base; + +template +struct query; + +template +struct query_builder; + +/** @} */ + +} + +/** + * @file addons/cpp/mixins/event/decl.hpp + * @brief Event declarations. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/event/builder.hpp + * @brief Event builder. + */ + +#pragma once + +#define ECS_EVENT_DESC_ID_COUNT_MAX (8) + +namespace flecs { + +/** + * @ingroup cpp_addons_event + * @{ + */ + +/** Event builder interface */ +template +struct event_builder_base { + event_builder_base(flecs::world_t *world, flecs::entity_t event) + : world_(world) + , desc_{} + , ids_{} + , ids_array_{} + { + desc_.event = event; + } + + /** Add component to emit for */ + template + Base& id() { + ids_.array = ids_array_; + ids_.array[ids_.count] = _::type().id(world_); + ids_.count ++; + return *this; + } + + /** + * Add pair to emit for + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template + Base& id() { + return id( + ecs_pair(_::type::id(this->world_), + _::type::id(this->world_))); + } + + /** + * Add pair to emit for + * @tparam First The first element of the pair. + * @param second The second element of the pair id. + */ + template + Base& id(entity_t second) { + return id(ecs_pair(_::type::id(this->world_), second)); + } + + /** + * Add pair to emit for + * @param first The first element of the pair type. + * @param second The second element of the pair id. + */ + Base& id(entity_t first, entity_t second) { + return id(ecs_pair(first, second)); + } + + template ::value> = 0> + Base& id(Enum value) { + const auto& et = enum_type(this->world_); + flecs::entity_t target = et.entity(value); + return id(et.entity(), target); + } + + /** Add (component) id to emit for */ + Base& id(flecs::id_t id) { + ids_.array = ids_array_; + ids_.array[ids_.count] = id; + ids_.count ++; + return *this; + } + + /** Set entity for which to emit event */ + Base& entity(flecs::entity_t e) { + desc_.entity = e; + return *this; + } + + /* Set table for which to emit event */ + Base& table(flecs::table_t *t, int32_t offset = 0, int32_t count = 0) { + desc_.table = t; + desc_.offset = offset; + desc_.count = count; + return *this; + } + + /* Set event data */ + Base& ctx(const E* ptr) { + desc_.const_param = ptr; + return *this; + } + + /* Set event data */ + Base& ctx(E* ptr) { + desc_.param = ptr; + return *this; + } + + void emit() { + ids_.array = ids_array_; + desc_.ids = &ids_; + desc_.observable = const_cast(ecs_get_world(world_)); + ecs_emit(world_, &desc_); + } + + void enqueue() { + ids_.array = ids_array_; + desc_.ids = &ids_; + desc_.observable = const_cast(ecs_get_world(world_)); + ecs_enqueue(world_, &desc_); + } + +protected: + flecs::world_t *world_; + ecs_event_desc_t desc_; + flecs::type_t ids_; + flecs::id_t ids_array_[ECS_EVENT_DESC_ID_COUNT_MAX]; + +private: + operator Base&() { + return *static_cast(this); + } +}; + +struct event_builder : event_builder_base { + using event_builder_base::event_builder_base; +}; + +template +struct event_builder_typed : event_builder_base, E> { +private: + using Class = event_builder_typed; + +public: + using event_builder_base::event_builder_base; + + /* Set event data */ + Class& ctx(const E& ptr) { + this->desc_.const_param = &ptr; + return *this; + } + + /* Set event data */ + Class& ctx(E&& ptr) { + this->desc_.param = &ptr; + return *this; + } +}; + +/** @} */ + +} + + +namespace flecs { +namespace _ { + +// Utility to derive event type from function +template +struct event_from_func; + +// Specialization for observer callbacks with a single argument +template +struct event_from_func::value == 1>> { + using type = decay_t>; +}; + +// Specialization for observer callbacks with an initial entity src argument +template +struct event_from_func::value == 2>> { + using type = decay_t>; +}; + +template +using event_from_func_t = typename event_from_func::type; + +} +} + +/** + * @file addons/cpp/mixins/observer/decl.hpp + * @brief Observer declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_observers Observers + * @ingroup cpp_core + * Observers let applications register callbacks for ECS events. + * + * @{ + */ + +struct observer; + +template +struct observer_builder; + +/** @} */ + +} + +#ifdef FLECS_SYSTEM +/** + * @file addons/cpp/mixins/system/decl.hpp + * @brief System module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_systems Systems + * @ingroup cpp_addons + * Systems are a query + function that can be ran manually or by a pipeline. + * + * @{ + */ + +using TickSource = EcsTickSource; + +struct system; + +template +struct system_builder; + +namespace _ { + +void system_init(flecs::world& world); + +/** @} */ + +} // namespace _ +} // namespace flecs + +#endif +#ifdef FLECS_PIPELINE +/** + * @file addons/cpp/mixins/pipeline/decl.hpp + * @brief Pipeline module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_pipelines Pipelines + * @ingroup cpp_addons + * Pipelines order and schedule systems for execution. + * + * @{ + */ + +template +struct pipeline; + +template +struct pipeline_builder; + +/* Builtin pipeline tags */ +static const flecs::entity_t OnStart = EcsOnStart; +static const flecs::entity_t PreFrame = EcsPreFrame; +static const flecs::entity_t OnLoad = EcsOnLoad; +static const flecs::entity_t PostLoad = EcsPostLoad; +static const flecs::entity_t PreUpdate = EcsPreUpdate; +static const flecs::entity_t OnUpdate = EcsOnUpdate; +static const flecs::entity_t OnValidate = EcsOnValidate; +static const flecs::entity_t PostUpdate = EcsPostUpdate; +static const flecs::entity_t PreStore = EcsPreStore; +static const flecs::entity_t OnStore = EcsOnStore; +static const flecs::entity_t PostFrame = EcsPostFrame; + +/** @} */ + +} + +#endif +#ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/decl.hpp + * @brief Timer module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_timer Timer + * @ingroup cpp_addons + * Run systems at a time interval. + * + * @{ + */ + +using Timer = EcsTimer; +using RateFilter = EcsRateFilter; + +struct timer; + +/** @} */ + +namespace _ { + +void timer_init(flecs::world& world); + +} // namespace _ +} // namespace flecs + +#endif +#ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/decl.hpp + * @brief Doc mixin declarations. + */ + +#pragma once + +namespace flecs { +namespace doc { + +/** + * @defgroup cpp_addons_doc Doc + * @ingroup cpp_addons + * Utilities for documenting entities, components and systems. + * + * @{ + */ + +/** flecs.doc.Description component */ +using Description = EcsDocDescription; + +/** flecs.doc.Uuid component */ +static const flecs::entity_t Uuid = EcsDocUuid; + +/** flecs.doc.Brief component */ +static const flecs::entity_t Brief = EcsDocBrief; + +/** flecs.doc.Detail component */ +static const flecs::entity_t Detail = EcsDocDetail; + +/** flecs.doc.Link component */ +static const flecs::entity_t Link = EcsDocLink; + +/** flecs.doc.Color component */ +static const flecs::entity_t Color = EcsDocColor; + +/** @private */ +namespace _ { +/** @private */ +void init(flecs::world& world); +} + +/** @} */ + +} +} + +#endif +#ifdef FLECS_REST +/** + * @file addons/cpp/mixins/rest/decl.hpp + * @brief Rest module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_rest Rest + * @ingroup cpp_addons + * REST API for querying and mutating entities. + * + * @{ + */ + +using Rest = EcsRest; + +namespace rest { + +namespace _ { + +void init(flecs::world& world); + +} +} + +/** @} */ + +} + +#endif +#ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/decl.hpp + * @brief Meta declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_meta Meta + * @ingroup cpp_addons + * Flecs reflection framework. + * + * @{ + */ + +/* Primitive type aliases */ +using bool_t = ecs_bool_t; +using char_t = ecs_char_t; +using u8_t = ecs_u8_t; +using u16_t = ecs_u16_t; +using u32_t = ecs_u32_t; +using u64_t = ecs_u64_t; +using uptr_t = ecs_uptr_t; +using i8_t = ecs_i8_t; +using i16_t = ecs_i16_t; +using i32_t = ecs_i32_t; +using i64_t = ecs_i64_t; +using iptr_t = ecs_iptr_t; +using f32_t = ecs_f32_t; +using f64_t = ecs_f64_t; + +/* Embedded type aliases */ +using member_t = ecs_member_t; +using enum_constant_t = ecs_enum_constant_t; +using bitmask_constant_t = ecs_bitmask_constant_t; + +/* Components */ +using Type = EcsType; +using TypeSerializer = EcsTypeSerializer; +using Primitive = EcsPrimitive; +using Enum = EcsEnum; +using Bitmask = EcsBitmask; +using Member = EcsMember; +using MemberRanges = EcsMemberRanges; +using Struct = EcsStruct; +using Array = EcsArray; +using Vector = EcsVector; +using Unit = EcsUnit; + +/** Base type for bitmasks */ +struct bitmask { + uint32_t value; +}; + +/* Handles to builtin reflection types */ +static const flecs::entity_t Bool = ecs_id(ecs_bool_t); +static const flecs::entity_t Char = ecs_id(ecs_char_t); +static const flecs::entity_t Byte = ecs_id(ecs_byte_t); +static const flecs::entity_t U8 = ecs_id(ecs_u8_t); +static const flecs::entity_t U16 = ecs_id(ecs_u16_t); +static const flecs::entity_t U32 = ecs_id(ecs_u32_t); +static const flecs::entity_t U64 = ecs_id(ecs_u64_t); +static const flecs::entity_t Uptr = ecs_id(ecs_uptr_t); +static const flecs::entity_t I8 = ecs_id(ecs_i8_t); +static const flecs::entity_t I16 = ecs_id(ecs_i16_t); +static const flecs::entity_t I32 = ecs_id(ecs_i32_t); +static const flecs::entity_t I64 = ecs_id(ecs_i64_t); +static const flecs::entity_t Iptr = ecs_id(ecs_iptr_t); +static const flecs::entity_t F32 = ecs_id(ecs_f32_t); +static const flecs::entity_t F64 = ecs_id(ecs_f64_t); +static const flecs::entity_t String = ecs_id(ecs_string_t); +static const flecs::entity_t Entity = ecs_id(ecs_entity_t); +static const flecs::entity_t Quantity = EcsQuantity; + +namespace meta { + +/* Type kinds supported by reflection system */ +using type_kind_t = ecs_type_kind_t; +static const type_kind_t PrimitiveType = EcsPrimitiveType; +static const type_kind_t BitmaskType = EcsBitmaskType; +static const type_kind_t EnumType = EcsEnumType; +static const type_kind_t StructType = EcsStructType; +static const type_kind_t ArrayType = EcsArrayType; +static const type_kind_t VectorType = EcsVectorType; +static const type_kind_t CustomType = EcsOpaqueType; +static const type_kind_t TypeKindLast = EcsTypeKindLast; + +/* Primitive type kinds supported by reflection system */ +using primitive_kind_t = ecs_primitive_kind_t; +static const primitive_kind_t Bool = EcsBool; +static const primitive_kind_t Char = EcsChar; +static const primitive_kind_t Byte = EcsByte; +static const primitive_kind_t U8 = EcsU8; +static const primitive_kind_t U16 = EcsU16; +static const primitive_kind_t U32 = EcsU32; +static const primitive_kind_t U64 = EcsU64; +static const primitive_kind_t I8 = EcsI8; +static const primitive_kind_t I16 = EcsI16; +static const primitive_kind_t I32 = EcsI32; +static const primitive_kind_t I64 = EcsI64; +static const primitive_kind_t F32 = EcsF32; +static const primitive_kind_t F64 = EcsF64; +static const primitive_kind_t UPtr = EcsUPtr; +static const primitive_kind_t IPtr = EcsIPtr; +static const primitive_kind_t String = EcsString; +static const primitive_kind_t Entity = EcsEntity; +static const primitive_kind_t PrimitiveKindLast = EcsPrimitiveKindLast; + +using op_t = ecs_meta_op_t; + +/** @} */ + +namespace _ { + +void init(flecs::world& world); + +} // namespace _ +} // namespace meta +} // namespace flecs + +/** + * @file addons/cpp/mixins/meta/opaque.hpp + * @brief Helpers for opaque type registration. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_meta Meta + * @ingroup cpp_addons + * Flecs reflection framework. + * + * @{ + */ + +/** Class for reading/writing dynamic values. + * + * @ingroup cpp_addons_meta + */ +struct cursor { + cursor(flecs::world_t *world, flecs::entity_t type_id, void *ptr) { + cursor_ = ecs_meta_cursor(world, type_id, ptr); + } + + /** Push value scope (such as a nested struct) */ + int push() { + return ecs_meta_push(&cursor_); + } + + /** Pop value scope */ + int pop() { + return ecs_meta_pop(&cursor_); + } + + /** Move to next member/element */ + int next() { + return ecs_meta_next(&cursor_); + } + + /** Move to member by name */ + int member(const char *name) { + return ecs_meta_member(&cursor_, name); + } + + /** Move to element by index */ + int elem(int32_t elem) { + return ecs_meta_elem(&cursor_, elem); + } + + /** Test if current scope is a collection type */ + bool is_collection() { + return ecs_meta_is_collection(&cursor_); + } + + /** Get member name */ + flecs::string_view get_member() const { + return flecs::string_view(ecs_meta_get_member(&cursor_)); + } + + /** Get type of value */ + flecs::entity get_type() const; + + /** Get unit of value */ + flecs::entity get_unit() const; + + /** Get untyped pointer to value */ + void* get_ptr() { + return ecs_meta_get_ptr(&cursor_); + } + + /** Set boolean value */ + int set_bool(bool value) { + return ecs_meta_set_bool(&cursor_, value); + } + + /** Set char value */ + int set_char(char value) { + return ecs_meta_set_char(&cursor_, value); + } + + /** Set signed int value */ + int set_int(int64_t value) { + return ecs_meta_set_int(&cursor_, value); + } + + /** Set unsigned int value */ + int set_uint(uint64_t value) { + return ecs_meta_set_uint(&cursor_, value); + } + + /** Set float value */ + int set_float(double value) { + return ecs_meta_set_float(&cursor_, value); + } + + /** Set string value */ + int set_string(const char *value) { + return ecs_meta_set_string(&cursor_, value); + } + + /** Set string literal value */ + int set_string_literal(const char *value) { + return ecs_meta_set_string_literal(&cursor_, value); + } + + /** Set entity value */ + int set_entity(flecs::entity_t value) { + return ecs_meta_set_entity(&cursor_, value); + } + + /** Set (component) id value */ + int set_id(flecs::id_t value) { + return ecs_meta_set_id(&cursor_, value); + } + + /** Set null value */ + int set_null() { + return ecs_meta_set_null(&cursor_); + } + + /** Get boolean value */ + bool get_bool() const { + return ecs_meta_get_bool(&cursor_); + } + + /** Get char value */ + char get_char() const { + return ecs_meta_get_char(&cursor_); + } + + /** Get signed int value */ + int64_t get_int() const { + return ecs_meta_get_int(&cursor_); + } + + /** Get unsigned int value */ + uint64_t get_uint() const { + return ecs_meta_get_uint(&cursor_); + } + + /** Get float value */ + double get_float() const { + return ecs_meta_get_float(&cursor_); + } + + /** Get string value */ + const char *get_string() const { + return ecs_meta_get_string(&cursor_); + } + + /** Get entity value */ + flecs::entity get_entity() const; + + /** Cursor object */ + ecs_meta_cursor_t cursor_; +}; + +/** @} */ + +} + +/** + * @file addons/cpp/mixins/meta/opaque.hpp + * @brief Helpers for opaque type registration. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_meta Meta + * @ingroup cpp_addons + * Flecs reflection framework. + * + * @{ + */ + +/** Serializer object, used for serializing opaque types */ +using serializer = ecs_serializer_t; + +/** Serializer function, used to serialize opaque types */ +using serialize_t = ecs_meta_serialize_t; + +/** Type safe variant of serializer function */ +template +using serialize = int(*)(const serializer *, const T*); + +/** Type safe variant of serialize_member function */ +template +using serialize_member = int(*)(const serializer *, const T*, const char* name); + +/** Type safe variant of serialize_element function */ +template +using serialize_element = int(*)(const serializer *, const T*, size_t element); + + +/** Type safe interface for opaque types */ +template +struct opaque { + opaque(flecs::world_t *w = nullptr) : world(w) { + if (world) { + desc.entity = _::type::id(world); + } + } + + /** Type that describes the type kind/structure of the opaque type */ + opaque& as_type(flecs::id_t func) { + this->desc.type.as_type = func; + return *this; + } + + /** Serialize function */ + opaque& serialize(flecs::serialize func) { + this->desc.type.serialize = + reinterpret_castdesc.type.serialize)>(func); + return *this; + } + + /** Serialize member function */ + opaque& serialize_member(flecs::serialize_member func) { + this->desc.type.serialize_member = + reinterpret_castdesc.type.serialize_member)>(func); + return *this; + } + + /** Serialize element function */ + opaque& serialize_element(flecs::serialize_element func) { + this->desc.type.serialize_element = + reinterpret_castdesc.type.serialize_element)>(func); + return *this; + } + + /** Assign bool value */ + opaque& assign_bool(void (*func)(T *dst, bool value)) { + this->desc.type.assign_bool = + reinterpret_castdesc.type.assign_bool)>(func); + return *this; + } + + /** Assign char value */ + opaque& assign_char(void (*func)(T *dst, char value)) { + this->desc.type.assign_char = + reinterpret_castdesc.type.assign_char)>(func); + return *this; + } + + /** Assign int value */ + opaque& assign_int(void (*func)(T *dst, int64_t value)) { + this->desc.type.assign_int = + reinterpret_castdesc.type.assign_int)>(func); + return *this; + } + + /** Assign unsigned int value */ + opaque& assign_uint(void (*func)(T *dst, uint64_t value)) { + this->desc.type.assign_uint = + reinterpret_castdesc.type.assign_uint)>(func); + return *this; + } + + /** Assign float value */ + opaque& assign_float(void (*func)(T *dst, double value)) { + this->desc.type.assign_float = + reinterpret_castdesc.type.assign_float)>(func); + return *this; + } + + /** Assign string value */ + opaque& assign_string(void (*func)(T *dst, const char *value)) { + this->desc.type.assign_string = + reinterpret_castdesc.type.assign_string)>(func); + return *this; + } + + /** Assign entity value */ + opaque& assign_entity( + void (*func)(T *dst, ecs_world_t *world, ecs_entity_t entity)) + { + this->desc.type.assign_entity = + reinterpret_castdesc.type.assign_entity)>(func); + return *this; + } + + /** Assign (component) id value */ + opaque& assign_id( + void (*func)(T *dst, ecs_world_t *world, ecs_id_t id)) + { + this->desc.type.assign_id = + reinterpret_castdesc.type.assign_id)>(func); + return *this; + } + + /** Assign null value */ + opaque& assign_null(void (*func)(T *dst)) { + this->desc.type.assign_null = + reinterpret_castdesc.type.assign_null)>(func); + return *this; + } + + /** Clear collection elements */ + opaque& clear(void (*func)(T *dst)) { + this->desc.type.clear = + reinterpret_castdesc.type.clear)>(func); + return *this; + } + + /** Ensure & get collection element */ + opaque& ensure_element(ElemType* (*func)(T *dst, size_t elem)) { + this->desc.type.ensure_element = + reinterpret_castdesc.type.ensure_element)>(func); + return *this; + } + + /** Ensure & get element */ + opaque& ensure_member(void* (*func)(T *dst, const char *member)) { + this->desc.type.ensure_member = + reinterpret_castdesc.type.ensure_member)>(func); + return *this; + } + + /** Return number of elements */ + opaque& count(size_t (*func)(const T *dst)) { + this->desc.type.count = + reinterpret_castdesc.type.count)>(func); + return *this; + } + + /** Resize to number of elements */ + opaque& resize(void (*func)(T *dst, size_t count)) { + this->desc.type.resize = + reinterpret_castdesc.type.resize)>(func); + return *this; + } + + ~opaque() { + if (world) { + ecs_opaque_init(world, &desc); + } + } + + /** Opaque type descriptor */ + flecs::world_t *world = nullptr; + ecs_opaque_desc_t desc = {}; +}; + +/** @} */ + +} + + +#endif +#ifdef FLECS_UNITS +/** + * @file addons/cpp/mixins/units/decl.hpp + * @brief Units module declarations. + */ + +#pragma once + +namespace flecs { +struct units { + +/** + * @defgroup cpp_addons_units Units + * @ingroup cpp_addons + * Common unit annotations for reflection framework. + * + * @{ + */ + +struct Prefixes { }; + +/** + * @defgroup cpp_addons_units_prefixes Prefixes + * @ingroup cpp_addons_units + * Prefixes to indicate unit count (e.g. Kilo, Mega) + * + * @{ + */ + +struct Yocto { }; +struct Zepto { }; +struct Atto { }; +struct Femto { }; +struct Pico { }; +struct Nano { }; +struct Micro { }; +struct Milli { }; +struct Centi { }; +struct Deci { }; +struct Deca { }; +struct Hecto { }; +struct Kilo { }; +struct Mega { }; +struct Giga { }; +struct Tera { }; +struct Peta { }; +struct Exa { }; +struct Zetta { }; +struct Yotta { }; +struct Kibi { }; +struct Mebi { }; +struct Gibi { }; +struct Tebi { }; +struct Pebi { }; +struct Exbi { }; +struct Zebi { }; +struct Yobi { }; + +/** @} */ + +/** + * @defgroup cpp_addons_units_quantities Quantities + * @ingroup cpp_addons_units + * Quantities that group units (e.g. Length) + * + * @{ + */ + +struct Duration { }; +struct Time { }; +struct Mass { }; +struct ElectricCurrent { }; +struct LuminousIntensity { }; +struct Force { }; +struct Amount { }; +struct Length { }; +struct Pressure { }; +struct Speed { }; +struct Temperature { }; +struct Data { }; +struct DataRate { }; +struct Angle { }; +struct Frequency { }; +struct Uri { }; +struct Color { }; + +/** @} */ + +struct duration { +/** + * @defgroup cpp_addons_units_duration Duration + * @ingroup cpp_addons_units + * @{ + */ + +struct PicoSeconds { }; +struct NanoSeconds { }; +struct MicroSeconds { }; +struct MilliSeconds { }; +struct Seconds { }; +struct Minutes { }; +struct Hours { }; +struct Days { }; + +/** @} */ +}; + +struct angle { +/** + * @defgroup cpp_addons_units_angle Angle + * @ingroup cpp_addons_units + * @{ + */ + +struct Radians { }; +struct Degrees { }; + +/** @} */ +}; + + +struct time { +/** + * @defgroup cpp_addons_units_time Time + * @ingroup cpp_addons_units + * @{ + */ + +struct Date { }; + +/** @} */ +}; + + +struct mass { +/** + * @defgroup cpp_addons_units_mass Mass + * @ingroup cpp_addons_units + * @{ + */ + +struct Grams { }; +struct KiloGrams { }; + +/** @} */ +}; + + +struct electric_current { +/** + * @defgroup cpp_addons_units_electric_current Electric Current + * @ingroup cpp_addons_units + * @{ + */ + +struct Ampere { }; + +/** @} */ +}; + + +struct amount { +/** + * @defgroup cpp_addons_units_amount Amount + * @ingroup cpp_addons_units + * @{ + */ + +struct Mole { }; + +/** @} */ +}; + + +struct luminous_intensity { +/** + * @defgroup cpp_addons_units_luminous_intensity Luminous Intensity + * @ingroup cpp_addons_units + * @{ + */ + +struct Candela { }; + +/** @} */ +}; + + +struct force { +/** + * @defgroup cpp_addons_units_force Force + * @ingroup cpp_addons_units + * @{ + */ + +struct Newton { }; + +/** @} */ +}; + + +struct length { +/** + * @defgroup cpp_addons_units_length Length + * @ingroup cpp_addons_units + * @{ + */ + +struct Meters { }; +struct PicoMeters { }; +struct NanoMeters { }; +struct MicroMeters { }; +struct MilliMeters { }; +struct CentiMeters { }; +struct KiloMeters { }; +struct Miles { }; +struct Pixels { }; + +/** @} */ +}; + + +struct pressure { +/** + * @defgroup cpp_addons_units_pressure Pressure + * @ingroup cpp_addons_units + * @{ + */ + +struct Pascal { }; +struct Bar { }; + +/** @} */ +}; + + +struct speed { +/** + * @defgroup cpp_addons_units_speed Speed + * @ingroup cpp_addons_units + * @{ + */ + +struct MetersPerSecond { }; +struct KiloMetersPerSecond { }; +struct KiloMetersPerHour { }; +struct MilesPerHour { }; + +/** @} */ +}; + + +struct temperature { +/** + * @defgroup cpp_addons_units_temperature Temperature + * @ingroup cpp_addons_units + * @{ + */ + +struct Kelvin { }; +struct Celsius { }; +struct Fahrenheit { }; + +/** @} */ +}; + + +struct data { +/** + * @defgroup cpp_addons_units_data Data + * @ingroup cpp_addons_units + * @{ + */ + +struct Bits { }; +struct KiloBits { }; +struct MegaBits { }; +struct GigaBits { }; +struct Bytes { }; +struct KiloBytes { }; +struct MegaBytes { }; +struct GigaBytes { }; +struct KibiBytes { }; +struct MebiBytes { }; +struct GibiBytes { }; + +/** @} */ +}; + +struct datarate { +/** + * @defgroup cpp_addons_units_datarate Data Rate + * @ingroup cpp_addons_units + * @{ + */ + +struct BitsPerSecond { }; +struct KiloBitsPerSecond { }; +struct MegaBitsPerSecond { }; +struct GigaBitsPerSecond { }; +struct BytesPerSecond { }; +struct KiloBytesPerSecond { }; +struct MegaBytesPerSecond { }; +struct GigaBytesPerSecond { }; + +/** @} */ +}; + + +struct frequency { +/** + * @defgroup cpp_addons_units_frequency Frequency + * @ingroup cpp_addons_units + * @{ + */ + +struct Hertz { }; +struct KiloHertz { }; +struct MegaHertz { }; +struct GigaHertz { }; + +/** @} */ +}; + + +struct uri { +/** + * @defgroup cpp_addons_units_uri Uri + * @ingroup cpp_addons_units + * @{ + */ + +struct Hyperlink { }; +struct Image { }; +struct File { }; + +/** @} */ +}; + + +struct color { +/** + * @defgroup cpp_addons_units_color Color + * @ingroup cpp_addons_units + * @{ + */ + +struct Rgb { }; +struct Hsl { }; +struct Css { }; + +/** @} */ +}; + +struct Percentage { }; +struct Bel { }; +struct DeciBel { }; + +units(flecs::world& world); + +/** @} */ + +}; +} + +#endif +#ifdef FLECS_STATS +/** + * @file addons/cpp/mixins/stats/decl.hpp + * @brief Stats module declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_stats Stats + * @ingroup cpp_addons + * The stats addon tracks statistics for the world and systems. + * + * @{ + */ + +/** Component that stores world statistics */ +using WorldStats = EcsWorldStats; + +/** Component that stores system/pipeline statistics */ +using PipelineStats = EcsPipelineStats; + +/** Component with world summary stats */ +using WorldSummary = EcsWorldSummary; + +struct stats { + stats(flecs::world& world); +}; + +/** @} */ + +} + +#endif +#ifdef FLECS_METRICS +/** + * @file addons/cpp/mixins/metrics/decl.hpp + * @brief Metrics declarations. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/metrics/builder.hpp + * @brief Metric builder. + */ + +#pragma once + +#define ECS_EVENT_DESC_ID_COUNT_MAX (8) + +namespace flecs { + +/** + * @ingroup cpp_addons_metrics + * @{ + */ + +/** Event builder interface */ +struct metric_builder { + metric_builder(flecs::world_t *world, flecs::entity_t entity) + : world_(world) + { + desc_.entity = entity; + } + + ~metric_builder(); + + metric_builder& member(flecs::entity_t e) { + desc_.member = e; + return *this; + } + + metric_builder& member(const char *name); + + template + metric_builder& member(const char *name); + + metric_builder& dotmember(const char *name); + + template + metric_builder& dotmember(const char *name); + + metric_builder& id(flecs::id_t the_id) { + desc_.id = the_id; + return *this; + } + + metric_builder& id(flecs::entity_t first, flecs::entity_t second) { + desc_.id = ecs_pair(first, second); + return *this; + } + + template + metric_builder& id() { + return id(_::type::id(world_)); + } + + template + metric_builder& id(flecs::entity_t second) { + return id(_::type::id(world_), second); + } + + template + metric_builder& id_second(flecs::entity_t first) { + return id(first, _::type::id(world_)); + } + + template + metric_builder& id() { + return id(_::type::id(world_)); + } + + metric_builder& targets(bool value = true) { + desc_.targets = value; + return *this; + } + + metric_builder& kind(flecs::entity_t the_kind) { + desc_.kind = the_kind; + return *this; + } + + template + metric_builder& kind() { + return kind(_::type::id(world_)); + } + + metric_builder& brief(const char *b) { + desc_.brief = b; + return *this; + } + + operator flecs::entity(); + +protected: + flecs::world_t *world_; + ecs_metric_desc_t desc_ = {}; + bool created_ = false; +}; + +/** + * @} + */ + +} + + +namespace flecs { + +/** + * @defgroup cpp_addons_metrics Metrics + * @ingroup cpp_addons + * The metrics module extracts metrics from components and makes them available + * through a unified component interface. + * + * @{ + */ + +struct metrics { + using Value = EcsMetricValue; + using Source = EcsMetricSource; + + struct Instance { }; + struct Metric { }; + struct Counter { }; + struct CounterIncrement { }; + struct CounterId { }; + struct Gauge { }; + + metrics(flecs::world& world); +}; + +/** @} */ + +} + +#endif +#ifdef FLECS_ALERTS +/** + * @file addons/cpp/mixins/alerts/decl.hpp + * @brief Alert declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_alerts Alerts + * @ingroup cpp_addons + * Alert implementation. + * + * @{ + */ + +/** Module */ +struct alerts { + using AlertsActive = EcsAlertsActive; + using Instance = EcsAlertInstance; + + struct Alert { }; + struct Info { }; + struct Warning { }; + struct Error { }; + + alerts(flecs::world& world); +}; + +template +struct alert; + +template +struct alert_builder; + +/** @} */ + +} + +#endif +#ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/decl.hpp + * @brief JSON addon declarations. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_json Json + * @ingroup cpp_addons + * Functions for serializing to/from JSON. + * + * @{ + */ + +using from_json_desc_t = ecs_from_json_desc_t; +using entity_to_json_desc_t = ecs_entity_to_json_desc_t; +using iter_to_json_desc_t = ecs_iter_to_json_desc_t; + +/** @} */ + +} + +#endif +#ifdef FLECS_APP +/** + * @file addons/cpp/mixins/app/decl.hpp + * @brief App addon declarations. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/app/builder.hpp + * @brief App builder. + */ + +#pragma once + +namespace flecs { + +/** + * @defgroup cpp_addons_app App + * @ingroup cpp_addons + * Optional addon for running the main application loop. + * + * @{ + */ + +/** App builder interface */ +struct app_builder { + app_builder(flecs::world_t *world) + : world_(world) + , desc_{} + { + const ecs_world_info_t *stats = ecs_get_world_info(world); + desc_.target_fps = stats->target_fps; + ecs_ftime_t t_zero = 0.0; + if (ECS_EQ(desc_.target_fps, t_zero)) { + desc_.target_fps = 60; + } + } + + app_builder& target_fps(ecs_ftime_t value) { + desc_.target_fps = value; + return *this; + } + + app_builder& delta_time(ecs_ftime_t value) { + desc_.delta_time = value; + return *this; + } + + app_builder& threads(int32_t value) { + desc_.threads = value; + return *this; + } + + app_builder& frames(int32_t value) { + desc_.frames = value; + return *this; + } + + app_builder& enable_rest(uint16_t port = 0) { + desc_.enable_rest = true; + desc_.port = port; + return *this; + } + + app_builder& enable_stats(bool value = true) { + desc_.enable_stats = value; + return *this; + } + + app_builder& init(ecs_app_init_action_t value) { + desc_.init = value; + return *this; + } + + app_builder& ctx(void *value) { + desc_.ctx = value; + return *this; + } + + int run() { + int result = ecs_app_run(world_, &desc_); + if (ecs_should_quit(world_)) { + // Only free world if quit flag is set. This ensures that we won't + // try to cleanup the world if the app is used in an environment + // that takes over the main loop, like with emscripten. + if (!flecs_poly_release(world_)) { + ecs_fini(world_); + } + } + return result; + } + +private: + flecs::world_t *world_; + ecs_app_desc_t desc_; +}; + +/** @} */ + +} + + +#endif +#ifdef FLECS_SCRIPT +/** + * @file addons/cpp/mixins/script/decl.hpp + * @brief Script declarations. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/script/builder.hpp + * @brief Script builder. + */ + +#pragma once + +namespace flecs { + +/** + * @ingroup cpp_addons_script + * @{ + */ + +/** Script builder interface */ +struct script_builder { + script_builder(flecs::world_t *world, const char *name = nullptr) + : world_(world) + , desc_{} + { + if (name != nullptr) { + ecs_entity_desc_t entity_desc = {}; + entity_desc.name = name; + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; + this->desc_.entity = ecs_entity_init(world, &entity_desc); + } + } + + script_builder& code(const char *str) { + desc_.code = str; + return *this; + } + + script_builder& filename(const char *str) { + desc_.filename = str; + return *this; + } + + flecs::entity run() const; + +protected: + flecs::world_t *world_; + ecs_script_desc_t desc_; +}; + +} + + +namespace flecs { + +/** + * @defgroup cpp_addons_script Script + * @ingroup cpp_addons + * + * @{ + */ + +struct script_builder; + +using Script = EcsScript; + +namespace script { +namespace _ { + +void init(flecs::world& world); + +} // namespace _ +} + +/** @} */ + +} + +#endif + +/** + * @file addons/cpp/log.hpp + * @brief Logging functions. + */ + +#pragma once + +namespace flecs { +namespace log { + +/** + * @defgroup cpp_log Logging + * @ingroup cpp_addons + * Logging functions. + * + * @{ + */ + +/** Set log level */ +inline void set_level(int level) { + ecs_log_set_level(level); +} + +inline int get_level() { + return ecs_log_get_level(); +} + +/** Enable colors in logging */ +inline void enable_colors(bool enabled = true) { + ecs_log_enable_colors(enabled); +} + +/** Enable timestamps in logging */ +inline void enable_timestamp(bool enabled = true) { + ecs_log_enable_timestamp(enabled); +} + +/** Enable time delta in logging */ +inline void enable_timedelta(bool enabled = true) { + ecs_log_enable_timedelta(enabled); +} + +/** Debug trace (level 1) */ +inline void dbg(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ecs_logv(1, fmt, args); + va_end(args); +} + +/** Trace (level 0) */ +inline void trace(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ecs_logv(0, fmt, args); + va_end(args); +} + +/** Trace (level -2) */ +inline void warn(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ecs_logv(-2, fmt, args); + va_end(args); +} + +/** Trace (level -3) */ +inline void err(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ecs_logv(-3, fmt, args); + va_end(args); +} + +/** Increase log indentation */ +inline void push(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ecs_logv(0, fmt, args); + va_end(args); + ecs_log_push(); +} + +/** Increase log indentation */ +inline void push() { + ecs_log_push(); +} + +/** Increase log indentation */ +inline void pop() { + ecs_log_pop(); +} + +/** @} */ + +} +} + +/** + * @file addons/cpp/pair.hpp + * @brief Utilities for working with compile time pairs. + */ + +#pragma once + +namespace flecs { + +namespace _ { + struct pair_base { }; +} // _ + + +/** + * @defgroup cpp_pair_type Pair type + * @ingroup cpp_core + * Compile time utilities for working with relationship pairs. + * + * @{ + */ + +/** Type that represents a pair. + * The pair type can be used to represent a pair at compile time, and is able + * to automatically derive the storage type associated with the pair, accessible + * through pair::type. + * + * The storage type is derived using the following rules: + * - if pair::first is non-empty, the storage type is pair::first + * - if pair::first is empty and pair::second is non-empty, the storage type is pair::second + * + * The pair type can hold a temporary value so that it can be used in the + * signatures of queries + */ +template +struct pair : _::pair_base { + using type = conditional_t::value || is_empty::value, First, Second>; + using first = First; + using second = Second; + + pair(type& v) : ref_(v) { } + + // This allows the class to be used as a temporary object + pair(const type& v) : ref_(const_cast(v)) { } + + operator type&() { + return ref_; + } + + operator const type&() const { + return ref_; + } + + type* operator->() { + return &ref_; + } + + const type* operator->() const { + return &ref_; + } + + type& operator*() { + return ref_; + } + + const type& operator*() const { + return ref_; + } + +private: + type& ref_; +}; + +template ::value> = 0> +using pair_object = pair; + +template +using raw_type_t = remove_pointer_t>; + +/** Test if type is a pair. */ +template +struct is_pair { + static constexpr bool value = is_base_of_v<_::pair_base, raw_type_t>; +}; + +template +inline constexpr bool is_pair_v = is_pair::value; + +/** Get pair::first from pair while preserving cv qualifiers. */ +template +using pair_first_t = transcribe_cv_t, typename raw_type_t

::first>; + +/** Get pair::second from pair while preserving cv qualifiers. */ +template +using pair_second_t = transcribe_cv_t, typename raw_type_t

::second>; + +/** Get pair::type type from pair while preserving cv qualifiers and pointer type. */ +template +using pair_type_t = transcribe_cvp_t, typename raw_type_t

::type>; + +/** Get actual type from a regular type or pair. */ +template +struct actual_type; + +template +struct actual_type::value >> { + using type = T; +}; + +template +struct actual_type::value >> { + using type = pair_type_t; +}; + +template +using actual_type_t = typename actual_type::type; + + +// Get type without const, *, & +template +struct base_type { + using type = decay_t< actual_type_t >; +}; + +template +using base_type_t = typename base_type::type; + + +// Get type without *, & (retains const which is useful for function args) +template +struct base_arg_type { + using type = remove_pointer_t< remove_reference_t< actual_type_t > >; +}; + +template +using base_arg_type_t = typename base_arg_type::type; + + +// Test if type is the same as its actual type +template +struct is_actual { + static constexpr bool value = is_same_v>; +}; + +template +inline constexpr bool is_actual_v = is_actual::value; + +} // flecs + +/** + * @file addons/cpp/lifecycle_traits.hpp + * @brief Utilities for discovering and registering component lifecycle hooks. + */ + +#pragma once + +namespace flecs +{ + +namespace _ +{ + +// T() +// Can't coexist with T(flecs::entity) or T(flecs::world, flecs::entity) +template +void ctor_impl(void *ptr, int32_t count, const ecs_type_info_t *info) { + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *arr = static_cast(ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&arr[i], T); + } +} + +// ~T() +template +void dtor_impl(void *ptr, int32_t count, const ecs_type_info_t *info) { + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *arr = static_cast(ptr); + for (int i = 0; i < count; i ++) { + arr[i].~T(); + } +} + +// T& operator=(const T&) +template +void copy_impl(void *dst_ptr, const void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + const T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + dst_arr[i] = src_arr[i]; + } +} + +// T& operator=(T&&) +template +void move_impl(void *dst_ptr, void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + dst_arr[i] = FLECS_MOV(src_arr[i]); + } +} + +// T(T&) +template +void copy_ctor_impl(void *dst_ptr, const void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + const T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(src_arr[i])); + } +} + +// T(T&&) +template +void move_ctor_impl(void *dst_ptr, void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(FLECS_MOV(src_arr[i]))); + } +} + +// T(T&&), ~T() +// Typically used when moving to a new table, and removing from the old table +template +void ctor_move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + FLECS_PLACEMENT_NEW(&dst_arr[i], T(FLECS_MOV(src_arr[i]))); + src_arr[i].~T(); + } +} + +// Move assign + dtor (non-trivial move assignment) +// Typically used when moving a component to a deleted component +template ::value > = 0> +void move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + // Move assignment should free dst & assign dst to src + dst_arr[i] = FLECS_MOV(src_arr[i]); + // Destruct src. Move should have left object in a state where it no + // longer holds resources, but it still needs to be destructed. + src_arr[i].~T(); + } +} + +// Move assign + dtor (trivial move assignment) +// Typically used when moving a component to a deleted component +template ::value > = 0> +void move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, + const ecs_type_info_t *info) +{ + (void)info; ecs_assert(info->size == ECS_SIZEOF(T), + ECS_INTERNAL_ERROR, NULL); + T *dst_arr = static_cast(dst_ptr); + T *src_arr = static_cast(src_ptr); + for (int i = 0; i < count; i ++) { + // Cleanup resources of dst + dst_arr[i].~T(); + // Copy src to dst + dst_arr[i] = FLECS_MOV(src_arr[i]); + // No need to destruct src. Since this is a trivial move the code + // should be agnostic to the address of the component which means we + // can pretend nothing got destructed. + } +} + +} // _ + +// Trait to test if type is constructible by flecs +template +struct is_flecs_constructible { + static constexpr bool value = + std::is_default_constructible>::value; +}; + +namespace _ +{ + +template +ecs_xtor_t ctor(ecs_flags32_t &flags) { + if constexpr (is_trivially_constructible_v) { + return nullptr; + } else if constexpr (!is_default_constructible_v) { + flags |= ECS_TYPE_HOOK_CTOR_ILLEGAL; + return nullptr; + } else { + return ctor_impl; + } +} -#endif -#ifdef FLECS_OS_API_IMPL -#ifdef FLECS_NO_OS_API_IMPL -#error "FLECS_NO_OS_API_IMPL failed: OS_API_IMPL is required by other addons" -#endif -/** - * @file os_api_impl.h - * @brief Default OS API implementation. - */ +template +ecs_xtor_t dtor(ecs_flags32_t &flags) { + if constexpr (is_trivially_destructible_v) { + return nullptr; + } else if constexpr (!is_destructible_v) { + flecs_static_assert(always_false::value, + "component type must be destructible"); + flags |= ECS_TYPE_HOOK_DTOR_ILLEGAL; + return nullptr; + } else { + return dtor_impl; + } +} -#ifdef FLECS_OS_API_IMPL +template +ecs_copy_t copy(ecs_flags32_t &flags) { + if constexpr (is_trivially_copyable_v) { + return nullptr; + } else if constexpr (!is_copy_assignable_v) { + flags |= ECS_TYPE_HOOK_COPY_ILLEGAL; + return nullptr; + } else { + return copy_impl; + } +} -#ifndef FLECS_OS_API_IMPL_H -#define FLECS_OS_API_IMPL_H +template +ecs_move_t move(ecs_flags32_t &flags) { + if constexpr (is_trivially_move_assignable_v) { + return nullptr; + } else if constexpr (!is_move_assignable_v) { + flags |= ECS_TYPE_HOOK_MOVE_ILLEGAL; + return nullptr; + } else { + return move_impl; + } +} -#ifdef __cplusplus -extern "C" { -#endif +template +ecs_copy_t copy_ctor(ecs_flags32_t &flags) { + if constexpr (is_trivially_copy_constructible_v) { + return nullptr; + } else if constexpr (!is_copy_constructible_v) { + flags |= ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL; + return nullptr; + } else { + return copy_ctor_impl; + } +} -FLECS_API -void ecs_set_os_api_impl(void); +template +ecs_move_t move_ctor(ecs_flags32_t &flags) { + if constexpr (is_trivially_move_constructible_v) { + return nullptr; + } else if constexpr (!is_move_constructible_v) { + flags |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; + return nullptr; + } else { + return move_ctor_impl; + } +} -#ifdef __cplusplus +template +ecs_move_t ctor_move_dtor(ecs_flags32_t &flags) { + if constexpr (is_trivially_move_constructible_v && is_trivially_destructible_v) { + return nullptr; + } else if constexpr (!is_move_constructible_v || !is_destructible_v) { + flags |= ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL; + return nullptr; + } else { + return ctor_move_dtor_impl; + } } -#endif -#endif // FLECS_OS_API_IMPL_H +template +ecs_move_t move_dtor(ecs_flags32_t &flags) { + if constexpr (is_trivially_move_assignable_v && is_trivially_destructible_v) { + return nullptr; + } else if constexpr (!is_move_assignable_v || !is_destructible_v) { + flags |= ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL; + return nullptr; + } else { + return move_dtor_impl; + } +} -#endif // FLECS_OS_API_IMPL +// Traits to check for operator<, operator>, and operator== +using std::void_t; +// These traits causes a "float comparison warning" in some compilers +// when `T` is float or double. +// Disable this warning with the following pragmas: +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) && !defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" #endif -#ifdef FLECS_MODULE -#ifdef FLECS_NO_MODULE -#error "FLECS_NO_MODULE failed: MODULE is required by other addons" -#endif -/** - * @file module.h - * @brief Module addon. - * - * The module addon allows for creating and importing modules. Flecs modules - * enable applications to organize components and systems into reusable units of - * code that can easily be across projects. - */ -#ifdef FLECS_MODULE +// Trait to check for operator< +template +struct has_operator_less : std::false_type {}; -#ifndef FLECS_MODULE_H -#define FLECS_MODULE_H +// Only enable if T has an operator< that takes T as the right-hand side (no implicit conversion) +template +struct has_operator_less() < std::declval())>> : + std::is_same() < std::declval()), bool> {}; -#ifdef __cplusplus -extern "C" { -#endif +// Trait to check for operator> +template +struct has_operator_greater : std::false_type {}; -/** Import a module. - * This operation will load a modules and store the public module handles in the - * handles_out out parameter. The module name will be used to verify if the - * module was already loaded, in which case it won't be reimported. The name - * will be translated from PascalCase to an entity path (pascal.case) before the - * lookup occurs. - * - * Module contents will be stored as children of the module entity. This - * prevents modules from accidentally defining conflicting identifiers. This is - * enforced by setting the scope before and after loading the module to the - * module entity id. - * - * A more convenient way to import a module is by using the ECS_IMPORT macro. - * - * @param world The world. - * @param module The module import function. - * @param module_name The name of the module. - * @return The module entity. - */ -FLECS_API -ecs_entity_t ecs_import( - ecs_world_t *world, - ecs_module_action_t module, - const char *module_name); +// Only enable if T has an operator> that takes T as the right-hand side (no implicit conversion) +template +struct has_operator_greater() > std::declval())>> : + std::is_same() > std::declval()), bool> {}; -/* Import a module from a library. - * Similar to ecs_import, except that this operation will attempt to load the - * module from a dynamic library. - * - * A library may contain multiple modules, which is why both a library name and - * a module name need to be provided. If only a library name is provided, the - * library name will be reused for the module name. - * - * The library will be looked up using a canonical name, which is in the same - * form as a module, like `flecs.components.transform`. To transform this - * identifier to a platform specific library name, the operation relies on the - * module_to_dl callback of the os_api which the application has to override if - * the default does not yield the correct library name. - * - * @param world The world. - * @param library_name The name of the library to load. - * @param module_name The name of the module to load. - */ -FLECS_API -ecs_entity_t ecs_import_from_library( - ecs_world_t *world, - const char *library_name, - const char *module_name); +// Trait to check for operator== +template +struct has_operator_equal : std::false_type {}; -/** Register a new module. - */ -FLECS_API -ecs_entity_t ecs_module_init( - ecs_world_t *world, - const ecs_component_desc_t *desc); +// Only enable if T has an operator== that takes T as the right-hand side (no implicit conversion) +template +struct has_operator_equal() == std::declval())>> : + std::is_same() == std::declval()), bool> {}; -/** Define module - */ -#define ECS_MODULE(world, id)\ - ecs_entity_t ecs_id(id) = ecs_module_init(world, &(ecs_component_desc_t){\ - .entity = {\ - .name = #id,\ - .add = {EcsModule}\ - }\ - });\ - ecs_set_scope(world, ecs_id(id));\ - (void)ecs_id(id); +// Selects the best comparison strategy based on available operators +template +int compare_impl(const void *a, const void *b, const ecs_type_info_t *) { + const T& lhs = *static_cast(a); + const T& rhs = *static_cast(b); + + if constexpr (has_operator_less::value && has_operator_equal::value) { + // 2. Compare function if `<` and `==` are defined (preferred) + if (lhs == rhs) return 0; + if (lhs < rhs) return -1; + return 1; + } else if constexpr (has_operator_greater::value && has_operator_equal::value) { + // 3. Compare function if `>` and `==` are defined, deducing `<` + if (lhs == rhs) return 0; + if (lhs > rhs) return 1; + return -1; + } else if constexpr (has_operator_less::value && has_operator_greater::value) { + // 1. Compare function if `<`, `>` are defined + if (lhs < rhs) return -1; + if (lhs > rhs) return 1; + return 0; + } else if constexpr (has_operator_less::value) { + // 4. Compare function if only `<` is defined + if (lhs < rhs) return -1; + if (rhs < lhs) return 1; + return 0; + } else if constexpr (has_operator_greater::value) { + // 5. Compare function if only `>` is defined + if (lhs > rhs) return 1; + if (rhs > lhs) return -1; + return 0; + } else { + // This branch should never be instantiated due to compare() check + return 0; + } +} -/** Wrapper around ecs_import. - * This macro provides a convenient way to load a module with the world. It can - * be used like this: - * - * ECS_IMPORT(world, FlecsSystemsPhysics, 0); - * - * This macro will define entity and type handles for the component associated - * with the module. The module component will be created as a singleton. - * - * The contents of a module component are module specific, although they - * typically contain handles to the content of the module. - */ -#define ECS_IMPORT(world, id) \ - char *FLECS__##id##_name = ecs_module_path_from_c(#id);\ - ecs_id_t ecs_id(id) = ecs_import(world, id##Import, FLECS__##id##_name);\ - ecs_os_free(FLECS__##id##_name);\ - (void)ecs_id(id) +// In order to have a generated compare hook, at least +// operator> or operator< must be defined: +template +ecs_cmp_t compare() { + if constexpr (has_operator_less::value || has_operator_greater::value) { + return compare_impl; + } else { + return NULL; + } +} -#ifdef __cplusplus +// Equals implementation +template +bool equals_impl(const void *a, const void *b, const ecs_type_info_t *) { + const T& lhs = *static_cast(a); + const T& rhs = *static_cast(b); + return lhs == rhs; } -#endif -#endif +template +ecs_equals_t equals() { + if constexpr (has_operator_equal::value) { + return equals_impl; + } else { + return NULL; + } +} +// re-enable the float comparison warning: +#if defined(__clang__) + #pragma clang diagnostic pop +#elif defined(__GNUC__) && !defined(__clang__) + #pragma GCC diagnostic pop #endif -#endif +} // _ +} // flecs -#ifdef FLECS_CPP -#ifdef FLECS_NO_CPP -#error "FLECS_NO_CPP failed: CPP is required by other addons" -#endif /** - * @file flecs_cpp.h - * @brief C++ utility functions - * - * This header contains utility functions that are accessible from both C and - * C++ code. These functions are not part of the public API and are not meant - * to be used directly by applications. + * @file addons/cpp/world.hpp + * @brief World class. */ -#ifdef FLECS_CPP +#pragma once -#ifndef FLECS_CPP_H -#define FLECS_CPP_H +namespace flecs +{ -#ifdef __cplusplus -extern "C" { -#endif +/* Static helper functions to assign a component value */ -#if defined(__clang__) -#define ECS_FUNC_NAME_FRONT(type, name) ((sizeof(#type) + sizeof(" flecs::_::() [T = ") + sizeof(#name)) - 3u) -#define ECS_FUNC_NAME_BACK (sizeof("]") - 1u) -#define ECS_FUNC_NAME __PRETTY_FUNCTION__ -#elif defined(__GNUC__) -#define ECS_FUNC_NAME_FRONT(type, name) ((sizeof(#type) + sizeof(" flecs::_::() [with T = ") + sizeof(#name)) - 3u) -#define ECS_FUNC_NAME_BACK (sizeof("]") - 1u) -#define ECS_FUNC_NAME __PRETTY_FUNCTION__ -#elif defined(_WIN32) -#define ECS_FUNC_NAME_FRONT(type, name) ((sizeof(#type) + sizeof(" __cdecl flecs::_::<") + sizeof(#name)) - 3u) -#define ECS_FUNC_NAME_BACK (sizeof(">(void)") - 1u) -#define ECS_FUNC_NAME __FUNCSIG__ -#else -#error "implicit component registration not supported" -#endif +// set(T&&) +template +inline void set(world_t *world, flecs::entity_t entity, T&& value, flecs::id_t id) { + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); -#define ECS_FUNC_TYPE_LEN(type, name, str)\ - (flecs::string::length(str) - (ECS_FUNC_NAME_FRONT(type, name) + ECS_FUNC_NAME_BACK)) + ecs_cpp_get_mut_t res = ecs_cpp_set(world, entity, id, &value, sizeof(T)); -FLECS_API -char* ecs_cpp_get_type_name( - char *type_name, - const char *func_name, - size_t len); + T& dst = *static_cast*>(res.ptr); + if constexpr (std::is_copy_assignable_v) { + dst = FLECS_FWD(value); + } else { + dst = FLECS_MOV(value); + } -FLECS_API -char* ecs_cpp_get_symbol_name( - char *symbol_name, - const char *type_name, - size_t len); + if (res.stage) { + flecs_defer_end(res.world, res.stage); + } -FLECS_API -char* ecs_cpp_get_constant_name( - char *constant_name, - const char *func_name, - size_t len); + if (res.call_modified) { + ecs_modified_id(world, entity, id); + } +} -FLECS_API -const char* ecs_cpp_trim_module( - ecs_world_t *world, - const char *type_name); +// set(const T&) +template +inline void set(world_t *world, flecs::entity_t entity, const T& value, flecs::id_t id) { + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); -FLECS_API -void ecs_cpp_component_validate( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - size_t size, - size_t alignment, - bool implicit_name); + ecs_cpp_get_mut_t res = ecs_cpp_set(world, entity, id, &value, sizeof(T)); -FLECS_API -ecs_entity_t ecs_cpp_component_register( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - const char *symbol, - ecs_size_t size, - ecs_size_t alignment); + T& dst = *static_cast*>(res.ptr); + dst = value; -FLECS_API -ecs_entity_t ecs_cpp_component_register_explicit( - ecs_world_t *world, - ecs_entity_t s_id, - ecs_entity_t id, - const char *name, - const char *type_name, - const char *symbol, - size_t size, - size_t alignment, - bool is_component); + if (res.stage) { + flecs_defer_end(res.world, res.stage); + } + + if (res.call_modified) { + ecs_modified_id(world, entity, id); + } +} + +// set(T&&) +template +inline void set(world_t *world, entity_t entity, A&& value) { + id_t id = _::type::id(world); + flecs::set(world, entity, FLECS_FWD(value), id); +} + +// set(const T&) +template +inline void set(world_t *world, entity_t entity, const A& value) { + id_t id = _::type::id(world); + flecs::set(world, entity, value, id); +} -FLECS_API -ecs_entity_t ecs_cpp_enum_constant_register( - ecs_world_t *world, - ecs_entity_t parent, - ecs_entity_t id, - const char *name, - int value); +// assign(T&&) +template +inline void assign(world_t *world, flecs::entity_t entity, T&& value, flecs::id_t id) { + ecs_assert(_::type>::size() != 0, + ECS_INVALID_PARAMETER, "operation invalid for empty type"); -FLECS_API -int32_t ecs_cpp_reset_count_get(void); + ecs_cpp_get_mut_t res = ecs_cpp_assign( + world, entity, id, &value, sizeof(T)); -FLECS_API -int32_t ecs_cpp_reset_count_inc(void); + T& dst = *static_cast*>(res.ptr); + if constexpr (std::is_copy_assignable_v) { + dst = FLECS_FWD(value); + } else { + dst = FLECS_MOV(value); + } -#ifdef __cplusplus + if (res.stage) { + flecs_defer_end(res.world, res.stage); + } + + if (res.call_modified) { + ecs_modified_id(world, entity, id); + } } -#endif -#endif // FLECS_CPP_H +// assign(const T&) +template +inline void assign(world_t *world, flecs::entity_t entity, const T& value, flecs::id_t id) { + ecs_assert(_::type>::size() != 0, + ECS_INVALID_PARAMETER, "operation invalid for empty type"); -#endif // FLECS_CPP + ecs_cpp_get_mut_t res = ecs_cpp_assign( + world, entity, id, &value, sizeof(T)); + T& dst = *static_cast*>(res.ptr); + dst = value; -#ifdef __cplusplus + if (res.stage) { + flecs_defer_end(res.world, res.stage); + } + + if (res.call_modified) { + ecs_modified_id(world, entity, id); + } } -/** - * @file flecs.hpp - * @brief Flecs C++ API. - * - * Modern C++11 API - */ +// set(T&&) +template +inline void assign(world_t *world, entity_t entity, A&& value) { + id_t id = _::type::id(world); + flecs::assign(world, entity, FLECS_FWD(value), id); +} -#pragma once +// set(const T&) +template +inline void assign(world_t *world, entity_t entity, const A& value) { + id_t id = _::type::id(world); + flecs::assign(world, entity, value, id); +} -// STL includes -#include -// Forward declarations -namespace flecs -{ +// emplace for T(Args...) +template , Args...>::value || + std::is_default_constructible>::value > = 0> +inline void emplace(world_t *world, flecs::entity_t entity, flecs::id_t id, Args&&... args) { + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + T& dst = *static_cast(ecs_emplace_id(world, entity, id, sizeof(T), nullptr)); -struct world; -struct world_async_stage; -struct iter; -struct entity_view; -struct entity; -struct untyped_component; + FLECS_PLACEMENT_NEW(&dst, T{FLECS_FWD(args)...}); -template -struct component; + ecs_modified_id(world, entity, id); +} -namespace _ -{ -template -struct cpp_type; +/** Return id without generation. + * + * @see ecs_strip_generation() + */ +inline flecs::id_t strip_generation(flecs::entity_t e) { + return ecs_strip_generation(e); +} -template -struct each_invoker; +/** Return entity generation. + */ +inline uint32_t get_generation(flecs::entity_t e) { + return ECS_GENERATION(e); +} -} // namespace _ -} // namespace flecs +struct scoped_world; -// Types imported from C API -//////////////////////////////////////////////////////////////////////////////// -//// Aliases for types/constants from C API -//////////////////////////////////////////////////////////////////////////////// +/** + * @defgroup cpp_world World + * @ingroup cpp_core + * World operations. + * + * @{ + */ -namespace flecs { +/** The world. + * The world is the container of all ECS data and systems. If the world is + * deleted, all data in the world will be deleted as well. + */ +struct world { + /** Create world. + */ + explicit world() + : world_( ecs_init() ) { + init_builtin_components(); + } -using world_t = ecs_world_t; -using id_t = ecs_id_t; -using ids_t = ecs_ids_t; -using entity_t = ecs_entity_t; -using type_t = ecs_type_t; -using table_t = ecs_table_t; -using filter_t = ecs_filter_t; -using query_t = ecs_query_t; -using rule_t = ecs_rule_t; -using ref_t = ecs_ref_t; -using iter_t = ecs_iter_t; -using ComponentLifecycle = EcsComponentLifecycle; + /** Create world with command line arguments. + * Currently command line arguments are not interpreted, but they may be + * used in the future to configure Flecs parameters. + */ + explicit world(int argc, char *argv[]) + : world_( ecs_init_w_args(argc, argv) ) { + init_builtin_components(); + } -enum inout_kind_t { - InOutDefault = EcsInOutDefault, - InOutFilter = EcsInOutFilter, - InOut = EcsInOut, - In = EcsIn, - Out = EcsOut -}; + /** Create world from C world. + */ + explicit world(world_t *w) + : world_( w ) { + if (w) { + flecs_poly_claim(w); + } + } -enum oper_kind_t { - And = EcsAnd, - Or = EcsOr, - Not = EcsNot, - Optional = EcsOptional, - AndFrom = EcsAndFrom, - OrFrom = EcsOrFrom, - NotFrom = EcsNotFrom -}; + /** Not allowed to copy a world. May only take a reference. + */ + world(const world& obj) { + this->world_ = obj.world_; + flecs_poly_claim(this->world_); + } -enum var_kind_t { - VarDefault = EcsVarDefault, - VarIsEntity = EcsVarIsEntity, - VarIsVariable = EcsVarIsVariable -}; + world& operator=(const world& obj) noexcept { + release(); + this->world_ = obj.world_; + flecs_poly_claim(this->world_); + return *this; + } -//////////////////////////////////////////////////////////////////////////////// -//// Builtin components and tags -//////////////////////////////////////////////////////////////////////////////// + world(world&& obj) noexcept { + world_ = obj.world_; + obj.world_ = nullptr; + } -/* Builtin components */ -using Component = EcsComponent; -using Type = EcsType; -using Identifier = EcsIdentifier; -using Query = EcsQuery; -using Trigger = EcsTrigger; -using Observer = EcsObserver; - -/* Builtin opaque components */ -static const flecs::entity_t System = ecs_id(EcsSystem); - -/* Builtin set constants */ -static const uint8_t DefaultSet = EcsDefaultSet; -static const uint8_t Self = EcsSelf; -static const uint8_t SuperSet = EcsSuperSet; -static const uint8_t SubSet = EcsSubSet; -static const uint8_t Cascade = EcsCascade; -static const uint8_t All = EcsAll; -static const uint8_t Nothing = EcsNothing; -static const uint8_t Parent = EcsParent; - -/* Builtin tag ids */ -static const flecs::entity_t Private = EcsPrivate; -static const flecs::entity_t Module = EcsModule; -static const flecs::entity_t Prefab = EcsPrefab; -static const flecs::entity_t Disabled = EcsDisabled; -static const flecs::entity_t Inactive = EcsInactive; -static const flecs::entity_t Monitor = EcsMonitor; -static const flecs::entity_t Pipeline = EcsPipeline; + world& operator=(world&& obj) noexcept { + release(); + world_ = obj.world_; + obj.world_ = nullptr; + return *this; + } -/* Trigger tags */ -static const flecs::entity_t OnAdd = EcsOnAdd; -static const flecs::entity_t OnRemove = EcsOnRemove; -static const flecs::entity_t OnSet = EcsOnSet; -static const flecs::entity_t UnSet = EcsUnSet; + /* Releases the underlying world object. If this is the last handle, the world + will be finalized. */ + void release() { + if (world_) { + if (!flecs_poly_release(world_)) { + if (ecs_stage_get_id(world_) == -1) { + ecs_stage_free(world_); + } else { + // before we call ecs_fini(), we increment the reference count back to 1 + // otherwise, copies of this object created during ecs_fini (e.g. a component on_remove hook) + // would call again this destructor and ecs_fini(). + flecs_poly_claim(world_); + ecs_fini(world_); + } + } + world_ = nullptr; + } + } -/** Builtin roles */ -static const flecs::entity_t Pair = ECS_PAIR; -static const flecs::entity_t Switch = ECS_SWITCH; -static const flecs::entity_t Case = ECS_CASE; -static const flecs::entity_t Override = ECS_OVERRIDE; + ~world() { + release(); + } -/* Builtin entity ids */ -static const flecs::entity_t Flecs = EcsFlecs; -static const flecs::entity_t FlecsCore = EcsFlecsCore; -static const flecs::entity_t World = EcsWorld; + /* Implicit conversion to world_t* */ + operator world_t*() const { return world_; } -/* Relation properties */ -static const flecs::entity_t Wildcard = EcsWildcard; -static const flecs::entity_t This = EcsThis; -static const flecs::entity_t Transitive = EcsTransitive; -static const flecs::entity_t Reflexive = EcsReflexive; -static const flecs::entity_t Final = EcsFinal; -static const flecs::entity_t DontInherit = EcsDontInherit; -static const flecs::entity_t Tag = EcsTag; -static const flecs::entity_t Exclusive = EcsExclusive; -static const flecs::entity_t Acyclic = EcsAcyclic; -static const flecs::entity_t Symmetric = EcsSymmetric; -static const flecs::entity_t With = EcsWith; -static const flecs::entity_t OneOf = EcsOneOf; + /** Make current world object owner of the world. This may only be called on + * one flecs::world object, an may only be called once. Failing to do so + * will result in undefined behavior. + * + * This operation allows a custom (C) world to be wrapped by a C++ object, + * and transfer ownership so that the world is automatically cleaned up. + */ + void make_owner() { + flecs_poly_release(world_); + } -/* Builtin relationships */ -static const flecs::entity_t IsA = EcsIsA; -static const flecs::entity_t ChildOf = EcsChildOf; + /** Deletes and recreates the world. */ + void reset() { + /* Make sure there's only one reference to the world */ + ecs_assert(flecs_poly_refcount(world_) == 1, ECS_INVALID_OPERATION, + "reset would invalidate other handles"); + ecs_fini(world_); + world_ = ecs_init(); + init_builtin_components(); + } -/* Builtin identifiers */ -static const flecs::entity_t Name = EcsName; -static const flecs::entity_t Symbol = EcsSymbol; + /** Obtain pointer to C world object. + */ + world_t* c_ptr() const { + return world_; + } -/* Cleanup policies */ -static const flecs::entity_t OnDelete = EcsOnDelete; -static const flecs::entity_t OnDeleteObject = EcsOnDeleteObject; -static const flecs::entity_t Remove = EcsRemove; -static const flecs::entity_t Delete = EcsDelete; -static const flecs::entity_t Throw = EcsThrow; + /** Signal application should quit. + * After calling this operation, the next call to progress() returns false. + */ + void quit() const { + ecs_quit(world_); + } -} + /** Register action to be executed when world is destroyed. + */ + void atfini(ecs_fini_action_t action, void *ctx = nullptr) const { + ecs_atfini(world_, action, ctx); + } + + /** Test if quit() has been called. + */ + bool should_quit() const { + return ecs_should_quit(world_); + } + /** Begin frame. + * When an application does not use progress() to control the main loop, it + * can still use Flecs features such as FPS limiting and time measurements. + * This operation needs to be invoked whenever a new frame is about to get + * processed. + * + * Calls to frame_begin() must always be followed by frame_end(). + * + * The function accepts a delta_time parameter, which will get passed to + * systems. This value is also used to compute the amount of time the + * function needs to sleep to ensure it does not exceed the target_fps, when + * it is set. When 0 is provided for delta_time, the time will be measured. + * + * This function should only be ran from the main thread. + * + * @param delta_time Time elapsed since the last frame. + * @return The provided delta_time, or measured time if 0 was provided. + * + * @see ecs_frame_begin() + * @see flecs::world::frame_end() + */ + ecs_ftime_t frame_begin(float delta_time = 0) const { + return ecs_frame_begin(world_, delta_time); + } -// C++ utilities -//////////////////////////////////////////////////////////////////////////////// -//// Flecs STL (FTL?) -//// Minimalistic utilities that allow for STL like functionality without having -//// to depend on the actual STL. -//////////////////////////////////////////////////////////////////////////////// + /** End frame. + * This operation must be called at the end of the frame, and always after + * frame_begin(). + * + * This function should only be ran from the main thread. + * + * @see ecs_frame_end() + * @see flecs::world::frame_begin() + */ + void frame_end() const { + ecs_frame_end(world_); + } -// Macros so that C++ new calls can allocate using ecs_os_api memory allocation functions -// Rationale: -// - Using macros here instead of a templated function bc clients might override ecs_os_malloc -// to contain extra debug info like source tracking location. Using a template function -// in that scenario would collapse all source location into said function vs. the -// actual call site -// - FLECS_PLACEMENT_NEW(): exists to remove any naked new calls/make it easy to identify any regressions -// by grepping for new/delete + /** Begin readonly mode. + * + * @param multi_threaded Whether to enable readonly/multi threaded mode. + * + * @return Whether world is currently readonly. + * + * @see ecs_readonly_begin() + * @see flecs::world::is_readonly() + * @see flecs::world::readonly_end() + */ + bool readonly_begin(bool multi_threaded = false) const { + return ecs_readonly_begin(world_, multi_threaded); + } -#define FLECS_PLACEMENT_NEW(_ptr, _type) ::new(flecs::_::placement_new_tag, _ptr) _type -#define FLECS_NEW(_type) FLECS_PLACEMENT_NEW(ecs_os_malloc(sizeof(_type)), _type) -#define FLECS_DELETE(_ptr) \ - do { \ - if (_ptr) { \ - flecs::_::destruct_obj(_ptr); \ - ecs_os_free(_ptr); \ - } \ - } while (false) + /** End readonly mode. + * + * @see ecs_readonly_end() + * @see flecs::world::is_readonly() + * @see flecs::world::readonly_begin() + */ + void readonly_end() const { + ecs_readonly_end(world_); + } + + /** Defer operations until end of frame. + * When this operation is invoked while iterating, operations inbetween the + * defer_begin() and defer_end() operations are executed at the end of the frame. + * + * This operation is thread safe. + * + * @return true if world changed from non-deferred mode to deferred mode. + * + * @see ecs_defer_begin() + * @see flecs::world::defer() + * @see flecs::world::defer_end() + * @see flecs::world::is_deferred() + * @see flecs::world::defer_resume() + * @see flecs::world::defer_suspend() + * @see flecs::world::is_defer_suspended() + */ + bool defer_begin() const { + return ecs_defer_begin(world_); + } + + /** End block of operations to defer. + * See defer_begin(). + * + * This operation is thread safe. + * + * @return true if world changed from deferred mode to non-deferred mode. + * + * @see ecs_defer_end() + * @see flecs::world::defer() + * @see flecs::world::defer_begin() + * @see flecs::world::is_deferred() + * @see flecs::world::defer_resume() + * @see flecs::world::defer_suspend() + * @see flecs::world::is_defer_suspended() + */ + bool defer_end() const { + return ecs_defer_end(world_); + } -/* Faster (compile time) alternatives to std::move / std::forward. From: - * https://www.foonathan.net/2020/09/move-forward/ - */ + /** Test whether deferring is enabled. + * + * @return True if deferred, false if not. + * + * @see ecs_is_deferred() + * @see flecs::world::defer() + * @see flecs::world::defer_begin() + * @see flecs::world::defer_end() + * @see flecs::world::defer_resume() + * @see flecs::world::defer_suspend() + * @see flecs::world::is_defer_suspended() + */ + bool is_deferred() const { + return ecs_is_deferred(world_); + } -#define FLECS_MOV(...) \ - static_cast&&>(__VA_ARGS__) + /** Test whether deferring is suspended. + * + * @return True if deferred, false if not. + * + * @see ecs_is_defer_suspended() + * @see flecs::world::defer() + * @see flecs::world::defer_begin() + * @see flecs::world::defer_end() + * @see flecs::world::is_deferred() + * @see flecs::world::defer_resume() + * @see flecs::world::defer_suspend() + */ + bool is_defer_suspended() const { + return ecs_is_defer_suspended(world_); + } -#define FLECS_FWD(...) \ - static_cast(__VA_ARGS__) + /** Configure world to have N stages. + * This initializes N stages, which allows applications to defer operations to + * multiple isolated defer queues. This is typically used for applications with + * multiple threads, where each thread gets its own queue, and commands are + * merged when threads are synchronized. + * + * Note that set_threads() already creates the appropriate number of stages. + * The set_stage_count() operation is useful for applications that want to manage + * their own stages and/or threads. + * + * @param stages The number of stages. + * + * @see ecs_set_stage_count() + * @see flecs::world::get_stage_count() + */ + void set_stage_count(int32_t stages) const { + ecs_set_stage_count(world_, stages); + } -namespace flecs -{ + /** Get number of configured stages. + * Return number of stages set by set_stage_count(). + * + * @return The number of stages used for threading. + * + * @see ecs_get_stage_count() + * @see flecs::world::set_stage_count() + */ + int32_t get_stage_count() const { + return ecs_get_stage_count(world_); + } -namespace _ -{ + /** Get current stage id. + * The stage id can be used by an application to learn about which stage it + * is using, which typically corresponds with the worker thread id. + * + * @return The stage id. + */ + int32_t get_stage_id() const { + return ecs_stage_get_id(world_); + } -// Dummy Placement new tag to disambiguate from any other operator new overrides -struct placement_new_tag_t{}; -constexpr placement_new_tag_t placement_new_tag{}; -template inline void destruct_obj(Ty* _ptr) { _ptr->~Ty(); } -template inline void free_obj(Ty* _ptr) { - if (_ptr) { - destruct_obj(_ptr); - ecs_os_free(_ptr); + /** Test if is a stage. + * If this function returns false, it is guaranteed that this is a valid + * world object. + * + * @return True if the world is a stage, false if not. + */ + bool is_stage() const { + ecs_assert( + flecs_poly_is(world_, ecs_world_t) || + flecs_poly_is(world_, ecs_stage_t), + ECS_INVALID_PARAMETER, + "flecs::world instance contains invalid reference to world or stage"); + return flecs_poly_is(world_, ecs_stage_t); } -} -} // namespace _ + /** Merge world or stage. + * When automatic merging is disabled, an application can call this + * operation on either an individual stage, or on the world which will merge + * all stages. This operation may only be called when staging is not enabled + * (either after progress() or after readonly_end()). + * + * This operation may be called on an already merged stage or world. + * + * @see ecs_merge() + */ + void merge() const { + ecs_merge(world_); + } -} // namespace flecs + /** Get stage-specific world pointer. + * Flecs threads can safely invoke the API as long as they have a private + * context to write to, also referred to as the stage. This function returns a + * pointer to a stage, disguised as a world pointer. + * + * Note that this function does not(!) create a new world. It simply wraps the + * existing world in a thread-specific context, which the API knows how to + * unwrap. The reason the stage is returned as an ecs_world_t is so that it + * can be passed transparently to the existing API functions, vs. having to + * create a dediated API for threading. + * + * @param stage_id The index of the stage to retrieve. + * @return A thread-specific pointer to the world. + */ + flecs::world get_stage(int32_t stage_id) const { + return flecs::world(ecs_get_stage(world_, stage_id)); + } -// Allows overriding flecs_static_assert, which is useful when testing -#ifndef flecs_static_assert -#define flecs_static_assert(cond, str) static_assert(cond, str) -#endif + /** Create asynchronous stage. + * An asynchronous stage can be used to asynchronously queue operations for + * later merging with the world. An asynchronous stage is similar to a regular + * stage, except that it does not allow reading from the world. + * + * Asynchronous stages are never merged automatically, and must therefore be + * manually merged with the ecs_merge function. It is not necessary to call + * defer_begin or defer_end before and after enqueuing commands, as an + * asynchronous stage unconditionally defers operations. + * + * The application must ensure that no commands are added to the stage while the + * stage is being merged. + * + * @return The stage. + */ + flecs::world async_stage() const { + ecs_world_t *as = ecs_stage_new(world_); + flecs_poly_release(as); // world object will claim + return flecs::world(as); + } -inline void* operator new(size_t, flecs::_::placement_new_tag_t, void* _ptr) noexcept { return _ptr; } -inline void operator delete(void*, flecs::_::placement_new_tag_t, void*) noexcept { } + /** Get actual world. + * If the current object points to a stage, this operation will return the + * actual world. + * + * @return The actual world. + */ + flecs::world get_world() const { + /* Safe cast, mutability is checked */ + return flecs::world( + world_ ? const_cast(ecs_get_world(world_)) : nullptr); + } -namespace flecs -{ + /** Test whether the current world object is readonly. + * This function allows the code to test whether the currently used world + * object is readonly or whether it allows for writing. + * + * @return True if the world or stage is readonly. + * + * @see ecs_stage_is_readonly() + * @see flecs::world::readonly_begin() + * @see flecs::world::readonly_end() + */ + bool is_readonly() const { + return ecs_stage_is_readonly(world_); + } + + /** Set world context. + * Set a context value that can be accessed by anyone that has a reference + * to the world. + * + * @param ctx A pointer to a user defined structure. + * @param ctx_free A function that is invoked with ctx when the world is freed. + * + * + * @see ecs_set_ctx() + * @see flecs::world::get_ctx() + */ + void set_ctx(void* ctx, ecs_ctx_free_t ctx_free = nullptr) const { + ecs_set_ctx(world_, ctx, ctx_free); + } -// C++11/C++14 convenience template replacements + /** Get world context. + * This operation retrieves a previously set world context. + * + * @return The context set with set_binding_ctx(). If no context was set, the + * function returns NULL. + * + * @see ecs_get_ctx() + * @see flecs::world::set_ctx() + */ + void* get_ctx() const { + return ecs_get_ctx(world_); + } -template -using conditional_t = typename std::conditional::type; + /** Set world binding context. + * + * Same as set_ctx() but for binding context. A binding context is intended + * specifically for language bindings to store binding specific data. + * + * @param ctx A pointer to a user defined structure. + * @param ctx_free A function that is invoked with ctx when the world is freed. + * + * @see ecs_set_binding_ctx() + * @see flecs::world::get_binding_ctx() + */ + void set_binding_ctx(void* ctx, ecs_ctx_free_t ctx_free = nullptr) const { + ecs_set_binding_ctx(world_, ctx, ctx_free); + } -template -using decay_t = typename std::decay::type; + /** Get world binding context. + * This operation retrieves a previously set world binding context. + * + * @return The context set with set_binding_ctx(). If no context was set, the + * function returns NULL. + * + * @see ecs_get_binding_ctx() + * @see flecs::world::set_binding_ctx() + */ + void* get_binding_ctx() const { + return ecs_get_binding_ctx(world_); + } -template -using enable_if_t = typename std::enable_if::type; + /** Preallocate memory for number of entities. + * This function preallocates memory for the entity index. + * + * @param entity_count Number of entities to preallocate memory for. + * + * @see ecs_dim() + */ + void dim(int32_t entity_count) const { + ecs_dim(world_, entity_count); + } -template -using remove_pointer_t = typename std::remove_pointer::type; + /** Set entity range. + * This function limits the range of issued entity ids between min and max. + * + * @param min Minimum entity id issued. + * @param max Maximum entity id issued. + * + * @see ecs_set_entity_range() + */ + void set_entity_range(entity_t min, entity_t max) const { + ecs_set_entity_range(world_, min, max); + } -template -using remove_reference_t = typename std::remove_reference::type; + /** Enforce that operations cannot modify entities outside of range. + * This function ensures that only entities within the specified range can + * be modified. Use this function if specific parts of the code only are + * allowed to modify a certain set of entities, as could be the case for + * networked applications. + * + * @param enabled True if range check should be enabled, false if not. + * + * @see ecs_enable_range_check() + */ + void enable_range_check(bool enabled = true) const { + ecs_enable_range_check(world_, enabled); + } -using std::is_base_of; -using std::is_empty; -using std::is_const; -using std::is_pointer; -using std::is_reference; -using std::is_volatile; -using std::is_same; -using std::is_enum; + /** Set current scope. + * + * @param scope The scope to set. + * @return The current scope; + * + * @see ecs_set_scope() + * @see flecs::world::get_scope() + */ + flecs::entity set_scope(const flecs::entity_t scope) const; + /** Get current scope. + * + * @return The current scope. + * + * @see ecs_get_scope() + * @see flecs::world::set_scope() + */ + flecs::entity get_scope() const; -// Apply cv modifiers from source type to destination type -// (from: https://stackoverflow.com/questions/52559336/add-const-to-type-if-template-arg-is-const) -template -using transcribe_const_t = conditional_t::value, Dst const, Dst>; + /** Same as set_scope but with type. + * + * @see ecs_set_scope() + * @see flecs::world::get_scope() + */ + template + flecs::entity set_scope() const; -template -using transcribe_volatile_t = conditional_t::value, Dst volatile, Dst>; + /** Set search path. + * + * @see ecs_set_lookup_path() + * @see flecs::world::lookup() + */ + flecs::entity_t* set_lookup_path(const flecs::entity_t *search_path) const { + return ecs_set_lookup_path(world_, search_path); + } -template -using transcribe_cv_t = transcribe_const_t< Src, transcribe_volatile_t< Src, Dst> >; + /** Lookup entity by name. + * + * @param name Entity name. + * @param recursive When false, only the current scope is searched. + * @result The entity if found, or 0 if not found. + */ + flecs::entity lookup(const char *name, const char *sep = "::", const char *root_sep = "::", bool recursive = true) const; + /** Set singleton component. + */ + template ::value > = 0> + void set(const T& value) const { + flecs::set(world_, _::type::id(world_), value); + } -// More convenience templates. The if_*_t templates use int as default type -// instead of void. This enables writing code that's a bit less cluttered when -// the templates are used in a template declaration: -// -// enable_if_t* = nullptr -// vs: -// if_t = 0 + /** Set singleton component. + */ + template ::value > = 0> + void set(T&& value) const { + flecs::set(world_, _::type::id(world_), + FLECS_FWD(value)); + } -template -using if_t = enable_if_t; + /** Set singleton pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + void set(const A& value) const { + flecs::set

(world_, _::type::id(world_), value); + } -template -using if_not_t = enable_if_t; + /** Set singleton pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + void set(A&& value) const { + flecs::set

(world_, _::type::id(world_), FLECS_FWD(value)); + } -namespace _ -{ + /** Set singleton pair. + */ + template + void set(Second second, const First& value) const; -// Utility to prevent static assert from immediately triggering -template -struct always_false { - static const bool value = false; -}; + /** Set singleton pair. + */ + template + void set(Second second, First&& value) const; -} // namespace _ + /** Set singleton component inside a callback. + */ + template ::value > = 0 > + void set(const Func& func) const; -} // namespace flecs + template + void emplace(Args&&... args) const { + flecs::id_t component_id = _::type::id(world_); + flecs::emplace(world_, component_id, component_id, FLECS_FWD(args)...); + } -#include -// Array class. Simple std::array like utility that is mostly there to aid -// template code where template expansion would lead to an array with size 0. + /** Ensure singleton component. + */ + #ifndef ensure + template + T& ensure() const; + #endif -namespace flecs { + /** Mark singleton component as modified. + */ + template + void modified() const; -template -struct array_iterator -{ - explicit array_iterator(T* value, int index) { - m_value = value; - m_index = index; - } + /** Get ref singleton component. + */ + template + ref get_ref() const; - bool operator!=(array_iterator const& other) const - { - return m_index != other.m_index; - } - T & operator*() const - { - return m_value[m_index]; - } + /* try_get */ - array_iterator& operator++() - { - ++m_index; - return *this; - } + /** Get singleton component. + */ + const void* try_get(flecs::id_t id) const; -private: - T* m_value; - int m_index; -}; + /** Get singleton pair. + */ + const void* try_get(flecs::entity_t r, flecs::entity_t t) const; -template -struct array final { }; + /** Get singleton component. + */ + template + const T* try_get() const; -template -struct array > final { - array() {}; + /** Get singleton pair. + */ + template , + typename A = actual_type_t

> + const A* try_get() const; - array(const T (&elems)[Size]) { - int i = 0; - for (auto it = this->begin(); it != this->end(); ++ it) { - *it = elems[i ++]; - } - } + /** Get singleton pair. + */ + template + const First* try_get(Second second) const; - T& operator[](int index) { - return m_array[index]; - } - T& operator[](size_t index) { - return m_array[index]; - } + /* get */ - array_iterator begin() { - return array_iterator(m_array, 0); - } + /** Get singleton component. + */ + const void* get(flecs::id_t id) const; - array_iterator end() { - return array_iterator(m_array, Size); - } + /** Get singleton component. + */ + const void* get(flecs::entity_t r, flecs::entity_t t) const; - size_t size() { - return Size; - } + template + const T& get() const; - T* ptr() { - return m_array; - } + /** Get singleton pair. + */ + template , + typename A = actual_type_t

> + const A& get() const; - template - void each(const Func& func) { - for (auto& elem : *this) { - func(elem); - } - } + /** Get singleton pair. + */ + template + const First& get(Second second) const; -private: - T m_array[Size]; -}; + /** Get singleton component inside a callback. + */ + template ::value > = 0 > + void get(const Func& func) const; -template -array to_array(const T (&elems)[Size]) { - return array(elems); -} -// Specialized class for zero-sized array -template -struct array> final { - array() {}; - array(const T* (&elems)) { (void)elems; } - T operator[](size_t index) { ecs_os_abort(); (void)index; return T(); } - array_iterator begin() { return array_iterator(nullptr, 0); } - array_iterator end() { return array_iterator(nullptr, 0); } + /* try_get_mut */ - size_t size() { - return 0; - } + /** Get mutable singleton component. + */ + void* try_get_mut(flecs::id_t id) const; - T* ptr() { - return NULL; - } -}; + /** Get mutable singleton pair. + */ + void* try_get_mut(flecs::entity_t r, flecs::entity_t t) const; -} + template + T* try_get_mut() const; -// String utility that doesn't implicitly allocate memory. + /** Get mutable singleton pair. + */ + template , + typename A = actual_type_t

> + A* try_get_mut() const; -namespace flecs { + /** Get mutable singleton pair. + */ + template + First* try_get_mut(Second second) const; -struct string_view; -// This removes dependencies on std::string (and therefore STL) and allows the -// API to return allocated strings without incurring additional allocations when -// wrapping in an std::string. -struct string { - explicit string() - : m_str(nullptr) - , m_const_str("") - , m_length(0) { } + /* get_mut */ - explicit string(char *str) - : m_str(str) - , m_const_str(str ? str : "") - , m_length(str ? ecs_os_strlen(str) : 0) { } + /** Get mutable singleton component. + */ + void* get_mut(flecs::id_t id) const; - ~string() { - // If flecs is included in a binary but is not used, it is possible that - // the OS API is not initialized. Calling ecs_os_free in that case could - // crash the application during exit. However, if a string has been set - // flecs has been used, and OS API should have been initialized. - if (m_str) { - ecs_os_free(m_str); - } - } + /** Get mutable singleton pair. + */ + void* get_mut(flecs::entity_t r, flecs::entity_t t) const; - string(string&& str) { - ecs_os_free(m_str); - m_str = str.m_str; - m_const_str = str.m_const_str; - m_length = str.m_length; - str.m_str = nullptr; - } + template + T& get_mut() const; - operator const char*() const { - return m_const_str; - } + /** Get mutable singleton pair. + */ + template , + typename A = actual_type_t

> + A& get_mut() const; - string& operator=(string&& str) { - ecs_os_free(m_str); - m_str = str.m_str; - m_const_str = str.m_const_str; - m_length = str.m_length; - str.m_str = nullptr; - return *this; - } + /** Get mutable singleton pair. + */ + template + First& get_mut(Second second) const; - // Ban implicit copies/allocations - string& operator=(const string& str) = delete; - string(const string& str) = delete; - bool operator==(const flecs::string& str) const { - if (str.m_const_str == m_const_str) { - return true; - } + /** Test if world has singleton component. + * + * @tparam T The component to check. + * @return Whether the world has the singleton component. + */ + template + bool has() const; - if (!m_const_str || !str.m_const_str) { - return false; - } + /** Test if world has the provided pair. + * + * @tparam First The first element of the pair + * @tparam Second The second element of the pair + * @return Whether the world has the singleton pair. + */ + template + bool has() const; - if (str.m_length != m_length) { - return false; - } + /** Test if world has the provided pair. + * + * @tparam First The first element of the pair + * @param second The second element of the pair. + * @return Whether the world has the singleton pair. + */ + template + bool has(flecs::id_t second) const; - return ecs_os_strcmp(str, m_const_str) == 0; - } + /** Test if world has the provided pair. + * + * @param first The first element of the pair + * @param second The second element of the pair + * @return Whether the world has the singleton pair. + */ + bool has(flecs::id_t first, flecs::id_t second) const; - bool operator!=(const flecs::string& str) const { - return !(*this == str); - } + /** Check for enum singleton constant + * + * @tparam E The enum type. + * @param value The enum constant to check. + * @return Whether the world has the specified enum constant. + */ + template ::value > = 0> + bool has(E value) const; - bool operator==(const char *str) const { - if (m_const_str == str) { - return true; - } + /** Add singleton component. + */ + template + void add() const; - if (!m_const_str || !str) { - return false; - } + /** Adds a pair to the singleton component. + * + * @tparam First The first element of the pair + * @tparam Second The second element of the pair + */ + template + void add() const; - return ecs_os_strcmp(str, m_const_str) == 0; - } + /** Adds a pair to the singleton component. + * + * @tparam First The first element of the pair + * @param second The second element of the pair. + */ + template + void add(flecs::entity_t second) const; - bool operator!=(const char *str) const { - return !(*this == str); - } + /** Adds a pair to the singleton entity. + * + * @param first The first element of the pair + * @param second The second element of the pair + */ + void add(flecs::entity_t first, flecs::entity_t second) const; - const char* c_str() const { - return m_const_str; - } + /** Add enum singleton constant + * + * @tparam E The enum type. + * @param value The enum constant. + */ + template ::value > = 0> + void add(E value) const; - std::size_t length() { - return static_cast(m_length); - } + /** Remove singleton component. + */ + template + void remove() const; - template - static constexpr size_t length( char const (&)[N] ) { - return N - 1; - } + /** Removes the pair singleton component. + * + * @tparam First The first element of the pair + * @tparam Second The second element of the pair + */ + template + void remove() const; - std::size_t size() { - return length(); - } + /** Removes the pair singleton component. + * + * @tparam First The first element of the pair + * @param second The second element of the pair. + */ + template + void remove(flecs::entity_t second) const; - void clear() { - ecs_os_free(m_str); - m_str = nullptr; - m_const_str = nullptr; - } + /** Removes the pair singleton component. + * + * @param first The first element of the pair + * @param second The second element of the pair + */ + void remove(flecs::entity_t first, flecs::entity_t second) const; -protected: - // Must be constructed through string_view. This allows for using the string - // class for both owned and non-owned strings, which can reduce allocations - // when code conditionally should store a literal or an owned string. - // Making this constructor private forces the code to explicitly create a - // string_view which emphasizes that the string won't be freed by the class. - string(const char *str) - : m_str(nullptr) - , m_const_str(str ? str : "") - , m_length(str ? ecs_os_strlen(str) : 0) { } + /** Iterate entities in root of world + * Accepts a callback with the following signature: + * + * @code + * void(*)(flecs::entity e); + * @endcode + */ + template + void children(Func&& f) const; - char *m_str = nullptr; - const char *m_const_str; - ecs_size_t m_length; -}; + /** Get singleton entity for type. + */ + template + flecs::entity singleton() const; -// For consistency, the API returns a string_view where it could have returned -// a const char*, so an application won't have to think about whether to call -// c_str() or not. The string_view is a thin wrapper around a string that forces -// the API to indicate explicitly when a string is owned or not. -struct string_view : string { - explicit string_view(const char *str) - : string(str) { } -}; + /** Get target for a given pair from a singleton entity. + * This operation returns the target for a given pair. The optional + * index can be used to iterate through targets, in case the entity has + * multiple instances for the same relationship. + * + * @tparam First The first element of the pair. + * @param index The index (0 for the first instance of the relationship). + */ + template + flecs::entity target(int32_t index = 0) const; -} + /** Get target for a given pair from a singleton entity. + * This operation returns the target for a given pair. The optional + * index can be used to iterate through targets, in case the entity has + * multiple instances for the same relationship. + * + * @param first The first element of the pair for which to retrieve the target. + * @param index The index (0 for the first instance of the relationship). + */ + template + flecs::entity target(flecs::entity_t first, int32_t index = 0) const; -#include + /** Get target for a given pair from a singleton entity. + * This operation returns the target for a given pair. The optional + * index can be used to iterate through targets, in case the entity has + * multiple instances for the same relationship. + * + * @param first The first element of the pair for which to retrieve the target. + * @param index The index (0 for the first instance of the relationship). + */ + flecs::entity target(flecs::entity_t first, int32_t index = 0) const; -#define FLECS_ENUM_MAX(T) _::to_constant::value -#define FLECS_ENUM_MAX_COUNT (FLECS_ENUM_MAX(int) + 1) + /** Create alias for component. + * + * @tparam T to create an alias for. + * @param alias Alias for the component. + * @return Entity representing the component. + */ + template + flecs::entity use(const char *alias = nullptr) const; + + /** Create alias for entity. + * + * @param name Name of the entity. + * @param alias Alias for the entity. + */ + flecs::entity use(const char *name, const char *alias = nullptr) const; -namespace flecs { + /** Create alias for entity. + * + * @param entity Entity for which to create the alias. + * @param alias Alias for the entity. + */ + void use(flecs::entity entity, const char *alias = nullptr) const; -/** Int to enum */ -namespace _ { -template -struct to_constant { - static constexpr E value = static_cast(Value); -}; + /** Count entities matching a component. + * + * @param component_id The component id. + */ + int count(flecs::id_t component_id) const { + return ecs_count_id(world_, component_id); + } -template -constexpr E to_constant::value; -} + /** Count entities matching a pair. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + int count(flecs::entity_t first, flecs::entity_t second) const { + return ecs_count_id(world_, ecs_pair(first, second)); + } -/** Convenience type with enum reflection data */ -template -struct enum_data; + /** Count entities matching a component. + * + * @tparam T The component type. + */ + template + int count() const { + return count(_::type::id(world_)); + } -template -static enum_data enum_type(flecs::world_t *world); + /** Count entities matching a pair. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template + int count(flecs::entity_t second) const { + return count(_::type::id(world_), second); + } -template -struct enum_last { - static constexpr E value = FLECS_ENUM_MAX(E); -}; + /** Count entities matching a pair. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + */ + template + int count() const { + return count( + _::type::id(world_), + _::type::id(world_)); + } -/* Utility macro to override enum_last trait */ -#define FLECS_ENUM_LAST(T, Last)\ - namespace flecs {\ - template<>\ - struct enum_last {\ - static constexpr T value = Last;\ - };\ + /** All entities created in function are created with id. + */ + template + void with(id_t with_id, const Func& func) const { + ecs_id_t prev = ecs_set_with(world_, with_id); + func(); + ecs_set_with(world_, prev); } -namespace _ { + /** All entities created in function are created with type. + */ + template + void with(const Func& func) const { + with(this->id(), func); + } -#ifdef ECS_TARGET_MSVC -#define ECS_SIZE_T_STR "unsigned __int64" -#elif defined(__clang__) -#define ECS_SIZE_T_STR "size_t" -#else -#define ECS_SIZE_T_STR "constexpr size_t; size_t = long unsigned int" -#endif + /** All entities created in function are created with pair. + */ + template + void with(const Func& func) const { + with(ecs_pair(this->id(), this->id()), func); + } -template -constexpr size_t enum_type_len() { - return ECS_FUNC_TYPE_LEN(, enum_type_len, ECS_FUNC_NAME) - - (sizeof(ECS_SIZE_T_STR) - 1u); -} + /** All entities created in function are created with pair. + */ + template + void with(id_t second, const Func& func) const { + with(ecs_pair(this->id(), second), func); + } -/** Test if value is valid for enumeration. - * This function leverages that when a valid value is provided, - * __PRETTY_FUNCTION__ contains the enumeration name, whereas if a value is - * invalid, the string contains a number. */ -#if defined(__clang__) -template -constexpr bool enum_constant_is_valid() { - return !( - (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + - enum_type_len() + 6 /* ', C = ' */] >= '0') && - (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + - enum_type_len() + 6 /* ', C = ' */] <= '9')); -} -#elif defined(__GNUC__) -template -constexpr bool enum_constant_is_valid() { - return (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(constepxr bool, enum_constant_is_valid) + - enum_type_len() + 8 /* ', E C = ' */] != '('); -} -#else -/* Use different trick on MSVC, since it uses hexadecimal representation for - * invalid enum constants. We can leverage that msvc inserts a C-style cast - * into the name, and the location of its first character ('(') is known. */ -template -constexpr bool enum_constant_is_valid() { - return ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + - enum_type_len() + 1] != '('; -} -#endif + /** All entities created in function are created with pair. + */ + template + void with(id_t first, id_t second, const Func& func) const { + with(ecs_pair(first, second), func); + } -template -struct enum_is_valid { - static constexpr bool value = enum_constant_is_valid(); -}; + /** All entities created in function are created in scope. All operations + * called in function (such as lookup) are relative to scope. + */ + template + void scope(id_t parent, const Func& func) const { + ecs_entity_t prev = ecs_set_scope(world_, parent); + func(); + ecs_set_scope(world_, prev); + } -/** Extract name of constant from string */ -template -static const char* enum_constant_to_name() { - static const size_t len = ECS_FUNC_TYPE_LEN(const char*, enum_constant_to_name, ECS_FUNC_NAME); - static char result[len + 1] = {}; - return ecs_cpp_get_constant_name( - result, ECS_FUNC_NAME, string::length(ECS_FUNC_NAME)); -} + /** Same as scope(parent, func), but with T as parent. + */ + template + void scope(const Func& func) const { + flecs::id_t parent = _::type::id(world_); + scope(parent, func); + } -/** Enumeration constant data */ -struct enum_constant_data { - flecs::entity_t id; - int next; -}; + /** Use provided scope for operations ran on returned world. + * Operations need to be ran in a single statement. + */ + flecs::scoped_world scope(id_t parent) const; -/** Enumeration type data */ -struct enum_data_impl { - flecs::entity_t id; - int min; - int max; - enum_constant_data constants[FLECS_ENUM_MAX_COUNT]; -}; + template + flecs::scoped_world scope() const; -/** Class that scans an enum for constants, extracts names & creates entities */ -template -struct enum_type { - static enum_data_impl data; + flecs::scoped_world scope(const char* name) const; - static enum_type& get() { - static _::enum_type instance; - return instance; + /** Delete all entities with specified id. */ + void delete_with(id_t the_id) const { + ecs_delete_with(world_, the_id); } - flecs::entity_t entity(E value) const { - return data.constants[static_cast(value)].id; + /** Delete all entities with specified pair. */ + void delete_with(entity_t first, entity_t second) const { + delete_with(ecs_pair(first, second)); } - void init(flecs::world_t *world, flecs::entity_t id) { -#if !defined(__clang__) && defined(__GNUC__) - ecs_assert(__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 5), - ECS_UNSUPPORTED, "enum component types require gcc 7.5 or higher"); -#endif - - ecs_log_push(); - ecs_add_id(world, id, flecs::Exclusive); - ecs_add_id(world, id, flecs::OneOf); - ecs_add_id(world, id, flecs::Tag); - data.id = id; - data.min = FLECS_ENUM_MAX(int); - init< enum_last::value >(world); - ecs_log_pop(); + /** Delete all entities with specified component. */ + template + void delete_with() const { + delete_with(_::type::id(world_)); } -private: - template - static constexpr int to_int() { - return static_cast(Value); + /** Delete all entities with specified pair. */ + template + void delete_with() const { + delete_with(_::type::id(world_), _::type::id(world_)); } - template - static constexpr E from_int() { - return to_constant::value; + /** Delete all entities with specified pair. */ + template + void delete_with(entity_t second) const { + delete_with(_::type::id(world_), second); } - template - static constexpr int is_not_0() { - return static_cast(Value != from_int<0>()); + /** Remove all instances of specified id. */ + void remove_all(id_t the_id) const { + ecs_remove_all(world_, the_id); } - template () > = 0> - static void init_constant(flecs::world_t*) { } - - template () > = 0> - static void init_constant(flecs::world_t *world) { - int v = to_int(); - const char *name = enum_constant_to_name(); - data.constants[v].next = data.min; - data.min = v; - if (!data.max) { - data.max = v; - } - - data.constants[v].id = ecs_cpp_enum_constant_register( - world, data.id, data.constants[v].id, name, v); + /** Remove all instances of specified pair. */ + void remove_all(entity_t first, entity_t second) const { + remove_all(ecs_pair(first, second)); } - template - static void init(flecs::world_t *world) { - init_constant(world); - if (is_not_0()) { - init() - is_not_0()>()>(world); - } + /** Remove all instances of specified component. */ + template + void remove_all() const { + remove_all(_::type::id(world_)); } -}; -template -enum_data_impl enum_type::data; - -template ::value > = 0> -inline static void init_enum(flecs::world_t *world, flecs::entity_t id) { - _::enum_type::get().init(world, id); -} + /** Remove all instances of specified pair. */ + template + void remove_all() const { + remove_all(_::type::id(world_), _::type::id(world_)); + } -template ::value > = 0> -inline static void init_enum(flecs::world_t*, flecs::entity_t) { } + /** Remove all instances of specified pair. */ + template + void remove_all(entity_t second) const { + remove_all(_::type::id(world_), second); + } -} // namespace _ + /** Defer all operations called in function. + * + * @see flecs::world::defer_begin() + * @see flecs::world::defer_end() + * @see flecs::world::defer_is_deferred() + * @see flecs::world::defer_resume() + * @see flecs::world::defer_suspend() + */ + template + void defer(const Func& func) const { + ecs_defer_begin(world_); + func(); + ecs_defer_end(world_); + } -/** Enumeration type data wrapper with world pointer */ -template -struct enum_data { - enum_data(flecs::world_t *world, _::enum_data_impl& impl) - : world_(world) - , impl_(impl) { } + /** Suspend deferring operations. + * + * @see ecs_defer_suspend() + * @see flecs::world::defer() + * @see flecs::world::defer_begin() + * @see flecs::world::defer_end() + * @see flecs::world::defer_is_deferred() + * @see flecs::world::defer_resume() + */ + void defer_suspend() const { + ecs_defer_suspend(world_); + } - bool is_valid(int value) { - return impl_.constants[value].id != 0; + /** Resume deferring operations. + * + * @see ecs_defer_resume() + * @see flecs::world::defer() + * @see flecs::world::defer_begin() + * @see flecs::world::defer_end() + * @see flecs::world::defer_is_deferred() + * @see flecs::world::defer_suspend() + */ + void defer_resume() const { + ecs_defer_resume(world_); } - int first() const { - return impl_.min; + /** Check if entity id exists in the world. + * + * @see ecs_exists() + * @see flecs::world::is_alive() + * @see flecs::world::is_valid() + */ + bool exists(flecs::entity_t e) const { + return ecs_exists(world_, e); } - int last() const { - return impl_.max; + /** Check if entity id exists in the world. + * + * @see ecs_is_alive() + * @see flecs::world::exists() + * @see flecs::world::is_valid() + */ + bool is_alive(flecs::entity_t e) const { + return ecs_is_alive(world_, e); } - int next(int cur) const { - return impl_.constants[cur].next; + /** Check if entity id is valid. + * Invalid entities cannot be used with API functions. + * + * @see ecs_is_valid() + * @see flecs::world::exists() + * @see flecs::world::is_alive() + */ + bool is_valid(flecs::entity_t e) const { + return ecs_is_valid(world_, e); } - flecs::entity entity() const; - flecs::entity entity(int value) const; - flecs::entity entity(E value) const; + /** Get alive entity for id. + * Returns the entity with the current generation. + * + * @see ecs_get_alive() + */ + flecs::entity get_alive(flecs::entity_t e) const; - flecs::world_t *world_; - _::enum_data_impl& impl_; -}; + /** + * @see ecs_make_alive() + */ + flecs::entity make_alive(flecs::entity_t e) const; -/** Convenience function for getting enum reflection data */ -template -enum_data enum_type(flecs::world_t *world) { - _::cpp_type::id(world); // Ensure enum is registered - auto& ref = _::enum_type::get(); - return enum_data(world, ref.data); -} + /** Set version of entity to provided. + * + * @see ecs_set_version() + */ + void set_version(flecs::entity_t e) const { + ecs_set_version(world_, e); + } -} // namespace flecs + /** Get version of provided entity. + * + * @see ecs_get_version() + */ + uint32_t get_version(flecs::entity_t e) const { + return ecs_get_version(e); + } -// Wrapper around ecs_strbuf_t that provides a simple stringstream like API. + /* Run callback after completing frame */ + void run_post_frame(ecs_fini_action_t action, void *ctx) const { + ecs_run_post_frame(world_, action, ctx); + } -namespace flecs { + /** Get the world info. + * + * @see ecs_get_world_info() + */ + const flecs::world_info_t* get_info() const{ + return ecs_get_world_info(world_); + } -struct stringstream { - explicit stringstream() - : m_buf({}) { } + /** Get delta_time */ + ecs_ftime_t delta_time() const { + return get_info()->delta_time; + } - ~stringstream() { - ecs_strbuf_reset(&m_buf); + /** Free unused memory. + * + * @see ecs_shrink() + */ + void shrink() const { + ecs_shrink(world_); } - stringstream(stringstream&& str) { - ecs_strbuf_reset(&m_buf); - m_buf = str.m_buf; - str.m_buf = {}; + /** Begin exclusive access + * + * @param thread_name Optional thread name for improved debug messages. + * @see ecs_exclusive_access_begin() + */ + void exclusive_access_begin(const char *thread_name = nullptr) { + ecs_exclusive_access_begin(world_, thread_name); } - stringstream& operator=(stringstream&& str) { - ecs_strbuf_reset(&m_buf); - m_buf = str.m_buf; - str.m_buf = {}; - return *this; + /** End exclusive access + * + * @param lock_world Lock world for all threads, allow readonly operations. + * @see ecs_exclusive_access_end() + */ + void exclusive_access_end(bool lock_world = false) { + ecs_exclusive_access_end(world_, lock_world); } - // Ban implicit copies/allocations - stringstream& operator=(const stringstream& str) = delete; - stringstream(const stringstream& str) = delete; + /** Return component id if it has been registered. + * This operation is similar to world::id() but will never automatically + * register the component. + * + * @tparam T The type for which to obtain the id. + */ + template + flecs::id_t id_if_registered() { + if (_::type::registered(world_)) { + return _::type::id(world_); + } + else { + return 0; + } + } - stringstream& operator<<(const char* str) { - ecs_strbuf_appendstr(&m_buf, str); - return *this; + /** Return type info */ + const flecs::type_info_t* type_info(flecs::id_t component) { + return ecs_get_type_info(world_, component); } - flecs::string str() { - return flecs::string(ecs_strbuf_get(&m_buf)); + /** Return type info */ + const flecs::type_info_t* type_info(flecs::entity_t r, flecs::entity_t t) { + return ecs_get_type_info(world_, ecs_pair(r, t)); } -private: - ecs_strbuf_t m_buf; -}; + /** Return type info */ + template + const flecs::type_info_t* type_info() { + return ecs_get_type_info(world_, _::type::id(world_)); + } -} + /** Return type info */ + template + const flecs::type_info_t* type_info(flecs::entity_t t) { + return type_info(_::type::id(world_), t); + } + /** Return type info */ + template + const flecs::type_info_t* type_info() { + return type_info(_::type::id(world_)); + } -// Neat utility to inspect arguments & returntype of a function type -// Code from: https://stackoverflow.com/questions/27024238/c-template-mechanism-to-get-the-number-of-function-arguments-which-would-work +/** + * @file addons/cpp/mixins/id/mixin.inl + * @brief Id world mixin. + */ -namespace flecs { -namespace _ { +/** Get id from a type. + * + * @memberof flecs::world + */ +template +flecs::id id() const; +/** Id factory. + * + * @memberof flecs::world + */ template -struct arg_list { }; +flecs::id id(Args&&... args) const; -// Base type that contains the traits -template -struct function_traits_defs -{ - static constexpr bool is_callable = true; - static constexpr size_t arity = sizeof...(Args); - using return_type = ReturnType; - using args = arg_list; -}; +/** Get pair id from relationship, object. + * + * @memberof flecs::world + */ +template +flecs::id pair() const; -// Primary template for function_traits_impl -template -struct function_traits_impl { - static constexpr bool is_callable = false; -}; +/** Get pair id from relationship, object. + * + * @memberof flecs::world + */ +template +flecs::id pair(entity_t o) const; -// Template specializations for the different kinds of function types (whew) -template -struct function_traits_impl - : function_traits_defs {}; +/** Get pair id from relationship, object. + * + * @memberof flecs::world + */ +flecs::id pair(entity_t r, entity_t o) const; -template -struct function_traits_impl - : function_traits_defs {}; +/** + * @file addons/cpp/mixins/component/mixin.inl + * @brief Component mixin. + */ -template -struct function_traits_impl - : function_traits_defs {}; +/** Find or register component. + * + * @ingroup cpp_components + * @memberof flecs::world + */ +template +flecs::component component(Args &&... args) const; -template -struct function_traits_impl - : function_traits_defs {}; +/** Find or register untyped component. + * Method available on flecs::world class. + * + * @ingroup cpp_components + * @memberof flecs::world + */ +template +flecs::untyped_component component(Args &&... args) const; -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; +/** + * @file addons/cpp/mixins/entity/mixin.inl + * @brief Entity world mixin. + */ -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; +/** Create an entity. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template +flecs::entity entity(Args &&... args) const; -template -struct function_traits_impl - : function_traits_defs {}; +/** Convert enum constant to entity. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template ::value > = 0> +flecs::id id(E value) const; -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; +/** Convert enum constant to entity. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template ::value > = 0> +flecs::entity entity(E value) const; -// Primary template for function_traits_no_cv. If T is not a function, the -// compiler will attempt to instantiate this template and fail, because its base -// is undefined. -template -struct function_traits_no_cv - : function_traits_impl {}; +/** Create a prefab. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template +flecs::entity prefab(Args &&... args) const; -// Specialized template for function types -template -struct function_traits_no_cv - : function_traits_impl {}; - -// Front facing template that decays T before ripping it apart. +/** Create an entity that's associated with a type. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ template -struct function_traits - : function_traits_no_cv< decay_t > {}; - -} // _ - +flecs::entity entity(const char *name = nullptr) const; +/** Create a prefab that's associated with a type. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ template -struct is_callable { - static constexpr bool value = _::function_traits::is_callable; -}; +flecs::entity prefab(const char *name = nullptr) const; -template -struct arity { - static constexpr int value = _::function_traits::arity; -}; +/** + * @file addons/cpp/mixins/event/mixin.inl + * @brief Event world mixin. + */ -template -using return_type_t = typename _::function_traits::return_type; +/** + * @defgroup cpp_addons_event Events + * @ingroup cpp_addons + * API for emitting events. + * + * @{ + */ -template -using arg_list_t = typename _::function_traits::args; +/** Create a new event. + * + * @memberof flecs::world + * + * @param evt The event id. + * @return Event builder. + */ +flecs::event_builder event(flecs::entity_t evt) const; +/** Create a new event. + * + * @memberof flecs::world + * + * @tparam E The event type. + * @return Event builder. + */ +template +flecs::event_builder_typed event() const; -template -struct first_arg_impl; +/** @} */ -template -struct first_arg_impl > { - using type = T; -}; +/** + * @file addons/cpp/mixins/term/mixin.inl + * @brief Term world mixin. + */ -template -struct first_arg { - using type = typename first_arg_impl>::type; -}; +/** + * @memberof flecs::world + * @ingroup cpp_core_queries + * + * @{ + */ -template -using first_arg_t = typename first_arg::type; +/** Create a term. + * + */ +template +flecs::term term(Args &&... args) const; -} // flecs +/** Create a term for a (component) type. + */ +template +flecs::term term() const; +/** Create a term for a pair. + */ +template +flecs::term term() const; +/** @} */ -// Mixin forward declarations -#pragma once +/** + * @file addons/cpp/mixins/observer/mixin.inl + * @brief Observer world mixin. + */ -namespace flecs { +/** Observer builder. + * + * @memberof flecs::world + * @ingroup cpp_observers + * + * @{ + */ -struct id; -struct entity; +/** Upcast entity to an observer. + * The provided entity must be an observer. + * + * @param e The entity. + * @return An observer object. + */ +flecs::observer observer(flecs::entity e) const; -/** Class that stores a flecs id. - * A flecs id is an identifier that can store an entity id, an relation-object - * pair, or role annotated id (such as SWITCH | Movement). +/** Create a new observer. + * + * @tparam Components The components to match on. + * @tparam Args Arguments passed to the constructor of flecs::observer_builder. + * @return Observer builder. */ -struct id { - id() - : m_world(nullptr) - , m_id(0) { } - - explicit id(flecs::id_t value) - : m_world(nullptr) - , m_id(value) { } +template +flecs::observer_builder observer(Args &&... args) const; - explicit id(flecs::world_t *world, flecs::id_t value = 0) - : m_world(world) - , m_id(value) { } +/** @} */ - explicit id(flecs::world_t *world, flecs::id_t relation, flecs::id_t object) - : m_world(world) - , m_id(ecs_pair(relation, object)) { } +/** + * @file addons/cpp/mixins/query/mixin.inl + * @brief Query world mixin. + */ - explicit id(flecs::id_t relation, flecs::id_t object) - : m_world(nullptr) - , m_id(ecs_pair(relation, object)) { } +/** + * @memberof flecs::world + * @ingroup cpp_core_queries + * + * @{ + */ - explicit id(const flecs::id& relation, const flecs::id& object) - : m_world(relation.m_world) - , m_id(ecs_pair(relation.m_id, object.m_id)) { } +/** Create a query. + * + * @see ecs_query_init + */ +template +flecs::query query(Args &&... args) const; - /** Test if id is pair (has relation, object) */ - bool is_pair() const { - return (m_id & ECS_ROLE_MASK) == flecs::Pair; - } +/** Create a query from entity. + * + * @see ecs_query_init + */ +flecs::query<> query(flecs::entity query_entity) const; - /* Test if id is a wildcard */ - bool is_wildcard() const { - return ecs_id_is_wildcard(m_id); - } +/** Create a query builder. + * + * @see ecs_query_init + */ +template +flecs::query_builder query_builder(Args &&... args) const; - /* Test if id has the Switch role */ - bool is_switch() const { - return (m_id & ECS_ROLE_MASK) == flecs::Switch; - } +/** Iterate over all entities with components in argument list of function. + * The function parameter must match the following signature: + * + * @code + * void(*)(T&, U&, ...) + * @endcode + * + * or: + * + * @code + * void(*)(flecs::entity, T&, U&, ...) + * @endcode + * + */ +template +void each(Func&& func) const; - /* Test if id has the Case role */ - bool is_case() const { - return (m_id & ECS_ROLE_MASK) == flecs::Case; - } +/** Iterate over all entities with provided component. + * The function parameter must match the following signature: + * + * @code + * void(*)(T&) + * @endcode + * + * or: + * + * @code + * void(*)(flecs::entity, T&) + * @endcode + * + */ +template +void each(Func&& func) const; - /* Test if id is entity */ - bool is_entity() const { - return !(m_id & ECS_ROLE_MASK); - } +/** Iterate over all entities with provided (component) id. */ +template +void each(flecs::id_t term_id, Func&& func) const; - /* Return id as entity (only allowed when id is valid entity) */ - flecs::entity entity() const; +/** @} */ - /* Return id with role added */ - flecs::entity add_role(flecs::id_t role) const; +/** + * @file addons/cpp/mixins/enum/mixin.inl + * @brief Enum world mixin. + */ - /* Return id with role removed */ - flecs::entity remove_role(flecs::id_t role) const; +/** Convert enum constant to entity. + * + * @memberof flecs::world + * @ingroup cpp_entities + */ +template ::value > = 0> +flecs::entity to_entity(E constant) const; - /* Return id without role */ - flecs::entity remove_role() const; - /* Return id without role */ - flecs::entity remove_generation() const; +# ifdef FLECS_MODULE +/** + * @file addons/cpp/mixins/module/mixin.inl + * @brief Module world mixin. + */ - /* Test if id has specified role */ - bool has_role(flecs::id_t role) const { - return ((m_id & ECS_ROLE_MASK) == role); - } +/** + * @memberof flecs::world + * @ingroup cpp_addons_modules + * + * @{ + */ - /* Test if id has any role */ - bool has_role() const { - return (m_id & ECS_ROLE_MASK) != 0; - } +/** Define a module. + * This operation is not mandatory, but can be called inside the module ctor to + * obtain the entity associated with the module, or override the module name. + * + * @tparam Module module class. + * @return Module entity. + */ +template +flecs::entity module(const char *name = nullptr) const; - flecs::entity role() const; +/** Import a module. + * + * @tparam Module module class. + * @return Module entity. + */ +template +flecs::entity import(); - /* Test if id has specified relation */ - bool has_relation(flecs::id_t relation) const { - if (!is_pair()) { - return false; - } - return ECS_PAIR_FIRST(m_id) == relation; - } +/** @} */ - /** Get first element from a pair. - * If the id is not a pair, this operation will fail. When the id has a - * world, the operation will ensure that the returned id has the correct - * generation count. */ - flecs::entity first() const; +# endif +# ifdef FLECS_PIPELINE +/** + * @file addons/cpp/mixins/pipeline/mixin.inl + * @brief Pipeline world mixin. + */ - /** Get second element from a pair. - * If the id is not a pair, this operation will fail. When the id has a - * world, the operation will ensure that the returned id has the correct - * generation count. */ - flecs::entity second() const; +/** + * @memberof flecs::world + * @ingroup cpp_pipelines + * + * @{ + */ - /** Same as first() */ - flecs::entity relation() const; +/** Create a new pipeline. + * + * @return A pipeline builder. + */ +flecs::pipeline_builder<> pipeline() const; - /** Same as second() */ - flecs::entity object() const; +/** Create a new pipeline. + * + * @tparam Pipeline Type associated with pipeline. + * @return A pipeline builder. + */ +template ::value > = 0> +flecs::pipeline_builder<> pipeline() const; - /* Convert id to string */ - flecs::string str() const { - return flecs::string(ecs_id_str(m_world, m_id)); - } +/** Set pipeline. + * @see ecs_set_pipeline + */ +void set_pipeline(const flecs::entity pip) const; - /** Convert role of id to string. */ - flecs::string role_str() const { - return flecs::string_view( ecs_role_str(m_id & ECS_ROLE_MASK)); - } +/** Set pipeline. + * @see ecs_set_pipeline + */ +template +void set_pipeline() const; - flecs::id_t raw_id() const { - return m_id; - } +/** Get pipeline. + * @see ecs_get_pipeline + */ +flecs::entity get_pipeline() const; - operator flecs::id_t() const { - return m_id; - } +/** Progress world one tick. + * @see ecs_progress + */ +bool progress(ecs_ftime_t delta_time = 0.0) const; - flecs::world world() const; - -protected: - /* World is optional, but guarantees that entity identifiers extracted from - * the id are valid */ - flecs::world_t *m_world; - flecs::id_t m_id; -}; +/** Run pipeline. + * @see ecs_run_pipeline + */ +void run_pipeline(const flecs::entity_t pip, ecs_ftime_t delta_time = 0.0) const; -} +/** Run pipeline. + * @tparam Pipeline Type associated with pipeline. + * @see ecs_run_pipeline + */ +template ::value > = 0> +void run_pipeline(ecs_ftime_t delta_time = 0.0) const; -#pragma once +/** Set timescale. + * @see ecs_set_time_scale + */ +void set_time_scale(ecs_ftime_t mul) const; -namespace flecs { +/** Set target FPS. + * @see ecs_set_target_fps + */ +void set_target_fps(ecs_ftime_t target_fps) const; -struct type; +/** Reset simulation clock. + * @see ecs_reset_clock + */ +void reset_clock() const; -} +/** Set number of threads. + * @see ecs_set_threads + */ +void set_threads(int32_t threads) const; -#pragma once +/** Set number of threads. + * @see ecs_get_stage_count + */ +int32_t get_threads() const; -namespace flecs { +/** Set number of task threads. + * @see ecs_set_task_threads + */ +void set_task_threads(int32_t task_threads) const; -struct term; -struct term_builder; +/** Returns true if task thread use has been requested. + * @see ecs_using_task_threads + */ +bool using_task_threads() const; -} +/** @} */ -#pragma once +# endif +# ifdef FLECS_SYSTEM +/** + * @file addons/cpp/mixins/system/mixin.inl + * @brief System module world mixin. + */ -namespace flecs { +/** + * @memberof flecs::world + * @ingroup cpp_addons_systems + * + * @{ +*/ -struct filter_base; +/** Upcast entity to a system. + * The provided entity must be a system. + * + * @param e The entity. + * @return A system object. + */ +flecs::system system(flecs::entity e) const; -template -struct filter; +/** Create a new system. + * + * @tparam Components The components to match on. + * @tparam Args Arguments passed to the constructor of flecs::system_builder. + * @return System builder. + */ +template +flecs::system_builder system(Args &&... args) const; -template -struct filter_builder; +/** @} */ -} +# endif +# ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/mixin.inl + * @brief Timer module mixin. + */ -#pragma once +/** + * @memberof flecs::world + * @ingroup cpp_addons_timer + */ -#pragma once +/** Find or register a singleton timer. */ +template +flecs::timer timer() const; -#define ECS_EVENT_DESC_ID_COUNT_MAX (8) +/** Find or register a timer. */ +template +flecs::timer timer(Args &&... args) const; -namespace flecs { +/** Enable randomization of initial time values for timers. + * @see ecs_randomize_timers + */ +void randomize_timers() const; -// Event builder interface -template -struct event_builder_base { - event_builder_base(flecs::world_t *world, flecs::entity_t event) - : m_world(world) - , m_desc{} - , m_ids{} - , m_ids_array{} - { - m_desc.event = event; - } +# endif +# ifdef FLECS_SCRIPT +/** + * @file addons/cpp/mixins/script/mixin.inl + * @brief Script world mixin. + */ - /** Add component to trigger on */ - template - Base& id() { - m_ids.array = m_ids_array; - m_ids.array[m_ids.count] = _::cpp_type().id(m_world); - m_ids.count ++; - return *this; - } - - /** - * Add pair to trigger on - * @tparam R the relation type. - * @tparam O the object type. - */ - template - Base& id() { - return id( - ecs_pair(_::cpp_type::id(this->m_world), - _::cpp_type::id(this->m_world))); - } +/** + * @defgroup cpp_addons_script Script + * @ingroup cpp_addons + * Data definition format for loading entity data. + * + * @{ + */ - /** - * Add pair to trigger on - * @tparam R the relation type. - * @param object The object id. - */ - template - Base& id(entity_t object) { - return id(ecs_pair(_::cpp_type::id(this->m_world), object)); - } +/** Run script. + * @see ecs_script_run + */ +int script_run(const char *name, const char *str) const { + return ecs_script_run(world_, name, str, nullptr); +} - /** - * Add pair to trigger on - * @param relation The relation type. - * @param object The object id. - */ - Base& id(entity_t relation, entity_t object) { - return id(ecs_pair(relation, object)); - } +/** Run script from file. + * @see ecs_script_run_file + */ +int script_run_file(const char *filename) const { + return ecs_script_run_file(world_, filename); +} - /** Add (component) id to trigger on */ - Base& id(flecs::id_t id) { - m_ids.array = m_ids_array; - m_ids.array[m_ids.count] = id; - m_ids.count ++; - return *this; - } +/** Build script. + * @see ecs_script_init + */ +script_builder script(const char *name = nullptr) const { + return script_builder(world_, name); +} - /** Set entity for which to trigger */ - Base& entity(flecs::entity_t e) { - ecs_record_t *r = ecs_record_find(m_world, e); - - /* can't trigger for empty entity */ - ecs_assert(r != nullptr, ECS_INVALID_PARAMETER, nullptr); - ecs_assert(r->table != nullptr, ECS_INVALID_PARAMETER, nullptr); +/** Convert value to string */ +flecs::string to_expr(flecs::entity_t tid, const void* value) { + char *expr = ecs_ptr_to_expr(world_, tid, value); + return flecs::string(expr); +} - m_desc.table = r->table; - m_desc.offset = ECS_RECORD_TO_ROW(r->row); - m_desc.count = 1; - return *this; - } +/** Convert value to string */ +template +flecs::string to_expr(const T* value) { + flecs::entity_t tid = _::type::id(world_); + return to_expr(tid, value); +} - /* Set table for which to trigger */ - Base& table(flecs::table_t *t, int32_t offset = 0, int32_t count = 0) { - m_desc.table = t; - m_desc.offset = offset; - m_desc.count = count; - return *this; - } +/** Get value of exported script variable. + * This operation will panic if no const var with the provided name was found, + * or if the type of the variable cannot be converted to the provided type. + * + * An exported variable can be created in a script like this: + * + * @code + * export const x = f64: 10 + * @endcode + * + * See the Flecs script manual for more details. + * + * @tparam T The type of the value to obtain. + * @param name The name of the exported variable. + * @param default_value Optional default value. Returned when const var lookup failed. + * @return The value of the variable. + */ +template +T get_const_var(const char *name, const T& default_value = {}) const; - /* Set event data */ - Base& ctx(const E* ptr) { - m_desc.param = ptr; - return *this; - } +/** Get value of exported script variable. + * This operation will panic if no const var with the provided name was found, + * or if the type of the variable cannot be converted to the provided type. + * + * An exported variable can be created in a script like this: + * + * @code + * export const x = f64: 10 + * @endcode + * + * See the Flecs script manual for more details. + * + * @tparam T The type of the value to obtain. + * @param name The name of the exported variable. + * @param out Optional pointer to out variable. Can be used to automatically deduce T. + * @param default_value Optional default value. Returned when const var lookup failed. + */ +template +void get_const_var(const char *name, T& out, const T& default_value = {}) const; - void emit() { - ecs_assert(m_ids.count != 0, ECS_INVALID_PARAMETER, NULL); - ecs_assert(m_desc.table != nullptr, ECS_INVALID_PARAMETER, NULL); - m_ids.array = m_ids_array; - m_desc.ids = &m_ids; - m_desc.observable = const_cast(ecs_get_world(m_world)); - ecs_emit(m_world, &m_desc); - } -protected: - flecs::world_t *m_world; - ecs_event_desc_t m_desc; - flecs::ids_t m_ids; - flecs::id_t m_ids_array[ECS_EVENT_DESC_ID_COUNT_MAX]; +/** @} */ -private: - operator Base&() { - return *static_cast(this); - } -}; +# endif +# ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/world.inl + * @brief Meta world mixin. + */ -struct event_builder : event_builder_base { - using event_builder_base::event_builder_base; -}; +/** + * @memberof flecs::world + * @ingroup cpp_addons_meta + * + * @{ + */ -template -struct event_builder_typed : event_builder_base, E> { -private: - using Class = event_builder_typed; +/** Return meta cursor to value */ +flecs::cursor cursor(flecs::entity_t tid, void *ptr) { + return flecs::cursor(world_, tid, ptr); +} -public: - using event_builder_base::event_builder_base; +/** Return meta cursor to value */ +template +flecs::cursor cursor(void *ptr) { + flecs::entity_t tid = _::type::id(world_); + return cursor(tid, ptr); +} - /* Set event data */ - Class& ctx(const E& ptr) { - this->m_desc.param = &ptr; - return *this; - } -}; +/** Create primitive type */ +flecs::entity primitive(flecs::meta::primitive_kind_t kind); -} +/** Create array type. */ +flecs::entity array(flecs::entity_t elem_id, int32_t array_count); +/** Create array type. */ +template +flecs::entity array(int32_t array_count); -#pragma once +/** Create vector type. */ +flecs::entity vector(flecs::entity_t elem_id); -namespace flecs { +/** Create vector type. */ +template +flecs::entity vector(); -struct query_base; +/** @} */ -template -struct query; +# endif +# ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/world.inl + * @brief JSON world mixin. + */ -template -struct query_builder; +/** Serialize untyped value to JSON. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +flecs::string to_json(flecs::entity_t tid, const void* value) const { + char *json = ecs_ptr_to_json(world_, tid, value); + return flecs::string(json); +} +/** Serialize value to JSON. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +template +flecs::string to_json(const T* value) const { + flecs::entity_t tid = _::type::id(world_); + return to_json(tid, value); } -#pragma once +/** Serialize world to JSON. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +flecs::string to_json() const { + return flecs::string( ecs_world_to_json(world_, nullptr) ); +} -namespace flecs { +/** Deserialize value from JSON. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +const char* from_json(flecs::entity_t tid, void* value, const char *json, flecs::from_json_desc_t *desc = nullptr) { + return ecs_ptr_from_json(world_, tid, value, json, desc); +} -struct trigger; +/** Deserialize value from JSON. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +template +const char* from_json(T* value, const char *json, flecs::from_json_desc_t *desc = nullptr) { + return ecs_ptr_from_json(world_, _::type::id(world_), + value, json, desc); +} -template -struct trigger_builder; +/** Deserialize JSON into world. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +const char* from_json(const char *json, flecs::from_json_desc_t *desc = nullptr) { + return ecs_world_from_json(world_, json, desc); +} +/** Deserialize JSON file into world. + * + * @memberof flecs::world + * @ingroup cpp_addons_json + */ +const char* from_json_file(const char *json, flecs::from_json_desc_t *desc = nullptr) { + return ecs_world_from_json_file(world_, json, desc); } -#pragma once +# endif +# ifdef FLECS_APP +/** + * @file addons/cpp/mixins/app/mixin.inl + * @brief App world addon mixin. + */ -namespace flecs { +/** + * @ingroup cpp_addons_app + * @memberof flecs::world + * + * @{ + */ -struct observer; +/** Return app builder. + * The app builder is a convenience wrapper around a loop that runs + * world::progress. An app allows for writing platform agnostic code, + * as it provides hooks to modules for overtaking the main loop which is + * required for frameworks like emscripten. + */ +flecs::app_builder app() { + flecs::world_t *w = world_; + world_ = nullptr; // Take ownership + return flecs::app_builder(w); +} -template -struct observer_builder; +/** @} */ -} +# endif +# ifdef FLECS_METRICS -#ifdef FLECS_SYSTEM -#pragma once +/** Create metric. + * + * @ingroup cpp_addons_metrics + * @memberof flecs::world + */ +template +flecs::metric_builder metric(Args &&... args) const; -namespace flecs { +# endif +# ifdef FLECS_ALERTS -using TickSource = EcsTickSource; +/** Create alert. + * + * @ingroup cpp_addons_alerts + * @memberof flecs::world + */ +template +flecs::alert_builder alert(Args &&... args) const; -struct system; +# endif -template -struct system_builder; +public: + void init_builtin_components(); -namespace _ { + world_t *world_; +}; -void system_init(flecs::world& world); +/** Scoped world. + * Utility class used by the world::scope method to create entities in a scope. + */ +struct scoped_world : world { + scoped_world( + flecs::world_t *w, + flecs::entity_t s) : world(w) + { + prev_scope_ = ecs_set_scope(w, s); + } -} // namespace _ -} // namespace flecs + ~scoped_world() { + ecs_set_scope(world_, prev_scope_); + } -#endif -#ifdef FLECS_PIPELINE -#pragma once + scoped_world(const scoped_world& obj) : world(nullptr) { + prev_scope_ = obj.prev_scope_; + world_ = obj.world_; + flecs_poly_claim(world_); + } -namespace flecs { + flecs::entity_t prev_scope_; +}; -struct pipeline; +/** @} */ -/* Builtin pipeline tags */ -static const flecs::entity_t PreFrame = EcsPreFrame; -static const flecs::entity_t OnLoad = EcsOnLoad; -static const flecs::entity_t PostLoad = EcsPostLoad; -static const flecs::entity_t PreUpdate = EcsPreUpdate; -static const flecs::entity_t OnUpdate = EcsOnUpdate; -static const flecs::entity_t OnValidate = EcsOnValidate; -static const flecs::entity_t PostUpdate = EcsPostUpdate; -static const flecs::entity_t PreStore = EcsPreStore; -static const flecs::entity_t OnStore = EcsOnStore; -static const flecs::entity_t PostFrame = EcsPostFrame; +} // namespace flecs -} -#endif -#ifdef FLECS_TIMER -#pragma once +/** + * @file addons/cpp/field.hpp + * @brief Wrapper classes for fields returned by flecs::iter. + */ -namespace flecs { +#pragma once -using Timer = EcsTimer; -using RateFilter = EcsRateFilter; +/** + * @defgroup cpp_field Fields + * @ingroup cpp_core + * Field helper types. + * + * @{ + */ -struct timer; +namespace flecs +{ -namespace _ { +/** Unsafe wrapper class around a field. + * This class can be used when a system does not know the type of a field at + * compile time. + * + * @ingroup cpp_iterator + */ +struct untyped_field { + untyped_field(void* array, size_t size, size_t count, bool is_shared = false) + : data_(array) + , size_(size) + , count_(count) + , is_shared_(is_shared) {} -void timer_init(flecs::world& world); + /** Return element in component array. + * This operator may only be used if the field is not shared. + * + * @param index Index of element. + * @return Reference to element. + */ + void* operator[](size_t index) const { + ecs_assert(!is_shared_ || !index, ECS_INVALID_PARAMETER, + "invalid usage of [] operator for shared component field"); + ecs_assert(index < count_, ECS_COLUMN_INDEX_OUT_OF_RANGE, + "index %d out of range for field", index); + return ECS_OFFSET(data_, size_ * index); + } -} // namespace _ -} // namespace flecs +protected: + void* data_; + size_t size_; + size_t count_; + bool is_shared_; +}; -#endif -#ifdef FLECS_SNAPSHOT -#pragma once +/** Wrapper class around a field. + * + * @tparam T component type of the field. + * + * @ingroup cpp_iterator + */ +template +struct field { + static_assert(std::is_empty::value == false, + "invalid type for field, cannot iterate empty type"); -namespace flecs { + /** Create field from component array. + * + * @param array Pointer to the component array. + * @param count Number of elements in component array. + * @param is_shared Is the component shared or not. + */ + field(T* array, size_t count, bool is_shared = false) + : data_(array) + , count_(count) + , is_shared_(is_shared) {} -using snapshot_t = ecs_snapshot_t; + /** Create field from iterator. + * + * @param iter Iterator object. + * @param field Index of the signature of the query being iterated over. + */ + field(iter &iter, int field); -struct snapshot; + /** Return element in component array. + * This operator may only be used if the field is not shared. + * + * @param index Index of element. + * @return Reference to element. + */ + T& operator[](size_t index) const; -} + /** Return first element of component array. + * This operator is typically used when the field is shared. + * + * @return Reference to the first element. + */ + T& operator*() const; -#endif -#ifdef FLECS_DOC -#pragma once + /** Return first element of component array. + * This operator is typically used when the field is shared. + * + * @return Pointer to the first element. + */ + T* operator->() const; -namespace flecs { -namespace doc { +protected: + T* data_; + size_t count_; + bool is_shared_; +}; -using Description = EcsDocDescription; -static const flecs::entity_t Brief = EcsDocBrief; -static const flecs::entity_t Detail = EcsDocDetail; -static const flecs::entity_t Link = EcsDocLink; +} // namespace flecs -namespace _ { -void init(flecs::world& world); -} +/** @} */ -} -} +/** + * @file addons/cpp/iter.hpp + * @brief Wrapper classes for ecs_iter_t and component arrays. + */ -#endif -#ifdef FLECS_REST #pragma once -namespace flecs { -namespace rest { +/** + * @defgroup cpp_iterator Iterators + * @ingroup cpp_core + * Iterator operations. + * + * @{ + */ -using Rest = EcsRest; +namespace flecs +{ + +//////////////////////////////////////////////////////////////////////////////// namespace _ { -void init(flecs::world& world); +//////////////////////////////////////////////////////////////////////////////// -} -} -} +/** Iterate over an integer range (used to iterate over entity range). + * + * @tparam T of the iterator + */ +template +struct range_iterator +{ + explicit range_iterator(T value) + : value_(value){} + + bool operator!=(range_iterator const& other) const + { + return value_ != other.value_; + } -#endif -#ifdef FLECS_RULES -#pragma once + T const& operator*() const + { + return value_; + } -namespace flecs { + range_iterator& operator++() + { + ++value_; + return *this; + } -struct rule_base; +private: + T value_; +}; -template -struct rule; +} // namespace _ -template -struct rule_builder; +} // namespace flecs -} +namespace flecs +{ -#endif -#ifdef FLECS_META -#pragma once +//////////////////////////////////////////////////////////////////////////////// -namespace flecs { +/** Class for iterating over query results. + * + * @ingroup cpp_iterator + */ +struct iter { +private: + using row_iterator = _::range_iterator; -using bool_t = ecs_bool_t; -using char_t = ecs_char_t; -using u8_t = ecs_u8_t; -using u16_t = ecs_u16_t; -using u32_t = ecs_u32_t; -using u64_t = ecs_u64_t; -using uptr_t = ecs_uptr_t; -using i8_t = ecs_i8_t; -using i16_t = ecs_i16_t; -using i32_t = ecs_i32_t; -using i64_t = ecs_i64_t; -using iptr_t = ecs_iptr_t; -using f32_t = ecs_f32_t; -using f64_t = ecs_f64_t; +public: + /** Construct iterator from C iterator object. + * This operation is typically not invoked directly by the user. + * + * @param it Pointer to C iterator. + */ + iter(ecs_iter_t *it) : iter_(it) { } -using type_kind_t = ecs_type_kind_t; -using primitive_kind_t = ecs_primitive_kind_t; -using member_t = ecs_member_t; -using enum_constant_t = ecs_enum_constant_t; -using bitmask_constant_t = ecs_bitmask_constant_t; + row_iterator begin() const { + return row_iterator(0); + } -using MetaType = EcsMetaType; -using MetaTypeSerialized = EcsMetaTypeSerialized; -using Primitive = EcsPrimitive; -using Enum = EcsEnum; -using Bitmask = EcsBitmask; -using Member = EcsMember; -using Struct = EcsStruct; -using Array = EcsArray; -using Vector = EcsVector; -using Unit = EcsUnit; + row_iterator end() const { + return row_iterator(static_cast(iter_->count)); + } -static const flecs::entity_t Bool = ecs_id(ecs_bool_t); -static const flecs::entity_t Char = ecs_id(ecs_char_t); -static const flecs::entity_t Byte = ecs_id(ecs_byte_t); -static const flecs::entity_t U8 = ecs_id(ecs_u8_t); -static const flecs::entity_t U16 = ecs_id(ecs_u16_t); -static const flecs::entity_t U32 = ecs_id(ecs_u32_t); -static const flecs::entity_t U64 = ecs_id(ecs_u64_t); -static const flecs::entity_t Uptr = ecs_id(ecs_uptr_t); -static const flecs::entity_t I8 = ecs_id(ecs_i8_t); -static const flecs::entity_t I16 = ecs_id(ecs_i16_t); -static const flecs::entity_t I32 = ecs_id(ecs_i32_t); -static const flecs::entity_t I64 = ecs_id(ecs_i64_t); -static const flecs::entity_t Iptr = ecs_id(ecs_iptr_t); -static const flecs::entity_t F32 = ecs_id(ecs_f32_t); -static const flecs::entity_t F64 = ecs_id(ecs_f64_t); -static const flecs::entity_t String = ecs_id(ecs_string_t); -static const flecs::entity_t Entity = ecs_id(ecs_entity_t); + flecs::entity system() const; -static const flecs::entity_t Constant = EcsConstant; -static const flecs::entity_t Quantity = EcsQuantity; + flecs::entity event() const; -namespace meta { + flecs::id event_id() const; -struct cursor { - cursor(flecs::world_t *world, flecs::entity_t type_id, void *ptr) { - m_cursor = ecs_meta_cursor(world, type_id, ptr); - } + flecs::world world() const; - int push() { - return ecs_meta_push(&m_cursor); + const flecs::iter_t* c_ptr() const { + return iter_; } - int pop() { - return ecs_meta_pop(&m_cursor); + size_t count() const { + ecs_check(iter_->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + return static_cast(iter_->count); + error: + return 0; } - int next() { - return ecs_meta_next(&m_cursor); + ecs_ftime_t delta_time() const { + return iter_->delta_time; } - int member(const char *name) { - return ecs_meta_member(&m_cursor, name); + ecs_ftime_t delta_system_time() const { + return iter_->delta_system_time; } - bool is_collection() { - return ecs_meta_is_collection(&m_cursor); - } + flecs::type type() const; - flecs::string_view get_member() const { - return flecs::string_view(ecs_meta_get_member(&m_cursor)); - } + flecs::table table() const; - flecs::entity get_type() const; + flecs::table other_table() const; - flecs::entity get_unit() const; + flecs::table_range range() const; - void* get_ptr() { - return ecs_meta_get_ptr(&m_cursor); + /** Access ctx. + * ctx contains the context pointer assigned to a system. + */ + void* ctx() { + return iter_->ctx; } - int set_bool(bool value) { - return ecs_meta_set_bool(&m_cursor, value); + /** Access ctx. + * ctx contains the context pointer assigned to a system. + */ + template + T* ctx() { + return static_cast(iter_->ctx); } - int set_char(char value) { - return ecs_meta_set_char(&m_cursor, value); + /** Access param. + * param contains the pointer passed to the param argument of system::run + */ + void* param() { + return iter_->param; } - int set_int(int64_t value) { - return ecs_meta_set_int(&m_cursor, value); + /** Access param. + * param contains the pointer passed to the param argument of system::run + */ + template + T* param() { + /* TODO: type check */ + return static_cast(iter_->param); } - int set_uint(uint64_t value) { - return ecs_meta_set_uint(&m_cursor, value); - } + /** Obtain mutable handle to entity being iterated over. + * + * @param row Row being iterated over. + */ + flecs::entity entity(size_t row) const; - int set_float(double value) { - return ecs_meta_set_float(&m_cursor, value); + /** Returns whether field is matched on self. + * + * @param index The field index. + */ + bool is_self(int8_t index) const { + return ecs_field_is_self(iter_, index); } - int set_string(const char *value) { - return ecs_meta_set_string(&m_cursor, value); + /** Returns whether field is set. + * + * @param index The field index. + */ + bool is_set(int8_t index) const { + return ecs_field_is_set(iter_, index); } - int set_string_literal(const char *value) { - return ecs_meta_set_string_literal(&m_cursor, value); + /** Returns whether field is readonly. + * + * @param index The field index. + */ + bool is_readonly(int8_t index) const { + return ecs_field_is_readonly(iter_, index); } - int set_entity(flecs::entity_t value) { - return ecs_meta_set_entity(&m_cursor, value); + /** Number of fields in iterator. + */ + int32_t field_count() const { + return iter_->field_count; } - int set_null() { - return ecs_meta_set_null(&m_cursor); + /** Size of field data type. + * + * @param index The field id. + */ + size_t size(int8_t index) const { + return ecs_field_size(iter_, index); } + /** Obtain field source (0 if This). + * + * @param index The field index. + */ + flecs::entity src(int8_t index) const; - bool get_bool() const { - return ecs_meta_get_bool(&m_cursor); - } - - char get_char() const { - return ecs_meta_get_char(&m_cursor); - } + /** Obtain id matched for field. + * + * @param index The field index. + */ + flecs::id id(int8_t index) const; - int64_t get_int() const { - return ecs_meta_get_int(&m_cursor); - } + /** Obtain pair id matched for field. + * This operation will fail if the id is not a pair. + * + * @param index The field index. + */ + flecs::id pair(int8_t index) const; - uint64_t get_uint() const { - return ecs_meta_get_uint(&m_cursor); + /** Obtain column index for field. + * + * @param index The field index. + */ + int32_t column_index(int8_t index) const { + return ecs_field_column(iter_, index); } - double get_float() const { - return ecs_meta_get_float(&m_cursor); + /** Obtain term that triggered an observer + */ + int8_t term_index() const { + return iter_->term_index; } - const char *get_string() const { - return ecs_meta_get_string(&m_cursor); + /** Convert current iterator result to string. + */ + flecs::string str() const { + char *s = ecs_iter_str(iter_); + return flecs::string(s); } - flecs::entity get_entity() const; + /** Get readonly access to field data. + * If the specified field index does not match with the provided type, the + * function will assert. + * + * This function should not be used in each() callbacks, unless it is to + * access a shared field. For access to non-shared fields in each(), use + * field_at. + * + * @tparam T Type of the field. + * @param index The field index. + * @return The field data. + */ + template , if_t> = 0> + flecs::field field(int8_t index) const; - ecs_meta_cursor_t m_cursor; -}; + /** Get read/write access to field data. + * If the matched id for the specified field does not match with the provided + * type or if the field is readonly, the function will assert. + * + * This function should not be used in each() callbacks, unless it is to + * access a shared field. For access to non-shared fields in each(), use + * field_at. + * + * @tparam T Type of the field. + * @param index The field index. + * @return The field data. + */ + template , if_not_t> = 0> + flecs::field field(int8_t index) const; -namespace _ { + /** Get unchecked access to field data. + * Unchecked access is required when a system does not know the type of a + * field at compile time. + * + * This function should not be used in each() callbacks, unless it is to + * access a shared field. For access to non-shared fields in each(), use + * field_at. + * + * @param index The field index. + */ + flecs::untyped_field field(int8_t index) const { + ecs_assert(!(iter_->flags & EcsIterCppEach) || + ecs_field_src(iter_, index) != 0, ECS_INVALID_OPERATION, + "cannot .field from .each, use .field_at(%d, row) instead", index); + return get_unchecked_field(index); + } -void init(flecs::world& world); + /** Get pointer to field at row. + * This function may be used to access shared fields when row is set to 0. + */ + void* field_at(int8_t index, size_t row) const { + if (iter_->row_fields & (1llu << index)) { + return get_unchecked_field_at(index, row)[0]; + } else { + return get_unchecked_field(index)[row]; + } + } -} // namespace _ -} // namespace meta -} // namespace flecs + /** Get reference to field at row. + * This function may be used to access shared fields when row is set to 0. + */ + template , if_t< is_const_v > = 0> + const A& field_at(int8_t index, size_t row) const { + if (iter_->row_fields & (1llu << index)) { + return get_field_at(index, row)[0]; + } else { + return get_field(index)[row]; + } + } -#endif -#ifdef FLECS_UNITS -#pragma once + /** Get reference to field at row. + * This function may be used to access shared fields when row is set to 0. + */ + template , if_not_t< is_const_v > = 0> + A& field_at(int8_t index, size_t row) const { + ecs_assert(!ecs_field_is_readonly(iter_, index), + ECS_ACCESS_VIOLATION, NULL); + if (iter_->row_fields & (1llu << index)) { + return get_field_at(index, row)[0]; + } else { + return get_field(index)[row]; + } + } -namespace flecs { -struct units { + /** Get readonly access to entity ids. + * + * @return The entity ids. + */ + flecs::field entities() const { + return flecs::field( + iter_->entities, static_cast(iter_->count), false); + } -//////////////////////////////////////////////////////////////////////////////// -//// Unit prefixes -//////////////////////////////////////////////////////////////////////////////// + /** Check if the current table has changed since the last iteration. + * Can only be used when iterating queries and/or systems. */ + bool changed() { + return ecs_iter_changed(iter_); + } -struct Prefixes { }; + /** Skip current table. + * This indicates to the query that the data in the current table is not + * modified. By default, iterating a table with a query will mark the + * iterated components as dirty if they are annotated with InOut or Out. + * + * When this operation is invoked, the components of the current table will + * not be marked dirty. */ + void skip() { + ecs_iter_skip(iter_); + } -struct Yocto { }; -struct Zepto { }; -struct Atto { }; -struct Femto { }; -struct Pico { }; -struct Nano { }; -struct Micro { }; -struct Milli { }; -struct Centi { }; -struct Deci { }; -struct Deca { }; -struct Hecto { }; -struct Kilo { }; -struct Mega { }; -struct Giga { }; -struct Tera { }; -struct Peta { }; -struct Exa { }; -struct Zetta { }; -struct Yotta { }; -struct Kibi { }; -struct Mebi { }; -struct Gibi { }; -struct Tebi { }; -struct Pebi { }; -struct Exbi { }; -struct Zebi { }; -struct Yobi { }; + /* Return group id for current table (grouped queries only) */ + uint64_t group_id() const { + return ecs_iter_get_group(iter_); + } + /** Get value of variable by id. + * Get value of a query variable for current result. + */ + flecs::entity get_var(int var_id) const; -//////////////////////////////////////////////////////////////////////////////// -//// Quantities -//////////////////////////////////////////////////////////////////////////////// + /** Get value of variable by name. + * Get value of a query variable for current result. + */ + flecs::entity get_var(const char *name) const; -struct Duration { }; -struct Time { }; -struct Mass { }; -struct ElectricCurrent { }; -struct LuminousIntensity { }; -struct Force { }; -struct Amount { }; -struct Length { }; -struct Pressure { }; -struct Speed { }; -struct Temperature { }; -struct Data { }; -struct DataRate { }; -struct Angle { }; + /** Progress iterator. + * This operation should only be called from a context where the iterator is + * not being progressed automatically. An example of a valid context is + * inside of a run() callback. An example of an invalid context is inside of + * an each() callback. + */ + bool next() { + if (iter_->flags & EcsIterIsValid && iter_->table) { + ECS_TABLE_UNLOCK(iter_->world, iter_->table); + } + bool result = iter_->next(iter_); + iter_->flags |= EcsIterIsValid; + if (result && iter_->table) { + ECS_TABLE_LOCK(iter_->world, iter_->table); + } + return result; + } + /** Forward to each. + * If a system has an each callback registered, this operation will forward + * the current iterator to the each callback. + */ + void each() { + iter_->callback(iter_); + } -//////////////////////////////////////////////////////////////////////////////// -//// Units -//////////////////////////////////////////////////////////////////////////////// + /** Iterate targets for pair field. + * + * @param index The field index. + * @param func Callback invoked for each target + */ + template + void targets(int8_t index, const Func& func); -struct duration { -struct PicoSeconds { }; -struct NanoSeconds { }; -struct MicroSeconds { }; -struct MilliSeconds { }; -struct Seconds { }; -struct Minutes { }; -struct Hours { }; -struct Days { }; -}; + /** Free iterator resources. + * This operation only needs to be called when the iterator is not iterated + * until completion (e.g. the last call to next() did not return false). + * + * Failing to call this operation on an unfinished iterator will throw a + * fatal LEAK_DETECTED error. + * + * @see ecs_iter_fini() + */ + void fini() { + if (iter_->flags & EcsIterIsValid && iter_->table) { + ECS_TABLE_UNLOCK(iter_->world, iter_->table); + } + ecs_iter_fini(iter_); + } -struct angle { -struct Radians { }; -struct Degrees { }; -}; +private: + /* Get field, check if correct type is used */ + template > + flecs::field get_field(int8_t index) const { -struct time { -struct Date { }; -}; +#ifndef FLECS_NDEBUG + ecs_entity_t term_id = ecs_field_id(iter_, index); + ecs_assert(ECS_HAS_ID_FLAG(term_id, PAIR) || + term_id == _::type::id(iter_->world), + ECS_COLUMN_TYPE_MISMATCH, NULL); +#endif -struct mass { -struct Grams { }; -struct KiloGrams { }; -}; + size_t count; + bool is_shared = !ecs_field_is_self(iter_, index); -struct electric_current { -struct Ampere { }; -}; + /* If a shared column is retrieved with 'column', there will only be a + * single value. Ensure that the application does not accidentally read + * out of bounds. */ + if (is_shared) { + count = 1; + } else { + /* If column is owned, there will be as many values as there are + * entities. */ + count = static_cast(iter_->count); + } -struct amount { -struct Mole { }; -}; + return flecs::field( + static_cast(ecs_field_w_size(iter_, sizeof(A), index)), + count, is_shared); + } -struct luminous_intensity { -struct Candela { }; -}; + /* Get field, check if correct type is used */ + template > + flecs::field get_field_at(int8_t index, int32_t row) const { -struct force { -struct Newton { }; -}; +#ifndef FLECS_NDEBUG + ecs_entity_t term_id = ecs_field_id(iter_, index); + ecs_assert(ECS_HAS_ID_FLAG(term_id, PAIR) || + term_id == _::type::id(iter_->world), + ECS_COLUMN_TYPE_MISMATCH, NULL); +#endif -struct length { -struct Meters { }; -struct PicoMeters { }; -struct NanoMeters { }; -struct MicroMeters { }; -struct MilliMeters { }; -struct CentiMeters { }; -struct KiloMeters { }; -struct Miles { }; -}; + return flecs::field( + static_cast(ecs_field_at_w_size(iter_, sizeof(A), index, row)), + 1, false); + } -struct pressure { -struct Pascal { }; -struct Bar { }; -}; + flecs::untyped_field get_unchecked_field(int8_t index) const { + size_t count; + size_t size = ecs_field_size(iter_, index); + bool is_shared = !ecs_field_is_self(iter_, index); -struct speed { -struct MetersPerSecond { }; -struct KiloMetersPerSecond { }; -struct KiloMetersPerHour { }; -struct MilesPerHour { }; -}; + /* If a shared column is retrieved with 'column', there will only be a + * single value. Ensure that the application does not accidentally read + * out of bounds. */ + if (is_shared) { + count = 1; + } else { + /* If column is owned, there will be as many values as there are + * entities. */ + count = static_cast(iter_->count); + } -struct temperature { -struct Kelvin { }; -struct Celsius { }; -struct Fahrenheit { }; -}; + return flecs::untyped_field( + ecs_field_w_size(iter_, size, index), size, count, is_shared); + } -struct data { -struct Bits { }; -struct KiloBits { }; -struct MegaBits { }; -struct GigaBits { }; -struct Bytes { }; -struct KiloBytes { }; -struct MegaBytes { }; -struct GigaBytes { }; -struct KibiBytes { }; -struct MebiBytes { }; -struct GibiBytes { }; -}; + flecs::untyped_field get_unchecked_field_at(int8_t index, size_t row) const { + size_t size = ecs_field_size(iter_, index); + return flecs::untyped_field( + ecs_field_at_w_size(iter_, size, index, static_cast(row)), + size, 1, false); + } -struct datarate { -struct BitsPerSecond { }; -struct KiloBitsPerSecond { }; -struct MegaBitsPerSecond { }; -struct GigaBitsPerSecond { }; -struct BytesPerSecond { }; -struct KiloBytesPerSecond { }; -struct MegaBytesPerSecond { }; -struct GigaBytesPerSecond { }; + flecs::iter_t *iter_; }; -struct Percentage { }; -struct Bel { }; -struct DeciBel { }; +} // namespace flecs -units(flecs::world& world); +/** @} */ -}; -} +/** + * @file addons/cpp/ref.hpp + * @brief Class that caches data to speedup get operations. + */ -#endif -#ifdef FLECS_JSON #pragma once -namespace flecs { - -using entity_to_json_desc_t = ecs_entity_to_json_desc_t; -using iter_to_json_desc_t = ecs_iter_to_json_desc_t; +namespace flecs +{ -} +/** + * @defgroup cpp_ref Refs + * @ingroup cpp_core + * Refs are a fast mechanism for referring to a specific entity/component. + * + * @{ + */ -#endif -#ifdef FLECS_APP -#pragma once +/** Untyped component reference. + * Reference to a component from a specific entity. + */ +struct untyped_ref { -#pragma once + untyped_ref () : world_(nullptr), ref_{} {} -namespace flecs { + untyped_ref(world_t *world, entity_t entity, flecs::id_t id) + : ref_() { + ecs_assert(id != 0, ECS_INVALID_PARAMETER, + "invalid id"); + // the world we were called with may be a stage; convert it to a world + // here if that is the case + world_ = world ? const_cast(ecs_get_world(world)) + : nullptr; -// App builder interface -struct app_builder { - app_builder(flecs::world_t *world) - : m_world(world) - , m_desc{} - { - const ecs_world_info_t *stats = ecs_get_world_info(world); - m_desc.target_fps = stats->target_fps; - if (m_desc.target_fps == static_cast(0.0)) { - m_desc.target_fps = 60; - } +#ifdef FLECS_DEBUG + flecs::entity_t type = ecs_get_typeid(world, id); + const flecs::type_info_t *ti = ecs_get_type_info(world, type); + ecs_assert(ti && ti->size != 0, ECS_INVALID_PARAMETER, + "cannot create ref to empty type"); +#endif + ref_ = ecs_ref_init_id(world_, entity, id); } - app_builder& target_fps(FLECS_FLOAT value) { - m_desc.target_fps = value; - return *this; - } + untyped_ref(flecs::entity entity, flecs::id_t id); - app_builder& delta_time(FLECS_FLOAT value) { - m_desc.delta_time = value; - return *this; + /** Return entity associated with reference. */ + flecs::entity entity() const; + + /** Return component associated with reference. */ + flecs::id component() const { + return flecs::id(world_, ref_.id); } - app_builder& threads(int32_t value) { - m_desc.threads = value; - return *this; + void* get() { + return ecs_ref_get_id(world_, &ref_, this->ref_.id); } - app_builder& enable_rest(bool value = true) { - m_desc.enable_rest = value; - return *this; + bool has() { + return !!try_get(); } - app_builder& init(ecs_app_init_action_t value) { - m_desc.init = value; - return *this; + flecs::world world() const { + return flecs::world(world_); } - app_builder& ctx(void *value) { - m_desc.ctx = value; - return *this; + /** implicit conversion to bool. return true if there is a valid + * component instance being referred to **/ + operator bool() { + return has(); } - int run() { - return ecs_app_run(m_world, &m_desc); + void* try_get() { + if (!world_ || !ref_.entity) { + return nullptr; + } + + return get(); } private: - flecs::world_t *m_world; - ecs_app_desc_t m_desc; + world_t *world_; + flecs::ref_t ref_; }; -} +/** Component reference. + * Reference to a component from a specific entity. + */ +template +struct ref : public untyped_ref { + ref() : untyped_ref() { } + ref(world_t *world, entity_t entity, flecs::id_t id = 0) + : untyped_ref(world, entity, id ? id : _::type::id(world)) + { } + ref(flecs::entity entity, flecs::id_t id = 0); -#endif + T* operator->() { + T* result = static_cast(get()); + ecs_assert(result != NULL, ECS_INVALID_PARAMETER, + "nullptr dereference by flecs::ref"); -namespace flecs { -namespace log { + return result; + } -inline void set_level(int level) { - ecs_log_set_level(level); -} + T* get() { + return static_cast(untyped_ref::get()); + } -inline void enable_colors(bool enabled) { - ecs_log_enable_colors(enabled); -} + T* try_get() { + return static_cast(untyped_ref::try_get()); + } +}; -inline void dbg(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ecs_logv(1, fmt, args); - va_end(args); -} +/** @} */ -inline void trace(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ecs_logv(0, fmt, args); - va_end(args); } -inline void warn(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ecs_logv(-2, fmt, args); - va_end(args); -} +/** + * @file addons/cpp/entity.hpp + * @brief Entity class. + * + * This class provides read/write access to entities. + */ + +#pragma once + +/** + * @file addons/cpp/entity_view.hpp + * @brief Entity class with only readonly operations. + * + * This class provides readonly access to entities. Using this class to store + * entities in components ensures valid handles, as this class will always store + * the actual world vs. a stage. The constructors of this class will never + * create a new entity. + * + * To obtain a mutable handle to the entity, use the "mut" function. + */ + +#pragma once -inline void err(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ecs_logv(-3, fmt, args); - va_end(args); -} +/** + * @file addons/cpp/entity_component_tuple.hpp + * @brief Utilities to fetch component as tuples from entities. + */ -inline void push() { - ecs_log_push(); -} +#pragma once -inline void pop() { - ecs_log_pop(); -} +/** + * @ingroup cpp_entities + * @{ + */ -} -} +namespace flecs +{ + template + struct tuple_builder {}; -namespace flecs { + // Size 2 -namespace _ { - struct pair_base { }; -} // _ + template + struct tuple_2 { + T val1; U val2; + }; + template + struct tuple_builder<2, Args...> { + using type = tuple_2; + using type_ptr = tuple_2; + using type_const = tuple_2; + using type_const_ptr = tuple_2; + }; -// Type that represents a pair and can encapsulate a temporary value -template -struct pair : _::pair_base { - // Traits used to deconstruct the pair + // Size 3 - // The actual type of the pair is determined by which type of the pair is - // empty. If both types are empty or not empty, the pair assumes the type - // of the relation. - using type = conditional_t::value || is_empty::value, R, O>; - using relation = R; - using object = O; + template + struct tuple_3 { + T val1; U val2; V val3; + }; - pair(type& v) : ref_(v) { } + template + struct tuple_builder<3, Args...> { + using type = tuple_3; + using type_ptr = tuple_3; + using type_const = tuple_3; + using type_const_ptr = tuple_3; + }; - // This allows the class to be used as a temporary object - pair(const type& v) : ref_(const_cast(v)) { } + // Size 4 - operator type&() { - return ref_; - } + template + struct tuple_4 { + T val1; U val2; V val3; W val4; + }; - operator const Type&() const { - return ref_; - } + template + struct tuple_builder<4, Args...> { + using type = tuple_4; + using type_ptr = tuple_4; + using type_const = tuple_4; + using type_const_ptr = tuple_4; + }; - type* operator->() { - return &ref_; - } + // Size 5 - const type* operator->() const { - return &ref_; - } + template + struct tuple_5 { + T val1; U val2; V val3; W val4; X val5; + }; - type& operator*() { - return &ref_; - } + template + struct tuple_builder<5, Args...> { + using type = tuple_5; + using type_ptr = tuple_5; + using type_const = tuple_5; + using type_const_ptr = tuple_5; + }; - const type& operator*() const { - return ref_; - } - -private: - type& ref_; -}; + // Size 6 -template ::value> = 0> -using pair_object = pair; + template + struct tuple_6 { + T val1; U val2; V val3; W val4; X val5; Y val6; + }; + template + struct tuple_builder<6, Args...> { + using type = tuple_6; + using type_ptr = tuple_6; + using type_const = tuple_6; + using type_const_ptr = tuple_6; + }; -// Utilities to test if type is a pair -template -struct is_pair { - static constexpr bool value = is_base_of<_::pair_base, remove_reference_t >::value; -}; + // Size 7 + template + struct tuple_7 { + T val1; U val2; V val3; W val4; X val5; Y val6; Z val7; + }; -// Get actual type, relation or object from pair while preserving cv qualifiers. -template -using pair_relation_t = transcribe_cv_t, typename remove_reference_t

::relation>; + template + struct tuple_builder<7, Args...> { + using type = tuple_7; + using type_ptr = tuple_7; + using type_const = tuple_7; + using type_const_ptr = tuple_7; + }; -template -using pair_object_t = transcribe_cv_t, typename remove_reference_t

::object>; + // Size 8 -template -using pair_type_t = transcribe_cv_t, typename remove_reference_t

::type>; + template + struct tuple_8 { + T val1; U val2; V val3; W val4; X val5; Y val6; Z val7; A val8; + }; + template + struct tuple_builder<8, Args...> { + using type = tuple_8; + using type_ptr = tuple_8; + using type_const = tuple_8; + using type_const_ptr = tuple_8; + }; -// Get actual type from a regular type or pair -template -struct actual_type; +} -template -struct actual_type::value >> { - using type = T; -}; +/** @} */ -template -struct actual_type::value >> { - using type = pair_type_t; -}; -template -using actual_type_t = typename actual_type::type; +/** + * @ingroup cpp_entities + * @{ + */ +namespace flecs +{ -// Get type without const, *, & -template -struct base_type { - using type = remove_pointer_t< decay_t< actual_type_t > >; -}; +/** Entity view. + * Class with read operations for entities. Base for flecs::entity. + * + * @ingroup cpp_entities + */ +struct entity_view : public id { -template -using base_type_t = typename base_type::type; + entity_view() : flecs::id() { } + /** Wrap an existing entity id. + * + * @param world The world in which the entity is created. + * @param id The entity id. + */ + explicit entity_view(flecs::world_t *world, flecs::id_t id) + : flecs::id(world + ? const_cast(ecs_get_world(world)) + : nullptr + , id ) { } -// Get type without *, & (retains const which is useful for function args) -template -struct base_arg_type { - using type = remove_pointer_t< remove_reference_t< actual_type_t > >; -}; + /** Implicit conversion from flecs::entity_t to flecs::entity_view. */ + entity_view(entity_t id) + : flecs::id( nullptr, id ) { } -template -using base_arg_type_t = typename base_arg_type::type; + /** Get entity id. + * @return The integer entity id. + */ + entity_t id() const { + return id_; + } + + /** Check if entity is valid. + * An entity is valid if: + * - its id is not 0 + * - the id contains a valid bit pattern for an entity + * - the entity is alive (see is_alive()) + * + * @return True if the entity is valid, false otherwise. + * @see ecs_is_valid() + */ + bool is_valid() const { + return world_ && ecs_is_valid(world_, id_); + } + + explicit operator bool() const { + return is_valid(); + } + /** Check if entity is alive. + * + * @return True if the entity is alive, false otherwise. + * @see ecs_is_alive() + */ + bool is_alive() const { + return world_ && ecs_is_alive(world_, id_); + } -// Test if type is the same as its actual type -template -struct is_actual { - static constexpr bool value = - std::is_same >::value && !is_enum::value; -}; + /** Return the entity name. + * + * @return The entity name. + */ + flecs::string_view name() const { + return flecs::string_view(ecs_get_name(world_, id_)); + } -} // flecs + /** Return the entity symbol. + * + * @return The entity symbol. + */ + flecs::string_view symbol() const { + return flecs::string_view(ecs_get_symbol(world_, id_)); + } -namespace flecs -{ + /** Return the entity path. + * + * @return The hierarchical entity path. + */ + flecs::string path(const char *sep = "::", const char *init_sep = "::") const { + return path_from(0, sep, init_sep); + } -namespace _ -{ + /** Return the entity path relative to a parent. + * + * @return The relative hierarchical entity path. + */ + flecs::string path_from(flecs::entity_t parent, const char *sep = "::", const char *init_sep = "::") const { + char *path = ecs_get_path_w_sep(world_, parent, id_, sep, init_sep); + return flecs::string(path); + } -inline void ecs_ctor_illegal(void *, int32_t, const ecs_type_info_t*) { - ecs_abort(ECS_INVALID_OPERATION, "invalid constructor"); -} + /** Return the entity path relative to a parent. + * + * @return The relative hierarchical entity path. + */ + template + flecs::string path_from(const char *sep = "::", const char *init_sep = "::") const { + return path_from(_::type::id(world_), sep, init_sep); + } -inline void ecs_dtor_illegal(void *, int32_t, const ecs_type_info_t*) { - ecs_abort(ECS_INVALID_OPERATION, "invalid destructor"); -} + bool enabled() const { + return !ecs_has_id(world_, id_, flecs::Disabled); + } -inline void ecs_copy_illegal( - void *, const void *, int32_t, const ecs_type_info_t *) -{ - ecs_abort(ECS_INVALID_OPERATION, "invalid copy assignment"); -} + /** Get the entity's type. + * + * @return The entity's type. + */ + flecs::type type() const; -inline void ecs_move_illegal(void *, void *, int32_t, const ecs_type_info_t *) { - ecs_abort(ECS_INVALID_OPERATION, "invalid move assignment"); -} + /** Get the entity's table. + * + * @return Returns the entity's table. + */ + flecs::table table() const; -inline void ecs_copy_ctor_illegal( - void *, const void *, int32_t, const ecs_type_info_t *) -{ - ecs_abort(ECS_INVALID_OPERATION, "invalid copy construct"); -} + /** Get table range for the entity. + * Returns a range with the entity's row as offset and count set to 1. If + * the entity is not stored in a table, the function returns a range with + * count 0. + * + * @return Returns the entity's table range. + */ + flecs::table_range range() const; -inline void ecs_move_ctor_illegal( - void *, void *, int32_t, const ecs_type_info_t *) -{ - ecs_abort(ECS_INVALID_OPERATION, "invalid move construct"); -} + /** Iterate (component) ids of an entity. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::id id) + * @endcode + * + * @param func The function invoked for each id. + */ + template + void each(const Func& func) const; + /** Iterate matching pair ids of an entity. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::id id) + * @endcode + * + * @param func The function invoked for each id. + */ + template + void each(flecs::id_t first, flecs::id_t second, const Func& func) const; -// T() -// Can't coexist with T(flecs::entity) or T(flecs::world, flecs::entity) -template -void ctor_impl(void *ptr, int32_t count, const ecs_type_info_t *info) { - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *arr = static_cast(ptr); - for (int i = 0; i < count; i ++) { - FLECS_PLACEMENT_NEW(&arr[i], T); - } -} + /** Iterate targets for a given relationship. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::entity target) + * @endcode + * + * @param rel The relationship for which to iterate the targets. + * @param func The function invoked for each target. + */ + template + void each(const flecs::entity_view& rel, const Func& func) const; -// ~T() -template -void dtor_impl(void *ptr, int32_t count, const ecs_type_info_t *info) { - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *arr = static_cast(ptr); - for (int i = 0; i < count; i ++) { - arr[i].~T(); + /** Iterate targets for a given relationship. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::entity target) + * @endcode + * + * @tparam First The relationship for which to iterate the targets. + * @param func The function invoked for each target. + */ + template + void each(const Func& func) const { + return each(_::type::id(world_), func); } -} -// T& operator=(const T&) -template -void copy_impl(void *dst_ptr, const void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - const T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - dst_arr[i] = src_arr[i]; - } -} + /** Iterate children for entity. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::entity target) + * @endcode + * + * @param rel The relationship to follow. + * @param func The function invoked for each child. + */ + template + void children(flecs::entity_t rel, Func&& func) const { + /* When the entity is a wildcard, this would attempt to query for all + * entities with (ChildOf, *) or (ChildOf, _) instead of querying for + * the children of the wildcard entity. */ + if (id_ == flecs::Wildcard || id_ == flecs::Any) { + /* This is correct, wildcard entities don't have children */ + return; + } -// T& operator=(T&&) -template -void move_impl(void *dst_ptr, void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - dst_arr[i] = FLECS_MOV(src_arr[i]); + flecs::world world(world_); + + ecs_iter_t it = ecs_children_w_rel(world_, rel, id_); + while (ecs_children_next(&it)) { + _::each_delegate(FLECS_MOV(func)).invoke(&it); + } } -} -// T(T&) -template -void copy_ctor_impl(void *dst_ptr, const void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - const T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - FLECS_PLACEMENT_NEW(&dst_arr[i], T(src_arr[i])); + /** Iterate children for entity. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::entity target) + * @endcode + * + * @tparam Rel The relationship to follow. + * @param func The function invoked for each child. + */ + template + void children(Func&& func) const { + children(_::type::id(world_), FLECS_MOV(func)); } -} -// T(T&&) -template -void move_ctor_impl(void *dst_ptr, void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - FLECS_PLACEMENT_NEW(&dst_arr[i], T(FLECS_MOV(src_arr[i]))); + /** Iterate children for entity. + * The function parameter must match the following signature: + * + * @code + * void(*)(flecs::entity target) + * @endcode + * + * This operation follows the ChildOf relationship. + * + * @param func The function invoked for each child. + */ + template + void children(Func&& func) const { + children(flecs::ChildOf, FLECS_MOV(func)); } -} -// T(T&&), ~T() -// Typically used when moving to a new table, and removing from the old table -template -void ctor_move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - FLECS_PLACEMENT_NEW(&dst_arr[i], T(FLECS_MOV(src_arr[i]))); - src_arr[i].~T(); + + /* try_get */ + + /** Get component value. + * + * @tparam T The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + template ::value > = 0> + const T* try_get() const { + auto comp_id = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return static_cast(ecs_get_id(world_, id_, comp_id)); } -} -// Move assign + dtor (non-trivial move assigmnment) -// Typically used when moving a component to a deleted component -template ::value > = 0> -void move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - // Move assignment should free dst & assign dst to src - dst_arr[i] = FLECS_MOV(src_arr[i]); - // Destruct src. Move should have left object in a state where it no - // longer holds resources, but it still needs to be destructed. - src_arr[i].~T(); + /** Get component value. + * Overload for when T is not the same as the actual type, which happens + * when using pair types. + * + * @tparam T The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + template , + if_t< flecs::is_pair::value > = 0> + const A* try_get() const { + auto comp_id = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return static_cast(ecs_get_id(world_, id_, comp_id)); } -} -// Move assign + dtor (trivial move assigmnment) -// Typically used when moving a component to a deleted component -template ::value > = 0> -void move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - // Cleanup resources of dst - dst_arr[i].~T(); - // Copy src to dst - dst_arr[i] = FLECS_MOV(src_arr[i]); - // No need to destruct src. Since this is a trivial move the code - // should be agnostic to the address of the component which means we - // can pretend nothing got destructed. + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value > = 0> + const A* try_get() const { + return this->try_get

(); } -} -} // _ + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template::value> = 0> + const First* try_get(Second second) const { + auto first = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return static_cast( + ecs_get_id(world_, id_, ecs_pair(first, second))); + } -// Trait to test if type is constructible by flecs -template -struct is_flecs_constructible { - static constexpr bool value = - std::is_default_constructible>::value; -}; + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @param constant the enum constant. + */ + template::value && !std::is_same::value > = 0> + const First* try_get(Second constant) const { + const auto& et = enum_type(this->world_); + flecs::entity_t target = et.entity(constant); + return try_get(target); + } -namespace _ -{ + /** Get component value (untyped). + * + * @param comp The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + const void* try_get(flecs::id_t comp) const { + return ecs_get_id(world_, id_, comp); + } -// Trivially constructible -template ::value > = 0> -ecs_xtor_t ctor() { - return nullptr; -} + /** Get a pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * first nor the second part of the pair are components, the operation + * will fail. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + const void* try_get(flecs::entity_t first, flecs::entity_t second) const { + return ecs_get_id(world_, id_, ecs_pair(first, second)); + } -// Not constructible by flecs -template ::value > = 0> -ecs_xtor_t ctor() { - return ecs_ctor_illegal; -} + template + auto try_get_n() const { + flecs_static_assert(sizeof...(Ts) > 1, "try_get_n requires at least two components"); + flecs_static_assert(sizeof...(Ts) < 9, "try_get_n cannot fetch more than eight components"); + return typename tuple_builder::type_const_ptr {try_get()...}; + } -// Default constructible -template ::value && - std::is_default_constructible::value > = 0> -ecs_xtor_t ctor() { - return ctor_impl; -} + /** Get the second part for a pair. + * This operation gets the value for a pair from the entity. The first + * part of the pair should not be a component. + * + * @tparam Second the second element of a pair. + * @param first The first part of the pair. + */ + template + const Second* try_get_second(flecs::entity_t first) const { + auto second = _::type::id(world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return static_cast( + ecs_get_id(world_, id_, ecs_pair(first, second))); + } + + /** Get the second part for a pair. + * This operation gets the value for a pair from the entity. The first + * part of the pair should not be a component. + * + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template + const Second* try_get_second() const { + return try_get>(); + } -// No dtor -template ::value > = 0> -ecs_xtor_t dtor() { - return nullptr; -} -// Dtor -template ::value && - ! std::is_trivially_destructible::value > = 0> -ecs_xtor_t dtor() { - return dtor_impl; -} + /* get */ -// Assert when the type cannot be destructed -template ::value > = 0> -ecs_xtor_t dtor() { - flecs_static_assert(always_false::value, - "component type must be destructible"); - return ecs_dtor_illegal; -} + /** Get component value. + * + * @tparam T The component to get. + * @return Ref to the component value, panics if the entity does not + * have the component. + */ + template ::value > = 0> + const T& get() const { + const T *r = try_get(); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get: entity does not have component '%s' (use try_get)", + flecs::_::type_name()); + return *r; + } -// Trivially copyable -template ::value > = 0> -ecs_copy_t copy() { - return nullptr; -} + /** Get component value. + * Overload for when T is not the same as the actual type, which happens + * when using pair types. + * + * @tparam T The component to get. + * @return Ref to the component value, panics if the entity does not + * have the component. + */ + template , + if_t< flecs::is_pair::value > = 0> + const A& get() const { + const A *r = try_get(); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get: entity does not have component '%s' (use try_get)", + flecs::_::type_name()); + return *r; + } + + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + * @return Ref to the component value, panics if the entity does not + * have the component. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value > = 0> + const A& get() const { + return this->get

(); + } -// Not copyable -template ::value && - ! std::is_copy_assignable::value > = 0> -ecs_copy_t copy() { - return ecs_copy_illegal; -} + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return Ref to the component value, panics if the entity does not + * have the component. + */ + template::value> = 0> + const First& get(Second second) const { + const First *r = try_get(second); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get: entity does not have component (use try_get)"); + return *r; + } -// Copy assignment -template ::value && - ! std::is_trivially_copyable::value > = 0> -ecs_copy_t copy() { - return copy_impl; -} + /** Get a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @param constant the enum constant. + * @return Ref to the component value, panics if the entity does not + * have the component. + */ + template::value && !std::is_same::value > = 0> + const First& get(Second constant) const { + const auto& et = enum_type(this->world_); + flecs::entity_t target = et.entity(constant); + return get(target); + } -// Trivially move assignable -template ::value > = 0> -ecs_move_t move() { - return nullptr; -} + /** Get component value (untyped). + * + * @param comp The component to get. + * @return Pointer to the component value, panics if the entity does not + * have the component. + */ + const void* get(flecs::id_t comp) const { + const void *r = ecs_get_id(world_, id_, comp); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get: entity does not have component (use try_get)"); + return r; + } -// Component types must be move assignable -template ::value > = 0> -ecs_move_t move() { - flecs_static_assert(always_false::value, - "component type must be move assignable"); - return ecs_move_illegal; -} + /** Get a pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * first nor the second part of the pair are components, the operation + * will fail. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return Pointer to the component value, panics if the entity does not + * have the component. + */ + const void* get(flecs::entity_t first, flecs::entity_t second) const { + const void *r = ecs_get_id(world_, id_, ecs_pair(first, second)); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get: entity does not have component (use try_get)"); + return r; + } -// Move assignment -template ::value && - ! std::is_trivially_move_assignable::value > = 0> -ecs_move_t move() { - return move_impl; -} + /** Get 1..N components. + * This operation accepts a callback with as arguments the components to + * retrieve. The callback will only be invoked when the entity has all + * the components. + * + * This operation is faster than individually calling get for each component + * as it only obtains entity metadata once. + * + * While the callback is invoked the table in which the components are + * stored is locked, which prevents mutations that could cause invalidation + * of the component references. Note that this is not an actual lock: + * invalid access causes a runtime panic and so it is still up to the + * application to ensure access is protected. + * + * The component arguments must be references and can be either const or + * non-const. When all arguments are const, the function will read-lock the + * table (see ecs_read_begin). If one or more arguments are non-const the + * function will write-lock the table (see ecs_write_begin). + * + * Example: + * + * @code + * e.get([](Position& p, Velocity& v) { // write lock + * p.x += v.x; + * }); + * + * e.get([](const Position& p) { // read lock + * std::cout << p.x << std::endl; + * }); + * @endcode + * + * @param func The callback to invoke. + * @return True if the entity has all components, false if not. + */ + template ::value > = 0> + bool get(const Func& func) const; -// Trivially copy constructible -template ::value > = 0> -ecs_copy_t copy_ctor() { - return nullptr; -} + template + auto get_n() const { + flecs_static_assert(sizeof...(Ts) > 1, "get_n requires at least two components"); + flecs_static_assert(sizeof...(Ts) < 9, "get_n cannot fetch more than eight components"); + return typename tuple_builder::type_const {get()...}; + } -// No copy ctor -template ::value > = 0> -ecs_copy_t copy_ctor() { - return ecs_copy_ctor_illegal; -} + /** Get the second part for a pair. + * This operation gets the value for a pair from the entity. The first + * part of the pair should not be a component. + * + * @tparam Second the second element of a pair. + * @param first The first part of the pair. + */ + template + const Second& get_second(flecs::entity_t first) const { + const Second *r = try_get_second(first); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get: entity does not have component (use try_get)"); + return *r; + } -// Copy ctor -template ::value && - ! std::is_trivially_copy_constructible::value > = 0> -ecs_copy_t copy_ctor() { - return copy_ctor_impl; -} + /** Get the second part for a pair. + * This operation gets the value for a pair from the entity. The first + * part of the pair should not be a component. + * + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template + const Second& get_second() const { + const Second *r = try_get(); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get: entity does not have component (use try_get)"); + return *r; + } -// Trivially move constructible -template ::value > = 0> -ecs_move_t move_ctor() { - return nullptr; -} -// Component types must be move constructible -template ::value > = 0> -ecs_move_t move_ctor() { - flecs_static_assert(always_false::value, - "component type must be move constructible"); - return ecs_move_ctor_illegal; -} + /* try_get_mut */ -// Move ctor -template ::value && - ! std::is_trivially_move_constructible::value > = 0> -ecs_move_t move_ctor() { - return move_ctor_impl; -} + /** Get mutable component value. + * + * @tparam T The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + template ::value > = 0> + T* try_get_mut() const { + auto comp_id = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return static_cast(ecs_get_mut_id(world_, id_, comp_id)); + } -// Trivial merge (move assign + dtor) -template ::value && - std::is_trivially_destructible::value > = 0> -ecs_move_t ctor_move_dtor() { - return nullptr; -} + /** Get mutable component value. + * Overload for when T is not the same as the actual type, which happens + * when using pair types. + * + * @tparam T The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + template , + if_t< flecs::is_pair::value > = 0> + A* try_get_mut() const { + auto comp_id = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return static_cast(ecs_get_mut_id(world_, id_, comp_id)); + } -// Component types must be move constructible and destructible -template ::value || - ! std::is_destructible::value > = 0> -ecs_move_t ctor_move_dtor() { - flecs_static_assert(always_false::value, - "component type must be move constructible and destructible"); - return ecs_move_ctor_illegal; -} + /** Get a mutable pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value > = 0> + A* try_get_mut() const { + return this->try_get_mut

(); + } -// Merge ctor + dtor -template ::value && - std::is_trivially_destructible::value) && - std::is_move_constructible::value && - std::is_destructible::value > = 0> -ecs_move_t ctor_move_dtor() { - return ctor_move_dtor_impl; -} + /** Get a mutable pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template::value> = 0> + First* try_get_mut(Second second) const { + auto first = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return static_cast( + ecs_get_mut_id(world_, id_, ecs_pair(first, second))); + } -// Trivial merge (move assign + dtor) -template ::value && - std::is_trivially_destructible::value > = 0> -ecs_move_t move_dtor() { - return nullptr; -} + /** Get a mutable pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @param constant the enum constant. + */ + template::value && !std::is_same::value > = 0> + First* try_get_mut(Second constant) const { + const auto& et = enum_type(this->world_); + flecs::entity_t target = et.entity(constant); + return get_mut(target); + } -// Component types must be move constructible and destructible -template ::value || - ! std::is_destructible::value > = 0> -ecs_move_t move_dtor() { - flecs_static_assert(always_false::value, - "component type must be move constructible and destructible"); - return ecs_move_ctor_illegal; -} + /** Get mutable component value (untyped). + * + * @param comp The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + void* try_get_mut(flecs::id_t comp) const { + return ecs_get_mut_id(world_, id_, comp); + } -// Merge assign + dtor -template ::value && - std::is_trivially_destructible::value) && - std::is_move_assignable::value && - std::is_destructible::value > = 0> -ecs_move_t move_dtor() { - return move_dtor_impl; -} + /** Get a mutable pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * first nor the second part of the pair are components, the operation + * will fail. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + void* try_get_mut(flecs::entity_t first, flecs::entity_t second) const { + return ecs_get_mut_id(world_, id_, ecs_pair(first, second)); + } -} // _ -} // flecs + template + auto try_get_mut_n() const { + flecs_static_assert(sizeof...(Ts) > 1, "try_get_mut_n requires at least two components"); + flecs_static_assert(sizeof...(Ts) < 9, "try_get_mut_n cannot fetch more than eight components"); + return typename tuple_builder::type_ptr {try_get_mut()...}; + } + /** Get the second part for a pair. + * This operation gets the value for a pair from the entity. The first + * part of the pair should not be a component. + * + * @tparam Second the second element of a pair. + * @param first The first part of the pair. + */ + template + Second* try_get_mut_second(flecs::entity_t first) const { + auto second = _::type::id(world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return static_cast( + ecs_get_mut_id(world_, id_, ecs_pair(first, second))); + } + + /** Get the second part for a pair. + * This operation gets the value for a pair from the entity. The first + * part of the pair should not be a component. + * + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template + Second* try_get_mut_second() const { + return get_mut>(); + } -namespace flecs -{ -/** Static helper functions to assign a component value */ + /* get_mut */ -// set(T&&), T = constructible -template ::value > = 0> -inline void set(world_t *world, entity_t entity, T&& value, ecs_id_t id) { - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + /** Get mutable component value. + * + * @tparam T The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + template ::value > = 0> + T& get_mut() const { + T* r = try_get_mut(); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get_mut: entity does not have component (use try_get_mut)"); + return *r; + } - T& dst = *static_cast(ecs_get_mut_id(world, entity, id, NULL)); - dst = FLECS_MOV(value); + /** Get mutable component value. + * Overload for when T is not the same as the actual type, which happens + * when using pair types. + * + * @tparam T The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + template , + if_t< flecs::is_pair::value > = 0> + A& get_mut() const { + A* r = try_get_mut(); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get_mut: entity does not have component (use try_get_mut)"); + return *r; + } - ecs_modified_id(world, entity, id); -} + /** Get a mutable pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value > = 0> + A& get_mut() const { + A* r = try_get_mut(); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get_mut: entity does not have component (use try_get_mut)"); + return *r; + } -// set(const T&), T = constructible -template ::value > = 0> -inline void set(world_t *world, entity_t entity, const T& value, ecs_id_t id) { - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + /** Get a mutable pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template::value> = 0> + First& get_mut(Second second) const { + First* r = try_get_mut(second); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get_mut: entity does not have component (use try_get_mut)"); + return *r; + } - T& dst = *static_cast(ecs_get_mut_id(world, entity, id, NULL)); - dst = value; + /** Get a mutable pair. + * This operation gets the value for a pair from the entity. + * + * @tparam First The first element of the pair. + * @param constant the enum constant. + */ + template::value && !std::is_same::value > = 0> + First& get_mut(Second constant) const { + const auto& et = enum_type(this->world_); + flecs::entity_t target = et.entity(constant); + return get_mut(target); + } - ecs_modified_id(world, entity, id); -} + /** Get mutable component value (untyped). + * + * @param comp The component to get. + * @return Pointer to the component value, nullptr if the entity does not + * have the component. + */ + void* get_mut(flecs::id_t comp) const { + void *r = ecs_get_mut_id(world_, id_, comp); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get_mut: entity does not have component (use try_get_mut)"); + return r; + } + + /** Get a mutable pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * first nor the second part of the pair are components, the operation + * will fail. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + void* get_mut(flecs::entity_t first, flecs::entity_t second) const { + void *r = ecs_get_mut_id(world_, id_, ecs_pair(first, second)); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get_mut: entity does not have component (use try_get_mut)"); + return r; + } -// set(T&&), T = not constructible -template ::value > = 0> -inline void set(world_t *world, entity_t entity, T&& value, ecs_id_t id) { - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + template + auto get_mut_n() const { + flecs_static_assert(sizeof...(Ts) > 1, "get_mut_n requires at least two components"); + flecs_static_assert(sizeof...(Ts) < 9, "get_mut_n cannot fetch more than eight components"); + return typename tuple_builder::type {get_mut()...}; + } - bool is_new = false; - T& dst = *static_cast(ecs_get_mut_id(world, entity, id, &is_new)); + /** Get the second part for a pair. + * This operation gets the value for a pair from the entity. The first + * part of the pair should not be a component. + * + * @tparam Second the second element of a pair. + * @param first The first part of the pair. + */ + template + Second& get_mut_second(flecs::entity_t first) const { + Second *r = try_get_mut_second(first); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get_mut: entity does not have component (use try_get_mut)"); + return *r; + } - /* If type is not constructible get_mut should assert on new values */ - ecs_assert(!is_new, ECS_INTERNAL_ERROR, NULL); + /** Get the second part for a pair. + * This operation gets the value for a pair from the entity. The first + * part of the pair should not be a component. + * + * @tparam First The first element of the pair. + * @tparam Second the second element of a pair. + */ + template + Second* get_mut_second() const { + Second *r = try_get_mut_second(); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get_mut: entity does not have component (use try_get_mut)"); + return *r; + } - dst = FLECS_MOV(value); + /** Get enum constant for enum relationship. */ + template + Enum get_constant() const; + + /** Get target for a given pair. + * This operation returns the target for a given pair. The optional + * index can be used to iterate through targets, in case the entity has + * multiple instances for the same relationship. + * + * @tparam First The first element of the pair. + * @param index The index (0 for the first instance of the relationship). + */ + template + flecs::entity target(int32_t index = 0) const; - ecs_modified_id(world, entity, id); -} + /** Get target for a given pair. + * This operation returns the target for a given pair. The optional + * index can be used to iterate through targets, in case the entity has + * multiple instances for the same relationship. + * + * @param first The first element of the pair for which to retrieve the target. + * @param index The index (0 for the first instance of the relationship). + */ + flecs::entity target(flecs::entity_t first, int32_t index = 0) const; -// set(const T&), T = not constructible -template ::value > = 0> -inline void set(world_t *world, id_t entity, const T& value, id_t id) { - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + /** Get the target of a pair for a given relationship id. + * This operation returns the first entity that has the provided id by following + * the specified relationship. If the entity itself has the id then entity will + * be returned. If the id cannot be found on the entity or by following the + * relationship, the operation will return 0. + * + * This operation can be used to lookup, for example, which prefab is providing + * a component by specifying the IsA pair: + * + * @code + * // Is Position provided by the entity or one of its base entities? + * ecs_get_target_for_id(world, entity, EcsIsA, ecs_id(Position)) + * @endcode + * + * @param relationship The relationship to follow. + * @param id The id to lookup. + * @return The entity for which the target has been found. + */ + flecs::entity target_for(flecs::entity_t relationship, flecs::id_t id) const; - bool is_new = false; - T& dst = *static_cast(ecs_get_mut_id(world, entity, id, &is_new)); + template + flecs::entity target_for(flecs::entity_t relationship) const; - /* If type is not constructible get_mut should assert on new values */ - ecs_assert(!is_new, ECS_INTERNAL_ERROR, NULL); - dst = value; + template + flecs::entity target_for(flecs::entity_t relationship) const; - ecs_modified_id(world, entity, id); -} + /** Get depth for given relationship. + * + * @param rel The relationship. + * @return The depth. + */ + int32_t depth(flecs::entity_t rel) const { + return ecs_get_depth(world_, id_, rel); + } -// emplace for T(Args...) -template , Args...>::value || - std::is_default_constructible>::value > = 0> -inline void emplace(world_t *world, id_t entity, Args&&... args) { - id_t id = _::cpp_type::id(world); + /** Get depth for given relationship. + * + * @tparam Rel The relationship. + * @return The depth. + */ + template + int32_t depth() const { + return this->depth(_::type::id(world_)); + } - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - T& dst = *static_cast(ecs_emplace_id(world, entity, id)); + /** Get parent of entity. + * Short for target(flecs::ChildOf). + * + * @return The parent of the entity. + */ + flecs::entity parent() const; - FLECS_PLACEMENT_NEW(&dst, T{FLECS_FWD(args)...}); + /** Lookup an entity by name. + * Lookup an entity in the scope of this entity. The provided path may + * contain double colons as scope separators, for example: "Foo::Bar". + * + * @param path The name of the entity to lookup. + * @param search_path When false, only the entity's scope is searched. + * @return The found entity, or entity::null if no entity matched. + */ + flecs::entity lookup(const char *path, bool search_path = false) const; - ecs_modified_id(world, entity, id); -} + /** Check if entity has the provided entity. + * + * @param e The entity to check. + * @return True if the entity has the provided entity, false otherwise. + */ + bool has(flecs::id_t e) const { + return ecs_has_id(world_, id_, e); + } -// emplace for T(flecs::entity, Args...) -template , flecs::entity, Args...>::value > = 0> -inline void emplace(world_t *world, id_t entity, Args&&... args); + /** Check if entity has the provided component. + * + * @tparam T The component to check. + * @return True if the entity has the provided component, false otherwise. + */ + template + bool has() const { + flecs::id_t cid = _::type::id(world_); + bool result = ecs_has_id(world_, id_, cid); + if (result) { + return result; + } -// set(T&&) -template -inline void set(world_t *world, entity_t entity, A&& value) { - id_t id = _::cpp_type::id(world); - flecs::set(world, entity, FLECS_FWD(value), id); -} + if (is_enum::value) { + return ecs_has_pair(world_, id_, cid, flecs::Wildcard); + } -// set(const T&) -template -inline void set(world_t *world, entity_t entity, const A& value) { - id_t id = _::cpp_type::id(world); - flecs::set(world, entity, value, id); -} + return false; + } -struct scoped_world; + /** Check if entity has the provided enum constant. + * + * @tparam E The enum type (can be deduced). + * @param value The enum constant to check. + * @return True if the entity has the provided constant, false otherwise. + */ + template ::value > = 0> + bool has(E value) const { + auto r = _::type::id(world_); + auto o = enum_type(world_).entity(value); + ecs_assert(o, ECS_INVALID_PARAMETER, + "Constant was not found in Enum reflection data." + " Did you mean to use has() instead of has(E)?"); + return ecs_has_pair(world_, id_, r, o); + } -/** The world. - * The world is the container of all ECS data and systems. If the world is - * deleted, all data in the world will be deleted as well. - */ -struct world { - /** Create world. + /** Check if entity has the provided pair. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @return True if the entity has the provided component, false otherwise. */ - explicit world() - : m_world( ecs_init() ) - , m_owned( true ) { init_builtin_components(); } + template + bool has() const { + return this->has(_::type::id(world_)); + } - /** Create world with command line arguments. - * Currently command line arguments are not interpreted, but they may be - * used in the future to configure Flecs parameters. + /** Check if entity has the provided pair. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return True if the entity has the provided component, false otherwise. + */ + template::value > = 0> + bool has(Second second) const { + auto comp_id = _::type::id(world_); + return ecs_has_id(world_, id_, ecs_pair(comp_id, second)); + } + + /** Check if entity has the provided pair. + * + * @tparam Second The second element of the pair. + * @param first The first element of the pair. + * @return True if the entity has the provided component, false otherwise. + */ + template + bool has_second(flecs::entity_t first) const { + return this->has(first, _::type::id(world_)); + } + + /** Check if entity has the provided pair. + * + * @tparam First The first element of the pair. + * @param value The enum constant. + * @return True if the entity has the provided component, false otherwise. */ - explicit world(int argc, char *argv[]) - : m_world( ecs_init_w_args(argc, argv) ) - , m_owned( true ) { init_builtin_components(); } + template::value && !std::is_same::value > = 0> + bool has(E value) const { + const auto& et = enum_type(this->world_); + flecs::entity_t second = et.entity(value); + return has(second); + } - /** Create world from C world. + /** Check if entity has the provided pair. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return True if the entity has the provided component, false otherwise. */ - explicit world(world_t *w) - : m_world( w ) - , m_owned( false ) { } + bool has(flecs::id_t first, flecs::id_t second) const { + return ecs_has_id(world_, id_, ecs_pair(first, second)); + } - /** Not allowed to copy a world. May only take a reference. + /** Check if entity owns the provided entity. + * An entity is owned if it is not shared from a base entity. + * + * @param e The entity to check. + * @return True if the entity owns the provided entity, false otherwise. */ - world(const world& obj) = delete; - - world(world&& obj) { - m_world = obj.m_world; - m_owned = obj.m_owned; - obj.m_world = nullptr; - obj.m_owned = false; + bool owns(flecs::id_t e) const { + return ecs_owns_id(world_, id_, e); } - /* Implicit conversion to world_t* */ - operator world_t*() const { return m_world; } - - /** Not allowed to copy a world. May only take a reference. + /** Check if entity owns the provided pair. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return True if the entity owns the provided component, false otherwise. */ - world& operator=(const world& obj) = delete; - - world& operator=(world&& obj) { - this->~world(); - - m_world = obj.m_world; - m_owned = obj.m_owned; - obj.m_world = nullptr; - obj.m_owned = false; - return *this; - } - - ~world() { - if (m_owned && ecs_stage_is_async(m_world)) { - ecs_async_stage_free(m_world); - } else - if (m_owned && m_world) { - ecs_fini(m_world); - } + template + bool owns(flecs::id_t second) const { + auto comp_id = _::type::id(world_); + return owns(ecs_pair(comp_id, second)); } - /** Obtain pointer to C world object. + /** Check if entity owns the provided pair. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return True if the entity owns the provided component, false otherwise. */ - world_t* c_ptr() const { - return m_world; + bool owns(flecs::id_t first, flecs::id_t second) const { + return owns(ecs_pair(first, second)); } - /** Get last delta_time. + /** Check if entity owns the provided component. + * An component is owned if it is not shared from a base entity. + * + * @tparam T The component to check. + * @return True if the entity owns the provided component, false otherwise. */ - FLECS_FLOAT delta_time() const { - const ecs_world_info_t *stats = ecs_get_world_info(m_world); - return stats->delta_time; + template + bool owns() const { + return owns(_::type::id(world_)); } - /** Get current tick. + /** Check if entity owns the provided pair. + * An pair is owned if it is not shared from a base entity. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @return True if the entity owns the provided pair, false otherwise. */ - int32_t tick() const { - const ecs_world_info_t *stats = ecs_get_world_info(m_world); - return stats->frame_count_total; + template + bool owns() const { + return owns( + _::type::id(world_), + _::type::id(world_)); } - /** Get current simulation time. + /** Check if entity owns the provided pair. + * + * @param first The first element of the pair. + * @tparam Second The second element of the pair. + * @return True if the entity owns the provided component, false otherwise. */ - FLECS_FLOAT time() const { - const ecs_world_info_t *stats = ecs_get_world_info(m_world); - return stats->world_time_total; + template + bool owns_second(flecs::entity_t first) const { + return owns(first, _::type::id(world_)); } - /** Signal application should quit. - * After calling this operation, the next call to progress() returns false. + /** Test if id is enabled. + * + * @param id The id to test. + * @return True if enabled, false if not. */ - void quit() { - ecs_quit(m_world); + bool enabled(flecs::id_t id) const { + return ecs_is_enabled_id(world_, id_, id); } - /** Register action to be executed when world is destroyed. + /** Test if component is enabled. + * + * @tparam T The component to test. + * @return True if enabled, false if not. */ - void atfini(ecs_fini_action_t action, void *ctx) const { - ecs_atfini(m_world, action, ctx); + template + bool enabled() const { + return this->enabled(_::type::id(world_)); } - /** Test if quit() has been called. + /** Test if pair is enabled. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return True if enabled, false if not. */ - bool should_quit() { - return ecs_should_quit(m_world); + bool enabled(flecs::id_t first, flecs::id_t second) const { + return this->enabled(ecs_pair(first, second)); } - /** Begin frame. - * When an application does not use progress() to control the main loop, it - * can still use Flecs features such as FPS limiting and time measurements. - * This operation needs to be invoked whenever a new frame is about to get - * processed. - * - * Calls to frame_begin must always be followed by frame_end. - * - * The function accepts a delta_time parameter, which will get passed to - * systems. This value is also used to compute the amount of time the - * function needs to sleep to ensure it does not exceed the target_fps, when - * it is set. When 0 is provided for delta_time, the time will be measured. - * - * This function should only be ran from the main thread. + /** Test if pair is enabled. * - * @param delta_time Time elapsed since the last frame. - * @return The provided delta_time, or measured time if 0 was provided. + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return True if enabled, false if not. */ - FLECS_FLOAT frame_begin(float delta_time = 0) { - return ecs_frame_begin(m_world, delta_time); + template + bool enabled(flecs::id_t second) const { + return this->enabled(_::type::id(world_), second); } - /** End frame. - * This operation must be called at the end of the frame, and always after - * ecs_frame_begin. + /** Test if pair is enabled. * - * This function should only be ran from the main thread. + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @return True if enabled, false if not. */ - void frame_end() { - ecs_frame_end(m_world); + template + bool enabled() const { + return this->enabled(_::type::id(world_)); } - /** Begin staging. - * When an application does not use ecs_progress to control the main loop, it - * can still use Flecs features such as the defer queue. When an application - * needs to stage changes, it needs to call this function after ecs_frame_begin. - * A call to ecs_staging_begin must be followed by a call to ecs_staging_end. + flecs::entity clone(bool clone_value = true, flecs::entity_t dst_id = 0) const; + + /** Return mutable entity handle for current stage + * When an entity handle created from the world is used while the world is + * in staged mode, it will only allow for readonly operations since + * structural changes are not allowed on the world while in staged mode. * - * When staging is enabled, modifications to entities are stored to a stage. - * This ensures that arrays are not modified while iterating. Modifications are - * merged back to the "main stage" when ecs_staging_end is invoked. + * To do mutations on the entity, this operation provides a handle to the + * entity that uses the stage instead of the actual world. * - * While the world is in staging mode, no structural changes (add/remove/...) - * can be made to the world itself. Operations must be executed on a stage - * instead (see ecs_get_stage). + * Note that staged entity handles should never be stored persistently, in + * components or elsewhere. An entity handle should always point to the + * main world. * - * This function should only be ran from the main thread. + * Also note that this operation is not necessary when doing mutations on an + * entity outside of a system. It is allowed to do entity operations + * directly on the world, as long as the world is not in staged mode. * - * @return Whether world is currently staged. + * @param stage The current stage. + * @return An entity handle that allows for mutations in the current stage. */ - bool staging_begin() { - return ecs_staging_begin(m_world); - } + flecs::entity mut(const flecs::world& stage) const; - /** End staging. - * Leaves staging mode. After this operation the world may be directly mutated - * again. By default this operation also merges data back into the world, unless - * automerging was disabled explicitly. + /** Same as mut(world), but for iterator. + * This operation allows for the construction of a mutable entity handle + * from an iterator. * - * This function should only be ran from the main thread. + * @param it An iterator that contains a reference to the world or stage. + * @return An entity handle that allows for mutations in the current stage. */ - void staging_end() { - ecs_staging_end(m_world); - } + flecs::entity mut(const flecs::iter& it) const; - /** Defer operations until end of frame. - * When this operation is invoked while iterating, operations inbetween the - * defer_begin and defer_end operations are executed at the end of the frame. + /** Same as mut(world), but for entity. + * This operation allows for the construction of a mutable entity handle + * from another entity. This is useful in each() functions, which only + * provide a handle to the entity being iterated over. * - * This operation is thread safe. + * @param e Another mutable entity. + * @return An entity handle that allows for mutations in the current stage. */ - bool defer_begin() { - return ecs_defer_begin(m_world); - } + flecs::entity mut(const flecs::entity_view& e) const; - /** End block of operations to defer. - * See defer_begin. - * - * This operation is thread safe. - */ - bool defer_end() { - return ecs_defer_end(m_world); - } +# ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/entity_view.inl + * @brief JSON entity mixin. + */ - /** Test whether deferring is enabled. +/** Serialize entity to JSON. + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_json + */ +flecs::string to_json(const flecs::entity_to_json_desc_t *desc = nullptr) const { + char *json = ecs_entity_to_json(world_, id_, desc); + return flecs::string(json); +} + +# endif +# ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/entity_view.inl + * @brief Doc entity view mixin. + */ + +/** Get human readable name. + * + * @see ecs_doc_get_name() + * @see flecs::doc::get_name() + * @see flecs::entity_builder::set_doc_name() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_name() const { + return ecs_doc_get_name(world_, id_); +} + +/** Get brief description. + * + * @see ecs_doc_get_brief() + * @see flecs::doc::get_brief() + * @see flecs::entity_builder::set_doc_brief() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_brief() const { + return ecs_doc_get_brief(world_, id_); +} + +/** Get detailed description. + * + * @see ecs_doc_get_detail() + * @see flecs::doc::get_detail() + * @see flecs::entity_builder::set_doc_detail() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_detail() const { + return ecs_doc_get_detail(world_, id_); +} + +/** Get link to external documentation. + * + * @see ecs_doc_get_link() + * @see flecs::doc::get_link() + * @see flecs::entity_builder::set_doc_link() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_link() const { + return ecs_doc_get_link(world_, id_); +} + +/** Get color. + * + * @see ecs_doc_get_color() + * @see flecs::doc::get_color() + * @see flecs::entity_builder::set_doc_color() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_color() const { + return ecs_doc_get_color(world_, id_); +} + +/** Get UUID. + * + * @see ecs_doc_get_uuid() + * @see flecs::doc::get_uuid() + * @see flecs::entity_builder::set_doc_uuid() + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_doc + */ +const char* doc_uuid() const { + return ecs_doc_get_uuid(world_, id_); +} + +# endif +# ifdef FLECS_ALERTS +/** + * @file addons/cpp/mixins/alerts/entity_view.inl + * @brief Alerts entity mixin. + */ + +/** Return number of alerts for entity. + * + * @memberof flecs::entity_view + * @ingroup cpp_addons_alerts + */ +int32_t alert_count(flecs::entity_t alert = 0) const { + return ecs_get_alert_count(world_, id_, alert); +} + +# endif + +/** + * @file addons/cpp/mixins/enum/entity_view.inl + * @brief Enum entity view mixin. + */ + +/** Convert entity to enum constant. + * + * @memberof flecs::entity_view + * @ingroup cpp_entities + */ +template +E to_constant() const; + + +/** + * @file addons/cpp/mixins/event/entity_view.inl + * @brief Event entity mixin. + */ + +/** Emit event for entity. + * + * @memberof flecs::entity_view + * + * @param evt The event to emit. + */ +void emit(flecs::entity_t evt) const { + flecs::world(world_) + .event(evt) + .entity(id_) + .emit(); +} + +/** Emit event for entity. + * + * @memberof flecs::entity_view + * + * @param evt The event to emit. + */ +void emit(flecs::entity evt) const; + +/** Emit event for entity. + * + * @memberof flecs::entity_view + * + * @tparam Evt The event to emit. + */ +template ::value> = 0> +void emit() const { + this->emit(_::type::id(world_)); +} + +/** Emit event with payload for entity. + * + * @memberof flecs::entity_view + * + * @tparam Evt The event to emit. + */ +template ::value> = 0> +void emit(const Evt& payload) const { + flecs::world(world_) + .event(_::type::id(world_)) + .entity(id_) + .ctx(&payload) + .emit(); +} + + +/** Enqueue event for entity. + * + * @memberof flecs::entity_view + * + * @param evt The event to enqueue. + */ +void enqueue(flecs::entity_t evt) const { + flecs::world(world_) + .event(evt) + .entity(id_) + .enqueue(); +} + +/** Enqueue event for entity. + * + * @memberof flecs::entity_view + * + * @param evt The event to enqueue. + */ +void enqueue(flecs::entity evt) const; + +/** Enqueue event for entity. + * + * @memberof flecs::entity_view + * + * @tparam Evt The event to enqueue. + */ +template ::value> = 0> +void enqueue() const { + this->enqueue(_::type::id(world_)); +} + +/** Enqueue event with payload for entity. + * + * @memberof flecs::entity_view + * + * @tparam Evt The event to enqueue. + */ +template ::value> = 0> +void enqueue(const Evt& payload) const { + flecs::world(world_) + .event(_::type::id(world_)) + .entity(id_) + .ctx(&payload) + .enqueue(); +} + + +private: + flecs::entity set_stage(world_t *stage); +}; + +} + +/** @} */ + +/** + * @file addons/cpp/mixins/entity/builder.hpp + * @brief Entity builder. + */ + +#pragma once + +namespace flecs +{ + +/** Entity builder. + * @ingroup cpp_entities + */ +template +struct entity_builder : entity_view { + + using entity_view::entity_view; + + /** Add a component to an entity. + * To ensure the component is initialized, it should have a constructor. + * + * @tparam T the component type to add. */ - bool is_deferred() { - return ecs_is_deferred(m_world); + template + const Self& add() const { + flecs_static_assert(is_flecs_constructible::value, + "cannot default construct type: add T::T() or use emplace()"); + ecs_add_id(this->world_, this->id_, _::type::id(this->world_)); + return to_base(); } - /** Configure world to have N stages. - * This initializes N stages, which allows applications to defer operations to - * multiple isolated defer queues. This is typically used for applications with - * multiple threads, where each thread gets its own queue, and commands are - * merged when threads are synchronized. - * - * Note that set_threads() already creates the appropriate number of stages. - * The set_stages() operation is useful for applications that want to manage - * their own stages and/or threads. + /** Add pair for enum constant. + * This operation will add a pair to the entity where the first element is + * the enumeration type, and the second element the enumeration constant. + * + * The operation may be used with regular (C style) enumerations as well as + * enum classes. * - * @param stages The number of stages. + * @param value The enumeration value. */ - void set_stages(int32_t stages) const { - ecs_set_stages(m_world, stages); - } + template ::value > = 0> + const Self& add(E value) const { + flecs::entity_t first = _::type::id(this->world_); + const auto& et = enum_type(this->world_); + flecs::entity_t second = et.entity(value); - /** Get number of configured stages. - * Return number of stages set by set_stages. - * - * @return The number of stages used for threading. - */ - int32_t get_stage_count() const { - return ecs_get_stage_count(m_world); + ecs_assert(second, ECS_INVALID_PARAMETER, "Component was not found in reflection data."); + return this->add(first, second); } - /** Get current stage id. - * The stage id can be used by an application to learn about which stage it - * is using, which typically corresponds with the worker thread id. + /** Add an entity to an entity. + * Add an entity to the entity. This is typically used for tagging. * - * @return The stage id. + * @param component The component to add. */ - int32_t get_stage_id() const { - return ecs_get_stage_id(m_world); + const Self& add(id_t component) const { + ecs_add_id(this->world_, this->id_, component); + return to_base(); } - /** Enable/disable automerging for world or stage. - * When automerging is enabled, staged data will automatically be merged - * with the world when staging ends. This happens at the end of progress(), - * at a sync point or when staging_end() is called. - * - * Applications can exercise more control over when data from a stage is - * merged by disabling automerging. This requires an application to - * explicitly call merge() on the stage. - * - * When this function is invoked on the world, it sets all current stages to - * the provided value and sets the default for new stages. When this - * function is invoked on a stage, automerging is only set for that specific - * stage. + /** Add a pair. + * This operation adds a pair to the entity. * - * @param automerge Whether to enable or disable automerging. + * @param first The first element of the pair. + * @param second The second element of the pair. */ - void set_automerge(bool automerge) { - ecs_set_automerge(m_world, automerge); + const Self& add(entity_t first, entity_t second) const { + ecs_add_pair(this->world_, this->id_, first, second); + return to_base(); } - /** Merge world or stage. - * When automatic merging is disabled, an application can call this - * operation on either an individual stage, or on the world which will merge - * all stages. This operation may only be called when staging is not enabled - * (either after progress() or after staging_end()). + /** Add a pair. + * This operation adds a pair to the entity. * - * This operation may be called on an already merged stage or world. + * @tparam First The first element of the pair + * @tparam Second The second element of the pair */ - void merge() { - ecs_merge(m_world); + template + const Self& add() const { + return this->add(_::type::id(this->world_)); } - /** Get stage-specific world pointer. - * Flecs threads can safely invoke the API as long as they have a private - * context to write to, also referred to as the stage. This function returns a - * pointer to a stage, disguised as a world pointer. - * - * Note that this function does not(!) create a new world. It simply wraps the - * existing world in a thread-specific context, which the API knows how to - * unwrap. The reason the stage is returned as an ecs_world_t is so that it - * can be passed transparently to the existing API functions, vs. having to - * create a dediated API for threading. + /** Add a pair. + * This operation adds a pair to the entity. * - * @param stage_id The index of the stage to retrieve. - * @return A thread-specific pointer to the world. + * @tparam First The first element of the pair + * @param second The second element of the pair. */ - flecs::world get_stage(int32_t stage_id) const { - return flecs::world(ecs_get_stage(m_world, stage_id)); + template::value > = 0> + const Self& add(Second second) const { + flecs_static_assert(is_flecs_constructible::value, + "cannot default construct type: add T::T() or use emplace()"); + return this->add(_::type::id(this->world_), second); } - /** Create asynchronous stage. - * An asynchronous stage can be used to asynchronously queue operations for - * later merging with the world. An asynchronous stage is similar to a regular - * stage, except that it does not allow reading from the world. - * - * Asynchronous stages are never merged automatically, and must therefore be - * manually merged with the ecs_merge function. It is not necessary to call - * defer_begin or defer_end before and after enqueuing commands, as an - * asynchronous stage unconditionally defers operations. - * - * The application must ensure that no commands are added to the stage while the - * stage is being merged. - * - * An asynchronous stage must be cleaned up by ecs_async_stage_free. + /** Add a pair. + * This operation adds a pair to the entity that consists out of a tag + * combined with an enum constant. * - * @return The stage. + * @tparam First The first element of the pair + * @param constant the enum constant. */ - flecs::world async_stage() const { - auto result = flecs::world(ecs_async_stage_new(m_world)); - result.m_owned = true; - return result; + template::value && !std::is_same::value > = 0> + const Self& add(Second constant) const { + flecs_static_assert(is_flecs_constructible::value, + "cannot default construct type: add T::T() or use emplace()"); + const auto& et = enum_type(this->world_); + return this->add(et.entity(constant)); } - /** Get actual world. - * If the current object points to a stage, this operation will return the - * actual world. + /** Add a pair. + * This operation adds a pair to the entity. * - * @return The actual world. + * @param first The first element of the pair + * @tparam Second The second element of the pair */ - flecs::world get_world() const { - /* Safe cast, mutability is checked */ - return flecs::world( - m_world ? const_cast(ecs_get_world(m_world)) : nullptr); + template + const Self& add_second(flecs::entity_t first) const { + return this->add(first, _::type::id(this->world_)); } - /** Test whether the current world object is readonly. - * This function allows the code to test whether the currently used world - * object is readonly or whether it allows for writing. - * - * @return True if the world or stage is readonly. + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @param cond The condition to evaluate. + * @param component The component to add. */ - bool is_readonly() const { - return ecs_stage_is_readonly(m_world); + const Self& add_if(bool cond, flecs::id_t component) const { + if (cond) { + return this->add(component); + } else { + return this->remove(component); + } } - /** Set world context. - * Set a context value that can be accessed by anyone that has a reference - * to the world. - * - * @param ctx The world context. + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @tparam T The component to add. + * @param cond The condition to evaluate. */ - void set_context(void* ctx) const { - ecs_set_context(m_world, ctx); + template + const Self& add_if(bool cond) const { + if (cond) { + return this->add(); + } else { + return this->remove(); + } } - /** Get world context. - * - * @return The configured world context. + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @param cond The condition to evaluate. + * @param first The first element of the pair. + * @param second The second element of the pair. */ - void* get_context() const { - return ecs_get_context(m_world); + const Self& add_if(bool cond, flecs::entity_t first, flecs::entity_t second) const { + if (cond) { + return this->add(first, second); + } else { + /* If second is 0 or if relationship is exclusive, use wildcard for + * second which will remove all instances of the relationship. + * Replacing 0 with Wildcard will make it possible to use the second + * as the condition. */ + if (!second || ecs_has_id(this->world_, first, flecs::Exclusive)) { + second = flecs::Wildcard; + } + return this->remove(first, second); + } } - /** Preallocate memory for number of entities. - * This function preallocates memory for the entity index. - * - * @param entity_count Number of entities to preallocate memory for. + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @tparam First The first element of the pair + * @param cond The condition to evaluate. + * @param second The second element of the pair. */ - void dim(int32_t entity_count) const { - ecs_dim(m_world, entity_count); + template + const Self& add_if(bool cond, flecs::entity_t second) const { + return this->add_if(cond, _::type::id(this->world_), second); } - /** Set entity range. - * This function limits the range of issued entity ids between min and max. - * - * @param min Minimum entity id issued. - * @param max Maximum entity id issued. + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @tparam First The first element of the pair + * @tparam Second The second element of the pair + * @param cond The condition to evaluate. */ - void set_entity_range(entity_t min, entity_t max) const { - ecs_set_entity_range(m_world, min, max); + template + const Self& add_if(bool cond) const { + return this->add_if(cond, _::type::id(this->world_)); } - /** Enforce that operations cannot modify entities outside of range. - * This function ensures that only entities within the specified range can - * be modified. Use this function if specific parts of the code only are - * allowed to modify a certain set of entities, as could be the case for - * networked applications. - * - * @param enabled True if range check should be enabled, false if not. + /** Conditional add. + * This operation adds if condition is true, removes if condition is false. + * + * @param cond The condition to evaluate. + * @param constant The enumeration constant. */ - void enable_range_check(bool enabled) const { - ecs_enable_range_check(m_world, enabled); + template ::value > = 0> + const Self& add_if(bool cond, E constant) const { + const auto& et = enum_type(this->world_); + return this->add_if(cond, et.entity(constant)); } - /** Set current scope. + /** Shortcut for `add(IsA, entity)`. * - * @param scope The scope to set. - * @return The current scope; - * @see ecs_set_scope + * @param second The second element of the pair. */ - flecs::entity set_scope(const flecs::entity_t scope) const; + const Self& is_a(entity_t second) const { + return this->add(flecs::IsA, second); + } - /** Get current scope. + /** Shortcut for `add(IsA, entity)`. * - * @return The current scope. - * * @see ecs_get_scope - */ - flecs::entity get_scope() const; - - /** Same as set_scope but with type. - * * @see ecs_set_scope + * @tparam T the type associated with the entity. */ template - flecs::entity set_scope() const; - - /** Set search path. - */ - flecs::entity_t* set_lookup_path(const flecs::entity_t *search_path) { - return ecs_set_lookup_path(m_world, search_path); + const Self& is_a() const { + return this->add(flecs::IsA, _::type::id(this->world_)); } - /** Lookup entity by name. - * - * @param name Entity name. - */ - flecs::entity lookup(const char *name) const; - - /** Set singleton component. + /** Shortcut for `add(ChildOf, entity)`. + * + * @param second The second element of the pair. */ - template ::value > = 0> - void set(const T& value) const { - flecs::set(m_world, _::cpp_type::id(m_world), value); - } - - template ::value > = 0> - void set(T&& value) const { - flecs::set(m_world, _::cpp_type::id(m_world), - FLECS_FWD(value)); + const Self& child_of(entity_t second) const { + return this->add(flecs::ChildOf, second); } - - /** Set singleton component inside a callback. - */ - template ::value > = 0 > - void set(const Func& func); - - template - void emplace(Args&&... args) const { - flecs::emplace(m_world, _::cpp_type::id(m_world), - FLECS_FWD(args)...); - } - /** Get mut singleton component. + /** Shortcut for `add(DependsOn, entity)`. + * + * @param second The second element of the pair. */ - template - T* get_mut() const; + const Self& depends_on(entity_t second) const { + return this->add(flecs::DependsOn, second); + } - /** Mark singleton component as modified. + /** Shortcut for `add(DependsOn, entity)`. + * + * @param second The second element of the pair. */ - template - void modified() const; + template ::value> = 0> + const Self& depends_on(E second) const { + const auto& et = enum_type(this->world_); + flecs::entity_t target = et.entity(second); + return depends_on(target); + } - /** Get singleton component. - */ - template - const T* get() const; - - /** Get singleton component inside a callback. + /** Shortcut for `add(SlotOf, entity)`. + * + * @param second The second element of the pair. */ - template ::value > = 0 > - void get(const Func& func); + const Self& slot_of(entity_t second) const { + return this->add(flecs::SlotOf, second); + } - /** Test if world has singleton component. + /** Shortcut for `add(SlotOf, target(ChildOf))`. */ - template - bool has() const; + const Self& slot() const { + ecs_check(ecs_get_target(world_, id_, flecs::ChildOf, 0), + ECS_INVALID_PARAMETER, "add ChildOf pair before using slot()"); + return this->slot_of(this->target(flecs::ChildOf)); + error: + return to_base(); + } - /** Add singleton component. + /** Shortcut for `add(ChildOf, entity)`. + * + * @tparam T the type associated with the entity. */ template - void add() const; - - /** Remove singleton component. + const Self& child_of() const { + return this->child_of(_::type::id(this->world_)); + } + + /** Shortcut for `add(DependsOn, entity)`. + * + * @tparam T the type associated with the entity. */ template - void remove() const; + const Self& depends_on() const { + return this->depends_on(_::type::id(this->world_)); + } - /** Get singleton entity for type. + /** Shortcut for `add(SlotOf, entity)`. + * + * @tparam T the type associated with the entity. */ template - flecs::entity singleton(); + const Self& slot_of() const { + return this->slot_of(_::type::id(this->world_)); + } - /** Create alias for component. + /** Remove a component from an entity. * - * @tparam T to create an alias for. - * @param alias Alias for the component. - * @return Entity representing the component. + * @tparam T the type of the component to remove. */ template - flecs::entity use(const char *alias = nullptr); + const Self& remove() const { + ecs_remove_id(this->world_, this->id_, _::type::id(this->world_)); + return to_base(); + } - /** Create alias for entity. + /** Remove an entity from an entity. * - * @param name Name of the entity. - * @param alias Alias for the entity. + * @param entity The entity to remove. */ - flecs::entity use(const char *name, const char *alias = nullptr); + const Self& remove(entity_t entity) const { + ecs_remove_id(this->world_, this->id_, entity); + return to_base(); + } - /** Create alias for entity. + /** Remove a pair. + * This operation removes a pair from the entity. * - * @param entity Entity for which to create the alias. - * @param alias Alias for the entity. + * @param first The first element of the pair. + * @param second The second element of the pair. */ - void use(flecs::entity entity, const char *alias = nullptr); + const Self& remove(entity_t first, entity_t second) const { + ecs_remove_pair(this->world_, this->id_, first, second); + return to_base(); + } - /** Count entities matching a component. + /** Removes a pair. + * This operation removes a pair from the entity. * - * @param component_id The component id. + * @tparam First The first element of the pair + * @tparam Second The second element of the pair */ - int count(flecs::id_t component_id) const { - return ecs_count_id(m_world, component_id); + template + const Self& remove() const { + return this->remove(_::type::id(this->world_)); } - /** Count entities matching a pair. + /** Remove a pair. + * This operation removes the pair from the entity. * - * @param rel The relation id. - * @param obj The object id. + * @tparam First The first element of the pair + * @param second The second element of the pair. */ - int count(flecs::entity_t rel, flecs::entity_t obj) const { - return ecs_count_id(m_world, ecs_pair(rel, obj)); + template::value > = 0> + const Self& remove(Second second) const { + return this->remove(_::type::id(this->world_), second); } - /** Count entities matching a component. + /** Removes a pair. + * This operation removes a pair from the entity. * - * @tparam T The component type. + * @tparam Second The second element of the pair + * @param first The first element of the pair */ - template - int count() const { - return count(_::cpp_type::id(m_world)); + template + const Self& remove_second(flecs::entity_t first) const { + return this->remove(first, _::type::id(this->world_)); } - /** Count entities matching a pair. + /** Remove a pair. + * This operation removes the pair from the entity. + * + * @tparam First The first element of the pair + * @param constant the enum constant. + */ + template::value > = 0> + const Self& remove(Second constant) const { + const auto& et = enum_type(this->world_); + flecs::entity_t second = et.entity(constant); + return this->remove(second); + } + + /** Mark id for auto-overriding. + * When an entity inherits from a base entity (using the `IsA` relationship) + * any ids marked for auto-overriding on the base will be overridden + * automatically by the entity. * - * @tparam Rel The relation type. - * @param obj The object id. + * @param id The id to mark for overriding. */ - template - int count(flecs::entity_t obj) const { - return count(_::cpp_type::id(m_world), obj); + const Self& auto_override(flecs::id_t id) const { + return this->add(ECS_AUTO_OVERRIDE | id); } - /** Count entities matching a pair. + /** Mark pair for auto-overriding. + * @see auto_override(flecs::id_t) const * - * @tparam Rel The relation type. - * @tparam Obj The object type. + * @param first The first element of the pair. + * @param second The second element of the pair. */ - template - int count() const { - return count( - _::cpp_type::id(m_world), - _::cpp_type::id(m_world)); + const Self& auto_override(flecs::entity_t first, flecs::entity_t second) const { + return this->auto_override(ecs_pair(first, second)); } - /** Enable locking. - * - * @param enabled True if locking should be enabled, false if not. + /** Mark component for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam T The component to mark for overriding. */ - bool enable_locking(bool enabled) { - return ecs_enable_locking(m_world, enabled); + template + const Self& auto_override() const { + return this->auto_override(_::type::id(this->world_)); } - /** Lock world. + /** Mark pair for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. */ - void lock() { - ecs_lock(m_world); + template + const Self& auto_override(flecs::entity_t second) const { + return this->auto_override(_::type::id(this->world_), second); } - /** Unlock world. + /** Mark pair for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. */ - void unlock() { - ecs_unlock(m_world); + template + const Self& auto_override() const { + return this->auto_override(_::type::id(this->world_)); } - /** All entities created in function are created with id. + /** Mark pair for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam Second The second element of the pair. + * @param first The first element of the pair. */ - template - void with(id_t with_id, const Func& func) const { - ecs_id_t prev = ecs_set_with(m_world, with_id); - func(); - ecs_set_with(m_world, prev); + template + const Self& auto_override_second(flecs::entity_t first) const { + return this->auto_override(first, _::type::id(this->world_)); } - /** All entities created in function are created with type. + /** Set component, mark component for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam T The component to set and for which to add the OVERRIDE flag + * @param val The value to set. */ - template - void with(const Func& func) const { - with(this->id(), func); + template + const Self& set_auto_override(const T& val) const { + this->auto_override(); + return this->set(val); } - /** All entities created in function are created with relation. + /** Set component, mark component for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam T The component to set and for which to add the OVERRIDE flag + * @param val The value to set. */ - template - void with(const Func& func) const { - with(ecs_pair(this->id(), this->id()), func); + template + const Self& set_auto_override(T&& val) const { + this->auto_override(); + return this->set(FLECS_FWD(val)); } - /** All entities created in function are created with relation. + /** Set pair, mark component for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @param val The value to set. */ - template - void with(id_t object, const Func& func) const { - with(ecs_pair(this->id(), object), func); - } + template + const Self& set_auto_override(flecs::entity_t second, const First& val) const { + this->auto_override(second); + return this->set(second, val); + } - /** All entities created in function are created with relation. + /** Set pair, mark component for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @param val The value to set. */ - template - void with(id_t relation, id_t object, const Func& func) const { - with(ecs_pair(relation, object), func); + template + const Self& set_auto_override(flecs::entity_t second, First&& val) const { + this->auto_override(second); + return this->set(second, FLECS_FWD(val)); } - /** All entities created in function are created in scope. All operations - * called in function (such as lookup) are relative to scope. + /** Set component, mark component for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @param val The value to set. */ - template - void scope(id_t parent, const Func& func) const { - ecs_entity_t prev = ecs_set_scope(m_world, parent); - func(); - ecs_set_scope(m_world, prev); + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + const Self& set_auto_override(const A& val) const { + this->auto_override(); + return this->set(val); } - - /** Same as scope(parent, func), but with T as parent. + + /** Set component, mark component for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @param val The value to set. */ - template - void scope(const Func& func) const { - flecs::id_t parent = _::cpp_type::id(m_world); - scope(parent, func); + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + const Self& set_auto_override(A&& val) const { + this->auto_override(); + return this->set(FLECS_FWD(val)); } - /** Use provided scope for operations ran on returned world. - * Operations need to be ran in a single statement. + /** Emplace component, mark component for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam T The component to emplace and override. + * @param args The arguments to pass to the constructor of `T`. */ - flecs::scoped_world scope(id_t parent); + template + const Self& emplace_auto_override(Args&&... args) const { + this->auto_override(); - template - flecs::scoped_world scope(); + flecs::emplace(this->world_, this->id_, + _::type::id(this->world_), FLECS_FWD(args)...); - /** Delete all entities with specified id. */ - void delete_with(id_t the_id) const { - ecs_delete_with(m_world, the_id); + return to_base(); } - /** Delete all entities with specified relation. */ - void delete_with(entity_t rel, entity_t obj) const { - delete_with(ecs_pair(rel, obj)); - } + /** Emplace pair, mark pair for auto-overriding. + * @see auto_override(flecs::id_t) const + * + * @tparam First The first element of the pair to emplace and override. + * @tparam Second The second element of the pair to emplace and override. + * @param args The arguments to pass to the constructor of `Second`. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0, + typename ... Args> + const Self& emplace_auto_override(Args&&... args) const { + this->auto_override(); + + flecs::emplace(this->world_, this->id_, + ecs_pair(_::type::id(this->world_), + _::type::id(this->world_)), + FLECS_FWD(args)...); - /** Delete all entities with specified component. */ - template - void delete_with() const { - delete_with(_::cpp_type::id()); + return to_base(); } - /** Delete all entities with specified relation. */ - template - void delete_with() const { - delete_with(_::cpp_type::id(), _::cpp_type::id()); + /** Enable an entity. + * Enabled entities are matched with systems and can be searched with + * queries. + */ + const Self& enable() const { + ecs_enable(this->world_, this->id_, true); + return to_base(); } - /** Remove all instances of specified id. */ - void remove_all(id_t the_id) const { - ecs_remove_all(m_world, the_id); + /** Disable an entity. + * Disabled entities are not matched with systems and cannot be searched + * with queries, unless explicitly specified in the query expression. + */ + const Self& disable() const { + ecs_enable(this->world_, this->id_, false); + return to_base(); } - /** Remove all instances of specified relation. */ - void remove_all(entity_t rel, entity_t obj) const { - remove_all(ecs_pair(rel, obj)); + /** Enable an id. + * This sets the enabled bit for this component. If this is the first time + * the component is enabled or disabled, the bitset is added. + * + * @param id The id to enable. + * @param toggle True to enable, false to disable (default = true). + * + * @see ecs_enable_id() + */ + const Self& enable(flecs::id_t id, bool toggle = true) const { + ecs_enable_id(this->world_, this->id_, id, toggle); + return to_base(); } - /** Remove all instances of specified component. */ - template - void remove_all() const { - remove_all(_::cpp_type::id()); + /** Enable a component. + * @see enable(flecs::id_t) const + * + * @tparam T The component to enable. + */ + template + const Self& enable() const { + return this->enable(_::type::id(this->world_)); } - /** Remove all instances of specified relation. */ - template - void remove_all() const { - remove_all(_::cpp_type::id(), _::cpp_type::id()); + /** Enable a pair. + * @see enable(flecs::id_t) const + * + * @param first The first element of the pair. + * @param second The second element of the pair. + */ + const Self& enable(flecs::id_t first, flecs::id_t second) const { + return this->enable(ecs_pair(first, second)); } - /** Defer all operations called in function. If the world is already in - * deferred mode, do nothing. + /** Enable a pair. + * @see enable(flecs::id_t) const + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. */ - template - void defer(const Func& func) const { - ecs_defer_begin(m_world); - func(); - ecs_defer_end(m_world); + template + const Self& enable(flecs::id_t second) const { + return this->enable(_::type::id(world_), second); } - /** Check if entity id exists in the world. - * Ignores entity relation. - * - * @see ecs_exists + /** Enable a pair. + * @see enable(flecs::id_t) const + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. */ - bool exists(flecs::entity_t e) const { - return ecs_exists(m_world, e); + template + const Self& enable() const { + return this->enable(_::type::id(world_)); } - /** Check if entity id exists in the world. - * Ignores entity relation. + /** Disable an id. + * This sets the enabled bit for this id. If this is the first time + * the id is enabled or disabled, the bitset is added. * - * @see ecs_is_alive + * @param id The id to disable. + * + * @see ecs_enable_id() + * @see enable(flecs::id_t) const */ - bool is_alive(flecs::entity_t e) const { - return ecs_is_alive(m_world, e); + const Self& disable(flecs::id_t id) const { + return this->enable(id, false); } - /** Check if entity id is valid. - * Invalid entities cannot be used with API functions. - * - * @see ecs_is_valid + /** Disable a component. + * @see disable(flecs::id_t) const + * + * @tparam T The component to enable. */ - bool is_valid(flecs::entity_t e) const { - return ecs_is_valid(m_world, e); + template + const Self& disable() const { + return this->disable(_::type::id(world_)); } - /** Get alive entity for id. - * Returns the entity with the current generation. - * - * @see ecs_get_alive + /** Disable a pair. + * @see disable(flecs::id_t) const + * + * @param first The first element of the pair. + * @param second The second element of the pair. */ - flecs::entity get_alive(flecs::entity_t e) const; + const Self& disable(flecs::id_t first, flecs::id_t second) const { + return this->disable(ecs_pair(first, second)); + } -/* Prevent clashing with Unreal define. Unreal applications will have to use - * ecs_ensure. */ -#ifndef ensure - /** Ensures that entity with provided generation is alive. - * Ths operation will fail if an entity exists with the same id and a - * different, non-zero generation. - * - * @see ecs_ensure + /** Disable a pair. + * @see disable(flecs::id_t) const + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. */ - flecs::entity ensure(flecs::entity_t e) const; -#endif - - /* Run callback after completing frame */ - void run_post_frame(ecs_fini_action_t action, void *ctx) { - ecs_run_post_frame(m_world, action, ctx); + template + const Self& disable(flecs::id_t second) const { + return this->disable(_::type::id(world_), second); } + /** Disable a pair. + * @see disable(flecs::id_t) const + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + */ + template + const Self& disable() const { + return this->disable(_::type::id(world_)); + } -/** Get id from a type. - */ -template -flecs::id id() const; - -/** Id factory. - */ -template -flecs::id id(Args&&... args) const; - -/** Get pair id from relation, object - */ -template -flecs::id pair() const; - -/** Get pair id from relation, object - */ -template -flecs::id pair(entity_t o) const; - -/** Get pair id from relation, object - */ -flecs::id pair(entity_t r, entity_t o) const; - - -/** Register a component. - */ -template -flecs::component component(Args &&... args) const; - -/** Register a component. - */ -template -flecs::untyped_component component(Args &&... args) const; - - -/** Create an entity. - */ -template -flecs::entity entity(Args &&... args) const; - -/** Get id from an enum constant. - */ -template ::value > = 0> -flecs::entity id(E value) const; - -/** Get id from an enum constant. - */ -template ::value > = 0> -flecs::entity entity(E value) const; - -/** Create a prefab. - */ -template -flecs::entity prefab(Args &&... args) const; - -/** Create an entity that's associated with a type. - */ -template -flecs::entity entity(const char *name = nullptr) const; - -/** Create a prefab that's associated with a type. - */ -template -flecs::entity prefab(const char *name = nullptr) const; - - -/** Create a new event. - * - * @param evt The event id. - * @return Event builder. - */ -flecs::event_builder event(flecs::entity_t evt) const; - -/** Create a new event. - * - * @tparam E The event type. - * @return Event builder. - */ -template -flecs::event_builder_typed event() const; - -/** Create a term. - * - */ -template -flecs::term term(Args &&... args) const; - -/** Create a term for a (component) type. - */ -template -flecs::term term(Args &&... args) const; + const Self& set_ptr(entity_t comp, size_t size, const void *ptr) const { + ecs_set_id(this->world_, this->id_, comp, size, ptr); + return to_base(); + } -/** Create a term for a pair. - */ -template -flecs::term term(Args &&... args) const; + const Self& set_ptr(entity_t comp, const void *ptr) const { + const ecs_type_info_t *type_info = ecs_get_type_info(this->world_, comp); -/** Create a filter. - * @see ecs_filter_init - */ -template -flecs::filter filter(Args &&... args) const; + /* Can't set if it's not a component */ + ecs_assert(type_info != NULL, ECS_INVALID_PARAMETER, NULL); -/** Create a filter builder. - * @see ecs_filter_init - */ -template -flecs::filter_builder filter_builder(Args &&... args) const; + return set_ptr(comp, type_info->size, ptr); + } -/** Iterate over all entities with components in argument list of function. - * The function parameter must match the following signature: - * void(*)(T&, U&, ...) or - * void(*)(flecs::entity, T&, U&, ...) - */ -template -void each(Func&& func) const; + /** Set a component for an entity. + * This operation sets the component value. If the entity did not yet have + * the component it will be added. + * + * @tparam T The component. + * @param value The value to set. + */ + template::value> = 0 > + const Self& set(T&& value) const { + flecs::set(this->world_, this->id_, FLECS_FWD(value)); + return to_base(); + } -/** Iterate over all entities with provided component. - * The function parameter must match the following signature: - * void(*)(T&) or - * void(*)(flecs::entity, T&) - */ -template -void each(Func&& func) const; + /** Set a component for an entity. + * This operation sets the component value. If the entity did not yet have + * the component it will be added. + * + * @tparam T The component. + * @param value The value to set. + */ + template::value > = 0> + const Self& set(const T& value) const { + flecs::set(this->world_, this->id_, value); + return to_base(); + } -/** Iterate over all entities with provided (component) id. - */ -template -void each(flecs::id_t term_id, Func&& func) const; + /** Set a component for an entity. + * This operation sets the component value. If the entity did not yet have + * the component it will be added. + * + * @tparam T The component. + * @param value The value to set. + */ + template, if_not_t< + is_actual::value > = 0> + const Self& set(A&& value) const { + flecs::set(this->world_, this->id_, FLECS_FWD(value)); + return to_base(); + } + /** Set a component for an entity. + * This operation sets the component value. If the entity did not yet have + * the component it will be added. + * + * @tparam T The component. + * @param value The value to set. + */ + template, if_not_t< + is_actual::value > = 0> + const Self& set(const A& value) const { + flecs::set(this->world_, this->id_, value); + return to_base(); + } -/** Create a new trigger. - * - * @tparam Components The components to match on. - * @tparam Args Arguments passed to the constructor of flecs::trigger_builder. - * @return Trigger builder. - */ -template -flecs::trigger_builder trigger(Args &&... args) const; + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair + * @param value The value to set. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + const Self& set(A&& value) const { + flecs::set

(this->world_, this->id_, FLECS_FWD(value)); + return to_base(); + } + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair + * @param value The value to set. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + const Self& set(const A& value) const { + flecs::set

(this->world_, this->id_, value); + return to_base(); + } -/** Create a new observer. - * - * @tparam Components The components to match on. - * @tparam Args Arguments passed to the constructor of flecs::observer_builder. - * @return Observer builder. - */ -template -flecs::observer_builder observer(Args &&... args) const; + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @param value The value to set. + */ + template ::value > = 0> + const Self& set(Second second, const First& value) const { + auto first = _::type::id(this->world_); + flecs::set(this->world_, this->id_, value, + ecs_pair(first, second)); + return to_base(); + } + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @param value The value to set. + */ + template ::value > = 0> + const Self& set(Second second, First&& value) const { + auto first = _::type::id(this->world_); + flecs::set(this->world_, this->id_, FLECS_FWD(value), + ecs_pair(first, second)); + return to_base(); + } -/** Create a query. - * @see ecs_query_init - */ -template -flecs::query query(Args &&... args) const; + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @param constant The enum constant. + * @param value The value to set. + */ + template ::value > = 0> + const Self& set(Second constant, const First& value) const { + const auto& et = enum_type(this->world_); + flecs::entity_t second = et.entity(constant); + return set(second, value); + } -/** Create a subquery. - * @see ecs_query_init - */ -template -flecs::query query(flecs::query_base& parent, Args &&... args) const; + /** Set a pair for an entity. + * This operation sets the pair value, and uses Second as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam Second The second element of the pair + * @param first The first element of the pair. + * @param value The value to set. + */ + template + const Self& set_second(entity_t first, const Second& value) const { + auto second = _::type::id(this->world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); + flecs::set(this->world_, this->id_, value, + ecs_pair(first, second)); + return to_base(); + } -/** Create a query builder. - * @see ecs_query_init - */ -template -flecs::query_builder query_builder(Args &&... args) const; + /** Set a pair for an entity. + * This operation sets the pair value, and uses Second as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam Second The second element of the pair + * @param first The first element of the pair. + * @param value The value to set. + */ + template + const Self& set_second(entity_t first, Second&& value) const { + auto second = _::type::id(this->world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); + flecs::set(this->world_, this->id_, FLECS_FWD(value), + ecs_pair(first, second)); + return to_base(); + } + /** Set a pair for an entity. + * This operation sets the pair value, and uses Second as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair + * @param value The value to set. + */ + template + const Self& set_second(const Second& value) const { + flecs::set>(this->world_, this->id_, value); + return to_base(); + } -/** Create a type. - */ -template -flecs::type type(Args &&... args) const; -/** Create a type associated with a component. - */ -template -flecs::type type(const char *name = nullptr) const; + /** Assign a component for an entity. + * This operation sets the component value. If the entity did not yet have + * the component the operation will panic. + * + * @tparam T The component. + * @param value The value to set. + */ + template::value> = 0 > + const Self& assign(T&& value) const { + flecs::assign(this->world_, this->id_, FLECS_FWD(value)); + return to_base(); + } + /** Assign a component for an entity. + * This operation sets the component value. If the entity did not yet have + * the component the operation will panic. + * + * @tparam T The component. + * @param value The value to set. + */ + template::value > = 0> + const Self& assign(const T& value) const { + flecs::assign(this->world_, this->id_, value); + return to_base(); + } -# ifdef FLECS_MODULE + /** Assign a component for an entity. + * This operation sets the component value. If the entity did not yet have + * the component the operation will panic. + * + * @tparam T The component. + * @param value The value to set. + */ + template, if_not_t< + is_actual::value > = 0> + const Self& assign(A&& value) const { + flecs::assign(this->world_, this->id_, FLECS_FWD(value)); + return to_base(); + } -/** Create a module. - * - * @tparam Module module class. - * @return Module entity. - */ -template -flecs::entity module() const; + /** Assign a component for an entity. + * This operation sets the component value. If the entity did not yet have + * the component the operation will panic. + * + * @tparam T The component. + * @param value The value to set. + */ + template, if_not_t< + is_actual::value > = 0> + const Self& assign(const A& value) const { + flecs::assign(this->world_, this->id_, value); + return to_base(); + } -/** Import a module. - * - * @tparam Module module class. - * @return Module entity. - */ -template -flecs::entity import(); + /** Assign a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the component the operation will panic. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair + * @param value The value to set. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + const Self& assign(A&& value) const { + flecs::assign

(this->world_, this->id_, FLECS_FWD(value)); + return to_base(); + } -# endif -# ifdef FLECS_PIPELINE + /** Assign a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the component the operation will panic. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair + * @param value The value to set. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + const Self& assign(const A& value) const { + flecs::assign

(this->world_, this->id_, value); + return to_base(); + } -/** Create a new pipeline. - * - * @tparam Args Arguments to pass into the constructor of flecs::system. - * @return The pipeline. - */ -template -flecs::pipeline pipeline(Args &&... args) const; + /** Assign a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the component the operation will panic. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @param value The value to set. + */ + template ::value > = 0> + const Self& assign(Second second, const First& value) const { + auto first = _::type::id(this->world_); + flecs::assign(this->world_, this->id_, value, + ecs_pair(first, second)); + return to_base(); + } -/** Create a new pipeline. - * - * @tparam Pipeline Type associated with pipeline. - * @return The pipeline. - */ -template ::value > = 0> -flecs::pipeline pipeline() const; + /** Assign a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the component the operation will panic. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @param value The value to set. + */ + template ::value > = 0> + const Self& assign(Second second, First&& value) const { + auto first = _::type::id(this->world_); + flecs::assign(this->world_, this->id_, FLECS_FWD(value), + ecs_pair(first, second)); + return to_base(); + } -/** Set pipeline. - * @see ecs_set_pipeline - */ -void set_pipeline(const flecs::pipeline& pip) const; + /** Assign a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the component the operation will panic. + * + * @tparam First The first element of the pair. + * @param constant The enum constant. + * @param value The value to set. + */ + template ::value > = 0> + const Self& assign(Second constant, const First& value) const { + const auto& et = enum_type(this->world_); + flecs::entity_t second = et.entity(constant); + return assign(second, value); + } -/** Set pipeline. - * @see ecs_set_pipeline - */ -template -void set_pipeline() const; + /** Assign a pair for an entity. + * This operation sets the pair value, and uses Second as type. If the + * entity did not yet have the component the operation will panic. + * + * @tparam Second The second element of the pair + * @param first The first element of the pair. + * @param value The value to set. + */ + template + const Self& assign_second(entity_t first, const Second& value) const { + auto second = _::type::id(this->world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); + flecs::assign(this->world_, this->id_, value, + ecs_pair(first, second)); + return to_base(); + } -/** Get pipeline. - * @see ecs_get_pipeline - */ -flecs::pipeline get_pipeline() const; + /** Assign a pair for an entity. + * This operation sets the pair value, and uses Second as type. If the + * entity did not yet have the component the operation will panic. + * + * @tparam Second The second element of the pair + * @param first The first element of the pair. + * @param value The value to set. + */ + template + const Self& assign_second(entity_t first, Second&& value) const { + auto second = _::type::id(this->world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); + flecs::assign(this->world_, this->id_, FLECS_FWD(value), + ecs_pair(first, second)); + return to_base(); + } -/** Progress world one tick. - * @see ecs_progress - */ -bool progress(FLECS_FLOAT delta_time = 0.0) const; + template + const Self& assign_second(const Second& value) const { + flecs::assign>(this->world_, this->id_, value); + return to_base(); + } -/** Run pipeline. - * @see ecs_run_pipeline - */ -void run_pipeline(const flecs::pipeline& pip, FLECS_FLOAT delta_time = 0.0) const; -/** Set timescale - * @see ecs_set_time_scale - */ -void set_time_scale(FLECS_FLOAT mul) const; + /** Set 1..N components. + * This operation accepts a callback with as arguments the components to + * set. If the entity does not have all of the provided components, they + * will be added. + * + * This operation is faster than individually calling get for each component + * as it only obtains entity metadata once. When this operation is called + * while deferred, its performance is equivalent to that of calling ensure + * for each component separately. + * + * The operation will invoke modified for each component after the callback + * has been invoked. + * + * @param func The callback to invoke. + */ + template + const Self& insert(const Func& func) const; -/** Get timescale - * @see ecs_get_time_scale - */ -FLECS_FLOAT get_time_scale() const; + /** Emplace component. + * Emplace constructs a component in the storage, which prevents calling the + * destructor on the value passed into the function. + * + * @tparam T the component to emplace + * @param args The arguments to pass to the constructor of T + */ + template> + const Self& emplace(Args&&... args) const { + flecs::emplace(this->world_, this->id_, + _::type::id(this->world_), FLECS_FWD(args)...); + return to_base(); + } -/** Get tick - * @return Monotonically increasing frame count. - */ -int32_t get_tick() const; + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + const Self& emplace(Args&&... args) const { + flecs::emplace(this->world_, this->id_, + ecs_pair(_::type::id(this->world_), + _::type::id(this->world_)), + FLECS_FWD(args)...); + return to_base(); + } -/** Set target FPS - * @see ecs_set_target_fps - */ -void set_target_fps(FLECS_FLOAT target_fps) const; + template + const Self& emplace_first(flecs::entity_t second, Args&&... args) const { + auto first = _::type::id(this->world_); + flecs::emplace(this->world_, this->id_, + ecs_pair(first, second), + FLECS_FWD(args)...); + return to_base(); + } -/** Get target FPS - * @return Configured frames per second. - */ -FLECS_FLOAT get_target_fps() const; + template + const Self& emplace_second(flecs::entity_t first, Args&&... args) const { + auto second = _::type::id(this->world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); + flecs::emplace(this->world_, this->id_, + ecs_pair(first, second), + FLECS_FWD(args)...); + return to_base(); + } -/** Reset simulation clock. - * @see ecs_reset_clock - */ -void reset_clock() const; + /** Entities created in function will have the current entity. + * This operation is thread safe. + * + * @param func The function to call. + */ + template + const Self& with(const Func& func) const { + ecs_id_t prev = ecs_set_with(this->world_, this->id_); + func(); + ecs_set_with(this->world_, prev); + return to_base(); + } -/** Deactivate systems. - * @see ecs_deactivate_systems. - */ -void deactivate_systems() const; + /** Entities created in function will have `(First, this)`. + * This operation is thread safe. + * + * @tparam First The first element of the pair + * @param func The function to call. + */ + template + const Self& with(const Func& func) const { + with(_::type::id(this->world_), func); + return to_base(); + } -/** Set number of threads. - * @see ecs_set_threads - */ -void set_threads(int32_t threads) const; + /** Entities created in function will have `(first, this)`. + * This operation is thread safe. + * + * @param first The first element of the pair. + * @param func The function to call. + */ + template + const Self& with(entity_t first, const Func& func) const { + ecs_id_t prev = ecs_set_with(this->world_, + ecs_pair(first, this->id_)); + func(); + ecs_set_with(this->world_, prev); + return to_base(); + } -/** Set number of threads. - * @see ecs_get_threads - */ -int32_t get_threads() const; + /** The function will be ran with the scope set to the current entity. */ + template + const Self& scope(const Func& func) const { + ecs_entity_t prev = ecs_set_scope(this->world_, this->id_); + func(); + ecs_set_scope(this->world_, prev); + return to_base(); + } -# endif -# ifdef FLECS_SNAPSHOT + /** Return world scoped to entity */ + scoped_world scope() const { + return scoped_world(world_, id_); + } -/** Create a snapshot. - */ -template -flecs::snapshot snapshot(Args &&... args) const; + /* Set the entity name. + */ + const Self& set_name(const char *name) const { + ecs_set_name(this->world_, this->id_, name); + return to_base(); + } -# endif -# ifdef FLECS_SYSTEM + /* Set entity alias. + */ + const Self& set_alias(const char *name) const { + ecs_set_alias(this->world_, this->id_, name); + return to_base(); + } -/** Upcast entity to a system. - * The provided entity must be a system. - * - * @param e The entity. - * @return A system object. +# ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/entity_builder.inl + * @brief Doc entity builder mixin. */ -flecs::system system(flecs::entity e) const; -/** Create a new system. - * - * @tparam Components The components to match on. - * @tparam Args Arguments passed to the constructor of flecs::system_builder. - * @return System builder. +/** Set human readable name. + * This adds `(flecs.doc.Description, flecs.Name)` to the entity. + * + * @see ecs_doc_set_name() + * @see flecs::doc::set_name() + * @see flecs::entity_view::doc_name() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc */ -template -flecs::system_builder system(Args &&... args) const; - -# endif -# ifdef FLECS_RULES +const Self& set_doc_name(const char *name) const { + ecs_doc_set_name(world_, id_, name); + return to_base(); +} -/** Create a rule. - * @see ecs_rule_init +/** Set brief description. + * This adds `(flecs.doc.Description, flecs.doc.Brief)` to the entity. + * + * @see ecs_doc_set_brief() + * @see flecs::doc::set_brief() + * @see flecs::entity_view::doc_brief() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc */ -template -flecs::rule rule(Args &&... args) const; +const Self& set_doc_brief(const char *brief) const { + ecs_doc_set_brief(world_, id_, brief); + return to_base(); +} -/** Create a subrule. - * @see ecs_rule_init +/** Set detailed description. + * This adds `(flecs.doc.Description, flecs.doc.Detail)` to the entity. + * + * @see ecs_doc_set_detail() + * @see flecs::doc::set_detail() + * @see flecs::entity_view::doc_detail() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc */ -template -flecs::rule rule(flecs::rule_base& parent, Args &&... args) const; +const Self& set_doc_detail(const char *detail) const { + ecs_doc_set_detail(world_, id_, detail); + return to_base(); +} -/** Create a rule builder. - * @see ecs_rule_init +/** Set link to external documentation. + * This adds `(flecs.doc.Description, flecs.doc.Link)` to the entity. + * + * @see ecs_doc_set_link() + * @see flecs::doc::set_link() + * @see flecs::entity_view::doc_link() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc */ -template -flecs::rule_builder rule_builder(Args &&... args) const; - -# endif -# ifdef FLECS_PLECS +const Self& set_doc_link(const char *link) const { + ecs_doc_set_link(world_, id_, link); + return to_base(); +} -/** Load plecs string. - * @see ecs_plecs_from_str +/** Set doc color. + * This adds `(flecs.doc.Description, flecs.doc.Color)` to the entity. + * + * @see ecs_doc_set_color() + * @see flecs::doc::set_color() + * @see flecs::entity_view::doc_color() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc */ -int plecs_from_str(const char *name, const char *str) const { - return ecs_plecs_from_str(m_world, name, str); +const Self& set_doc_color(const char *color) const { + ecs_doc_set_color(world_, id_, color); + return to_base(); } -/** Load plecs from file. - * @see ecs_plecs_from_file +/** Set doc UUID. + * This adds `(flecs.doc.Description, flecs.doc.Uuid)` to the entity. + * + * @see ecs_doc_set_uuid() + * @see flecs::doc::set_uuid() + * @see flecs::entity_view::doc_uuid() + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_doc */ -int plecs_from_file(const char *filename) const { - return ecs_plecs_from_file(m_world, filename); +const Self& set_doc_uuid(const char *uuid) const { + ecs_doc_set_uuid(world_, id_, uuid); + return to_base(); } # endif -# ifdef FLECS_META -flecs::string to_expr(flecs::entity_t tid, const void* value) { - char *expr = ecs_ptr_to_expr(m_world, tid, value); - return flecs::string(expr); -} +# ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/entity_builder.inl + * @brief Meta entity builder mixin. + */ -template -flecs::string to_expr(const T* value) { - flecs::entity_t tid = _::cpp_type::id(m_world); - return to_expr(tid, value); -} +/** + * @memberof flecs::entity_view + * @ingroup cpp_addons_meta + * + * @{ + */ -flecs::meta::cursor cursor(flecs::entity_t tid, void *ptr) { - return flecs::meta::cursor(m_world, tid, ptr); -} +/** Make entity a unit */ +const Self& unit( + const char *symbol, + flecs::entity_t prefix = 0, + flecs::entity_t base = 0, + flecs::entity_t over = 0, + int32_t factor = 0, + int32_t power = 0) const +{ + ecs_unit_desc_t desc = {}; + desc.entity = this->id_; + desc.symbol = const_cast(symbol); /* safe, will be copied in */ + desc.base = base; + desc.over = over; + desc.prefix = prefix; + desc.translation.factor = factor; + desc.translation.power = power; + ecs_unit_init(this->world(), &desc); -template -flecs::meta::cursor cursor(void *ptr) { - flecs::entity_t tid = _::cpp_type::id(m_world); - return cursor(tid, ptr); + return to_base(); } -# endif -# ifdef FLECS_JSON - -flecs::string to_json(flecs::entity_t tid, const void* value) { - char *json = ecs_ptr_to_json(m_world, tid, value); - return flecs::string(json); -} +/** Make entity a derived unit */ +const Self& unit( + flecs::entity_t prefix = 0, + flecs::entity_t base = 0, + flecs::entity_t over = 0, + int32_t factor = 0, + int32_t power = 0) const +{ + ecs_unit_desc_t desc = {}; + desc.entity = this->id_; + desc.base = base; + desc.over = over; + desc.prefix = prefix; + desc.translation.factor = factor; + desc.translation.power = power; + ecs_unit_init(this->world(), &desc); -template -flecs::string to_json(const T* value) { - flecs::entity_t tid = _::cpp_type::id(m_world); - return to_json(tid, value); + return to_base(); } -# endif -# ifdef FLECS_APP +/** Make entity a derived unit */ +const Self& unit_prefix( + const char *symbol, + int32_t factor = 0, + int32_t power = 0) const +{ + ecs_unit_prefix_desc_t desc = {}; + desc.entity = this->id_; + desc.symbol = const_cast(symbol); /* safe, will be copied in */ + desc.translation.factor = factor; + desc.translation.power = power; + ecs_unit_prefix_init(this->world(), &desc); -flecs::app_builder app() const { - return flecs::app_builder(m_world); + return to_base(); } -# endif - -public: - void init_builtin_components(); - - world_t *m_world; - bool m_owned; -}; - -struct scoped_world : world { - scoped_world( - flecs::world_t *w, - flecs::entity_t s) : world(nullptr) - { - m_prev_scope = ecs_set_scope(w, s); - m_world = w; - m_owned = false; - } - - ~scoped_world() { - ecs_set_scope(m_world, m_prev_scope); - } - - scoped_world(const scoped_world& obj) : world(nullptr) { - m_prev_scope = obj.m_prev_scope; - m_world = obj.m_world; - m_owned = obj.m_owned; - } - - flecs::entity_t m_prev_scope; -}; - -/** Return id without generation. - * - * @see ecs_strip_generation - */ -inline flecs::id_t strip_generation(flecs::entity_t e) { - return ecs_strip_generation(e); +/** Add quantity to unit */ +const Self& quantity(flecs::entity_t quantity) const { + ecs_add_pair(this->world(), this->id(), flecs::Quantity, quantity); + return to_base(); } -/** Return entity generation. - */ -inline uint32_t get_generation(flecs::entity_t e) { - return ECS_GENERATION(e); +/** Make entity a unity prefix */ +template +const Self& quantity() const { + return this->quantity(_::type::id(this->world())); } -} // namespace flecs +/** Make entity a quantity */ +const Self& quantity() const { + ecs_add_id(this->world(), this->id(), flecs::Quantity); + return to_base(); +} +/** @} */ -namespace flecs -{ +# endif -/** Unsafe wrapper class around a column. - * This class can be used when a system does not know the type of a column at - * compile time. +# ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/entity_builder.inl + * @brief JSON entity mixin. */ -struct unsafe_column { - unsafe_column(void* array, size_t size, size_t count, bool is_shared = false) - : m_array(array) - , m_size(size) - , m_count(count) - , m_is_shared(is_shared) {} - - /** Return element in component array. - * This operator may only be used if the column is not shared. - * - * @param index Index of element. - * @return Reference to element. - */ - void* operator[](size_t index) { - ecs_assert(index < m_count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); - ecs_assert(!m_is_shared, ECS_INVALID_PARAMETER, NULL); - return ECS_OFFSET(m_array, m_size * index); - } - -protected: - void* m_array; - size_t m_size; - size_t m_count; - bool m_is_shared; -}; -/** Wrapper class around a column. +/** Set component from JSON. * - * @tparam T component type of the column. + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json */ -template -struct column { - static_assert(std::is_empty::value == false, - "invalid type for column, cannot iterate empty type"); - - /** Create column from component array. - * - * @param array Pointer to the component array. - * @param count Number of elements in component array. - * @param is_shared Is the component shared or not. - */ - column(T* array, size_t count, bool is_shared = false) - : m_array(array) - , m_count(count) - , m_is_shared(is_shared) {} - - /** Create column from iterator. - * - * @param iter Iterator object. - * @param column Index of the signature of the query being iterated over. - */ - column(iter &iter, int column); - - /** Return element in component array. - * This operator may only be used if the column is not shared. - * - * @param index Index of element. - * @return Reference to element. - */ - T& operator[](size_t index) { - ecs_assert(index < m_count, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); - ecs_assert(!index || !m_is_shared, ECS_INVALID_PARAMETER, NULL); - ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); - return m_array[index]; - } - - /** Return first element of component array. - * This operator is typically used when the column is shared. - * - * @return Reference to the first element. - */ - T& operator*() { - ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); - return *m_array; - } - - /** Return first element of component array. - * This operator is typically used when the column is shared. - * - * @return Pointer to the first element. - */ - T* operator->() { - ecs_assert(m_array != nullptr, ECS_COLUMN_INDEX_OUT_OF_RANGE, NULL); - return m_array; +const Self& set_json( + flecs::id_t e, + const char *json, + flecs::from_json_desc_t *desc = nullptr) const +{ + const flecs::type_info_t *ti = ecs_get_type_info(world_, e); + if (!ti) { + ecs_err("id is not a type"); + return to_base(); } -protected: - T* m_array; - size_t m_count; - bool m_is_shared; -}; - + flecs::entity_t type = ti->component; -//////////////////////////////////////////////////////////////////////////////// - -namespace _ { + void *ptr = ecs_ensure_id(world_, id_, e, static_cast(ti->size)); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_ptr_from_json(world_, type, ptr, json, desc); + ecs_modified_id(world_, id_, e); -//////////////////////////////////////////////////////////////////////////////// + return to_base(); +} -/** Iterate over an integer range (used to iterate over entity range). - * - * @tparam T of the iterator +/** Set pair from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json */ -template -struct range_iterator +const Self& set_json( + flecs::entity_t r, + flecs::entity_t t, + const char *json, + flecs::from_json_desc_t *desc = nullptr) const { - explicit range_iterator(T value) - : m_value(value){} - - bool operator!=(range_iterator const& other) const - { - return m_value != other.m_value; - } - - T const& operator*() const - { - return m_value; - } - - range_iterator& operator++() - { - ++m_value; - return *this; - } - -private: - T m_value; -}; - -} // namespace _ + return set_json(ecs_pair(r, t), json, desc); +} -} // namespace flecs +/** Set component from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json + */ +template +const Self& set_json( + const char *json, + flecs::from_json_desc_t *desc = nullptr) const +{ + return set_json(_::type::id(world_), json, desc); +} -namespace flecs +/** Set pair from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json + */ +template +const Self& set_json( + const char *json, + flecs::from_json_desc_t *desc = nullptr) const { + return set_json( + _::type::id(world_), + _::type::id(world_), + json, desc); +} -//////////////////////////////////////////////////////////////////////////////// +/** Set pair from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json + */ +template +const Self& set_json( + flecs::entity_t t, + const char *json, + flecs::from_json_desc_t *desc = nullptr) const +{ + return set_json( + _::type::id(world_), t, + json, desc); +} -/** Class that enables iterating over table columns. +/** Set pair from JSON. + * + * @memberof flecs::entity_builder + * @ingroup cpp_addons_json */ -struct iter { -private: - using row_iterator = _::range_iterator; - -public: - /** Construct iterator from C iterator object. - * This operation is typically not invoked directly by the user. - * - * @param it Pointer to C iterator. - */ - iter(ecs_iter_t *it) : m_iter(it) { - m_begin = 0; - m_end = static_cast(it->count); - } +template +const Self& set_json_second( + flecs::entity_t r, + const char *json, + flecs::from_json_desc_t *desc = nullptr) const +{ + return set_json( + r, _::type::id(world_), + json, desc); +} - row_iterator begin() const { - return row_iterator(m_begin); - } +# endif - row_iterator end() const { - return row_iterator(m_end); - } +/** + * @file addons/cpp/mixins/event/entity_builder.inl + * @brief Event entity mixin. + */ - flecs::entity system() const; +/** Observe event on entity + * + * @memberof flecs::entity_builder + * + * @param evt The event id. + * @param callback The observer callback. + * @return Event builder. + */ +template +const Self& observe(flecs::entity_t evt, Func&& callback) const; - flecs::entity event() const; +/** Observe event on entity + * + * @memberof flecs::entity_builder + * + * @tparam Evt The event type. + * @param callback The observer callback. + * @return Event builder. + */ +template +const Self& observe(Func&& callback) const; - flecs::id event_id() const; +/** Observe event on entity + * + * @memberof flecs::entity_builder + * + * @param callback The observer callback. + * @return Event builder. + */ +template +const Self& observe(Func&& callback) const; - flecs::world world() const; - const flecs::iter_t* c_ptr() const { - return m_iter; - } - size_t count() const { - return static_cast(m_iter->count); - } - FLECS_FLOAT delta_time() const { - return m_iter->delta_time; +protected: + const Self& to_base() const { + return *static_cast(this); } +}; - FLECS_FLOAT delta_system_time() const { - return m_iter->delta_system_time; - } +} - flecs::type type() const; - /** Is current type a module or does it contain module contents? */ - bool has_module() const { - return ecs_table_has_module(m_iter->table); - } +/** + * @defgroup cpp_entities Entities + * @ingroup cpp_core + * Entity operations. + * + * @{ + */ - /** Access self. - * 'self' is an entity that can be associated with a trigger, observer or - * system when they are created. */ - flecs::entity self() const; +namespace flecs +{ - /** Access ctx. - * ctx contains the context pointer assigned to a system. - */ - void* ctx() { - return m_iter->ctx; - } +/** Entity. + * Class with read/write operations for entities. + * + * @ingroup cpp_entities +*/ +struct entity : entity_builder +{ + entity() : entity_builder() { } - /** Access param. - * param contains the pointer passed to the param argument of system::run + /** Wrap an existing entity id. + * + * @param world The world in which the entity is created. + * @param id The entity id. */ - void* param() { - return m_iter->param; + explicit entity(const flecs::world_t *world, flecs::entity_t id) { + world_ = const_cast(world); + id_ = id; } - /** Access param. - * param contains the pointer passed to the param argument of system::run + /** Create a new entity. + * + * @param world The world in which to create the entity. */ - template - T* param() { - /* TODO: type check */ - return static_cast(m_iter->param); + explicit entity(world_t *world) + : entity_builder() + { + world_ = world; + id_ = ecs_cpp_new(world, 0, nullptr, nullptr, nullptr); } - /** Obtain mutable handle to entity being iterated over. + /** Create a named entity. * - * @param row Row being iterated over. + * @param world The world in which to create the entity. + * @param name The entity name. + * @param sep String used to indicate scoping (Foo::Bar). + * @param root_sep String used to indicate name is fully scoped (::Foo::Bar). */ - flecs::entity entity(size_t row) const; + explicit entity( + world_t *world, + const char *name, + const char *sep = "::", + const char *root_sep = "::") : entity_builder() + { + world_ = world; + + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.sep = sep; + desc.root_sep = root_sep; + id_ = ecs_entity_init(world, &desc); + } - /** Returns whether term is owned. + /** Create a named entity for parent using ChildOf hierarchy storage. * - * @param index The term index. + * @param world The world in which to create the entity. + * @param name The entity name. + * @param sep String used to indicate scoping (Foo::Bar). + * @param root_sep String used to indicate name is fully scoped (::Foo::Bar). */ - bool is_owned(int32_t index) const { - return ecs_term_is_owned(m_iter, index); + explicit entity( + world_t *world, + flecs::entity_t parent, + const char *name, + const char *sep = "::", + const char *root_sep = "::") : entity_builder() + { + world_ = world; + + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.parent = parent; + desc.sep = sep; + desc.root_sep = root_sep; + id_ = ecs_entity_init(world, &desc); } - /** Returns whether term is set. + /** Create a named entity for parent using Parent hierarchy storage. + * The specified name cannot be a scoped identifier. For example: + * - OK: "Foo" + * - Not OK: "Foo::Bar" * - * @param index The term index. + * @param world The world in which to create the entity. + * @param name The entity name (optional). */ - bool is_set(int32_t index) const { - return ecs_term_is_set(m_iter, index); + explicit entity( + world_t *world, + const flecs::Parent& parent, + const char *name = nullptr) : entity_builder() + { + world_ = world; + id_ = ecs_new_w_parent(world, parent.value, name); } - /** Returns whether term is readonly. + /** Conversion from flecs::entity_t to flecs::entity. * - * @param index The term index. + * @param id The entity_t value to convert. */ - bool is_readonly(int32_t index) const { - return ecs_term_is_readonly(m_iter, index); - } + explicit entity(entity_t id) + : entity_builder( nullptr, id ) { } + + #ifndef ensure - /** Number of terms in iteator. + /** Get mutable component value. + * This operation returns a mutable pointer to the component. If the entity + * did not yet have the component, it will be added. If a base entity had + * the component, it will be overridden, and the value of the base component + * will be copied to the entity before this function returns. + * + * @tparam T The component to get. + * @return Pointer to the component value. */ - int32_t term_count() const { - return m_iter->term_count; + template + T& ensure() const { + auto comp_id = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return *static_cast(ecs_ensure_id(world_, id_, comp_id, sizeof(T))); } - /** Size of term data type. + /** Get mutable component value (untyped). + * This operation returns a mutable pointer to the component. If the entity + * did not yet have the component, it will be added. If a base entity had + * the component, it will be overridden, and the value of the base component + * will be copied to the entity before this function returns. * - * @param index The term id. + * @param comp The component to get. + * @return Pointer to the component value. */ - size_t size(int32_t index) const { - return ecs_term_size(m_iter, index); + void* ensure(entity_t comp) const { + const flecs::type_info_t *ti = ecs_get_type_info(world_, comp); + ecs_assert(ti != nullptr && ti->size != 0, ECS_INVALID_PARAMETER, + "provided component is not a type or has size 0"); + return ecs_ensure_id(world_, id_, comp, static_cast(ti->size)); } - /** Obtain term source (0 if self) + /** Get mutable pointer for a pair. + * This operation gets the value for a pair from the entity. * - * @param index The term index. - */ - flecs::entity source(int32_t index) const; + * @tparam First The first part of the pair. + * @tparam Second the second part of the pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + A& ensure() const { + return *static_cast(ecs_ensure_id(world_, id_, ecs_pair( + _::type::id(world_), + _::type::id(world_)), sizeof(A))); + } - /** Obtain component id of term. + /** Get mutable pointer for the first element of a pair. + * This operation gets the value for a pair from the entity. * - * @param index The term index. + * @tparam First The first part of the pair. + * @param second The second element of the pair. */ - flecs::entity id(int32_t index) const; + template + First& ensure(entity_t second) const { + auto first = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return *static_cast( + ecs_ensure_id(world_, id_, ecs_pair(first, second), sizeof(First))); + } - /** Obtain pair id of term. - * This operation will fail if the term is not a pair. - * - * @param index The term index. + /** Get mutable pointer for a pair (untyped). + * This operation gets the value for a pair from the entity. If neither the + * first nor second element of the pair is a component, the operation will + * fail. + * + * @param first The first element of the pair. + * @param second The second element of the pair. */ - flecs::id pair(int32_t index) const; + void* ensure(entity_t first, entity_t second) const { + return ensure(ecs_pair(first, second)); + } - /** Convert current iterator result to string. + /** Get mutable pointer for the second element of a pair. + * This operation gets the value for a pair from the entity. + * + * @tparam Second The second element of the pair. + * @param first The first element of the pair. */ - flecs::string str() const { - char *s = ecs_iter_str(m_iter); - return flecs::string(s); + template + Second& ensure_second(entity_t first) const { + auto second = _::type::id(world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + return *static_cast( + ecs_ensure_id(world_, id_, ecs_pair(first, second), sizeof(Second))); } - /** Obtain term with const type. - * If the specified term index does not match with the provided type, the - * function will assert. + #endif + + /** Signal that component was modified. * - * @tparam T Type of the term. - * @param index The term index. - * @return The term data. + * @tparam T component that was modified. */ - template , - typename std::enable_if::value, void>::type* = nullptr> - - flecs::column term(int32_t index) const { - return get_term(index); + template + void modified() const { + auto comp_id = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + this->modified(comp_id); } - /** Obtain term with non-const type. - * If the specified term id does not match with the provided type or if - * the term is readonly, the function will assert. + /** Signal that the first element of a pair was modified. * - * @tparam T Type of the term. - * @param index The term index. - * @return The term data. + * @tparam First The first part of the pair. + * @tparam Second the second part of the pair. */ - template , - typename std::enable_if< - std::is_const::value == false, void>::type* = nullptr> - - flecs::column term(int32_t index) const { - ecs_assert(!ecs_term_is_readonly(m_iter, index), - ECS_COLUMN_ACCESS_VIOLATION, NULL); - return get_term(index); + template >> + void modified() const { + auto first = _::type::id(world_); + auto second = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + this->modified(first, second); } - /** Obtain unsafe term. - * Unsafe terms are required when a system does not know at compile time - * which component will be passed to it. + /** Signal that the first part of a pair was modified. * - * @param index The term index. + * @tparam First The first part of the pair. + * @param second The second element of the pair. */ - flecs::unsafe_column term(int32_t index) const { - return get_unsafe_term(index); + template + void modified(entity_t second) const { + auto first = _::type::id(world_); + ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, + "operation invalid for empty type"); + this->modified(first, second); } - /** Obtain owned term. - * Same as iter::term, but ensures that term is owned. + /** Signal that a pair has modified (untyped). + * If neither the first or second element of the pair are a component, the + * operation will fail. * - * @tparam T of the term. - * @param index The term index. - * @return The term data. + * @param first The first element of the pair. + * @param second The second element of the pair. */ - template > - flecs::column term_owned(int32_t index) const { - ecs_assert(!!ecs_term_is_owned(m_iter, index), ECS_COLUMN_IS_SHARED, NULL); - return this->term(index); + void modified(entity_t first, entity_t second) const { + this->modified(ecs_pair(first, second)); } - /** Obtain shared term. - * Same as iter::term, but ensures that term is shared. + /** Signal that component was modified. * - * @tparam T of the term. - * @param index The term index. - * @return The component term. + * @param comp component that was modified. */ - template > - const T& term_shared(int32_t index) const { - ecs_assert( - ecs_term_id(m_iter, index) == - _::cpp_type::id(m_iter->world), - ECS_COLUMN_TYPE_MISMATCH, NULL); - - ecs_assert(!ecs_term_is_owned(m_iter, index), - ECS_COLUMN_IS_NOT_SHARED, NULL); - - return *static_cast(ecs_term_w_size(m_iter, sizeof(A), index)); + void modified(entity_t comp) const { + ecs_modified_id(world_, id_, comp); } - /** Obtain the total number of tables the iterator will iterate over. + /** Get reference to component specified by id. + * A reference allows for quick and safe access to a component value, and is + * a faster alternative to repeatedly calling 'get' for the same component. + * + * The method accepts a component id argument, which can be used to create a + * ref to a component that is different from the provided type. This allows + * for creating a base type ref that points to a derived type: + * + * @code + * flecs::ref r = e.get_ref(world.id()); + * @endcode + * + * If the provided component id is not binary compatible with the specified + * type, the behavior is undefined. + * + * @tparam T component for which to get a reference. + * @return The reference. */ - int32_t table_count() const { - return m_iter->table_count; + template ::value > = 0> + ref get_ref_w_id(flecs::id_t component) const { + _::type::id(world_); // ensure type is registered + return ref(world_, id_, component); } - /** Obtain untyped pointer to table column. + /** Get reference to component. + * A reference allows for quick and safe access to a component value, and is + * a faster alternative to repeatedly calling 'get' for the same component. * - * @param column Id of table column (corresponds with location in table type). - * @return Pointer to table column. + * @tparam T component for which to get a reference. + * @return The reference. */ - void* table_column(int32_t column) const { - return ecs_iter_column_w_size(m_iter, 0, column); + template ::value > = 0> + ref get_ref() const { + return ref(world_, id_, _::type::id(world_)); } - /** Obtain typed pointer to table column. - * If the table does not contain a column with the specified type, the - * function will assert. + /** Get reference to component. + * Overload for when T is not the same as the actual type, which happens + * when using pair types. + * A reference allows for quick and safe access to a component value, and is + * a faster alternative to repeatedly calling 'get' for the same component. * - * @tparam T Type of the table column. + * @tparam T component for which to get a reference. + * @return The reference. */ - template > - flecs::column table_column() const { - auto col = ecs_iter_find_column(m_iter, _::cpp_type::id()); - ecs_assert(col != -1, ECS_INVALID_PARAMETER, NULL); - - return flecs::column(static_cast(ecs_iter_column_w_size(m_iter, - sizeof(A), col)), static_cast(m_iter->count), false); + template , if_t< flecs::is_pair::value > = 0> + ref get_ref() const { + return ref(world_, id_, + ecs_pair(_::type::id(world_), + _::type::id(world_))); } - template - flecs::column table_column(flecs::id_t obj) const { - auto col = ecs_iter_find_column(m_iter, - ecs_pair(_::cpp_type::id(), obj)); - ecs_assert(col != -1, ECS_INVALID_PARAMETER, NULL); - return flecs::column(static_cast(ecs_iter_column_w_size(m_iter, - sizeof(T), col)), static_cast(m_iter->count), false); + template , + typename A = actual_type_t

> + ref get_ref() const { + return ref(world_, id_, + ecs_pair(_::type::id(world_), _::type::id(world_))); } - /** Check if the current table has changed since the last iteration. - * Can only be used when iterating queries and/or systems. */ - bool changed() { - return ecs_query_changed(nullptr, m_iter); + template + ref get_ref(flecs::entity_t second) const { + auto first = _::type::id(world_); + return ref(world_, id_, ecs_pair(first, second)); } - /** Skip current table. - * This indicates to the query that the data in the current table is not - * modified. By default, iterating a table with a query will mark the - * iterated components as dirty if they are annotated with InOut or Out. - * - * When this operation is invoked, the components of the current table will - * not be marked dirty. */ - void skip() { - ecs_query_skip(m_iter); + untyped_ref get_ref(flecs::id_t component) const { + return untyped_ref(world_, id_, component); + } + + untyped_ref get_ref(flecs::id_t first, flecs::id_t second) const { + return untyped_ref(world_, id_, ecs_pair(first, second)); + } + + template + ref get_ref_second(flecs::entity_t first) const { + auto second = _::type::id(world_); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, + ECS_INVALID_PARAMETER, "pair is not a component"); + ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, + ECS_INVALID_PARAMETER, "type of pair is not Second"); + return ref(world_, id_, ecs_pair(first, second)); } -#ifdef FLECS_RULES - /** Get value of variable by id. - * Get value of a query variable for current result. + /** Clear an entity. + * This operation removes all components from an entity without recycling + * the entity id. + * + * @see ecs_clear() */ - flecs::entity get_var(int var_id) const; + void clear() const { + ecs_clear(world_, id_); + } - /** Get value of variable by name. - * Get value of a query variable for current result. + /** Delete an entity. + * Entities have to be deleted explicitly, and are not deleted when the + * entity object goes out of scope. + * + * @see ecs_delete() */ - flecs::entity get_var(const char *name) const; -#endif - -private: - /* Get term, check if correct type is used */ - template > - flecs::column get_term(int32_t index) const { + void destruct() const { + ecs_delete(world_, id_); + } -#ifndef FLECS_NDEBUG - ecs_entity_t term_id = ecs_term_id(m_iter, index); - ecs_assert(term_id & ECS_PAIR || term_id & ECS_SWITCH || - term_id & ECS_CASE || - term_id == _::cpp_type::id(m_iter->world), - ECS_COLUMN_TYPE_MISMATCH, NULL); -#endif + /** Create child */ + template + flecs::entity child(flecs::entity_t r = flecs::ChildOf, Args&&... args) { + flecs::world world(world_); + return world.entity(FLECS_FWD(args)...).add(r, id_); + } - size_t count; - bool is_shared = !ecs_term_is_owned(m_iter, index); + template + flecs::entity child(Args&&... args) { + flecs::world world(world_); + return world.entity(FLECS_FWD(args)...).add(_::type::id(world_), id_); + } - /* If a shared column is retrieved with 'column', there will only be a - * single value. Ensure that the application does not accidentally read - * out of bounds. */ - if (is_shared) { - count = 1; - } else { - /* If column is owned, there will be as many values as there are - * entities. */ - count = static_cast(m_iter->count); - } - - return flecs::column( - static_cast(ecs_term_w_size(m_iter, sizeof(A), index)), - count, is_shared); + /** Set child order. + * Changes the order of children as returned by entity::children(). Only + * applicableo to entities with the flecs::OrderedChildren trait. + * + * @see ecs_set_child_order() + */ + void set_child_order(flecs::entity_t *children, int32_t child_count) const { + ecs_set_child_order(world_, id_, children, child_count); } - flecs::unsafe_column get_unsafe_term(int32_t index) const { - size_t count; - size_t size = ecs_term_size(m_iter, index); - bool is_shared = !ecs_term_is_owned(m_iter, index); + /** Return entity as entity_view. + * This returns an entity_view instance for the entity which is a readonly + * version of the entity class. + * + * This is similar to a regular upcast, except that this method ensures that + * the entity_view instance is instantiated with a world vs. a stage, which + * a regular upcast does not guarantee. + */ + flecs::entity_view view() const { + return flecs::entity_view( + const_cast(ecs_get_world(world_)), id_); + } - /* If a shared column is retrieved with 'column', there will only be a - * single value. Ensure that the application does not accidentally read - * out of bounds. */ - if (is_shared) { - count = 1; - } else { - /* If column is owned, there will be as many values as there are - * entities. */ - count = static_cast(m_iter->count); - } + /** Entity id 0. + * This function is useful when the API must provide an entity that + * belongs to a world, but the entity id is 0. + * + * @param world The world. + */ + static + flecs::entity null(const flecs::world_t *world) { + flecs::entity result; + result.world_ = const_cast(world); + return result; + } - return flecs::unsafe_column( - ecs_term_w_size(m_iter, 0, index), size, count, is_shared); - } + static + flecs::entity null() { + return flecs::entity(); + } + +# ifdef FLECS_JSON + +/** Deserialize entity to JSON. + * + * @memberof flecs::entity + * @ingroup cpp_addons_json + */ +const char* from_json(const char *json) { + return ecs_entity_from_json(world_, id_, json, nullptr); +} - flecs::iter_t *m_iter; - std::size_t m_begin; - std::size_t m_end; +# endif }; } // namespace flecs +/** @} */ + +/** + * @file addons/cpp/delegate.hpp + * @brief Wrappers around C++ functions that provide callbacks for C APIs. + */ + +#pragma once + +#include // std::declval + namespace flecs { -template -struct ref { - ref() - : m_world( nullptr ) - , m_entity( 0 ) - , m_ref() { } - - ref(world_t *world, entity_t entity) - : m_world( world ) - , m_entity( entity ) - , m_ref() - { - auto comp_id = _::cpp_type::id(world); - - ecs_assert(_::cpp_type::size() != 0, - ECS_INVALID_PARAMETER, NULL); +namespace _ +{ - ecs_get_ref_id( - m_world, &m_ref, m_entity, comp_id); +// Binding ctx for component hooks +struct component_binding_ctx { + void *on_add = nullptr; + void *on_remove = nullptr; + void *on_set = nullptr; + void *on_replace = nullptr; + ecs_ctx_free_t free_on_add = nullptr; + ecs_ctx_free_t free_on_remove = nullptr; + ecs_ctx_free_t free_on_set = nullptr; + ecs_ctx_free_t free_on_replace = nullptr; + + ~component_binding_ctx() { + if (on_add && free_on_add) { + free_on_add(on_add); + } + if (on_remove && free_on_remove) { + free_on_remove(on_remove); + } + if (on_set && free_on_set) { + free_on_set(on_set); + } + if (on_replace && free_on_replace) { + free_on_replace(on_replace); + } } +}; + +// Utility to convert template argument pack to array of term ptrs +struct field_ptr { + void *ptr = nullptr; + int8_t index = 0; + bool is_ref = false; + bool is_row = false; +}; - const T* operator->() { - const T* result = static_cast(ecs_get_ref_id( - m_world, &m_ref, m_entity, _::cpp_type::id(m_world))); +template +struct field_ptrs { + using array = flecs::array<_::field_ptr, sizeof...(Components)>; - ecs_assert(result != NULL, ECS_INVALID_PARAMETER, NULL); + void populate(const ecs_iter_t *iter) { + populate_impl(iter, std::index_sequence_for{}); + } - return result; + void populate_self(const ecs_iter_t *iter) { + populate_self_impl(iter, std::index_sequence_for{}); } - const T* get() { - if (m_entity) { - ecs_get_ref_id( - m_world, &m_ref, m_entity, _::cpp_type::id(m_world)); + array fields_; + +private: + template + void populate_field(const ecs_iter_t *iter, size_t index) { + using A = remove_pointer_t>; + if constexpr (!is_empty_v) { + if (iter->row_fields & (1llu << index)) { + /* Need to fetch the value with ecs_field_at() */ + fields_[index].is_row = true; + fields_[index].is_ref = true; + fields_[index].index = static_cast(index); + } else { + fields_[index].ptr = ecs_field_w_size(iter, sizeof(A), + static_cast(index)); + fields_[index].is_ref = iter->sources[index] != 0; + } } + } - return static_cast(m_ref.ptr); + template + void populate_self_field(const ecs_iter_t *iter, size_t index) { + (void)iter; (void)index; + + using A = remove_pointer_t>; + if constexpr (!is_empty_v) { + fields_[index].ptr = ecs_field_w_size(iter, sizeof(A), + static_cast(index)); + fields_[index].is_ref = false; + } } - flecs::entity entity() const; + template + void populate_impl(const ecs_iter_t *iter, std::index_sequence) { + (void)iter; + (populate_field(iter, Is), ...); + } -private: - world_t *m_world; - entity_t m_entity; - flecs::ref_t m_ref; + template + void populate_self_impl(const ecs_iter_t *iter, std::index_sequence) { + (void)iter; + (populate_self_field(iter, Is), ...); + } }; -} +struct delegate { }; -#pragma once +// Template that figures out from the template parameters of a query/system +// how to pass the value to the each callback +template +struct each_field { }; -#pragma once +// Base class +struct each_column_base { + each_column_base(const _::field_ptr& field, size_t row) + : field_(field), row_(row) { + } -namespace flecs +protected: + const _::field_ptr& field_; + size_t row_; +}; + +// If type is not a pointer, return a reference to the type (default case) +template +struct each_field::value && + !is_empty>::value && is_actual::value > > + : each_column_base { + each_field(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } -/** Entity view class - * This class provides readonly access to entities. Using this class to store - * entities in components ensures valid handles, as this class will always store - * the actual world vs. a stage. The constructors of this class will never - * create a new entity. - * - * To obtain a mutable handle to the entity, use the "mut" function. - */ -struct entity_view : public id { + T& get_row() { + return static_cast(this->field_.ptr)[this->row_]; + } +}; - entity_view() : flecs::id() { } +// If argument type is not the same as actual component type, return by value. +// This requires that the actual type can be converted to the type. +// A typical scenario where this happens is when using flecs::pair types. +template +struct each_field::value && + !is_empty>::value && !is_actual::value> > + : each_column_base +{ + each_field(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } - /** Wrap an existing entity id. - * - * @param world The world in which the entity is created. - * @param id The entity id. - */ - explicit entity_view(flecs::world_t *world, flecs::id_t id) - : flecs::id(world - ? const_cast(ecs_get_world(world)) - : nullptr - , id ) { } + T get_row() { + return static_cast*>(this->field_.ptr)[this->row_]; + } +}; - /** Implicit conversion from flecs::entity_t to flecs::entity_view. */ - entity_view(entity_t id) - : flecs::id( nullptr, id ) { } +// If type is empty (indicating a tag) the query will pass a nullptr. To avoid +// returning nullptr to reference arguments, return a temporary value. +template +struct each_field>::value && + !is_pointer::value > > + : each_column_base +{ + each_field(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } - /** Get entity id. - * @return The integer entity id. - */ - entity_t id() const { - return m_id; + T get_row() { + return actual_type_t(); } +}; - /** Check is entity is valid. - * - * @return True if the entity is alive, false otherwise. - */ - bool is_valid() const { - return m_world && ecs_is_valid(m_world, m_id); - } - - explicit operator bool() const { - return is_valid(); - } +// If type is a pointer (indicating an optional value) don't index with row if +// the field is not set. +template +struct each_field::value && + !is_empty>::value > > + : each_column_base +{ + each_field(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } - /** Check is entity is alive. - * - * @return True if the entity is alive, false otherwise. - */ - bool is_alive() const { - return m_world && ecs_is_alive(m_world, m_id); + actual_type_t get_row() { + if (this->field_.ptr) { + return &static_cast>(this->field_.ptr)[this->row_]; + } else { + // optional argument doesn't have a value + return nullptr; + } } +}; - /** Return the entity name. - * - * @return The entity name. - */ - flecs::string_view name() const { - return flecs::string_view(ecs_get_name(m_world, m_id)); - } +// If the query contains component references to other entities, check if the +// current argument is one. +template +struct each_ref_field : public each_field { + using A = remove_pointer_t>; - /** Return the entity symbol. - * - * @return The entity symbol. - */ - flecs::string_view symbol() const { - return flecs::string_view(ecs_get_symbol(m_world, m_id)); - } + each_ref_field(const flecs::iter_t *iter, _::field_ptr& field, size_t row) + : each_field(iter, field, row) { - /** Return the entity path. - * - * @return The hierarchical entity path. - */ - flecs::string path(const char *sep = "::", const char *init_sep = "::") const { - char *path = ecs_get_path_w_sep(m_world, 0, m_id, sep, init_sep); - return flecs::string(path); - } + if (field.is_ref) { + // If this is a reference, set the row to 0 as a ref always is a + // single value, not an array. This prevents the application from + // having to do an if-check on whether the column is owned. + // + // This check only happens when the current table being iterated + // over caused the query to match a reference. The check is + // performed once per iterated table. + this->row_ = 0; + } - bool enabled() { - return !ecs_has_id(m_world, m_id, flecs::Disabled); + if (field.is_row) { + field.ptr = ecs_field_at_w_size(iter, sizeof(A), field.index, + static_cast(row)); + } } +}; - /** Return the type. - * - * @return Returns the entity type. - */ - flecs::type type() const; +// Type that handles passing components to each callbacks +template +struct each_delegate : public delegate { + using Terms = typename field_ptrs::array; - /** Return the table. - * - * @return Returns the entity type. - */ - flecs::table_t* table() const { - return ecs_get_table(m_world, m_id); - } + template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> + explicit each_delegate(Func&& func) noexcept + : func_(FLECS_MOV(func)) { } - /** Iterate (component) ids of an entity. - * The function parameter must match the following signature: - * void(*)(flecs::id id) - * - * @param func The function invoked for each id. - */ - template - void each(const Func& func) const; + explicit each_delegate(const Func& func) noexcept + : func_(func) { } - /** Iterate matching relation ids of an entity. - * The function parameter must match the following signature: - * void(*)(flecs::id id) - * - * @param func The function invoked for each id. - */ - template - void each(flecs::id_t rel, flecs::id_t obj, const Func& func) const; + // Invoke object directly. This operation is useful when the calling + // function has just constructed the delegate, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + field_ptrs terms; - /** Iterate objects for a given relationship. - * The function parameter must match the following signature: - * void(*)(flecs::entity object) - * - * @param rel The relationship for which to iterate the objects. - * @param func The function invoked for each object. - */ - template - void each(const flecs::entity_view& rel, const Func& func) const; + iter->flags |= EcsIterCppEach; - /** Iterate objects for a given relationship. - * The function parameter must match the following signature: - * void(*)(flecs::entity object) - * - * @tparam Rel The relationship for which to iterate the objects. - * @param func The function invoked for each object. - */ - template - void each(const Func& func) const { - return each(_::cpp_type::id(m_world), func); + if (iter->ref_fields | iter->up_fields) { + terms.populate(iter); + invoke_unpack< each_ref_field >(iter, func_, 0, terms.fields_); + } else { + terms.populate_self(iter); + invoke_unpack< each_field >(iter, func_, 0, terms.fields_); + } } - /** Iterate children for entity. - * The function parameter must match the following signature: - * void(*)(flecs::entity object) - * - * @param func The function invoked for each child. - */ - template - void children(Func&& func) const { - flecs::world world(m_world); - - ecs_filter_t f; - ecs_filter_desc_t desc = {}; - desc.terms[0].id = ecs_pair(flecs::ChildOf, m_id); - desc.terms[1].id = flecs::Prefab; - desc.terms[1].oper = EcsOptional; - ecs_filter_init(m_world, &f, &desc); - - ecs_iter_t it = ecs_filter_iter(m_world, &f); - while (ecs_filter_next(&it)) { - _::each_invoker(FLECS_MOV(func)).invoke(&it); + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + auto self = static_cast(iter->callback_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->invoke(iter); + } + + // Static function that can be used as callback for systems/triggers. + // Different from run() in that it loops the iterator. + static void run_each(ecs_iter_t *iter) { + auto self = static_cast(iter->run_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + while (iter->next(iter)) { + self->invoke(iter); } } - /** Get component value. - * - * @tparam T The component to get. - * @return Pointer to the component value, nullptr if the entity does not - * have the component. - */ - template ::value > = 0> - const T* get() const { - auto comp_id = _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - return static_cast(ecs_get_id(m_world, m_id, comp_id)); + // Create instance of delegate + static each_delegate* make(const Func& func) { + return FLECS_NEW(each_delegate)(func); } - /** Get component value. - * Overload for when T is not the same as the actual type, which happens - * when using pair types. - * - * @tparam T The component to get. - * @return Pointer to the component value, nullptr if the entity does not - * have the component. - */ - template , - if_t< flecs::is_pair::value > = 0> - const A* get() const { - auto comp_id = _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - return static_cast(ecs_get_id(m_world, m_id, comp_id)); + // Function that can be used as callback to free delegate + static void destruct(void *obj) { + _::free_obj(obj); + } + + // Static function to call for component on_add hook + static void run_add(ecs_iter_t *iter) { + component_binding_ctx *ctx = reinterpret_cast( + iter->callback_ctx); + iter->callback_ctx = ctx->on_add; + run(iter); } - /** Get a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam R the relation type. - * @tparam O the object type. - */ - template , - typename A = actual_type_t

, if_not_t< flecs::is_pair::value > = 0> - const A* get() const { - return this->get

(); + // Static function to call for component on_remove hook + static void run_remove(ecs_iter_t *iter) { + component_binding_ctx *ctx = reinterpret_cast( + iter->callback_ctx); + iter->callback_ctx = ctx->on_remove; + run(iter); } - /** Get a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam R the relation type. - * @param object the object. - */ - template::value> = 0> - const R* get(O object) const { - auto comp_id = _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - return static_cast( - ecs_get_id(m_world, m_id, ecs_pair(comp_id, object))); + // Static function to call for component on_set hook + static void run_set(ecs_iter_t *iter) { + component_binding_ctx *ctx = reinterpret_cast( + iter->callback_ctx); + iter->callback_ctx = ctx->on_set; + run(iter); } - /** Get a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam R the relation type. - * @param constant the enum constant. - */ - template::value> = 0> - const R* get(O constant) const { - const auto& et = enum_type(this->m_world); - flecs::entity_t object = et.entity(constant); - return get(object); + // Static function to call for component on_replace hook + static void run_replace(ecs_iter_t *iter) { + component_binding_ctx *ctx = reinterpret_cast( + iter->callback_ctx); + iter->callback_ctx = ctx->on_replace; + run(iter); } - /** Get component value (untyped). - * - * @param comp The component to get. - * @return Pointer to the component value, nullptr if the entity does not - * have the component. - */ - const void* get(flecs::id_t comp) const { - return ecs_get_id(m_world, m_id, comp); +private: + // func(flecs::entity, Components...) + template class ColumnType, + typename... Args, + typename Fn = Func, + decltype(std::declval()( + std::declval(), + std::declval > >().get_row()...), 0) = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t i, Args... comps) + { + ecs_assert(iter->entities != nullptr, ECS_INVALID_PARAMETER, + "query does not return entities ($this variable is not populated)"); + func(flecs::entity(iter->world, iter->entities[i]), + (ColumnType< remove_reference_t >(iter, comps, i) + .get_row())...); } - /** Get a pair (untyped). - * This operation gets the value for a pair from the entity. If neither the - * relation nor the object part of the pair are components, the operation - * will fail. - * - * @param relation the relation. - * @param object the object. - */ - const void* get(flecs::entity_t relation, flecs::entity_t object) const { - return ecs_get_id(m_world, m_id, ecs_pair(relation, object)); + // func(flecs::iter&, size_t row, Components...) + template class ColumnType, + typename... Args, + typename Fn = Func, + decltype(std::declval()( + std::declval(), + std::declval(), + std::declval > >().get_row()...), 0) = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t i, Args... comps) + { + flecs::iter it(iter); + func(it, i, (ColumnType< remove_reference_t >(iter, comps, i) + .get_row())...); } - /** Get 1..N components. - * This operation accepts a callback with as arguments the components to - * retrieve. The callback will only be invoked when the entity has all - * the components. - * - * This operation is faster than individually calling get for each component - * as it only obtains entity metadata once. - * - * @param func The callback to invoke. - * @return True if the entity has all components, false if not. - */ - template ::value > = 0> - bool get(const Func& func) const; + // func(Components...) + template class ColumnType, + typename... Args, + typename Fn = Func, + decltype(std::declval()( + std::declval > >().get_row()...), 0) = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t i, Args... comps) + { + func((ColumnType< remove_reference_t >(iter, comps, i) + .get_row())...); + } - /** Get enum constant. - * - * @tparam T The enum type for which to get the constant - * @return Constant entity if found, 0 entity if not. - */ - template ::value > = 0> - const T* get() const; + template class ColumnType, + typename... Args, if_t< + sizeof...(Components) == sizeof...(Args)> = 0> + static void invoke_unpack( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + ECS_TABLE_LOCK(iter->world, iter->table); - /** Get the object part from a pair. - * This operation gets the value for a pair from the entity. The relation - * part of the pair should not be a component. - * - * @tparam O the object type. - * @param relation the relation. - */ - template - const O* get_w_object(flecs::entity_t relation) const { - auto comp_id = _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - return static_cast( - ecs_get_id(m_world, m_id, ecs_pair(relation, comp_id))); - } + size_t count = static_cast(iter->count); + if (count == 0 && !iter->table) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } - /** Get the object part from a pair. - * This operation gets the value for a pair from the entity. The relation - * part of the pair should not be a component. - * - * @tparam R the relation type. - * @tparam O the object type. - */ - template - const O* get_w_object() const { - return get>(); + for (size_t i = 0; i < count; i ++) { + invoke_callback(iter, func, i, comps...); + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); } - /** Get object for a given relation. - * This operation returns the object for a given relation. The optional - * index can be used to iterate through objects, in case the entity has - * multiple instances for the same relation. - * - * @tparam R The relation for which to retrieve the object. - * @param index The index (0 for the first instance of the relation). - */ - template - flecs::entity get_object(int32_t index = 0) const; + template class ColumnType, + typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> + static void invoke_unpack(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Args... comps) + { + invoke_unpack( + iter, func, index + 1, columns, comps..., columns[index]); + } - /** Get object for a given relation. - * This operation returns the object for a given relation. The optional - * index can be used to iterate through objects, in case the entity has - * multiple instances for the same relation. - * - * @param relation The relation for which to retrieve the object. - * @param index The index (0 for the first instance of the relation). - */ - flecs::entity get_object(flecs::entity_t relation, int32_t index = 0) const; +public: + Func func_; +}; - /** Get the object of a relation for a given id. - * This operation returns the first entity that has the provided id by following - * the specified relationship. If the entity itself has the id then entity will - * be returned. If the id cannot be found on the entity or by following the - * relation, the operation will return 0. - * - * This operation can be used to lookup, for example, which prefab is providing - * a component by specifying the IsA relation: - * - * // Is Position provided by the entity or one of its base entities? - * ecs_get_object_for_id(world, entity, EcsIsA, ecs_id(Position)) - * - * @param relation The relationship to follow. - * @param id The id to lookup. - * @return The entity for which the object has been found. - */ - flecs::entity get_object_for(flecs::entity_t relation, flecs::id_t id) const; +template +struct find_delegate : public delegate { + using Terms = typename field_ptrs::array; - template - flecs::entity get_object_for(flecs::entity_t relation) const; + template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> + explicit find_delegate(Func&& func) noexcept + : func_(FLECS_MOV(func)) { } - template - flecs::entity get_object_for(flecs::entity_t relation) const; - - /** Lookup an entity by name. - * Lookup an entity in the scope of this entity. The provided path may - * contain double colons as scope separators, for example: "Foo::Bar". - * - * @param path The name of the entity to lookup. - * @return The found entity, or entity::null if no entity matched. - */ - flecs::entity lookup(const char *path) const; + explicit find_delegate(const Func& func) noexcept + : func_(func) { } - /** Check if entity has the provided entity. - * - * @param e The entity to check. - * @return True if the entity has the provided entity, false otherwise. - */ - bool has(flecs::id_t e) const { - return ecs_has_id(m_world, m_id, e); - } + // Invoke object directly. This operation is useful when the calling + // function has just constructed the delegate, such as what happens when + // iterating a query. + flecs::entity invoke(ecs_iter_t *iter) const { + field_ptrs terms; - /** Check if entity has the provided component. - * - * @tparam T The component to check. - * @return True if the entity has the provided component, false otherwise. - */ - template - bool has() const { - flecs::id_t cid = _::cpp_type::id(m_world); - bool result = ecs_has_id(m_world, m_id, cid); - if (result) { - return result; + iter->flags |= EcsIterCppEach; + + if (iter->ref_fields | iter->up_fields) { + terms.populate(iter); + return invoke_callback< each_ref_field >(iter, func_, 0, terms.fields_); + } else { + terms.populate_self(iter); + return invoke_callback< each_field >(iter, func_, 0, terms.fields_); } + } - if (is_enum::value) { - return ecs_has_pair(m_world, m_id, cid, flecs::Wildcard); +private: + // Number of function arguments is one more than number of components, pass + // entity as argument. + template class ColumnType, + typename... Args, + typename Fn = Func, + if_t = 0, + decltype(bool(std::declval()( + std::declval(), + std::declval > >().get_row()...))) = true> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + ECS_TABLE_LOCK(iter->world, iter->table); + + ecs_world_t *world = iter->world; + size_t count = static_cast(iter->count); + flecs::entity result; + + for (size_t i = 0; i < count; i ++) { + if (func(flecs::entity(world, iter->entities[i]), + (ColumnType< remove_reference_t >(iter, comps, i) + .get_row())...)) + { + result = flecs::entity(world, iter->entities[i]); + break; + } } - return false; - } + ECS_TABLE_UNLOCK(iter->world, iter->table); - /** Check if entity has the provided enum constant. - * - * @tparam E The enum type (can be deduced). - * @param value The enum constant to check. - * @return True if the entity has the provided constant, false otherwise. - */ - template ::value > = 0> - bool has(E value) const { - auto r = _::cpp_type::id(m_world); - auto o = enum_type(m_world).entity(value); - return ecs_has_pair(m_world, m_id, r, o); + return result; } - /** Check if entity has the provided pair. - * - * @tparam Relation The relation type. - * @tparam Object The object type. - * @return True if the entity has the provided component, false otherwise. - */ - template - bool has() const { - return this->has(_::cpp_type::id(m_world)); - } + // Number of function arguments is two more than number of components, pass + // iter + index as argument. + template class ColumnType, + typename... Args, + typename Fn = Func, + if_t = 0, + decltype(bool(std::declval()( + std::declval(), + std::declval(), + std::declval > >().get_row()...))) = true> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + size_t count = static_cast(iter->count); + if (count == 0) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } - /** Check if entity has the provided pair. - * - * @tparam R The relation type. - * @param object The object. - * @return True if the entity has the provided component, false otherwise. - */ - template::value > = 0> - bool has(O object) const { - auto comp_id = _::cpp_type::id(m_world); - return ecs_has_id(m_world, m_id, ecs_pair(comp_id, object)); + flecs::iter it(iter); + flecs::entity result; + + ECS_TABLE_LOCK(iter->world, iter->table); + + for (size_t i = 0; i < count; i ++) { + if (func(it, i, + (ColumnType< remove_reference_t >(iter, comps, i) + .get_row())...)) + { + result = flecs::entity(iter->world, iter->entities[i]); + break; + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + + return result; } - /** Check if entity has the provided pair. - * - * @tparam R The relation type. - * @param value The enum constant. - * @return True if the entity has the provided component, false otherwise. - */ - template::value > = 0> - bool has(E value) const { - const auto& et = enum_type(this->m_world); - flecs::entity_t object = et.entity(value); - return has(object); + // Number of function arguments is equal to number of components, no entity + template class ColumnType, + typename... Args, + typename Fn = Func, + if_t = 0, + decltype(bool(std::declval()( + std::declval > >().get_row()...))) = true> + static flecs::entity invoke_callback( + ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) + { + size_t count = static_cast(iter->count); + if (count == 0) { + // If query has no This terms, count can be 0. Since each does not + // have an entity parameter, just pass through components + count = 1; + } + + flecs::iter it(iter); + flecs::entity result; + + ECS_TABLE_LOCK(iter->world, iter->table); + + for (size_t i = 0; i < count; i ++) { + if (func( + (ColumnType< remove_reference_t >(iter, comps, i) + .get_row())...)) + { + result = flecs::entity(iter->world, iter->entities[i]); + break; + } + } + + ECS_TABLE_UNLOCK(iter->world, iter->table); + + return result; } - /** Check if entity has the provided pair. - * - * @param relation The relation. - * @param object The object. - * @return True if the entity has the provided component, false otherwise. - */ - bool has(flecs::id_t relation, flecs::id_t object) const { - return ecs_has_id(m_world, m_id, ecs_pair(relation, object)); + template class ColumnType, + typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> + static flecs::entity invoke_callback(ecs_iter_t *iter, const Func& func, + size_t index, Terms& columns, Args... comps) + { + return invoke_callback( + iter, func, index + 1, columns, comps..., columns[index]); } - /** Check if entity has the provided pair. - * - * @tparam Object The object type. - * @param relation The relation. - * @return True if the entity has the provided component, false otherwise. - */ - template - bool has_w_object(flecs::id_t relation) const { - auto comp_id = _::cpp_type::id(m_world); - return ecs_has_id(m_world, m_id, ecs_pair(relation, comp_id)); + Func func_; +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke a system iterate action +//////////////////////////////////////////////////////////////////////////////// + +template +struct run_delegate : delegate { + template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> + explicit run_delegate(Func&& func) noexcept + : func_(FLECS_MOV(func)) { } + + explicit run_delegate(const Func& func) noexcept + : func_(func) { } + + // Invoke object directly. This operation is useful when the calling + // function has just constructed the delegate, such as what happens when + // iterating a query. + void invoke(ecs_iter_t *iter) const { + flecs::iter it(iter); + iter->flags &= ~EcsIterIsValid; + func_(it); } - /** Check if entity owns the provided entity. - * An entity is owned if it is not shared from a base entity. - * - * @param e The entity to check. - * @return True if the entity owns the provided entity, false otherwise. - */ - bool owns(flecs::id_t e) const { - return ecs_owns_id(m_world, m_id, e); + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + auto self = static_cast(iter->run_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->invoke(iter); } - /** Check if entity owns the provided pair. - * - * @tparam Relation The relation type. - * @param object The object. - * @return True if the entity owns the provided component, false otherwise. - */ - template - bool owns(flecs::id_t object) const { - auto comp_id = _::cpp_type::id(m_world); - return owns(ecs_pair(comp_id, object)); + Func func_; +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Utility class to invoke an entity observer delegate +//////////////////////////////////////////////////////////////////////////////// + +template +struct entity_observer_delegate : delegate { + explicit entity_observer_delegate(Func&& func) noexcept + : func_(FLECS_MOV(func)) { } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + invoke(iter); } - /** Check if entity owns the provided pair. - * - * @param relation The relation. - * @param object The object. - * @return True if the entity owns the provided component, false otherwise. - */ - bool owns(flecs::id_t relation, flecs::id_t object) const { - return owns(ecs_pair(relation, object)); +private: + template ()(std::declval()), 0) = 0> + static void invoke(ecs_iter_t *iter) { + auto self = static_cast(iter->callback_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->func_(flecs::entity(iter->world, ecs_field_src(iter, 0))); } - /** Check if entity owns the provided component. - * An component is owned if it is not shared from a base entity. - * - * @tparam T The component to check. - * @return True if the entity owns the provided component, false otherwise. - */ - template - bool owns() const { - return owns(_::cpp_type::id(m_world)); + template ()(), 0) = 0> + static void invoke(ecs_iter_t *iter) { + auto self = static_cast(iter->callback_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + self->func_(); } - /** Check if entity has the provided switch. - * - * @param sw The switch to check. - * @return True if the entity has the provided switch, false otherwise. - */ - bool has_switch(const flecs::type& sw) const; + Func func_; +}; - template - bool has_switch() const { - return ecs_has_id(m_world, m_id, - flecs::Switch | _::cpp_type::id(m_world)); +template +struct entity_payload_observer_delegate : delegate { + explicit entity_payload_observer_delegate(Func&& func) noexcept + : func_(FLECS_MOV(func)) { } + + // Static function that can be used as callback for systems/triggers + static void run(ecs_iter_t *iter) { + invoke(iter); } - /** Check if entity has the provided case. - * - * @param sw_case The case to check. - * @return True if the entity has the provided case, false otherwise. - */ - bool has_case(flecs::id_t sw_case) const { - return ecs_has_id(m_world, m_id, flecs::Case | sw_case); +private: + template ()( + std::declval()), 0) = 0> + static void invoke(ecs_iter_t *iter) { + auto self = static_cast( + iter->callback_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + ecs_assert(iter->param != nullptr, ECS_INVALID_OPERATION, + "entity observer invoked without payload"); + + Event *data = static_cast(iter->param); + self->func_(*data); } - template - bool has_case() const { - return this->has_case(_::cpp_type::id(m_world)); + template ()( + std::declval(), + std::declval()), 0) = 0> + static void invoke(ecs_iter_t *iter) { + auto self = static_cast( + iter->callback_ctx); + ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); + ecs_assert(iter->param != nullptr, ECS_INVALID_OPERATION, + "entity observer invoked without payload"); + + Event *data = static_cast(iter->param); + self->func_(flecs::entity(iter->world, ecs_field_src(iter, 0)), *data); } - /** Get case for switch. - * - * @param sw The switch for which to obtain the case. - * @return The entity representing the case. - */ - flecs::entity get_case(flecs::id_t sw) const; + Func func_; +}; - /** Get case for switch. - * - * @tparam T The switch type for which to obtain the case. - * @return The entity representing the case. - */ - template - flecs::entity get_case() const; - /** Get case for switch. - * - * @param sw The switch for which to obtain the case. - * @return The entity representing the case. - */ - flecs::entity get_case(const flecs::type& sw) const; +//////////////////////////////////////////////////////////////////////////////// +//// Utility to invoke callback on entity if it has components in signature +//////////////////////////////////////////////////////////////////////////////// - /** Test if component is enabled. - * - * @tparam T The component to test. - * @return True if the component is enabled, false if it has been disabled. - */ - template - bool is_enabled() { - return ecs_is_component_enabled_w_id( - m_world, m_id, _::cpp_type::id(m_world)); - } +template +struct entity_with_delegate_impl; - /** Test if component is enabled. - * - * @param e The component to test. - * @return True if the component is enabled, false if it has been disabled. - */ - bool is_enabled(const flecs::entity_view& e) { - return ecs_is_component_enabled_w_id( - m_world, m_id, e); - } +template +struct entity_with_delegate_impl> { + using ColumnArray = flecs::array; + using ArrayType = flecs::array; + using DummyArray = flecs::array; + using IdArray = flecs::array; - /** Get current delta time. - * Convenience function so system implementations can get delta_time, even - * if they are using the .each() function. - * - * @return Current delta_time. - */ - FLECS_FLOAT delta_time() const { - const ecs_world_info_t *stats = ecs_get_world_info(m_world); - return stats->delta_time; + static constexpr bool const_args() { + return (is_const_v> && ...); } - flecs::entity clone(bool clone_value = true, flecs::entity_t dst_id = 0) const; - - /** Return mutable entity handle for current stage - * When an entity handle created from the world is used while the world is - * in staged mode, it will only allow for readonly operations since - * structural changes are not allowed on the world while in staged mode. - * - * To do mutations on the entity, this operation provides a handle to the - * entity that uses the stage instead of the actual world. - * - * Note that staged entity handles should never be stored persistently, in - * components or elsewhere. An entity handle should always point to the - * main world. - * - * Also note that this operation is not necessary when doing mutations on an - * entity outside of a system. It is allowed to do entity operations - * directly on the world, as long as the world is not in staged mode. - * - * @param stage The current stage. - * @return An entity handle that allows for mutations in the current stage. - */ - flecs::entity mut(const flecs::world& stage) const; + static + bool get_ptrs(world_t *world, flecs::entity_t e, const ecs_record_t *r, ecs_table_t *table, + ArrayType& ptrs) + { + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - /** Same as mut(world), but for iterator. - * This operation allows for the construction of a mutable entity handle - * from an iterator. - * - * @param it An iterator that contains a reference to the world or stage. - * @return An entity handle that allows for mutations in the current stage. - */ - flecs::entity mut(const flecs::iter& it) const; + /* table_index_of needs real world */ + const flecs::world_t *real_world = ecs_get_world(world); - /** Same as mut(world), but for entity. - * This operation allows for the construction of a mutable entity handle - * from another entity. This is useful in each() functions, which only - * provide a handle to the entity being iterated over. - * - * @param e Another mutable entity. - * @return An entity handle that allows for mutations in the current stage. - */ - flecs::entity mut(const flecs::entity_view& e) const; + IdArray ids ({ + _::type().id(world)... + }); -# ifdef FLECS_JSON + /* Get column indices for components */ + ColumnArray columns ({ + ecs_table_get_column_index(real_world, table, + _::type().id(world))... + }); -flecs::string to_json(const flecs::entity_to_json_desc_t *desc = nullptr) { - char *json = ecs_entity_to_json(m_world, m_id, desc); - return flecs::string(json); -} + /* Get pointers for columns for entity */ + size_t i = 0; + for (int32_t column : columns) { + if (column == -1) { + /* Component could be sparse */ + void *ptr = ecs_get_mut_id(world, e, ids[i]); + if (!ptr) { + return false; + } -# endif -# ifdef FLECS_DOC + ptrs[i ++] = ptr; + continue; + } -const char* doc_name() { - return ecs_doc_get_name(m_world, m_id); -} + ptrs[i ++] = ecs_record_get_by_column(r, column, 0); + } -const char* doc_brief() { - return ecs_doc_get_brief(m_world, m_id); -} + return true; + } -const char* doc_detail() { - return ecs_doc_get_detail(m_world, m_id); -} + static bool ensure_ptrs(world_t *world, ecs_entity_t e, ArrayType& ptrs) { + /* Get pointers w/ensure */ + size_t i = 0; + DummyArray dummy ({ + (ptrs[i ++] = ecs_ensure_id(world, e, + _::type().id(world), sizeof(Args)), 0)... + }); -const char* doc_link() { - return ecs_doc_get_link(m_world, m_id); -} + return true; + } -# endif + template + static bool invoke_read(world_t *world, entity_t e, const Func& func) { + const ecs_record_t *r = ecs_read_begin(world, e); + if (!r) { + return false; + } -private: - flecs::entity set_stage(world_t *stage); -}; + ecs_table_t *table = r->table; + if (!table) { + return false; + } -} + ArrayType ptrs; + bool has_components = get_ptrs(world, e, r, table, ptrs); + if (has_components) { + invoke_callback(func, 0, ptrs); + } -#pragma once + ecs_read_end(r); -namespace flecs -{ + return has_components; + } -template -struct entity_builder : entity_view { + template + static bool invoke_write(world_t *world, entity_t e, const Func& func) { + ecs_record_t *r = ecs_write_begin(world, e); + if (!r) { + return false; + } - using entity_view::entity_view; + ecs_table_t *table = r->table; + if (!table) { + return false; + } - /** Add a component to an entity. - * To ensure the component is initialized, it should have a constructor. - * - * @tparam T the component type to add. - */ - template - Self& add() { - flecs_static_assert(is_flecs_constructible::value, - "cannot default construct type: add T::T() or use emplace()"); - ecs_add_id(this->m_world, this->m_id, _::cpp_type::id(this->m_world)); - return to_base(); - } + ArrayType ptrs; + bool has_components = get_ptrs(world, e, r, table, ptrs); + if (has_components) { + invoke_callback(func, 0, ptrs); + } - /** Add an entity to an entity. - * Add an entity to the entity. This is typically used for tagging. - * - * @param component The component to add. - */ - Self& add(id_t component) { - ecs_add_id(this->m_world, this->m_id, component); - return to_base(); - } + ecs_write_end(r); - /** Add a pair. - * This operation adds a pair to the entity. - * - * @param relation The relation. - * @param object The object. - */ - Self& add(entity_t relation, entity_t object) { - ecs_add_pair(this->m_world, this->m_id, relation, object); - return to_base(); + return has_components; } - /** Add a pair. - * This operation adds a pair to the entity. - * - * @tparam R The relation type - * @tparam O The object type. - */ - template - Self& add() { - return this->add(_::cpp_type::id(this->m_world)); + template + static bool invoke_get(world_t *world, entity_t e, const Func& func) { + if constexpr (const_args()) { + return invoke_read(world, e, func); + } else { + return invoke_write(world, e, func); + } } - /** Add a pair. - * This operation adds a pair to the entity. - * - * @tparam R The relation type - * @param object The object. - */ - template::value > = 0> - Self& add(O object) { - flecs_static_assert(is_flecs_constructible::value, - "cannot default construct type: add T::T() or use emplace()"); - return this->add(_::cpp_type::id(this->m_world), object); + // Utility for storing id in array in pack expansion + static size_t store_added(IdArray& added, size_t elem, ecs_table_t *prev, + ecs_table_t *next, id_t id) + { + // Array should only contain ids for components that are actually added, + // so check if the prev and next tables are different. + if (prev != next) { + added[elem] = id; + elem ++; + } + return elem; } - /** Add a pair. - * This operation adds a pair to the entity that consists out of a tag - * combined with an enum constant. - * - * @tparam R The relation type - * @param constant the enum constant. - */ - template::value > = 0> - Self& add(O constant) { - flecs_static_assert(is_flecs_constructible::value, - "cannot default construct type: add T::T() or use emplace()"); - const auto& et = enum_type(this->m_world); - flecs::entity_t object = et.entity(constant); - return this->add(object); - } + struct InvokeCtx { + InvokeCtx(flecs::table_t *table_arg) : table(table_arg) { } + flecs::table_t *table; + size_t component_count = 0; + IdArray added = {}; + }; - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @tparam T The component to add. - * @param cond The condition to evaluate. - */ - template - Self& add_if(bool cond) { - if (cond) { - return this->add(); + static int invoke_add( + flecs::world& w, + flecs::entity_t entity, + flecs::id_t component_id, + InvokeCtx& ctx) + { + ecs_table_diff_t diff; + flecs::table_t *next = flecs_table_traverse_add( + w, ctx.table, &component_id, &diff); + if (next != ctx.table) { + ctx.added[ctx.component_count] = component_id; + ctx.component_count ++; } else { - return this->remove(); + if (diff.added_flags & EcsTableHasDontFragment) { + w.entity(entity).add(component_id); + + ctx.added[ctx.component_count] = component_id; + ctx.component_count ++; + } } + + ctx.table = next; + + return 0; } - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @param cond The condition to evaluate. - * @param component The component to add. - */ - Self& add_if(bool cond, flecs::id_t component) { - if (cond) { - return this->add(component); - } else { - return this->remove(component); - } - } + template + static bool invoke_ensure( + world_t *world, + entity_t id, + const Func& func) + { + flecs::world w(world); + + ArrayType ptrs; + ecs_table_t *table = NULL; + + // When not deferred take the fast path. + if (!w.is_deferred()) { + // Bit of low level code so we only do at most one table move & one + // entity lookup for the entire operation. + + // Make sure the object is not a stage. Operations on a stage are + // only allowed when the stage is in deferred mode, which is when + // the world is in readonly mode. + ecs_assert(!w.is_stage(), ECS_INVALID_PARAMETER, NULL); + + // Find table for entity + ecs_record_t *r = ecs_record_find(world, id); + if (r) { + table = r->table; + } + + // Iterate components, only store added component ids in added array + InvokeCtx ctx(table); + DummyArray dummy_before ({ ( + invoke_add(w, id, w.id(), ctx) + )... }); + + (void)dummy_before; + + // If table is different, move entity straight to it + if (table != ctx.table) { + ecs_type_t ids; + ids.array = ctx.added.ptr(); + ids.count = static_cast(ctx.component_count); + ecs_commit(world, id, r, ctx.table, &ids, NULL); + table = ctx.table; + } + + if (!get_ptrs(w, id, r, table, ptrs)) { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @tparam R The relation type - * @tparam O The object type. - * @param cond The condition to evaluate. - */ - template - Self& add_if(bool cond) { - if (cond) { - return this->add(); - } else { - return this->remove(); - } - } + ECS_TABLE_LOCK(world, table); - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @tparam R The relation type - * @param cond The condition to evaluate. - * @param object The relation object. - */ - template - Self& add_if(bool cond, flecs::entity_t object) { - if (cond) { - return this->add(object); + // When deferred, obtain pointers with regular ensure } else { - return this->remove(object); + ensure_ptrs(world, id, ptrs); } - } - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @param cond The condition to evaluate. - * @param relation The relation. - * @param object The relation object. - */ - Self& add_if(bool cond, flecs::entity_t relation, flecs::entity_t object) { - if (cond) { - return this->add(relation, object); - } else { - return this->remove(relation, object); + invoke_callback(func, 0, ptrs); + + if (!w.is_deferred()) { + ECS_TABLE_UNLOCK(world, table); } - } - /** Shortcut for add(IsA, obj). - * - * @param object The object. - */ - Self& is_a(entity_t object) { - return this->add(flecs::IsA, object); - } + // Call modified on each component + DummyArray dummy_after ({ + ( ecs_modified_id(world, id, w.id()), 0)... + }); + (void)dummy_after; - /** Shortcut for add(IsA, obj). - * - * @tparam T the type associated with the O. - */ - template - Self& is_a() { - return this->add(flecs::IsA, _::cpp_type::id(this->m_world)); - } + return true; + } - /** Shortcut for add(ChildOf, obj). - * - * @param object The object. - */ - Self& child_of(entity_t object) { - return this->add(flecs::ChildOf, object); +private: + template = 0> + static void invoke_callback( + const Func& f, size_t, ArrayType&, TArgs&& ... comps) + { + f(*static_cast::type*>(comps)...); } - /** Shortcut for add(ChildOf, obj). - * - * @tparam T the type associated with the O. - */ - template - Self& child_of() { - return this->add(flecs::ChildOf, _::cpp_type::id(this->m_world)); - } - - /** Add a pair with O type. - * This operation adds a pair to the entity. The R part of the pair - * should not be a component. - * - * @param relation The relation. - * @tparam O The object type. - */ - template - Self& add_w_object(entity_t relation) { - flecs_static_assert(is_flecs_constructible::value, - "cannot default construct type: add T::T() or use emplace()"); - return this->add(relation, _::cpp_type::id(this->m_world)); + template = 0> + static void invoke_callback(const Func& f, size_t arg, ArrayType& ptrs, + TArgs&& ... comps) + { + invoke_callback(f, arg + 1, ptrs, comps..., ptrs[arg]); } +}; - /** Remove a component from an entity. - * - * @tparam T the type of the component to remove. - */ - template ::value > = 0> - Self& remove() { - ecs_remove_id(this->m_world, this->m_id, _::cpp_type::id(this->m_world)); - return to_base(); - } +template +struct entity_with_delegate { + static_assert(function_traits::value, "type is not callable"); +}; - /** Remove an entity from an entity. - * - * @param entity The entity to remove. - */ - Self& remove(entity_t entity) { - ecs_remove_id(this->m_world, this->m_id, entity); - return to_base(); - } +template +struct entity_with_delegate::value > > + : entity_with_delegate_impl< arg_list_t > +{ + static_assert(function_traits::arity > 0, + "function must have at least one argument"); +}; - /** Remove a pair. - * This operation removes a pair from the entity. - * - * @param relation The relation. - * @param object The object. - */ - Self& remove(entity_t relation, entity_t object) { - ecs_remove_pair(this->m_world, this->m_id, relation, object); - return to_base(); - } +} // namespace _ - /** Removes a pair. - * This operation removes a pair from the entity. - * - * @tparam R The relation type - * @tparam O The object type. - */ - template - Self& remove() { - return this->remove(_::cpp_type::id(this->m_world)); - } +// Experimental: allows using the each delegate for use cases outside of flecs +template +using delegate = _::each_delegate::type, Args...>; - /** Remove a pair. - * This operation adds a pair to the entity. - * - * @tparam R The relation type - * @param object The object. - */ - template::value > = 0> - Self& remove(O object) { - return this->remove(_::cpp_type::id(this->m_world), object); - } +} // namespace flecs - /** Remove a pair. - * This operation adds a pair to the entity. - * - * @tparam R The relation type - * @param constant the enum constant. - */ - template::value > = 0> - Self& remove(O constant) { - const auto& et = enum_type(this->m_world); - flecs::entity_t object = et.entity(constant); - return this->remove(object); - } +/** + * @file addons/cpp/component.hpp + * @brief Registering/obtaining info from components. + */ - /** Removes a pair with O type. - * This operation removes a pair from the entity. - * - * @param relation The relation. - * @tparam O The object type. - */ - template - Self& remove_w_object(entity_t relation) { - return this->remove(relation, _::cpp_type::id(this->m_world)); - } +#pragma once - /** Add owned flag for component (forces ownership when instantiating) - * - * @param entity The entity for which to add the OVERRIDE flag - */ - Self& override(entity_t entity) { - ecs_add_id(this->m_world, this->m_id, ECS_OVERRIDE | entity); - return to_base(); - } +/** + * @defgroup cpp_components Components + * @ingroup cpp_core + * Registering and working with components. + * + * @{ + */ - /** Add owned flag for component (forces ownership when instantiating) - * - * @tparam T The component for which to add the OVERRIDE flag - */ - template - Self& override() { - ecs_add_id(this->m_world, this->m_id, ECS_OVERRIDE | _::cpp_type::id(this->m_world)); - return to_base(); - } +namespace flecs { - /** Set value, add owned flag. - * - * @tparam T The component to set and for which to add the OVERRIDE flag - */ - template - Self& set_override(T&& val) { - this->override(); - this->set(FLECS_FWD(val)); - return to_base(); - } +namespace _ { - /** Set value, add owned flag. - * - * @tparam T The component to set and for which to add the OVERRIDE flag - */ - template - Self& set_override(const T& val) { - this->override(); - this->set(val); - return to_base(); - } +// Translate a typename into a language-agnostic identifier. This allows for +// registration of components/modules across language boundaries. +template +inline const char* symbol_name() { + static const size_t len = ECS_FUNC_TYPE_LEN(const char*, symbol_name, ECS_FUNC_NAME); + static char result[len + 1] = {}; + static const char* cppSymbolName = ecs_cpp_get_symbol_name(result, type_name(), len); + return cppSymbolName; +} - /** Add a switch to an entity by id. - * The switch entity must be a type, that is it must have the EcsType - * component. Entities created with flecs::type are valid here. - * - * @param sw The switch entity id to add. - */ - Self& add_switch(entity_t sw) { - ecs_add_id(this->m_world, this->m_id, ECS_SWITCH | sw); - return to_base(); - } +template <> inline const char* symbol_name() { + return "u8"; +} +template <> inline const char* symbol_name() { + return "u16"; +} +template <> inline const char* symbol_name() { + return "u32"; +} +template <> inline const char* symbol_name() { + return "u64"; +} +template <> inline const char* symbol_name() { + return "i8"; +} +template <> inline const char* symbol_name() { + return "i16"; +} +template <> inline const char* symbol_name() { + return "i32"; +} +template <> inline const char* symbol_name() { + return "i64"; +} +template <> inline const char* symbol_name() { + return "f32"; +} +template <> inline const char* symbol_name() { + return "f64"; +} - /** Add a switch to an entity by C++ type. - * The C++ type must be associated with a switch type. - * - * @tparam T The switch to add. - */ - template - Self& add_switch() { - ecs_add_id(this->m_world, this->m_id, - ECS_SWITCH | _::cpp_type::id()); - return to_base(); +// If type is trivial, don't register lifecycle actions. While the functions +// that obtain the lifecycle callback do detect whether the callback is required +// adding a special case for trivial types eases the burden a bit on the +// compiler as it reduces the number of templates to evaluate. +template +void register_lifecycle_actions( + ecs_world_t *world, + ecs_entity_t component) +{ + (void)world; (void)component; + if constexpr (!std::is_trivial::value) { + // If the component is non-trivial, register component lifecycle actions. + // Depending on the type not all callbacks may be available. + ecs_type_hooks_t cl{}; + cl.ctor = ctor(cl.flags); + cl.dtor = dtor(cl.flags); + + cl.copy = copy(cl.flags); + cl.copy_ctor = copy_ctor(cl.flags); + cl.move = move(cl.flags); + cl.move_ctor = move_ctor(cl.flags); + + cl.ctor_move_dtor = ctor_move_dtor(cl.flags); + cl.move_dtor = move_dtor(cl.flags); + + cl.flags &= ECS_TYPE_HOOKS_ILLEGAL; + ecs_set_hooks_id(world, component, &cl); + + if (cl.flags & (ECS_TYPE_HOOK_MOVE_ILLEGAL|ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL)) + { + ecs_add_id(world, component, flecs::Sparse); + } } +} - /** Add a switch to an entity. - * Any instance of flecs::type can be used as a switch. - * - * @param sw The switch to add. - */ - Self& add_switch(const flecs::type& sw); +template +struct type_impl { + static_assert(is_pointer::value == false, + "pointer types are not allowed for components"); - /** Remove a switch from an entity by id. - * - * @param sw The switch to remove. - */ - Self& remove_switch(entity_t sw) { - ecs_remove_id(this->m_world, this->m_id, ECS_SWITCH | sw); - return to_base(); - } - - /** Add a switch to an entity by C++ type. - * The C++ type must be associated with a switch type. - * - * @tparam T The switch to remove. - */ - template - Self& remove_switch() { - ecs_remove_id(this->m_world, this->m_id, - ECS_SWITCH | _::cpp_type::id()); - return to_base(); + // Initialize component identifier + static void init( + bool allow_tag = true) + { + index(); // Make sure global component index is initialized + + s_allow_tag = allow_tag; + s_size = sizeof(T); + s_alignment = alignof(T); + if (is_empty::value && allow_tag) { + s_size = 0; + s_alignment = 0; + } } - /** Remove a switch from an entity. - * Any instance of flecs::type can be used as a switch. - * - * @param sw The switch to remove. - */ - Self& remove_switch(const flecs::type& sw); + static void init_builtin( + flecs::world_t *world, + flecs::entity_t id, + bool allow_tag = true) + { + init(allow_tag); + flecs_component_ids_set(world, index(), id); + } + + // Register component id. + static entity_t register_id( + world_t *world, // The world + const char *name = nullptr, // User provided name (overrides typename) + bool allow_tag = true, // Register empty types as zero-sized components + bool is_component = true, // Add flecs::Component to result + bool explicit_registration = false, // Entered from world.component()? + flecs::id_t id = 0) // User provided component id + { + init(allow_tag); + ecs_assert(index() != 0, ECS_INTERNAL_ERROR, NULL); - /** Add a switch to an entity by id. - * The case must belong to a switch that is already added to the entity. - * - * @param sw_case The case entity id to add. - */ - Self& add_case(entity_t sw_case) { - ecs_add_id(this->m_world, this->m_id, ECS_CASE | sw_case); - return to_base(); - } + bool registered = false, existing = false; - /** Add a switch to an entity by id. - * The case must belong to a switch that is already added to the entity. - * - * @tparam T The case to add. - */ - template - Self& add_case() { - return this->add_case(_::cpp_type::id()); + flecs::entity_t c = ecs_cpp_component_register( + world, id, index(), name, type_name(), + symbol_name(), size(), alignment(), + is_component, explicit_registration, ®istered, &existing); + + ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); + + if (registered) { + // Register lifecycle callbacks, but only if the component has a + // size. Components that don't have a size are tags, and tags don't + // require construction/destruction/copy/move's. + if (size() && !existing) { + register_lifecycle_actions(world, c); + } + + // If component is enum type, register constants. Make sure to do + // this after setting the component id, because the enum code will + // be calling type::id(). + #if FLECS_CPP_ENUM_REFLECTION_SUPPORT + _::init_enum(world, c); + #endif + } + + return c; } - /** Remove a case from an entity by id. - * The case must belong to a switch that is already added to the entity. - * - * @param sw_case The case entity id to remove. - */ - Self& remove_case(entity_t sw_case) { - ecs_remove_id(this->m_world, this->m_id, ECS_CASE | sw_case); - return to_base(); + // Get type (component) id. + // If type was not yet registered and automatic registration is allowed, + // this function will also register the type. + static entity_t id(world_t *world) + { +#ifdef FLECS_CPP_NO_AUTO_REGISTRATION + ecs_assert(registered(world), ECS_INVALID_OPERATION, + "component '%s' must be registered before use", + type_name()); + + flecs::entity_t c = flecs_component_ids_get(world, index()); + ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_is_alive(world, c), ECS_INVALID_OPERATION, + "component '%s' was deleted, reregister before using", + type_name()); +#else + flecs::entity_t c = flecs_component_ids_get_alive(world, index()); + if (!c) { + c = register_id(world); + } +#endif + return c; } - /** Remove a switch from an entity by id. - * The case must belong to a switch that is already added to the entity. - * - * @tparam T The case to remove. - */ - template - Self& remove_case() { - return this->remove_case(_::cpp_type::id()); + // Return the size of a component. + static size_t size() { + return s_size; } - /** Add pair for enum constant. - * This operation will add a pair to the entity where R is the enumeration - * type, and O is the entity representing the enum constant. - * - * The operation may be used with regular (C style) enumerations as well as - * enum classes. - * - * @param value The enumeration value. - */ - template ::value > = 0> - Self& add(E value) { - flecs::entity_t relation = _::cpp_type::id(this->m_world); - const auto& et = enum_type(this->m_world); - flecs::entity_t object = et.entity(value); - return this->add(relation, object); + // Return the alignment of a component. + static size_t alignment() { + return s_alignment; } - /** Remove pair for enum. - * This operation will remove any (Enum, *) pair from the entity. - * - * @tparam E The enumeration type. - */ - template ::value > = 0> - Self& remove() { - flecs::entity_t relation = _::cpp_type::id(this->m_world); - return this->remove(relation, flecs::Wildcard); + // Was the component already registered. + static bool registered(flecs::world_t *world) { + ecs_assert(world != nullptr, ECS_INVALID_PARAMETER, NULL); + + if (!flecs_component_ids_get(world, index())) { + return false; + } + + return true; } - /** Enable an entity. - * Enabled entities are matched with systems and can be searched with - * queries. - */ - Self& enable() { - ecs_enable(this->m_world, this->m_id, true); - return to_base(); + // This function is only used to test cross-translation unit features. No + // code other than test cases should invoke this function. + static void reset() { + s_size = 0; + s_alignment = 0; + s_allow_tag = true; } - /** Disable an entity. - * Disabled entities are not matched with systems and cannot be searched - * with queries, unless explicitly specified in the query expression. - */ - Self& disable() { - ecs_enable(this->m_world, this->m_id, false); - return to_base(); + static int32_t index() { + static int32_t index_ = flecs_component_ids_index_get(); + return index_; } - /** Enable a component. - * This sets the enabled bit for this component. If this is the first time - * the component is enabled or disabled, the bitset is added. - * - * @tparam T The component to enable. - */ - template - Self& enable() { - ecs_enable_component_w_id(this->m_world, this->m_id, _::cpp_type::id(), true); - return to_base(); - } + static size_t s_size; + static size_t s_alignment; + static bool s_allow_tag; +}; - /** Disable a component. - * This sets the enabled bit for this component. If this is the first time - * the component is enabled or disabled, the bitset is added. - * - * @tparam T The component to enable. - */ - template - Self& disable() { - ecs_enable_component_w_id(this->m_world, this->m_id, _::cpp_type::id(), false); - return to_base(); - } +// Global templated variables that hold component identifier and other info +template inline size_t type_impl::s_size; +template inline size_t type_impl::s_alignment; +template inline bool type_impl::s_allow_tag( true ); - /** Enable a component. - * See enable. - * - * @param comp The component to enable. - */ - Self& enable(entity_t comp) { - ecs_enable_component_w_id(this->m_world, this->m_id, comp, true); - return to_base(); - } +// Front facing class for implicitly registering a component & obtaining +// static component data - /** Disable a component. - * See disable. - * - * @param comp The component to disable. - */ - Self& disable(entity_t comp) { - ecs_enable_component_w_id(this->m_world, this->m_id, comp, false); - return to_base(); - } +// Regular type +template +struct type::value >> + : type_impl> { }; - Self& set_ptr(entity_t comp, size_t size, const void *ptr) { - ecs_set_id(this->m_world, this->m_id, comp, size, ptr); - return to_base(); +// Pair type +template +struct type::value >> +{ + // Override id method to return id of pair + static id_t id(world_t *world = nullptr) { + return ecs_pair( + type< pair_first_t >::id(world), + type< pair_second_t >::id(world)); } +}; - Self& set_ptr(entity_t comp, const void *ptr) { - const flecs::Component *cptr = ecs_get( - this->m_world, comp, EcsComponent); +} // namespace _ - /* Can't set if it's not a component */ - ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL); +/** Untyped component class. + * Generic base class for flecs::component. + * + * @ingroup cpp_components + */ +struct untyped_component : entity { + using entity::entity; + + untyped_component() : entity() { } + explicit untyped_component(flecs::world_t *world, flecs::entity_t id) : entity(world, id) { } + explicit untyped_component(flecs::entity_t id) : entity(id) { } - return set_ptr(comp, cptr->size, ptr); - } + explicit untyped_component(flecs::world_t *world, const char *name) + { + world_ = world; - template::value && is_actual::value> = 0 > - Self& set(T&& value) { - flecs::set(this->m_world, this->m_id, FLECS_FWD(value)); - return to_base(); + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.sep = "::"; + desc.root_sep = "::"; + desc.use_low_id = true; + id_ = ecs_entity_init(world, &desc); } - template::value && is_actual::value > = 0> - Self& set(const T& value) { - flecs::set(this->m_world, this->m_id, value); - return to_base(); - } + explicit untyped_component(world_t *world, const char *name, const char *sep, const char *root_sep) + { + world_ = world; - template, if_not_t< - is_callable::value || is_actual::value > = 0> - Self& set(A&& value) { - flecs::set(this->m_world, this->m_id, FLECS_FWD(value)); - return to_base(); + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.sep = sep; + desc.root_sep = root_sep; + desc.use_low_id = true; + id_ = ecs_entity_init(world, &desc); } - template, if_not_t< - is_callable::value || is_actual::value > = 0> - Self& set(const A& value) { - flecs::set(this->m_world, this->m_id, value); - return to_base(); - } +protected: - /** Set a pair for an entity. - * This operation sets the pair value, and uses the R as type. If the - * entity did not yet have the pair, it will be added. - * - * @tparam R The relation type. - * @tparam O The object type. - * @param value The value to set. - */ - template , - typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> - Self& set(const A& value) { - flecs::set

(this->m_world, this->m_id, value); - return to_base(); +flecs::type_hooks_t get_hooks() const { + const flecs::type_hooks_t* h = ecs_get_hooks_id(world_, id_); + if (h) { + return *h; + } else { + return {}; } +} - /** Set a pair for an entity. - * This operation sets the pair value, and uses the R as type. If the - * entity did not yet have the pair, it will be added. - * - * @tparam R The relation type. - * @param object The object. - * @param value The value to set. - */ - template ::value > = 0> - Self& set(O object, const R& value) { - auto relation = _::cpp_type::id(this->m_world); - flecs::set(this->m_world, this->m_id, value, - ecs_pair(relation, object)); - return to_base(); - } +void set_hooks(flecs::type_hooks_t &h) { + h.flags &= ECS_TYPE_HOOKS_ILLEGAL; + ecs_set_hooks_id(world_, id_, &h); +} - /** Set a pair for an entity. - * This operation sets the pair value, and uses the R as type. If the - * entity did not yet have the pair, it will be added. - * - * @tparam R The relation type. - * @param constant The enum constant. - * @param value The value to set. - */ - template ::value > = 0> - Self& set(O constant, const R& value) { - const auto& et = enum_type(this->m_world); - flecs::entity_t object = et.entity(constant); - return set(object, value); - } +public: - /** Set a pair for an entity. - * This operation sets the pair value, and uses the R as type. If the - * entity did not yet have the pair, it will be added. - * - * @tparam O The object type. - * @param relation The relation. - * @param value The value to set. - */ - template - Self& set_w_object(entity_t relation, const O& value) { - auto object = _::cpp_type::id(this->m_world); - flecs::set(this->m_world, this->m_id, value, - ecs_pair(relation, object)); - return to_base(); - } +untyped_component& on_compare( + ecs_cmp_t compare_callback) +{ + ecs_assert(compare_callback, ECS_INVALID_PARAMETER, NULL); + flecs::type_hooks_t h = get_hooks(); + h.cmp = compare_callback; + h.flags &= ~ECS_TYPE_HOOK_CMP_ILLEGAL; + if(h.flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) { + h.flags &= ~ECS_TYPE_HOOK_EQUALS_ILLEGAL; + h.equals = NULL; + } + set_hooks(h); + return *this; +} - template - Self& set_w_object(const O& value) { - flecs::set>(this->m_world, this->m_id, value); - return to_base(); - } +untyped_component& on_equals( + ecs_equals_t equals_callback) +{ + ecs_assert(equals_callback, ECS_INVALID_PARAMETER, NULL); + flecs::type_hooks_t h = get_hooks(); + h.equals = equals_callback; + h.flags &= ~ECS_TYPE_HOOK_EQUALS_ILLEGAL; + set_hooks(h); + return *this; +} - /** Set 1..N components. - * This operation accepts a callback with as arguments the components to - * set. If the entity does not have all of the provided components, they - * will be added. - * - * This operation is faster than individually calling get for each component - * as it only obtains entity metadata once. When this operation is called - * while deferred, its performance is equivalent to that of calling get_mut - * for each component separately. - * - * The operation will invoke modified for each component after the callback - * has been invoked. - * - * @param func The callback to invoke. - */ - template ::value > = 0> - Self& set(const Func& func); +# ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/untyped_component.inl + * @brief Meta component mixin. + */ - /** Emplace component. - * Emplace constructs a component in the storage, which prevents calling the - * destructor on the O passed into the function. - * - * Emplace attempts the following signatures to construct the component: - * T{Args...} - * T{flecs::entity, Args...} - * - * If the second signature matches, emplace will pass in the current entity - * as argument to the constructor, which is useful if the component needs - * to be aware of the entity to which it has been added. - * - * Emplace may only be called for components that have not yet been added - * to the entity. - * - * @tparam T the component to emplace - * @param args The arguments to pass to the constructor of T - */ - template - Self& emplace(Args&&... args) { - flecs::emplace(this->m_world, this->m_id, - FLECS_FWD(args)...); - return to_base(); - } +/** + * @memberof flecs::component + * @ingroup cpp_addons_meta + * + * @{ + */ - /** Entities created in function will have the current entity. - * - * @param func The function to call. - */ - template - Self& with(const Func& func) { - ecs_id_t prev = ecs_set_with(this->m_world, this->m_id); - func(); - ecs_set_with(this->m_world, prev); - return to_base(); - } +private: - /** Entities created in function will have (relation, this) - * This operation is thread safe. - * - * @tparam R The R to use. - * @param func The function to call. - */ - template - Self& with(const Func& func) { - with(_::cpp_type::id(this->m_world), func); - return to_base(); - } +/** Private method that adds member to component. */ +untyped_component& internal_member( + flecs::entity_t type_id, + flecs::entity_t unit, + const char *name, + int32_t count = 0, + size_t offset = 0, + bool use_offset = false) +{ + ecs_member_t m = {}; + m.name = name; + m.type = type_id; + m.unit = unit; + m.count = count; + m.offset = static_cast(offset); + m.use_offset = use_offset; + + ecs_struct_add_member(world_, id_, &m); - /** Entities created in function will have (relation, this) - * - * @param relation The relation. - * @param func The function to call. - */ - template - Self& with(entity_t relation, const Func& func) { - ecs_id_t prev = ecs_set_with(this->m_world, - ecs_pair(relation, this->m_id)); - func(); - ecs_set_with(this->m_world, prev); - return to_base(); - } + return *this; +} - /** The function will be ran with the scope set to the current entity. */ - template - Self& scope(const Func& func) { - ecs_entity_t prev = ecs_set_scope(this->m_world, this->m_id); - func(); - ecs_set_scope(this->m_world, prev); - return to_base(); - } +public: - /* Set the entity name. - */ - Self& set_name(const char *name) { - ecs_set_name(this->m_world, this->m_id, name); - return to_base(); - } +/** Add member with unit. */ +untyped_component& member( + flecs::entity_t type_id, + flecs::entity_t unit, + const char *name, + int32_t count = 0) +{ + return internal_member(type_id, unit, name, count, 0, false); +} -# ifdef FLECS_DOC +/** Add member with unit, count and offset. */ +untyped_component& member( + flecs::entity_t type_id, + flecs::entity_t unit, + const char *name, + int32_t count, + size_t offset) +{ + return internal_member(type_id, unit, name, count, offset, true); +} -Self& set_doc_name(const char *name) { - ecs_doc_set_name(m_world, m_id, name); - return to_base(); +/** Add member. */ +untyped_component& member( + flecs::entity_t type_id, + const char* name, + int32_t count = 0) +{ + return member(type_id, 0, name, count); +} + +/** Add member with count and offset. */ +untyped_component& member( + flecs::entity_t type_id, + const char* name, + int32_t count, + size_t offset) +{ + return member(type_id, 0, name, count, offset); +} + +/** Add member. */ +template +untyped_component& member( + const char *name, + int32_t count = 0) +{ + flecs::entity_t type_id = _::type::id(world_); + return member(type_id, name, count); +} + +/** Add member. */ +template +untyped_component& member( + const char *name, + int32_t count, + size_t offset) +{ + flecs::entity_t type_id = _::type::id(world_); + return member(type_id, name, count, offset); +} + +/** Add member with unit. */ +template +untyped_component& member( + flecs::entity_t unit, + const char *name, + int32_t count = 0) +{ + flecs::entity_t type_id = _::type::id(world_); + return member(type_id, unit, name, count); +} + +/** Add member with unit. */ +template +untyped_component& member( + flecs::entity_t unit, + const char *name, + int32_t count, + size_t offset) +{ + flecs::entity_t type_id = _::type::id(world_); + return member(type_id, unit, name, count, offset); } -Self& set_doc_brief(const char *brief) { - ecs_doc_set_brief(m_world, m_id, brief); - return to_base(); +/** Add member with unit. */ +template +untyped_component& member( + const char *name, + int32_t count = 0) +{ + flecs::entity_t type_id = _::type::id(world_); + flecs::entity_t unit_id = _::type::id(world_); + return member(type_id, unit_id, name, count); } -Self& set_doc_detail(const char *detail) { - ecs_doc_set_detail(m_world, m_id, detail); - return to_base(); +/** Add member with unit. */ +template +untyped_component& member( + const char *name, + int32_t count, + size_t offset) +{ + flecs::entity_t type_id = _::type::id(world_); + flecs::entity_t unit_id = _::type::id(world_); + return member(type_id, unit_id, name, count, offset); } -Self& set_doc_link(const char *link) { - ecs_doc_set_link(m_world, m_id, link); - return to_base(); +/** Add member using pointer-to-member. */ +template ::type> +untyped_component& member( + const char* name, + const MemberType ComponentType::* ptr) +{ + flecs::entity_t type_id = _::type::id(world_); + size_t offset = reinterpret_cast(&(static_cast(nullptr)->*ptr)); + return member(type_id, name, std::extent::value, offset); +} + +/** Add member with unit using pointer-to-member. */ +template ::type> +untyped_component& member( + flecs::entity_t unit, + const char* name, + const MemberType ComponentType::* ptr) +{ + flecs::entity_t type_id = _::type::id(world_); + size_t offset = reinterpret_cast(&(static_cast(nullptr)->*ptr)); + return member(type_id, unit, name, std::extent::value, offset); } -# endif - -# ifdef FLECS_META - -/** Make entity a unit */ -Self& unit( - const char *symbol, - flecs::entity_t prefix = 0, - flecs::entity_t base = 0, - flecs::entity_t over = 0, - int32_t factor = 0, - int32_t power = 0) +/** Add member with unit using pointer-to-member. */ +template ::type> +untyped_component& member( + const char* name, + const MemberType ComponentType::* ptr) { - ecs_unit_desc_t desc = {}; - desc.entity.entity = this->m_id; - desc.symbol = const_cast(symbol); /* safe, will be copied in */ - desc.base = base; - desc.over = over; - desc.prefix = prefix; - desc.translation.factor = factor; - desc.translation.power = power; - ecs_unit_init(this->world(), &desc); - - return to_base(); + flecs::entity_t type_id = _::type::id(world_); + flecs::entity_t unit_id = _::type::id(world_); + size_t offset = reinterpret_cast(&(static_cast(nullptr)->*ptr)); + return member(type_id, unit_id, name, std::extent::value, offset); } -/** Make entity a derived unit */ -Self& unit( - flecs::entity_t prefix = 0, - flecs::entity_t base = 0, - flecs::entity_t over = 0, - int32_t factor = 0, - int32_t power = 0) +/** Add constant. */ +template +untyped_component& constant( + const char *name, + T value) { - ecs_unit_desc_t desc = {}; - desc.entity.entity = this->m_id; - desc.base = base; - desc.over = over; - desc.prefix = prefix; - desc.translation.factor = factor; - desc.translation.power = power; - ecs_unit_init(this->world(), &desc); + ecs_add_id(world_, id_, _::type::id(world_)); - return to_base(); + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.parent = id_; + ecs_entity_t eid = ecs_entity_init(world_, &desc); + ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_set_id(world_, eid, + ecs_pair(flecs::Constant, _::type::id(world_)), sizeof(T), + &value); + + return *this; } -/** Make entity a derived unit */ -Self& unit_prefix( - const char *symbol, - int32_t factor = 0, - int32_t power = 0) +/** Add bitmask constant. */ +template +untyped_component& bit( + const char *name, + T value) { - ecs_unit_prefix_desc_t desc = {}; - desc.entity.entity = this->m_id; - desc.symbol = const_cast(symbol); /* safe, will be copied in */ - desc.translation.factor = factor; - desc.translation.power = power; - ecs_unit_prefix_init(this->world(), &desc); + ecs_add_id(world_, id_, _::type::id(world_)); - return to_base(); -} + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.parent = id_; + ecs_entity_t eid = ecs_entity_init(world_, &desc); + ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); -/** Add quantity to unit */ -Self& quantity(flecs::entity_t quantity) { - ecs_add_pair(this->world(), this->id(), flecs::Quantity, quantity); - return to_base(); -} + ecs_set_id(world_, eid, + ecs_pair(flecs::Constant, _::type::id(world_)), sizeof(T), + &value); -/** Make entity a unity prefix */ -template -Self& quantity() { - return this->quantity(_::cpp_type::id(this->world())); + return *this; } -/** Make entity a quantity */ -Self& quantity() { - ecs_add_id(this->world(), this->id(), flecs::Quantity); - return to_base(); +/** Register array metadata for component */ +template +untyped_component& array( + int32_t elem_count) +{ + ecs_array_desc_t desc = {}; + desc.entity = id_; + desc.type = _::type::id(world_); + desc.count = elem_count; + ecs_array_init(world_, &desc); + return *this; } -# endif - -protected: - Self& to_base() { - return *static_cast(this); +/** Add member value range */ +untyped_component& range( + double min, + double max) +{ + flecs::member_t *m = ecs_cpp_last_member(world_, id_); + if (!m) { + return *this; } -}; -} + m->range.min = min; + m->range.max = max; + if (m->member) { + flecs::world w(world_); + flecs::entity me = w.entity(m->member); + flecs::MemberRanges *mr = static_cast( + ecs_ensure_id(w, me, w.id(), + sizeof(flecs::MemberRanges))); + mr->value.min = min; + mr->value.max = max; + me.modified(); + } -namespace flecs -{ + return *this; +} -/** Entity class - * This class provides access to entities. */ -struct entity : entity_builder +/** Add member warning range */ +untyped_component& warning_range( + double min, + double max) { - entity() : entity_builder() { } - - /** Create entity. - * - * @param world The world in which to create the entity. - */ - explicit entity(world_t *world) - : entity_builder() - { - m_world = world; - m_id = ecs_new(world, 0); + flecs::member_t *m = ecs_cpp_last_member(world_, id_); + if (!m) { + return *this; } - /** Wrap an existing entity id. - * - * @param world The world in which the entity is created. - * @param id The entity id. - */ - explicit entity(const flecs::world_t *world, flecs::id_t id) { - m_world = const_cast(world); - m_id = id; + m->warning_range.min = min; + m->warning_range.max = max; + + if (m->member) { + flecs::world w(world_); + flecs::entity me = w.entity(m->member); + flecs::MemberRanges *mr = static_cast( + ecs_ensure_id(w, me, w.id(), + sizeof(flecs::MemberRanges))); + mr->warning.min = min; + mr->warning.max = max; + me.modified(); } - /** Create a named entity. - * Named entities can be looked up with the lookup functions. Entity names - * may be scoped, where each element in the name is separated by "::". - * For example: "Foo::Bar". If parts of the hierarchy in the scoped name do - * not yet exist, they will be automatically created. - * - * @param world The world in which to create the entity. - * @param name The entity name. - */ - explicit entity(world_t *world, const char *name) - : entity_builder() - { - m_world = world; + return *this; +} - ecs_entity_desc_t desc = {}; - desc.name = name; - desc.sep = "::"; - desc.root_sep = "::"; - m_id = ecs_entity_init(world, &desc); +/** Add member error range */ +untyped_component& error_range( + double min, + double max) +{ + flecs::member_t *m = ecs_cpp_last_member(world_, id_); + if (!m) { + return *this; } - /** Conversion from flecs::entity_t to flecs::entity. - * - * @param id The entity_t value to convert. - */ - explicit entity(entity_t id) - : entity_builder( nullptr, id ) { } + m->error_range.min = min; + m->error_range.max = max; - /** Get mutable component value. - * This operation returns a mutable pointer to the component. If the entity - * did not yet have the component, it will be added. If a base entity had - * the component, it will be overridden, and the value of the base component - * will be copied to the entity before this function returns. - * - * @tparam T The component to get. - * @param is_added If provided, this parameter will be set to true if the component was added. - * @return Pointer to the component value. - */ - template - T* get_mut(bool *is_added = nullptr) const { - auto comp_id = _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - return static_cast( - ecs_get_mut_id(m_world, m_id, comp_id, is_added)); + if (m->member) { + flecs::world w(world_); + flecs::entity me = w.entity(m->member); + flecs::MemberRanges *mr = static_cast( + ecs_ensure_id(w, me, w.id(), + sizeof(flecs::MemberRanges))); + mr->error.min = min; + mr->error.max = max; + me.modified(); } - /** Get mutable component value (untyped). - * This operation returns a mutable pointer to the component. If the entity - * did not yet have the component, it will be added. If a base entity had - * the component, it will be overridden, and the value of the base component - * will be copied to the entity before this function returns. - * - * @param comp The component to get. - * @param is_added If provided, this parameter will be set to true if the component was added. - * @return Pointer to the component value. - */ - void* get_mut(entity_t comp, bool *is_added = nullptr) const { - return ecs_get_mut_id(m_world, m_id, comp, is_added); - } + return *this; +} - /** Get mutable pointer for a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam Relation the relation type. - * @tparam Object the object type. - */ - template - Relation* get_mut(bool *is_added = nullptr) const { - return this->get_mut( - _::cpp_type::id(m_world), is_added); - } +/** @} */ - /** Get mutable pointer for a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam Relation the relation type. - * @param object the object. - */ - template - Relation* get_mut(entity_t object, bool *is_added = nullptr) const { - auto comp_id = _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - return static_cast( - ecs_get_mut_id(m_world, m_id, - ecs_pair(comp_id, object), is_added)); - } +# endif +# ifdef FLECS_METRICS +/** + * @file addons/cpp/mixins/meta/untyped_component.inl + * @brief Metrics component mixin. + */ - /** Get mutable pointer for a pair (untyped). - * This operation gets the value for a pair from the entity. If neither the - * relation or object are a component, the operation will fail. - * - * @param relation the relation. - * @param object the object. - */ - void* get_mut(entity_t relation, entity_t object, bool *is_added = nullptr) const { - return ecs_get_mut_id(m_world, m_id, - ecs_pair(relation, object), is_added); - } +/** + * @memberof flecs::component + * @ingroup cpp_addons_metrics + * + * @{ + */ - /** Get mutable pointer for the object from a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam Object the object type. - * @param relation the relation. - */ - template - Object* get_mut_w_object(entity_t relation, bool *is_added = nullptr) const { - auto comp_id = _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - return static_cast( - ecs_get_mut_id(m_world, m_id, - ecs_pair(relation, comp_id), is_added)); - } +/** Register member as metric. + * When no explicit name is provided, this operation will derive the metric name + * from the member name. When the member name is "value", the operation will use + * the name of the component. + * + * When the brief parameter is provided, it is set on the metric as if + * set_doc_brief is used. The brief description can be obtained with + * get_doc_brief. + * + * @tparam Kind Metric kind (Counter, CounterIncrement or Gauge). + * @param parent Parent entity of the metric (optional). + * @param brief Description for metric (optional). + * @param name Name of metric (optional). + */ +template +untyped_component& metric( + flecs::entity_t parent = 0, + const char *brief = nullptr, + const char *name = nullptr); - /** Signal that component was modified. - * - * @tparam T component that was modified. - */ - template - void modified() const { - auto comp_id = _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - this->modified(comp_id); - } +/** @} */ - /** Signal that the relation part of a pair was modified. - * - * @tparam Relation the relation type. - * @tparam Object the object type. - */ - template - void modified() const { - this->modified(_::cpp_type::id(m_world)); - } +# endif +}; - /** Signal that the relation part of a pair was modified. +/** Component class. + * Class used to register components and component metadata. + * + * @ingroup cpp_components + */ +template +struct component : untyped_component { + /** Register a component. + * If the component was already registered, this operation will return a handle + * to the existing component. * - * @tparam Relation the relation type. - * @param object the object. + * @param world The world for which to register the component. + * @param name Optional name (overrides typename). + * @param allow_tag If true, empty types will be registered with size 0. + * @param id Optional id to register component with. */ - template - void modified(entity_t object) const { - auto comp_id = _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - this->modified(comp_id, object); + component( + flecs::world_t *world, + const char *name = nullptr, + bool allow_tag = true, + flecs::id_t id = 0) + { + world_ = world; + id_ = _::type::register_id(world, name, allow_tag, true, true, id); } - /** Signal that a pair has modified (untyped). - * If neither the relation or object part of the pair are a component, the - * operation will fail. - * - * @param relation the relation. - * @param object the object. - */ - void modified(entity_t relation, entity_t object) const { - this->modified(ecs_pair(relation, object)); + /** Register on_add hook. */ + template + component& on_add(Func&& func) { + using Delegate = typename _::each_delegate::type, T>; + flecs::type_hooks_t h = get_hooks(); + ecs_assert(h.on_add == nullptr, ECS_INVALID_OPERATION, + "on_add hook is already set"); + BindingCtx *ctx = get_binding_ctx(h); + h.on_add = Delegate::run_add; + ctx->on_add = FLECS_NEW(Delegate)(FLECS_FWD(func)); + ctx->free_on_add = _::free_obj; + set_hooks(h); + return *this; } - /** Signal that component was modified. - * - * @param comp component that was modified. - */ - void modified(entity_t comp) const { - ecs_modified_id(m_world, m_id, comp); + /** Register on_remove hook. */ + template + component& on_remove(Func&& func) { + using Delegate = typename _::each_delegate< + typename std::decay::type, T>; + flecs::type_hooks_t h = get_hooks(); + ecs_assert(h.on_remove == nullptr, ECS_INVALID_OPERATION, + "on_remove hook is already set"); + BindingCtx *ctx = get_binding_ctx(h); + h.on_remove = Delegate::run_remove; + ctx->on_remove = FLECS_NEW(Delegate)(FLECS_FWD(func)); + ctx->free_on_remove = _::free_obj; + set_hooks(h); + return *this; } - /** Get reference to component. - * A reference allows for quick and safe access to a component value, and is - * a faster alternative to repeatedly calling 'get' for the same component. - * - * @tparam T component for which to get a reference. - * @return The reference. - */ - template - ref get_ref() const { - // Ensure component is registered - _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); - return ref(m_world, m_id); + /** Register on_set hook. */ + template + component& on_set(Func&& func) { + using Delegate = typename _::each_delegate< + typename std::decay::type, T>; + flecs::type_hooks_t h = get_hooks(); + ecs_assert(h.on_set == nullptr, ECS_INVALID_OPERATION, + "on_set hook is already set"); + BindingCtx *ctx = get_binding_ctx(h); + h.on_set = Delegate::run_set; + ctx->on_set = FLECS_NEW(Delegate)(FLECS_FWD(func)); + ctx->free_on_set = _::free_obj; + set_hooks(h); + return *this; } - /** Clear an entity. - * This operation removes all components from an entity without recycling - * the entity id. - */ - void clear() const { - ecs_clear(m_world, m_id); + /** Register on_replace hook. */ + template + component& on_replace(Func&& func) { + using Delegate = typename _::each_delegate< + typename std::decay::type, T, T>; + flecs::type_hooks_t h = get_hooks(); + ecs_assert(h.on_set == nullptr, ECS_INVALID_OPERATION, + "on_set hook is already set"); + BindingCtx *ctx = get_binding_ctx(h); + h.on_replace = Delegate::run_replace; + ctx->on_replace = FLECS_NEW(Delegate)(FLECS_FWD(func)); + ctx->free_on_replace = _::free_obj; + set_hooks(h); + return *this; } - /** Delete an entity. - * Entities have to be deleted explicitly, and are not deleted when the - * flecs::entity object goes out of scope. - */ - void destruct() const { - ecs_delete(m_world, m_id); + /** Register operator compare hook. */ + using untyped_component::on_compare; + component& on_compare() { + ecs_cmp_t handler = _::compare(); + ecs_assert(handler != NULL, ECS_INVALID_OPERATION, + "Type does not have operator> or operator< const or is inaccessible"); + on_compare(handler); + return *this; } - /** Entity id 0. - * This function is useful when the API must provide an entity object that - * belongs to a world, but the entity id is 0. - * - * @param world The world. - */ - static - flecs::entity null(const flecs::world_t *world) { - flecs::entity result; - result.m_world = const_cast(world); - return result; + /** Type safe variant of compare op function */ + using cmp_hook = int(*)(const T* a, const T* b, const ecs_type_info_t *ti); + component& on_compare(cmp_hook callback) { + on_compare(reinterpret_cast(callback)); + return *this; + } + + /** Register operator equals hook. */ + using untyped_component::on_equals; + component& on_equals() { + ecs_equals_t handler = _::equals(); + ecs_assert(handler != NULL, ECS_INVALID_OPERATION, + "Type does not have operator== const or is inaccessible"); + on_equals(handler); + return *this; } - static - flecs::entity null() { - return flecs::entity(); + /** Type safe variant of equals op function */ + using equals_hook = bool(*)(const T* a, const T* b, const ecs_type_info_t *ti); + component& on_equals(equals_hook callback) { + on_equals(reinterpret_cast(callback)); + return *this; } -}; -} // namespace flecs +# ifdef FLECS_META +/** Register opaque type interface */ +template +component& opaque(const Func& type_support) { + flecs::world world(world_); + auto ts = type_support(world); + ts.desc.entity = _::type::id(world_); + ecs_opaque_init(world_, &ts.desc); + return *this; +} -//////////////////////////////////////////////////////////////////////////////// -//// Utility class to invoke a system each -//////////////////////////////////////////////////////////////////////////////// +flecs::opaque opaque(flecs::entity_t as_type) { + return flecs::opaque(world_).as_type(as_type); +} -namespace flecs -{ +flecs::opaque opaque(flecs::entity as_type) { + return this->opaque(as_type.id()); +} -namespace _ -{ +flecs::opaque opaque(flecs::untyped_component as_type) { + return this->opaque(as_type.id()); +} -// Utility to convert template argument pack to array of term ptrs -struct term_ptr { - void *ptr; - bool is_ref; -}; +/** Return opaque type builder for collection type */ +template +flecs::opaque opaque(flecs::id_t as_type) { + return flecs::opaque(world_).as_type(as_type); +} -template -struct term_ptrs { - using array = flecs::array<_::term_ptr, sizeof...(Components)>; +/** Add constant. */ +component& constant(const char *name, T value) { + using U = typename std::underlying_type::type; - bool populate(const ecs_iter_t *iter) { - return populate(iter, 0, static_cast< - remove_reference_t< - remove_pointer_t> - *>(nullptr)...); - } + ecs_add_id(world_, id_, _::type::id(world_)); - array m_terms; + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.parent = id_; + ecs_entity_t eid = ecs_entity_init(world_, &desc); + ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); -private: - /* Populate terms array without checking for references */ - bool populate(const ecs_iter_t*, size_t) { return false; } - - template - bool populate(const ecs_iter_t *iter, size_t index, T, Targs... comps) { - m_terms[index].ptr = iter->ptrs[index]; - bool is_ref = iter->subjects && iter->subjects[index] != 0; - m_terms[index].is_ref = is_ref; - is_ref |= populate(iter, index + 1, comps ...); - return is_ref; - } -}; + flecs::id_t pair = ecs_pair(flecs::Constant, _::type::id(world_)); + U *ptr = static_cast(ecs_ensure_id(world_, eid, pair, sizeof(U))); + *ptr = static_cast(value); + ecs_modified_id(world_, eid, pair); -struct invoker { }; + // If we're not using automatic enum reflection, manually set static data + #ifdef FLECS_CPP_NO_ENUM_REFLECTION + auto et = enum_type(world_); + et.register_constant(world_, static_cast(value), eid); + #endif -// Template that figures out from the template parameters of a query/system -// how to pass the value to the each callback -template -struct each_column { }; + return *this; +} -// Base class -struct each_column_base { - each_column_base(const _::term_ptr& term, size_t row) - : m_term(term), m_row(row) { } +# endif -protected: - const _::term_ptr& m_term; - size_t m_row; +private: + using BindingCtx = _::component_binding_ctx; + + BindingCtx* get_binding_ctx(flecs::type_hooks_t& h){ + BindingCtx *result = static_cast(h.binding_ctx); + if (!result) { + result = FLECS_NEW(BindingCtx); + h.binding_ctx = result; + h.binding_ctx_free = _::free_obj; + } + return result; + } }; -// If type is not a pointer, return a reference to the type (default case) -template -struct each_column::value && - !is_empty>::value && is_actual::value > > - : each_column_base -{ - each_column(const _::term_ptr& term, size_t row) - : each_column_base(term, row) { } +} - T& get_row() { - return static_cast(this->m_term.ptr)[this->m_row]; - } -}; +/** @} */ -// If argument type is not the same as actual component type, return by value. -// This requires that the actual type can be converted to the type. -// A typical scenario where this happens is when using flecs::pair types. -template -struct each_column::value && - !is_empty>::value && !is_actual::value> > - : each_column_base -{ - each_column(const _::term_ptr& term, size_t row) - : each_column_base(term, row) { } +/** + * @file addons/cpp/type.hpp + * @brief Utility functions for id vector. + */ - T get_row() { - return static_cast*>(this->m_term.ptr)[this->m_row]; - } -}; +#pragma once +namespace flecs { -// If type is empty (indicating a tag) the query will pass a nullptr. To avoid -// returning nullptr to reference arguments, return a temporary value. -template -struct each_column>::value && - !is_pointer::value > > - : each_column_base -{ - each_column(const _::term_ptr& term, size_t row) - : each_column_base(term, row) { } +/** + * @defgroup cpp_types Types + * @ingroup cpp_core + * @brief Type operations. + * + * @{ + */ - T get_row() { - return actual_type_t(); - } -}; +/** Type class. + * A type is a vector of component ids which can be requested from entities or tables. + */ +struct type { + type() : world_(nullptr), type_(nullptr) { } + type(world_t *world, const type_t *t) + : world_(world) + , type_(t) { } -// If type is a pointer (indicating an optional value) return the type as is -template -struct each_column::value && - !is_empty>::value > > - : each_column_base -{ - each_column(const _::term_ptr& term, size_t row) - : each_column_base(term, row) { } + /** Convert type to comma-separated string */ + flecs::string str() const { + return flecs::string(ecs_type_str(world_, type_)); + } - T get_row() { - if (this->m_term.ptr) { - return &static_cast>(this->m_term.ptr)[this->m_row]; - } else { - // optional argument doesn't hava a value - return nullptr; + /** Return number of ids in type */ + int32_t count() const { + if (!type_) { + return 0; } + return type_->count; } -}; -// If the query contains component references to other entities, check if the -// current argument is one. -template -struct each_ref_column : public each_column { - each_ref_column(const _::term_ptr& term, size_t row) - : each_column(term, row) { - - if (term.is_ref) { - // If this is a reference, set the row to 0 as a ref always is a - // single value, not an array. This prevents the application from - // having to do an if-check on whether the column is owned. - // - // This check only happens when the current table being iterated - // over caused the query to match a reference. The check is - // performed once per iterated table. - this->m_row = 0; + /** Return pointer to array. */ + flecs::id_t* array() const { + if (!type_) { + return nullptr; } + return type_->array; } -}; - -template -struct each_invoker : public invoker { - // If the number of arguments in the function signature is one more than the - // number of components in the query, an extra entity arg is required. - static constexpr bool PassEntity = - (sizeof...(Components) + 1) == (arity::value); - - // If the number of arguments in the function is two more than the number of - // components in the query, extra iter + index arguments are required. - static constexpr bool PassIter = - (sizeof...(Components) + 2) == (arity::value); - static_assert(arity::value > 0, - "each() must have at least one argument"); - - using Terms = typename term_ptrs::array; - - template < if_not_t< is_same< void(Func), void(Func)& >::value > = 0> - explicit each_invoker(Func&& func) noexcept - : m_func(FLECS_MOV(func)) { } - - explicit each_invoker(const Func& func) noexcept - : m_func(func) { } - - // Invoke object directly. This operation is useful when the calling - // function has just constructed the invoker, such as what happens when - // iterating a query. - void invoke(ecs_iter_t *iter) const { - term_ptrs terms; + /** Get id at specified index in type */ + flecs::id get(int32_t index) const { + ecs_assert(type_ != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(type_->count > index, ECS_OUT_OF_RANGE, NULL); + if (!type_) { + return flecs::id(); + } + return flecs::id(world_, type_->array[index]); + } - if (terms.populate(iter)) { - invoke_callback< each_ref_column >(iter, m_func, 0, terms.m_terms); + const flecs::id_t* begin() const { + if (type_ && type_->count) { + return type_->array; } else { - invoke_callback< each_column >(iter, m_func, 0, terms.m_terms); - } + return &empty_; + } } - // Static function that can be used as callback for systems/triggers - static void run(ecs_iter_t *iter) { - auto self = static_cast(iter->binding_ctx); - ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); - self->invoke(iter); + const flecs::id_t* end() const { + if (type_ && type_->count) { + return &type_->array[type_->count]; + } else { + return &empty_; + } } - // Each invokers always use instanced iterators - static bool instanced() { - return true; + /** Implicit conversion to type_t */ + operator const type_t*() const { + return type_; } - private: - // Number of function arguments is one more than number of components, pass - // entity as argument. - template class ColumnType, - typename... Args, if_t< - sizeof...(Components) == sizeof...(Args) && PassEntity> = 0> - static void invoke_callback( - ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) - { - ECS_TABLE_LOCK(iter->world, iter->table); + world_t *world_; + const type_t *type_; + flecs::id_t empty_; +}; - ecs_world_t *world = iter->world; - size_t count = static_cast(iter->count); +/** #} */ - for (size_t i = 0; i < count; i ++) { - func(flecs::entity(world, iter->entities[i]), - (ColumnType< remove_reference_t >(comps, i) - .get_row())...); - } +} - ECS_TABLE_UNLOCK(iter->world, iter->table); - } +/** + * @file addons/cpp/table.hpp + * @brief Direct access to table data. + */ +#pragma once - // Number of function arguments is two more than number of components, pass - // iter + index as argument. - template class ColumnType, - typename... Args, int Enabled = PassIter, if_t< - sizeof...(Components) == sizeof...(Args) && Enabled> = 0> - static void invoke_callback( - ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) - { - ECS_TABLE_LOCK(iter->world, iter->table); +namespace flecs { - size_t count = static_cast(iter->count); - flecs::iter it(iter); +/** + * @defgroup cpp_tables Tables + * @ingroup cpp_core + * Table operations. + * + * @{ + */ - for (size_t i = 0; i < count; i ++) { - func(it, i, (ColumnType< remove_reference_t >(comps, i) - .get_row())...); - } +struct table { + table() : world_(nullptr), table_(nullptr) { } - ECS_TABLE_UNLOCK(iter->world, iter->table); + table(world_t *world, table_t *t) + : world_(world) + , table_(t) { } + + virtual ~table() { } + + /** Convert table type to string. */ + flecs::string str() const { + return flecs::string(ecs_table_str(world_, table_)); } + /** Get table type. */ + flecs::type type() const { + return flecs::type(world_, ecs_table_get_type(table_)); + } - // Number of function arguments is equal to number of components, no entity - template class ColumnType, - typename... Args, if_t< - sizeof...(Components) == sizeof...(Args) && !PassEntity && !PassIter> = 0> - static void invoke_callback( - ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) - { - flecs::iter it(iter); - for (auto row : it) { - func( (ColumnType< remove_reference_t >(comps, row) - .get_row())...); - } + /** Get table count. */ + int32_t count() const { + return ecs_table_count(table_); } - template class ColumnType, - typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> - static void invoke_callback(ecs_iter_t *iter, const Func& func, - size_t index, Terms& columns, Args... comps) - { - invoke_callback( - iter, func, index + 1, columns, comps..., columns[index]); - } + /** Get number of allocated elements in table. */ + int32_t size() const { + return ecs_table_size(table_); + } - Func m_func; -}; + /** Get array with entity ids. */ + const flecs::entity_t* entities() const { + return ecs_table_entities(table_); + } + /** Delete entities in table. */ + void clear_entities() const { + ecs_table_clear_entities(world_, table_); + } -//////////////////////////////////////////////////////////////////////////////// -//// Utility class to invoke a system iterate action -//////////////////////////////////////////////////////////////////////////////// + /** Find type index for (component) id. + * + * @param id The (component) id. + * @return The index of the id in the table type, -1 if not found/ + */ + int32_t type_index(flecs::id_t id) const { + return ecs_table_get_type_index(world_, table_, id); + } -template -struct iter_invoker : invoker { -private: - static constexpr bool IterOnly = arity::value == 1; + /** Find type index for type. + * + * @tparam T The type. + * @return True if the table has the type, false if not. + */ + template + int32_t type_index() const { + return type_index(_::type::id(world_)); + } - using Terms = typename term_ptrs::array; + /** Find type index for pair. + * @param first First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + int32_t type_index(flecs::entity_t first, flecs::entity_t second) const { + return type_index(ecs_pair(first, second)); + } -public: - template < if_not_t< is_same< void(Func), void(Func)& >::value > = 0> - explicit iter_invoker(Func&& func) noexcept - : m_func(FLECS_MOV(func)) { } + /** Find type index for pair. + * @tparam First First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + int32_t type_index(flecs::entity_t second) const { + return type_index(_::type::id(world_), second); + } - explicit iter_invoker(const Func& func) noexcept - : m_func(func) { } + /** Find type index for pair. + * @tparam First First element of pair. + * @tparam Second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + int32_t type_index() const { + return type_index(_::type::id(world_)); + } - // Invoke object directly. This operation is useful when the calling - // function has just constructed the invoker, such as what happens when - // iterating a query. - void invoke(ecs_iter_t *iter) const { - term_ptrs terms; - terms.populate(iter); - invoke_callback(iter, m_func, 0, terms.m_terms); + /** Find column index for (component) id. + * + * @param id The (component) id. + * @return The index of the id in the table type, -1 if not found/ + */ + int32_t column_index(flecs::id_t id) const { + return ecs_table_get_column_index(world_, table_, id); } - // Static function that can be used as callback for systems/triggers - static void run(ecs_iter_t *iter) { - auto self = static_cast(iter->binding_ctx); - ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); - self->invoke(iter); + /** Find column index for type. + * + * @tparam T The type. + * @return True if the table has the type, false if not. + */ + template + int32_t column_index() const { + return column_index(_::type::id(world_)); } - // Instancing needs to be enabled explicitly for iter invokers - static bool instanced() { - return false; + /** Find column index for pair. + * @param first First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + int32_t column_index(flecs::entity_t first, flecs::entity_t second) const { + return column_index(ecs_pair(first, second)); } -private: - template = 0> - static void invoke_callback(ecs_iter_t *iter, const Func& func, - size_t, Terms&, Args...) - { - flecs::iter it(iter); - func(it); + /** Find column index for pair. + * @tparam First First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + int32_t column_index(flecs::entity_t second) const { + return column_index(_::type::id(world_), second); } - template = 0> - static void invoke_callback(ecs_iter_t *iter, const Func& func, size_t, - Terms&, Targs... comps) - { - flecs::iter it(iter); + /** Find column index for pair. + * @tparam First First element of pair. + * @tparam Second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + int32_t column_index() const { + return column_index(_::type::id(world_)); + } - ECS_TABLE_LOCK(iter->world, iter->table); + /** Test if table has (component) id. + * + * @param id The (component) id. + * @return True if the table has the id, false if not. + */ + bool has(flecs::id_t id) const { + return type_index(id) != -1; + } + + /** Test if table has the type. + * + * @tparam T The type. + * @return True if the table has the type, false if not. + */ + template + bool has() const { + return type_index() != -1; + } - func(it, ( static_cast< - remove_reference_t< - remove_pointer_t< - actual_type_t > >* > - (comps.ptr))...); + /** Test if table has the pair. + * + * @param first First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + bool has(flecs::entity_t first, flecs::entity_t second) const { + return type_index(first, second) != -1; + } - ECS_TABLE_UNLOCK(iter->world, iter->table); + /** Test if table has the pair. + * + * @tparam First First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + bool has(flecs::entity_t second) const { + return type_index(second) != -1; } - template = 0> - static void invoke_callback(ecs_iter_t *iter, const Func& func, - size_t index, Terms& columns, Targs... comps) - { - invoke_callback(iter, func, index + 1, columns, comps..., - columns[index]); + /** Test if table has the pair. + * + * @tparam First First element of pair. + * @tparam Second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + bool has() const { + return type_index() != -1; + } + + /** Get pointer to component array by column index. + * + * @param index The column index. + * @return Pointer to the column, NULL if not a component. + */ + virtual void* get_column(int32_t index) const { + return ecs_table_get_column(table_, index, 0); } - Func m_func; -}; - -//////////////////////////////////////////////////////////////////////////////// -//// Utility to invoke callback on entity if it has components in signature -//////////////////////////////////////////////////////////////////////////////// + /* get */ -template -struct entity_with_invoker_impl; + /** Get pointer to component array by component. + * + * @param id The component id. + * @return Pointer to the column, NULL if not found. + */ + void* try_get(flecs::id_t id) const { + int32_t index = column_index(id); + if (index == -1) { + return NULL; + } + return get_column(index); + } -template -struct entity_with_invoker_impl> { - using ColumnArray = flecs::array; - using ConstPtrArray = flecs::array; - using PtrArray = flecs::array; - using DummyArray = flecs::array; - using IdArray = flecs::array; + /** Get pointer to component array by pair. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + void* try_get(flecs::entity_t first, flecs::entity_t second) const { + return try_get(ecs_pair(first, second)); + } - template - static bool get_ptrs(world_t *world, ecs_record_t *r, ecs_table_t *table, - ArrayType& ptrs) - { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + /** Get pointer to component array by component. + * + * @tparam T The component. + * @return Pointer to the column, NULL if not found. + */ + template ::value > = 0> + T* try_get() const { + return static_cast(try_get(_::type::id(world_))); + } - ecs_table_t *storage_table = ecs_table_get_storage_table(table); - if (!storage_table) { - return false; - } + /** Get pointer to component array by component. + * + * @tparam T The component. + * @return Pointer to the column, NULL if not found. + */ + template , + if_t< flecs::is_pair::value > = 0> + A* try_get() const { + return static_cast(try_get(_::type::id(world_))); + } - /* table_index_of needs real world */ - const flecs::world_t *real_world = ecs_get_world(world); + /** Get pointer to component array by pair. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + template + First* try_get(flecs::entity_t second) const { + return static_cast(try_get(_::type::id(world_), second)); + } - /* Get column indices for components */ - ColumnArray columns ({ - ecs_search_offset(real_world, storage_table, 0, - _::cpp_type().id(world), 0)... - }); - /* Get pointers for columns for entity */ - size_t i = 0; - for (int32_t column : columns) { - if (column == -1) { - return false; - } + /* get */ - ptrs[i ++] = ecs_record_get_column(r, column, 0); + /** Get pointer to component array by component. + * + * @param id The component id. + * @return Pointer to the column, NULL if not found. + */ + void* get(flecs::id_t id) const { + int32_t index = column_index(id); + if (index == -1) { + return NULL; } - - return true; + void *r = get_column(index); + ecs_assert(r != nullptr, ECS_INVALID_OPERATION, + "invalid get_mut: table does not have component (use try_get)"); + return r; } - template - static bool get_mut_ptrs(world_t *world, ecs_entity_t e, ArrayType& ptrs) { - /* Get pointers w/get_mut */ - size_t i = 0; - DummyArray dummy ({ - (ptrs[i ++] = ecs_get_mut_id(world, e, - _::cpp_type().id(world), NULL), 0)... - }); - - return true; - } + /** Get pointer to component array by pair. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + void* get(flecs::entity_t first, flecs::entity_t second) const { + return get(ecs_pair(first, second)); + } - template - static bool invoke_get(world_t *world, entity_t id, const Func& func) { - ecs_record_t *r = ecs_record_find(world, id); - if (!r) { - return false; - } + /** Get pointer to component array by component. + * + * @tparam T The component. + * @return Pointer to the column, NULL if not found. + */ + template ::value > = 0> + T* get() const { + return static_cast(get(_::type::id(world_))); + } - ecs_table_t *table = r->table; - if (!table) { - return false; - } + /** Get pointer to component array by component. + * + * @tparam T The component. + * @return Pointer to the column, NULL if not found. + */ + template , + if_t< flecs::is_pair::value > = 0> + A* get() const { + return static_cast(get(_::type::id(world_))); + } - ConstPtrArray ptrs; - if (!get_ptrs(world, r, table, ptrs)) { - return false; - } + /** Get pointer to component array by pair. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + template + First* get(flecs::entity_t second) const { + return static_cast(get(_::type::id(world_), second)); + } - invoke_callback(func, 0, ptrs); - return true; + /** Get pointer to component array by pair. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + A* get() const { + return static_cast(get(_::type::id(world_))); } - // Utility for storing id in array in pack expansion - static size_t store_added(IdArray& added, size_t elem, ecs_table_t *prev, - ecs_table_t *next, id_t id) - { - // Array should only contain ids for components that are actually added, - // so check if the prev and next tables are different. - if (prev != next) { - added[elem] = id; - elem ++; - } - return elem; + /** Get column size. */ + size_t column_size(int32_t index) const { + return ecs_table_get_column_size(table_, index); } - template - static bool invoke_get_mut(world_t *world, entity_t id, const Func& func) { - flecs::world w(world); + /** Get depth for given relationship. + * + * @param rel The relationship. + * @return The depth. + */ + int32_t depth(flecs::entity_t rel) const { + return ecs_table_get_depth(world_, table_, rel); + } - PtrArray ptrs; + /** Get depth for given relationship. + * + * @tparam Rel The relationship. + * @return The depth. + */ + template + int32_t depth() const { + return depth(_::type::id(world_)); + } - // When not deferred take the fast path. - if (!w.is_deferred()) { - // Bit of low level code so we only do at most one table move & one - // entity lookup for the entire operation. + /** Get table records array */ + ecs_table_records_t records() const { + return flecs_table_records(table_); + } - // Find table for entity - ecs_record_t *r = ecs_record_find(world, id); - ecs_table_t *table = NULL; - if (r) { - table = r->table; - } + /** Get table id. */ + uint64_t id() const { + return flecs_table_id(table_); + } - // Find destination table that has all components - ecs_table_t *prev = table, *next; - size_t elem = 0; - IdArray added; + /** Lock table. */ + void lock() const { + ecs_table_lock(world_, table_); + } - // Iterate components, only store added component ids in added array - DummyArray dummy_before ({ ( - next = ecs_table_add_id(world, prev, w.id()), - elem = store_added(added, elem, prev, next, w.id()), - prev = next, 0 - )... }); - (void)dummy_before; + /** Unlock table. */ + void unlock() const { + ecs_table_unlock(world_, table_); + } - // If table is different, move entity straight to it - if (table != next) { - ecs_ids_t ids; - ids.array = added.ptr(); - ids.count = static_cast(elem); - ecs_commit(world, id, r, next, &ids, NULL); - table = next; - } + /** Check if table has flags. */ + bool has_flags(ecs_flags32_t flags) const { + return ecs_table_has_flags(table_, flags); + } - if (!get_ptrs(w, r, table, ptrs)) { - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } + /** Get table. + * + * @return The table. + */ + table_t* get_table() const { + return table_; + } - // When deferred, obtain pointers with regular get_mut - } else { - get_mut_ptrs(world, id, ptrs); - } + /* Implicit conversion to table_t */ + operator table_t*() const { + return table_; + } - invoke_callback(func, 0, ptrs); +protected: + world_t *world_; + table_t *table_; +}; - // Call modified on each component - DummyArray dummy_after ({ - ( ecs_modified_id(world, id, w.id()), 0)... - }); - (void)dummy_after; +struct table_range : table { + table_range() + : table() + , offset_(0) + , count_(0) { } - return true; - } + table_range(world_t *world, table_t *t, int32_t offset, int32_t count) + : table(world, t) + , offset_(offset) + , count_(count) { } -private: - template = 0> - static void invoke_callback( - const Func& f, size_t, ArrayType&, TArgs&& ... comps) - { - f(*static_cast::type*>(comps)...); + int32_t offset() const { + return offset_; } - template = 0> - static void invoke_callback(const Func& f, size_t arg, ArrayType& ptrs, - TArgs&& ... comps) - { - invoke_callback(f, arg + 1, ptrs, comps..., ptrs[arg]); + int32_t count() const { + return count_; } -}; -template -struct entity_with_invoker { - static_assert(function_traits::value, "type is not callable"); -}; + /** Get pointer to component array by column index. + * + * @param index The column index. + * @return Pointer to the column, NULL if not a component. + */ + void* get_column(int32_t index) const override { + return ecs_table_get_column(table_, index, offset_); + } -template -struct entity_with_invoker::value > > - : entity_with_invoker_impl< arg_list_t > -{ - static_assert(function_traits::arity > 0, - "function must have at least one argument"); +private: + int32_t offset_ = 0; + int32_t count_ = 0; }; -} // namespace _ +/** @} */ -} // namespace flecs +} +/** + * @file addons/cpp/utils/iterable.hpp + * @brief Base class for iterable objects, like queries. + */ namespace flecs { @@ -18802,41 +31370,65 @@ struct worker_iterable; template struct iterable { + /** Each iterator. * The "each" iterator accepts a function that is invoked for each matching * entity. The following function signatures are valid: * - func(flecs::entity e, Components& ...) - * - func(flecs::iter& it, int32_t index, Components& ....) + * - func(flecs::iter& it, size_t index, Components& ....) * - func(Components& ...) - * - * Each iterators are automatically instanced. */ template void each(Func&& func) const { - iterate<_::each_invoker>(FLECS_FWD(func), - this->next_each_action()); + ecs_iter_t it = this->get_iter(nullptr); + ecs_iter_next_action_t next = this->next_action(); + while (next(&it)) { + _::each_delegate(func).invoke(&it); + } } - /** Iter iterator. - * The "iter" iterator accepts a function that is invoked for each matching - * table. The following function signatures are valid: - * - func(flecs::iter& it, Components* ...) - * - func(Components& ...) - * - * Iter iterators are not automatically instanced. When a result contains - * shared components, entities of the result will be iterated one by one. - * This ensures that applications can't accidentally read out of bounds by - * accessing a shared component as an array. + /** Run iterator. + * The "each" iterator accepts a function that is invoked once for a query + * with a valid iterator. The following signature is valid: + * - func(flecs::iter&) */ template - void iter(Func&& func) const { - iterate<_::iter_invoker>(FLECS_FWD(func), this->next_action()); + void run(Func&& func) const { + ecs_iter_t it = this->get_iter(nullptr); + _::run_delegate(func).invoke(&it); + } + + template + flecs::entity find(Func&& func) const { + ecs_iter_t it = this->get_iter(nullptr); + ecs_iter_next_action_t next = this->next_action(); + + flecs::entity result; + while (!result && next(&it)) { + result = _::find_delegate(func).invoke(&it); + } + + if (result) { + ecs_iter_fini(&it); + } + + return result; } /** Create iterator. * Create an iterator object that can be modified before iterating. */ - iter_iterable iter() const; + iter_iterable iter(flecs::world_t *world = nullptr) const; + + /** Create iterator. + * Create an iterator object that can be modified before iterating. + */ + iter_iterable iter(flecs::iter& iter) const; + + /** Create iterator. + * Create an iterator object that can be modified before iterating. + */ + iter_iterable iter(flecs::entity e) const; /** Page iterator. * Create an iterator that limits the returned entities with offset/limit. @@ -18857,62 +31449,122 @@ struct iterable { */ worker_iterable worker(int32_t index, int32_t count); + /** Return number of entities matched by iterable. */ + int32_t count() const { + return this->iter().count(); + } + + /** Return whether iterable has any matches. */ + bool is_true() const { + return this->iter().is_true(); + } + + /** Return first entity matched by iterable. */ + flecs::entity first() const { + return this->iter().first(); + } + + iter_iterable set_var(int var_id, flecs::entity_t value) const { + return this->iter().set_var(var_id, value); + } + + iter_iterable set_var(const char *name, flecs::entity_t value) const { + return this->iter().set_var(name, value); + } + + iter_iterable set_var(const char *name, flecs::table_t *value) const { + return this->iter().set_var(name, value); + } + + iter_iterable set_var(const char *name, ecs_table_range_t value) const { + return this->iter().set_var(name, value); + } + + iter_iterable set_var(const char *name, flecs::table_range value) const { + return this->iter().set_var(name, value); + } + + // Limit results to tables with specified group id (grouped queries only) + iter_iterable set_group(uint64_t group_id) const { + return this->iter().set_group(group_id); + } + + // Limit results to tables with specified group id (grouped queries only) + template + iter_iterable set_group() const { + return this->iter().template set_group(); + } + virtual ~iterable() { } protected: friend iter_iterable; friend page_iterable; friend worker_iterable; - virtual ecs_iter_t get_iter() const = 0; + virtual ecs_iter_t get_iter(flecs::world_t *stage) const = 0; virtual ecs_iter_next_action_t next_action() const = 0; - virtual ecs_iter_next_action_t next_each_action() const = 0; - - template < template class Invoker, typename Func, typename NextFunc, typename ... Args> - void iterate(Func&& func, NextFunc next, Args &&... args) const { - ecs_iter_t it = this->get_iter(); - if (Invoker::instanced()) { - ECS_BIT_SET(it.flags, EcsIterIsInstanced); - } - - while (next(&it, FLECS_FWD(args)...)) { - Invoker(func).invoke(&it); - } - } }; template struct iter_iterable final : iterable { template - iter_iterable(Iterable *it) + iter_iterable(Iterable *it, flecs::world_t *world) { - m_it = it->get_iter(); - m_next = it->next_action(); - m_next_each = it->next_action(); + it_ = it->get_iter(world); + next_ = it->next_action(); + next_each_ = it->next_action(); + ecs_assert(next_ != nullptr, ECS_INTERNAL_ERROR, NULL); + ecs_assert(next_each_ != nullptr, ECS_INTERNAL_ERROR, NULL); } -# ifdef FLECS_RULES + iter_iterable& set_var(int var_id, flecs::entity_t value) { + ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, 0); + ecs_iter_set_var(&it_, var_id, value); + return *this; + } -iter_iterable& set_var(int var_id, flecs::entity_t value) { - ecs_assert(m_it.next == ecs_rule_next, ECS_INVALID_OPERATION, NULL); - ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, 0); - ecs_iter_set_var(&m_it, var_id, value); - return *this; -} + iter_iterable& set_var(const char *name, flecs::entity_t value) { + int var_id = ecs_query_find_var(it_.query, name); + ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, name); + ecs_iter_set_var(&it_, var_id, value); + return *this; + } -iter_iterable& set_var(const char *name, flecs::entity_t value) { - ecs_assert(m_it.next == ecs_rule_next, ECS_INVALID_OPERATION, NULL); - ecs_rule_iter_t *rit = &m_it.priv.iter.rule; - int var_id = ecs_rule_find_var(rit->rule, name); - ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, name); - ecs_iter_set_var(&m_it, var_id, value); - return *this; -} + iter_iterable& set_var(const char *name, flecs::table_t *value) { + int var_id = ecs_query_find_var(it_.query, name); + ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, name); + ecs_iter_set_var_as_table(&it_, var_id, value); + return *this; + } + + iter_iterable& set_var(const char *name, ecs_table_range_t value) { + int var_id = ecs_query_find_var(it_.query, name); + ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, name); + ecs_iter_set_var_as_range(&it_, var_id, &value); + return *this; + } + + iter_iterable& set_var(const char *name, flecs::table_range value) { + ecs_table_range_t range; + range.table = value.get_table(); + range.offset = value.offset(); + range.count = value.count(); + return set_var(name, range); + } -# endif # ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/iterable.inl + * @brief JSON iterable mixin. + */ +/** Serialize iterator result to JSON. + * + * @memberof flecs::iter + * @ingroup cpp_addons_json + */ flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { - char *json = ecs_iter_to_json(m_it.real_world, &m_it, desc); + char *json = ecs_iter_to_json(&it_, desc); return flecs::string(json); } @@ -18921,73 +31573,105 @@ flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { // Return total number of entities in result. int32_t count() { int32_t result = 0; - while (m_next_each(&m_it)) { - result += m_it.count; + while (next_each_(&it_)) { + result += it_.count; } return result; } // Returns true if iterator yields at least once result. bool is_true() { - bool result = m_next_each(&m_it); + bool result = next_each_(&it_); if (result) { - ecs_iter_fini(&m_it); + ecs_iter_fini(&it_); } return result; } -protected: - ecs_iter_t get_iter() const { - return m_it; + // Return first matching entity. + flecs::entity first() { + flecs::entity result; + if (next_each_(&it_) && it_.count) { + result = flecs::entity(it_.world, it_.entities[0]); + ecs_iter_fini(&it_); + } + return result; } - ecs_iter_next_action_t next_action() const { - return m_next; + // Limit results to tables with specified group id (grouped queries only) + iter_iterable& set_group(uint64_t group_id) { + ecs_iter_set_group(&it_, group_id); + return *this; + } + + // Limit results to tables with specified group id (grouped queries only) + template + iter_iterable& set_group() { + ecs_iter_set_group(&it_, _::type().id(it_.real_world)); + return *this; + } + +protected: + ecs_iter_t get_iter(flecs::world_t *world) const override { + if (world) { + ecs_iter_t result = it_; + result.world = world; + return result; + } + return it_; } - ecs_iter_next_action_t next_each_action() const { - return m_next_each; + ecs_iter_next_action_t next_action() const override { + return next_; } private: - ecs_iter_t m_it; - ecs_iter_next_action_t m_next; - ecs_iter_next_action_t m_next_each; + ecs_iter_t it_; + ecs_iter_next_action_t next_; + ecs_iter_next_action_t next_each_; }; template -iter_iterable iterable::iter() const +iter_iterable iterable::iter(flecs::world_t *world) const { - return iter_iterable(this); + return iter_iterable(this, world); +} + +template +iter_iterable iterable::iter(flecs::iter& it) const +{ + return iter_iterable(this, it.world()); +} + +template +iter_iterable iterable::iter(flecs::entity e) const +{ + return iter_iterable(this, e.world()); } template struct page_iterable final : iterable { template page_iterable(int32_t offset, int32_t limit, Iterable *it) - : m_offset(offset) - , m_limit(limit) + : offset_(offset) + , limit_(limit) { - m_chain_it = it->get_iter(); + chain_it_ = it->get_iter(nullptr); } protected: - ecs_iter_t get_iter() const { - return ecs_page_iter(&m_chain_it, m_offset, m_limit); + ecs_iter_t get_iter(flecs::world_t*) const { + return ecs_page_iter(&chain_it_, offset_, limit_); } ecs_iter_next_action_t next_action() const { return ecs_page_next; } - ecs_iter_next_action_t next_each_action() const { - return ecs_page_next; - } - private: - ecs_iter_t m_chain_it; - int32_t m_offset; - int32_t m_limit; + ecs_iter_t chain_it_; + int32_t offset_; + int32_t limit_; }; template @@ -19001,29 +31685,25 @@ page_iterable iterable::page( template struct worker_iterable final : iterable { worker_iterable(int32_t offset, int32_t limit, iterable *it) - : m_offset(offset) - , m_limit(limit) + : offset_(offset) + , limit_(limit) { - m_chain_it = it->get_iter(); + chain_it_ = it->get_iter(nullptr); } protected: - ecs_iter_t get_iter() const { - return ecs_worker_iter(&m_chain_it, m_offset, m_limit); + ecs_iter_t get_iter(flecs::world_t*) const { + return ecs_worker_iter(&chain_it_, offset_, limit_); } ecs_iter_next_action_t next_action() const { return ecs_worker_next; } - ecs_iter_next_action_t next_each_action() const { - return ecs_worker_next; - } - private: - ecs_iter_t m_chain_it; - int32_t m_offset; - int32_t m_limit; + ecs_iter_t chain_it_; + int32_t offset_; + int32_t limit_; }; template @@ -19036,1913 +31716,2022 @@ worker_iterable iterable::worker( } + +// Mixin implementations +/** + * @file addons/cpp/mixins/id/impl.hpp + * @brief Id class implementation. + */ + #pragma once -#include -#include namespace flecs { -namespace _ { +inline flecs::entity id::entity() const { + ecs_assert(!is_pair(), ECS_INVALID_OPERATION, NULL); + ecs_assert(!flags(), ECS_INVALID_OPERATION, NULL); + return flecs::entity(world_, id_); +} -// Trick to obtain typename from type, as described here -// https://blog.molecular-matters.com/2015/12/11/getting-the-type-of-a-template-argument-as-string-without-rtti/ -// -// The code from the link has been modified to work with more types, and across -// multiple compilers. The resulting string should be the same on all platforms -// for all compilers. -// +inline flecs::entity id::flags() const { + return flecs::entity(world_, id_ & ECS_ID_FLAGS_MASK); +} -#if defined(__GNUC__) || defined(_WIN32) -template -inline static const char* type_name() { - static const size_t len = ECS_FUNC_TYPE_LEN(const char*, type_name, ECS_FUNC_NAME); - static char result[len + 1] = {}; - return ecs_cpp_get_type_name(result, ECS_FUNC_NAME, len); -} -#else -#error "implicit component registration not supported" -#endif +inline flecs::entity id::first() const { + ecs_assert(is_pair(), ECS_INVALID_OPERATION, NULL); + + flecs::entity_t e = ECS_PAIR_FIRST(id_); + if (world_) { + return flecs::entity(world_, ecs_get_alive(world_, e)); + } else { + return flecs::entity(e); + } +} + +inline flecs::entity id::second() const { + flecs::entity_t e = ECS_PAIR_SECOND(id_); + if (world_) { + return flecs::entity(world_, ecs_get_alive(world_, e)); + } else { + return flecs::entity(e); + } +} + +inline flecs::entity id::add_flags(flecs::id_t flags) const { + return flecs::entity(world_, id_ | flags); +} + +inline flecs::entity id::remove_flags(flecs::id_t flags) const { + (void)flags; + ecs_assert((id_ & ECS_ID_FLAGS_MASK) == flags, ECS_INVALID_PARAMETER, NULL); + return flecs::entity(world_, id_ & ECS_COMPONENT_MASK); +} + +inline flecs::entity id::remove_flags() const { + return flecs::entity(world_, id_ & ECS_COMPONENT_MASK); +} + +inline flecs::entity id::remove_generation() const { + return flecs::entity(world_, static_cast(id_)); +} + +inline flecs::world id::world() const { + return flecs::world(world_); +} + +inline flecs::entity id::type_id() const { + return flecs::entity(world_, ecs_get_typeid(world_, id_)); +} + + +// Id mixin implementation -// Translate a typename into a language-agnostic identifier. This allows for -// registration of components/modules across language boundaries. template -inline static const char* symbol_name() { - static const size_t len = ECS_FUNC_TYPE_LEN(const char*, symbol_name, ECS_FUNC_NAME); - static char result[len + 1] = {}; - return ecs_cpp_get_symbol_name(result, type_name(), len); +inline flecs::id world::id() const { + return flecs::id(world_, _::type::id(world_)); } -template <> inline const char* symbol_name() { - return "u8"; +template +inline flecs::id world::id(Args&&... args) const { + return flecs::id(world_, FLECS_FWD(args)...); } -template <> inline const char* symbol_name() { - return "u16"; + +template +inline flecs::id world::pair() const { + return flecs::id( + world_, + ecs_pair( + _::type::id(world_), + _::type::id(world_))); } -template <> inline const char* symbol_name() { - return "u32"; + +template +inline flecs::id world::pair(entity_t o) const { + ecs_assert(!ECS_IS_PAIR(o), ECS_INVALID_PARAMETER, + "cannot create nested pairs"); + + return flecs::id( + world_, + ecs_pair( + _::type::id(world_), + o)); } -template <> inline const char* symbol_name() { - return "u64"; + +inline flecs::id world::pair(entity_t r, entity_t o) const { + ecs_assert(!ECS_IS_PAIR(r) && !ECS_IS_PAIR(o), ECS_INVALID_PARAMETER, + "cannot create nested pairs"); + + return flecs::id( + world_, + ecs_pair(r, o)); } -template <> inline const char* symbol_name() { - return "i8"; + } -template <> inline const char* symbol_name() { - return "i16"; + +/** + * @file addons/cpp/mixins/entity/impl.hpp + * @brief Entity implementation. + */ + +#pragma once + +namespace flecs { + +inline untyped_ref::untyped_ref(flecs::entity entity, flecs::id_t id) + : untyped_ref(entity.world(), entity.id(), id) { } + +inline flecs::entity untyped_ref::entity() const { + return flecs::entity(world_, ref_.entity); } -template <> inline const char* symbol_name() { - return "i32"; + +template +flecs::ref::ref(flecs::entity entity, flecs::id_t id) + : ref(entity.world(), entity.id(), id) { } + +template +template +inline const Self& entity_builder::insert(const Func& func) const { + _::entity_with_delegate::invoke_ensure( + this->world_, this->id_, func); + return to_base(); } -template <> inline const char* symbol_name() { - return "i64"; + +template +inline Enum entity_view::get_constant() const { + flecs::entity tgt = this->target(); + return tgt.to_constant(); } -template <> inline const char* symbol_name() { - return "f32"; + +template +inline flecs::entity entity_view::target(int32_t index) const +{ + return flecs::entity(world_, + ecs_get_target(world_, id_, _::type::id(world_), index)); } -template <> inline const char* symbol_name() { - return "f64"; + +inline flecs::entity entity_view::target( + flecs::entity_t relationship, + int32_t index) const +{ + return flecs::entity(world_, + ecs_get_target(world_, id_, relationship, index)); } -// If type is trivial, don't register lifecycle actions. While the functions -// that obtain the lifecycle callback do detect whether the callback is required -// adding a special case for trivial types eases the burden a bit on the -// compiler as it reduces the number of templates to evaluate. -template::value == true - >* = nullptr> -void register_lifecycle_actions(ecs_world_t*, ecs_entity_t) { } - -// If the component is non-trivial, register component lifecycle actions. -// Depending on the type not all callbacks may be available. -template::value == false - >* = nullptr> -void register_lifecycle_actions( - ecs_world_t *world, - ecs_entity_t component) +inline flecs::entity entity_view::target_for( + flecs::entity_t relationship, + flecs::id_t id) const { - if (!ecs_component_has_actions(world, component)) { - EcsComponentLifecycle cl{}; - cl.ctor = ctor(); - cl.dtor = dtor(); + return flecs::entity(world_, + ecs_get_target_for_id(world_, id_, relationship, id)); +} + +template +inline flecs::entity entity_view::target_for(flecs::entity_t relationship) const { + return target_for(relationship, _::type::id(world_)); +} + +template +inline flecs::entity entity_view::target_for(flecs::entity_t relationship) const { + return target_for(relationship, _::type::id(world_)); +} + +inline flecs::entity entity_view::parent() const { + return flecs::entity(world_, ecs_get_parent(world_, id_)); +} + +inline flecs::entity entity_view::mut(const flecs::world& stage) const { + ecs_assert(!stage.is_readonly(), ECS_INVALID_PARAMETER, + "cannot use readonly world/stage to create mutable handle"); + return flecs::entity(id_).set_stage(stage.c_ptr()); +} + +inline flecs::entity entity_view::mut(const flecs::iter& it) const { + ecs_assert(!it.world().is_readonly(), ECS_INVALID_PARAMETER, + "cannot use iterator created for readonly world/stage to create mutable handle"); + return flecs::entity(id_).set_stage(it.world().c_ptr()); +} + +inline flecs::entity entity_view::mut(const flecs::entity_view& e) const { + ecs_assert(!e.world().is_readonly(), ECS_INVALID_PARAMETER, + "cannot use entity created for readonly world/stage to create mutable handle"); + return flecs::entity(id_).set_stage(e.world_); +} - cl.copy = copy(); - cl.copy_ctor = copy_ctor(); - cl.move = move(); - cl.move_ctor = move_ctor(); +inline flecs::entity entity_view::set_stage(world_t *stage) { + return flecs::entity(stage, id_); +} + +inline flecs::type entity_view::type() const { + return flecs::type(world_, ecs_get_type(world_, id_)); +} - cl.ctor_move_dtor = ctor_move_dtor(); - cl.move_dtor = move_dtor(); +inline flecs::table entity_view::table() const { + return flecs::table(world_, ecs_get_table(world_, id_)); +} - ecs_set_component_actions_w_id( world, component, &cl); +inline flecs::table_range entity_view::range() const { + ecs_record_t *r = ecs_record_find(world_, id_); + if (r) { + return flecs::table_range(world_, r->table, + ECS_RECORD_TO_ROW(r->row), 1); } + return flecs::table_range(); } -// Class that manages component ids across worlds & binaries. -// The cpp_type class stores the component id for a C++ type in a static global -// variable that is shared between worlds. Whenever a component is used this -// class will check if it already has been registered (has the global id been -// set), and if not, register the component with the world. -// -// If the id has been set, the class will ensure it is known by the world. If it -// is not known the component has been registered by another world and will be -// registered with the world using the same id. If the id does exist, the class -// will register it as a component, and verify whether the input is consistent. -template -struct cpp_type_impl { - // Initialize component identifier - static void init( - world_t* world, - entity_t entity, - bool allow_tag = true) +template +inline void entity_view::each(const Func& func) const { + const ecs_type_t *type = ecs_get_type(world_, id_); + if (!type) { + return; + } + + const ecs_id_t *ids = type->array; + int32_t count = type->count; + + for (int i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + flecs::id ent(world_, id); + func(ent); + } +} + +template +inline void entity_view::each(flecs::id_t pred, flecs::id_t obj, const Func& func) const { + flecs::world_t *real_world = const_cast( + ecs_get_world(world_)); + + const ecs_table_t *table = ecs_get_table(world_, id_); + if (!table) { + return; + } + + const ecs_type_t *type = ecs_table_get_type(table); + if (!type) { + return; + } + + flecs::id_t pattern = pred; + if (obj) { + pattern = ecs_pair(pred, obj); + } + + int32_t cur = 0; + id_t *ids = type->array; + + while (-1 != (cur = ecs_search_offset(real_world, table, cur, pattern, 0))) { - if (s_reset_count != ecs_cpp_reset_count_get()) { - reset(); - } + flecs::id ent(world_, ids[cur]); + func(ent); + cur ++; + } +} - // If an identifier was already set, check for consistency - if (s_id) { - ecs_assert(s_name.c_str() != nullptr, ECS_INTERNAL_ERROR, NULL); - ecs_assert(s_id == entity, ECS_INCONSISTENT_COMPONENT_ID, - type_name()); - ecs_assert(allow_tag == s_allow_tag, ECS_INVALID_PARAMETER, NULL); +template +inline void entity_view::each(const flecs::entity_view& rel, const Func& func) const { + return this->each(rel, flecs::Wildcard, [&](flecs::id id) { + flecs::entity obj = id.second(); + func(obj); + }); +} - // Component was already registered and data is consistent with new - // identifier, so nothing else to be done. - return; - } +template ::value > > +inline bool entity_view::get(const Func& func) const { + return _::entity_with_delegate::invoke_get(world_, id_, func); +} - // Component wasn't registered yet, set the values. Register component - // name as the fully qualified flecs path. - char *path = ecs_get_fullpath(world, entity); - s_id = entity; - s_name = flecs::string(path); - s_allow_tag = allow_tag; - s_size = sizeof(T); - s_alignment = alignof(T); - if (is_empty::value && allow_tag) { - s_size = 0; - s_alignment = 0; - } +inline flecs::entity entity_view::lookup(const char *path, bool search_path) const { + ecs_assert(id_ != 0, ECS_INVALID_PARAMETER, "invalid lookup from null handle"); + auto id = ecs_lookup_path_w_sep(world_, id_, path, "::", "::", search_path); + return flecs::entity(world_, id); +} - s_reset_count = ecs_cpp_reset_count_get(); +inline flecs::entity entity_view::clone(bool copy_value, flecs::entity_t dst_id) const { + if (!dst_id) { + dst_id = ecs_new(world_); } - // Obtain a component identifier for explicit component registration. - static entity_t id_explicit(world_t *world = nullptr, - const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0, - bool is_component = true) - { - if (!s_id) { - // If no world was provided the component cannot be registered - ecs_assert(world != nullptr, ECS_COMPONENT_NOT_REGISTERED, name); - } else { - ecs_assert(!id || s_id == id, ECS_INCONSISTENT_COMPONENT_ID, NULL); - ecs_assert(s_allow_tag == allow_tag, ECS_INVALID_PARAMETER, NULL); - } + flecs::entity dst = flecs::entity(world_, dst_id); + ecs_clone(world_, dst_id, id_, copy_value); + return dst; +} + +// Entity mixin implementation +template +inline flecs::entity world::entity(Args &&... args) const { + return flecs::entity(world_, FLECS_FWD(args)...); +} + +template ::value >> +inline flecs::id world::id(E value) const { + flecs::entity_t constant = enum_type(world_).entity(value); + return flecs::id(world_, constant); +} + +template ::value >> +inline flecs::entity world::entity(E value) const { + flecs::entity_t constant = enum_type(world_).entity(value); + return flecs::entity(world_, constant); +} + +template +inline flecs::entity world::entity(const char *name) const { + return flecs::entity(world_, _::type::register_id(world_, name, true, false) ); +} + +template +inline flecs::entity world::prefab(Args &&... args) const { + flecs::entity result = flecs::entity(world_, FLECS_FWD(args)...); + result.add(flecs::Prefab); + return result; +} + +template +inline flecs::entity world::prefab(const char *name) const { + flecs::entity result = this->entity(name); + result.add(flecs::Prefab); + return result; +} - // If no id has been registered yet for the component (indicating the - // component has not yet been registered, or the component is used - // across more than one binary), or if the id does not exists in the - // world (indicating a multi-world application), register it. */ - if (!s_id || (world && !ecs_exists(world, s_id))) { - if (!s_id) { - s_id = id; - } +} - ecs_assert(!id || s_id == id, ECS_INTERNAL_ERROR, NULL); +/** + * @file addons/cpp/mixins/component/impl.hpp + * @brief Component mixin implementation + */ - init(world, s_id, allow_tag); +#pragma once - entity_t entity = ecs_cpp_component_register_explicit( - world, s_id, id, name, type_name(), symbol_name(), - s_size, s_alignment, is_component); +namespace flecs { - s_id = entity; +template +inline flecs::component world::component(Args &&... args) const { + return flecs::component(world_, FLECS_FWD(args)...); +} - // If component is enum type, register constants - _::init_enum(world, entity); - } +template +inline flecs::untyped_component world::component(Args &&... args) const { + return flecs::untyped_component(world_, FLECS_FWD(args)...); +} - // By now the identifier must be valid and known with the world. - ecs_assert(s_id != 0 && ecs_exists(world, s_id), - ECS_INTERNAL_ERROR, NULL); +} // namespace flecs - return s_id; - } +/** + * @file addons/cpp/mixins/term/impl.hpp + * @brief Term implementation. + */ - // Obtain a component identifier for implicit component registration. This - // is almost the same as id_explicit, except that this operation - // automatically registers lifecycle callbacks. - // Additionally, implicit registration temporarily resets the scope & with - // state of the world, so that the component is not implicitly created with - // the scope/with of the code it happens to be first used by. - static id_t id(world_t *world = nullptr, const char *name = nullptr, - bool allow_tag = true) - { - // If no id has been registered yet, do it now. - if (!registered() || (world && !ecs_exists(world, s_id))) { - ecs_entity_t prev_scope = 0; - ecs_id_t prev_with = 0; - - if (world) { - prev_scope = ecs_set_scope(world, 0); - prev_with = ecs_set_with(world, 0); - } - - // This will register a component id, but will not register - // lifecycle callbacks. - id_explicit(world, name, allow_tag); +#pragma once - // Register lifecycle callbacks, but only if the component has a - // size. Components that don't have a size are tags, and tags don't - // require construction/destruction/copy/move's. */ - if (size()) { - register_lifecycle_actions(world, s_id); - } - - if (prev_with) { - ecs_set_with(world, prev_with); - } - if (prev_scope) { - ecs_set_scope(world, prev_scope); - } - } +/** + * @file addons/cpp/mixins/term/builder_i.hpp + * @brief Term builder interface. + */ - // By now we should have a valid identifier - ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); +#pragma once - return s_id; - } +/** + * @file addons/cpp/utils/signature.hpp + * @brief Compile time utilities for deriving query attributes from param pack. + */ - // Obtain a component name - static const char* name(world_t *world = nullptr) { - // If no id has been registered yet, do it now. - if (!s_id) { - id(world); - } +#pragma once - // By now we should have a valid identifier - ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); +namespace flecs { +namespace _ { - // If the id is set, the name should also have been set - return s_name.c_str(); + template ::value > = 0> + constexpr flecs::inout_kind_t type_to_inout() { + return flecs::In; } - // Return the size of a component. - static size_t size() { - ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); - return s_size; + template ::value > = 0> + constexpr flecs::inout_kind_t type_to_inout() { + return flecs::InOut; } - // Return the alignment of a component. - static size_t alignment() { - ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); - return s_alignment; + template ::value || is_reference::value > = 0> + constexpr flecs::inout_kind_t type_to_inout() { + return flecs::InOutDefault; } - // Was the component already registered. - static bool registered() { - if (s_reset_count != ecs_cpp_reset_count_get()) { - reset(); - } - return s_id != 0; + template ::value > = 0> + constexpr flecs::oper_kind_t type_to_oper() { + return flecs::Optional; } - // This function is only used to test cross-translation unit features. No - // code other than test cases should invoke this function. - static void reset() { - s_id = 0; - s_size = 0; - s_alignment = 0; - s_allow_tag = true; - s_name.clear(); + template ::value > = 0> + constexpr flecs::oper_kind_t type_to_oper() { + return flecs::And; } - static entity_t s_id; - static flecs::string s_name; - static size_t s_size; - static size_t s_alignment; - static bool s_allow_tag; - static int32_t s_reset_count; -}; - -// Global templated variables that hold component identifier and other info -template entity_t cpp_type_impl::s_id; -template flecs::string cpp_type_impl::s_name; -template size_t cpp_type_impl::s_size; -template size_t cpp_type_impl::s_alignment; -template bool cpp_type_impl::s_allow_tag( true ); -template int32_t cpp_type_impl::s_reset_count; - -// Front facing class for implicitly registering a component & obtaining -// static component data + template + struct sig { + sig(flecs::world_t *world) + : world_(world) + , ids({ (_::type>::id(world))... }) + , inout ({ (type_to_inout())... }) + , oper ({ (type_to_oper())... }) + { } -// Regular type -template -struct cpp_type::value >> - : cpp_type_impl> { }; + flecs::world_t *world_; + flecs::array ids; + flecs::array inout; + flecs::array oper; -// Pair type -template -struct cpp_type::value >> -{ - // Override id method to return id of pair - static id_t id(world_t *world = nullptr) { - return ecs_pair( - cpp_type< pair_relation_t >::id(world), - cpp_type< pair_object_t >::id(world)); - } -}; + template + void populate(const Builder& b) { + size_t i = 0; + for (auto id : ids) { + b->with(id).inout(inout[i]).oper(oper[i]); + i ++; + } + } + }; } // namespace _ +} // namespace flecs -struct untyped_component : entity { - using entity::entity; - -# ifdef FLECS_META - -/** Add member. */ -untyped_component& member(flecs::entity_t type_id, const char *name, int32_t count = 0, size_t offset = 0) { - ecs_entity_desc_t desc = {}; - desc.name = name; - desc.add[0] = ecs_pair(flecs::ChildOf, m_id); - ecs_entity_t eid = ecs_entity_init(m_world, &desc); - ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); +#include - flecs::entity e(m_world, eid); +namespace flecs +{ - Member m = {}; - m.type = type_id; - m.count = count; - m.offset = static_cast(offset); - e.set(m); +/** Term identifier builder. + * A term identifier describes a single identifier in a term. Identifier + * descriptions can reference entities by id, name or by variable, which means + * the entity will be resolved when the term is evaluated. + * + * @ingroup cpp_core_queries + */ +template +struct term_ref_builder_i { + term_ref_builder_i() : term_ref_(nullptr) { } - return *this; -} + virtual ~term_ref_builder_i() { } -/** Add member with unit. */ -untyped_component& member(flecs::entity_t type_id, flecs::entity_t unit, const char *name, int32_t count = 0, size_t offset = 0) { - ecs_entity_desc_t desc = {}; - desc.name = name; - desc.add[0] = ecs_pair(flecs::ChildOf, m_id); - ecs_entity_t eid = ecs_entity_init(m_world, &desc); - ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); + /* The self flag indicates the term identifier itself is used */ + Base& self() { + this->assert_term_ref(); + term_ref_->id |= flecs::Self; + return *this; + } - flecs::entity e(m_world, eid); + /* Specify value of identifier by id */ + Base& id(flecs::entity_t id) { + this->assert_term_ref(); + term_ref_->id = id; + return *this; + } - Member m = {}; - m.type = type_id; - m.unit = unit; - m.count = count; - m.offset = static_cast(offset); - e.set(m); + /* Specify value of identifier by id. Almost the same as id(entity), but this + * operation explicitly sets the flecs::IsEntity flag. This forces the id to + * be interpreted as entity, whereas not setting the flag would implicitly + * convert ids for builtin variables such as flecs::This to a variable. + * + * This function can also be used to disambiguate id(0), which would match + * both id(entity_t) and id(const char*). + */ + Base& entity(flecs::entity_t entity) { + this->assert_term_ref(); + term_ref_->id = entity | flecs::IsEntity; + return *this; + } - return *this; -} + /* Specify value of identifier by name */ + Base& name(const char *name) { + this->assert_term_ref(); + term_ref_->id |= flecs::IsEntity; + term_ref_->name = const_cast(name); + return *this; + } -/** Add member. */ -template -untyped_component& member(const char *name, int32_t count = 0, size_t offset = 0) { - flecs::entity_t type_id = _::cpp_type::id(m_world); - return member(type_id, name, count, offset); -} + /* Specify identifier is a variable (resolved at query evaluation time) */ + Base& var(const char *var_name) { + this->assert_term_ref(); + term_ref_->id |= flecs::IsVariable; + term_ref_->name = const_cast(var_name); + return *this; + } -/** Add member with unit. */ -template -untyped_component& member(flecs::entity_t unit, const char *name, int32_t count = 0, size_t offset = 0) { - flecs::entity_t type_id = _::cpp_type::id(m_world); - return member(type_id, unit, name, count, offset); -} + /* Override term id flags */ + Base& flags(flecs::flags64_t flags) { + this->assert_term_ref(); + term_ref_->id = flags; + return *this; + } -/** Add member with unit. */ -template -untyped_component& member(const char *name, int32_t count = 0, size_t offset = 0) { - flecs::entity_t type_id = _::cpp_type::id(m_world); - flecs::entity_t unit_id = _::cpp_type::id(m_world); - return member(type_id, unit_id, name, count, offset); -} + ecs_term_ref_t *term_ref_; -/** Add constant. */ -untyped_component& constant(const char *name, int32_t value) { - ecs_add_id(m_world, m_id, _::cpp_type::id(m_world)); +protected: + virtual flecs::world_t* world_v() = 0; - ecs_entity_desc_t desc = {}; - desc.name = name; - desc.add[0] = ecs_pair(flecs::ChildOf, m_id); - ecs_entity_t eid = ecs_entity_init(m_world, &desc); - ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); + void assert_term_ref() { + ecs_assert(term_ref_ != NULL, ECS_INVALID_PARAMETER, + "no active term (call .with() first)"); + } - ecs_set_id(m_world, eid, - ecs_pair(flecs::Constant, flecs::I32), sizeof(int32_t), - &value); +private: + operator Base&() { + return *static_cast(this); + } +}; - return *this; -} +/** Term builder interface. + * A term is a single element of a query expression. + * + * @ingroup cpp_core_queries + */ +template +struct term_builder_i : term_ref_builder_i { + term_builder_i() : term_(nullptr) { } -/** Add bitmask constant. */ -untyped_component& bit(const char *name, uint32_t value) { - ecs_add_id(m_world, m_id, _::cpp_type::id(m_world)); + term_builder_i(ecs_term_t *term_ptr) { + set_term(term_ptr); + } - ecs_entity_desc_t desc = {}; - desc.name = name; - desc.add[0] = ecs_pair(flecs::ChildOf, m_id); - ecs_entity_t eid = ecs_entity_init(m_world, &desc); - ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); + Base& term(id_t id) { + return this->id(id); + } - ecs_set_id(m_world, eid, - ecs_pair(flecs::Constant, flecs::U32), sizeof(uint32_t), - &value); + /* Call prior to setting values for src identifier */ + Base& src() { + this->assert_term(); + this->term_ref_ = &term_->src; + return *this; + } - return *this; -} + /* Call prior to setting values for first identifier. This is either the + * component identifier, or first element of a pair (in case second is + * populated as well). */ + Base& first() { + this->assert_term(); + this->term_ref_ = &term_->first; + return *this; + } -# endif -}; + /* Call prior to setting values for second identifier. This is the second + * element of a pair. Requires that first() is populated as well. */ + Base& second() { + this->assert_term(); + this->term_ref_ = &term_->second; + return *this; + } -template -struct component : untyped_component { - /** Register a component. - * If the component was already registered, this operation will return a handle - * to the existing component. - * - * @param world The world for which to register the component. - * @param name Optional name (overrides typename). - * @param allow_tag If true, empty types will be registered with size 0. - * @param id Optional id to register component with. - */ - component( - flecs::world_t *world, - const char *name = nullptr, - bool allow_tag = true, - flecs::id_t id = 0) - { - const char *n = name; - bool implicit_name = false; - if (!n) { - n = _::type_name(); - - /* Keep track of whether name was explicitly set. If not, and the - * component was already registered, just use the registered name. - * - * The registered name may differ from the typename as the registered - * name includes the flecs scope. This can in theory be different from - * the C++ namespace though it is good practice to keep them the same */ - implicit_name = true; - } + /* Select src identifier, initialize it with entity id */ + Base& src(flecs::entity_t id) { + this->src(); + this->id(id); + return *this; + } - if (_::cpp_type::registered()) { - /* Obtain component id. Because the component is already registered, - * this operation does nothing besides returning the existing id */ - id = _::cpp_type::id_explicit(world, name, allow_tag, id); + /* Select src identifier, initialize it with id associated with type */ + template + Base& src() { + this->src(_::type::id(this->world_v())); + return *this; + } - ecs_cpp_component_validate(world, id, n, - _::cpp_type::size(), - _::cpp_type::alignment(), - implicit_name); + /* Select src identifier, initialize it with name. If name starts with a $ + * the name is interpreted as a variable. */ + Base& src(const char *name) { + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + this->src(); + if (name[0] == '$') { + this->var(&name[1]); } else { - /* If component is registered from an existing scope, ignore the - * namespace in the name of the component. */ - if (implicit_name && (ecs_get_scope(world) != 0)) { - const char *last_elem = strrchr(n, ':'); - if (last_elem) { - name = last_elem + 1; - implicit_name = false; - } - } - - /* Find or register component */ - id = ecs_cpp_component_register(world, id, n, _::symbol_name(), - ECS_SIZEOF(T), ECS_ALIGNOF(T)); - - /* Initialize static component data */ - id = _::cpp_type::id_explicit(world, name, allow_tag, id); - } - - /* Initialize lifecycle actions (ctor, dtor, copy, move) */ - if (_::cpp_type::size()) { - _::register_lifecycle_actions(world, id); + this->name(name); } - - m_world = world; - m_id = id; + return *this; } -}; -/** Get id currently assigned to component. If no world has registered the - * component yet, this operation will return 0. */ -template -flecs::entity_t type_id() { - if (_::cpp_type::s_reset_count == ecs_cpp_reset_count_get()) { - return _::cpp_type::s_id; - } else { - return 0; + /* Select first identifier, initialize it with entity id */ + Base& first(flecs::entity_t id) { + this->first(); + this->id(id); + return *this; } -} -/** Reset static component variables. - * When components are registered their component ids are stored in a static - * type specific variable. This stored id is passed into component registration - * functions to ensure consistent ids across worlds. - * - * In some cases this can be undesirable, like when a process repeatedly creates - * worlds with different components. A typical example where this can happen is - * when running multiple tests in a single process, where each test registers - * its own set of components. - * - * This operation can be used to prevent reusing of component ids and force - * generating a new ids upon registration. - * - * Note that this operation should *never* be called while there are still - * alive worlds in a process. Doing so results in undefined behavior. - * - * Also note that this operation does not actually change the static component - * variables. It only ensures that the next time a component id is requested, a - * new id will be generated. - */ -inline void reset() { - ecs_cpp_reset_count_inc(); -} + /* Select first identifier, initialize it with id associated with type */ + template + Base& first() { + this->first(_::type::id(this->world_v())); + return *this; + } -} + /* Select first identifier, initialize it with name. If name starts with a $ + * the name is interpreted as a variable. */ + Base& first(const char *name) { + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + this->first(); + if (name[0] == '$') { + this->var(&name[1]); + } else { + this->name(name); + } + return *this; + } -#pragma once + /* Select second identifier, initialize it with entity id */ + Base& second(flecs::entity_t id) { + this->second(); + this->id(id); + return *this; + } -namespace flecs { + /* Select second identifier, initialize it with id associated with type */ + template + Base& second() { + this->second(_::type::id(this->world_v())); + return *this; + } -template -struct type_base { - explicit type_base( - world_t *world, const char *name = nullptr, const char *expr = nullptr) - { - ecs_type_desc_t desc = {}; - desc.entity.name = name; - desc.ids_expr = expr; - m_entity = flecs::entity(world, ecs_type_init(world, &desc)); - sync_from_flecs(); - } - - explicit type_base( - world_t *world, entity_t e) - { - ecs_type_desc_t desc = {}; - desc.entity.entity = e; - m_entity = flecs::entity(world, ecs_type_init(world, &desc)); - ecs_assert(!e || e == m_entity, ECS_INTERNAL_ERROR, nullptr); - sync_from_flecs(); - } - - explicit type_base(world_t *world, table_t *t) - : m_entity( flecs::entity::null(world) ) - , m_table( t ) - { } - - type_base(table_t *t) - : m_table( t ) { } - - Base& add(id_t id) { - if (!m_table) { - for (auto type_id : this->vector()) { - m_table = ecs_table_add_id(world(), m_table, type_id); - } + /* Select second identifier, initialize it with name. If name starts with a $ + * the name is interpreted as a variable. */ + Base& second(const char *name) { + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + this->second(); + if (name[0] == '$') { + this->var(&name[1]); + } else { + this->name(name); } + return *this; + } - m_table = ecs_table_add_id(world(), m_table, id); - sync_from_me(); + /* The up flag indicates that the term identifier may be substituted by + * traversing a relationship upwards. For example: substitute the identifier + * with its parent by traversing the ChildOf relationship. */ + Base& up(flecs::entity_t trav = 0) { + this->assert_term_ref(); + ecs_check(this->term_ref_ != &term_->first, ECS_INVALID_PARAMETER, + "up traversal can only be applied to term source"); + ecs_check(this->term_ref_ != &term_->second, ECS_INVALID_PARAMETER, + "up traversal can only be applied to term source"); + this->term_ref_->id |= flecs::Up; + if (trav) { + term_->trav = trav; + } + error: return *this; } - template - Base& add() { - return this->add(_::cpp_type::id(world())); + template + Base& up() { + return this->up(_::type::id(this->world_v())); } - Base& add(entity_t relation, entity_t object) { - return this->add(ecs_pair(relation, object)); + /* The cascade flag is like up, but returns results in breadth-first order. + * Only supported for flecs::query */ + Base& cascade(flecs::entity_t trav = 0) { + this->assert_term_ref(); + this->up(); + this->term_ref_->id |= flecs::Cascade; + if (trav) { + term_->trav = trav; + } + return *this; } - template - Base& add() { - return this->add(_::cpp_type::id(world())); + template + Base& cascade() { + return this->cascade(_::type::id(this->world_v())); } - Base& is_a(entity_t object) { - return this->add(flecs::IsA, object); + /* Use with cascade to iterate results in descending (bottom -> top) order */ + Base& desc() { + this->assert_term_ref(); + this->term_ref_->id |= flecs::Desc; + return *this; } - Base& child_of(entity_t object) { - return this->add(flecs::ChildOf, object); - } + /* Same as up(), exists for backwards compatibility */ + Base& parent() { + return this->up(); + } - template - Base& add(entity_t object) { - return this->add(_::cpp_type::id(world()), object); - } + /* Specify relationship to traverse, and flags to indicate direction */ + Base& trav(flecs::entity_t trav, flecs::flags32_t flags = 0) { + this->assert_term_ref(); + term_->trav = trav; + this->term_ref_->id |= flags; + return *this; + } - template - Base& add_w_object(entity_t relation) { - return this->add(relation, _::cpp_type::id(world())); + /** Set id flags for term. */ + Base& id_flags(id_t flags) { + this->assert_term(); + term_->id |= flags; + return *this; } - bool has(id_t id) { - const flecs::world_t *w = ecs_get_world(world()); - return ecs_search(w, m_table, id, 0) != -1; + /** Set read/write access of term. */ + Base& inout(flecs::inout_kind_t inout) { + this->assert_term(); + term_->inout = static_cast(inout); + return *this; } - bool has(id_t relation, id_t object) { - return this->has(ecs_pair(relation, object)); - } + /** Set read/write access for stage. Use this when a system reads or writes + * components other than the ones provided by the query. This information + * can be used by schedulers to insert sync/merge points between systems + * where deferred operations are flushed. + * + * Setting this is optional. If not set, the value of the accessed component + * may be out of sync for at most one frame. + */ + Base& inout_stage(flecs::inout_kind_t inout) { + this->assert_term(); + term_->inout = static_cast(inout); + if (term_->oper != EcsNot) { + this->src().entity(0); + } + return *this; + } - template - bool has() { - return this->has(_::cpp_type::id(world())); + /** Short for inout_stage(flecs::Out). + * Use when system uses add, remove or set. + */ + Base& write() { + return this->inout_stage(flecs::Out); } - template - bool has() { - return this->has(_::cpp_type>::id(world())); + /** Short for inout_stage(flecs::In). + * Use when system uses get. + */ + Base& read() { + return this->inout_stage(flecs::In); } - flecs::string str() const { - const flecs::world_t *w = ecs_get_world(world()); - char *str = ecs_type_str(w, ecs_table_get_type(m_table)); - return flecs::string(str); + /** Short for inout_stage(flecs::InOut). + * Use when system uses ensure. + */ + Base& read_write() { + return this->inout_stage(flecs::InOut); } - type_t c_ptr() const { - return ecs_table_get_type(m_table); + /** Short for inout(flecs::In) */ + Base& in() { + return this->inout(flecs::In); } - flecs::id_t id() const { - return m_entity.id(); + /** Short for inout(flecs::Out) */ + Base& out() { + return this->inout(flecs::Out); } - flecs::entity entity() const { - return m_entity; + /** Short for inout(flecs::InOut) */ + Base& inout() { + return this->inout(flecs::InOut); } - flecs::world world() const { - return m_entity.world(); - } + /** Short for inout(flecs::In) */ + Base& inout_none() { + return this->inout(flecs::InOutNone); + } - void enable() const { - ecs_enable(world(), id(), true); + /** Set operator of term. */ + Base& oper(flecs::oper_kind_t oper) { + this->assert_term(); + term_->oper = static_cast(oper); + return *this; } - void disable() const { - ecs_enable(world(), id(), false); + /* Short for oper(flecs::And) */ + Base& and_() { + return this->oper(flecs::And); } - flecs::vector vector() { - return flecs::vector( const_cast( - ecs_table_get_type(m_table))); + /* Short for oper(flecs::Or) */ + Base& or_() { + return this->oper(flecs::Or); } - flecs::id get(int32_t index) { - const flecs::world_t *w = ecs_get_world(world()); - return flecs::id(const_cast(w), vector().get(index)); + /* Short for oper(flecs::Or) */ + Base& not_() { + return this->oper(flecs::Not); } - size_t count() { - return vector().count(); + /* Short for oper(flecs::Or) */ + Base& optional() { + return this->oper(flecs::Optional); } - /* Implicit conversion to type_t */ - operator type_t() const { return ecs_table_get_type(m_table); } + /* Short for oper(flecs::AndFrom) */ + Base& and_from() { + return this->oper(flecs::AndFrom); + } - operator Base&() { return *static_cast(this); } + /* Short for oper(flecs::OrFrom) */ + Base& or_from() { + return this->oper(flecs::OrFrom); + } -private: - void sync_from_me() { - if (!id()) { - return; - } + /* Short for oper(flecs::NotFrom) */ + Base& not_from() { + return this->oper(flecs::NotFrom); + } - EcsType *tc = ecs_get_mut(world(), id(), EcsType, NULL); - ecs_assert(tc != NULL, ECS_INTERNAL_ERROR, NULL); - tc->type = ecs_table_get_type(m_table); - tc->normalized = m_table; - ecs_modified(world(), id(), EcsType); + /* Query terms are not triggered on by observers */ + Base& filter() { + term_->inout = EcsInOutFilter; + return *this; } - void sync_from_flecs() { - if (!id()) { - return; - } + ecs_term_t *term_; - const EcsType *tc = ecs_get(world(), id(), EcsType); - if (!tc) { - m_table = nullptr; +protected: + virtual flecs::world_t* world_v() override = 0; + + void set_term(ecs_term_t *term) { + term_ = term; + if (term) { + this->term_ref_ = &term_->src; // default to subject } else { - m_table = tc->normalized; + this->term_ref_ = nullptr; } } - flecs::entity m_entity; - table_t *m_table = nullptr; -}; +private: + void assert_term() { + ecs_assert(term_ != NULL, ECS_INVALID_PARAMETER, + "no active term (call .with() first)"); + } -struct type : type_base { - using type_base::type_base; + operator Base&() { + return *static_cast(this); + } }; } -// Mixin implementations - namespace flecs { -inline flecs::entity id::entity() const { - ecs_assert(!is_pair(), ECS_INVALID_OPERATION, NULL); - ecs_assert(!role(), ECS_INVALID_OPERATION, NULL); - return flecs::entity(m_world, m_id); -} +/** Class that describes a term. + * + * @ingroup cpp_core_queries + */ +struct term final : term_builder_i { + term() + : term_builder_i(&value) + , value({}) + , world_(nullptr) { } -inline flecs::entity id::role() const { - return flecs::entity(m_world, m_id & ECS_ROLE_MASK); -} + term(flecs::world_t *world_ptr) + : term_builder_i(&value) + , value({}) + , world_(world_ptr) { } -inline flecs::entity id::first() const { - ecs_assert(is_pair(), ECS_INVALID_OPERATION, NULL); + term(flecs::world_t *world_ptr, ecs_term_t t) + : term_builder_i(&value) + , value({}) + , world_(world_ptr) { + value = t; + this->set_term(&value); + } - flecs::entity_t e = ECS_PAIR_FIRST(m_id); - if (m_world) { - return flecs::entity(m_world, ecs_get_alive(m_world, e)); - } else { - return flecs::entity(e); - } -} + term(flecs::world_t *world_ptr, id_t component_id) + : term_builder_i(&value) + , value({}) + , world_(world_ptr) { + if (component_id & ECS_ID_FLAGS_MASK) { + value.id = component_id; + } else { + value.first.id = component_id; + } + this->set_term(&value); + } -inline flecs::entity id::second() const { - flecs::entity_t e = ECS_PAIR_SECOND(m_id); - if (m_world) { - return flecs::entity(m_world, ecs_get_alive(m_world, e)); - } else { - return flecs::entity(m_world, e); - } -} + term(flecs::world_t *world_ptr, entity_t first, entity_t second) + : term_builder_i(&value) + , value({}) + , world_(world_ptr) { + value.id = ecs_pair(first, second); + this->set_term(&value); + } -inline flecs::entity id::relation() const { - return first(); -} + term(id_t component_id) + : term_builder_i(&value) + , value({}) + , world_(nullptr) { + if (component_id & ECS_ID_FLAGS_MASK) { + value.id = component_id; + } else { + value.first.id = component_id; + } + } -inline flecs::entity id::object() const { - return second(); -} + term(id_t first, id_t second) + : term_builder_i(&value) + , value({}) + , world_(nullptr) { + value.first.id = first; + value.second.id = second; + } -inline flecs::entity id::add_role(flecs::id_t role) const { - return flecs::entity(m_world, m_id | role); -} + void reset() { + value = {}; + this->set_term(nullptr); + } -inline flecs::entity id::remove_role(flecs::id_t role) const { - (void)role; - ecs_assert((m_id & ECS_ROLE_MASK) == role, ECS_INVALID_PARAMETER, NULL); - return flecs::entity(m_world, m_id & ECS_COMPONENT_MASK); -} + bool is_set() { + return ecs_term_is_initialized(&value); + } -inline flecs::entity id::remove_role() const { - return flecs::entity(m_world, m_id & ECS_COMPONENT_MASK); -} + flecs::id id() { + return flecs::id(world_, value.id); + } -inline flecs::entity id::remove_generation() const { - return flecs::entity(m_world, static_cast(m_id)); -} + flecs::inout_kind_t inout() { + return static_cast(value.inout); + } -inline flecs::world id::world() const { - return flecs::world(m_world); -} + flecs::oper_kind_t oper() { + return static_cast(value.oper); + } + flecs::entity get_src() { + return flecs::entity(world_, ECS_TERM_REF_ID(&value.src)); + } -// Id mixin implementation + flecs::entity get_first() { + return flecs::entity(world_, ECS_TERM_REF_ID(&value.first)); + } -template -inline flecs::id world::id() const { - return flecs::id(m_world, _::cpp_type::id(m_world)); -} + flecs::entity get_second() { + return flecs::entity(world_, ECS_TERM_REF_ID(&value.second)); + } -template -inline flecs::id world::id(Args&&... args) const { - return flecs::id(m_world, FLECS_FWD(args)...); -} + operator flecs::term_t() const { + return value; + } -template -inline flecs::id world::pair() const { - return flecs::id( - m_world, - ecs_pair( - _::cpp_type::id(m_world), - _::cpp_type::id(m_world))); -} + flecs::term_t value; -template -inline flecs::id world::pair(entity_t o) const { - return flecs::id( - m_world, - ecs_pair( - _::cpp_type::id(m_world), - o)); -} +protected: + flecs::world_t* world_v() override { return world_; } -inline flecs::id world::pair(entity_t r, entity_t o) const { - return flecs::id( - m_world, - ecs_pair(r, o)); -} +private: + flecs::world_t *world_; +}; +// Term mixin implementation +template +inline flecs::term world::term(Args &&... args) const { + return flecs::term(world_, FLECS_FWD(args)...); } -#pragma once - -namespace flecs { - template -flecs::entity ref::entity() const { - return flecs::entity(m_world, m_entity); +inline flecs::term world::term() const { + return flecs::term(world_, _::type::id(world_)); } -template -inline Self& entity_builder::add_switch(const flecs::type& sw) { - return add_switch(sw.id()); +template +inline flecs::term world::term() const { + return flecs::term(world_, ecs_pair( + _::type::id(world_), + _::type::id(world_))); } -template -inline Self& entity_builder::remove_switch(const flecs::type& sw) { - return remove_switch(sw.id()); } -template -template ::value > > -inline Self& entity_builder::set(const Func& func) { - _::entity_with_invoker::invoke_get_mut( - this->m_world, this->m_id, func); - return to_base(); -} +/** + * @file addons/cpp/mixins/query/impl.hpp + * @brief Query implementation. + */ -inline bool entity_view::has_switch(const flecs::type& type) const { - return ecs_has_id(m_world, m_id, flecs::Switch | type.id()); -} +#pragma once -inline flecs::entity entity_view::get_case(const flecs::type& sw) const { - return flecs::entity(m_world, ecs_get_case(m_world, m_id, sw.id())); -} +/** + * @file addons/cpp/mixins/query/builder.hpp + * @brief Query builder. + */ -inline flecs::entity entity_view::get_case(flecs::id_t sw) const { - return flecs::entity(m_world, ecs_get_case(m_world, m_id, sw)); -} +#pragma once -template -inline flecs::entity entity_view::get_case() const { - return get_case(_::cpp_type::id(m_world)); -} +/** + * @file addons/cpp/utils/builder.hpp + * @brief Builder base class. + * + * Generic functionality for builder classes. + */ -template ::value > > -const T* entity_view::get() const { - entity_t r = _::cpp_type::id(m_world); - entity_t c = ecs_get_object(m_world, m_id, r, 0); +#pragma once - if (c) { - // Get constant value from constant entity - const T* v = static_cast(ecs_get_id(m_world, c, r)); - ecs_assert(v != NULL, ECS_INTERNAL_ERROR, - "missing enum constant value"); - return v; - } else { - // If there is no matching pair for (r, *), try just r - return static_cast(ecs_get_id(m_world, m_id, r)); - } -} +namespace flecs { +namespace _ { -template -inline flecs::entity entity_view::get_object(int32_t index) const -{ - return flecs::entity(m_world, - ecs_get_object(m_world, m_id, _::cpp_type::id(m_world), index)); -} +// Macros for template types so we don't go cross-eyed +#define FLECS_TBUILDER template class +#define FLECS_IBUILDER template class -inline flecs::entity entity_view::get_object( - flecs::entity_t relation, - int32_t index) const +template +struct builder : IBuilder { - return flecs::entity(m_world, - ecs_get_object(m_world, m_id, relation, index)); -} + using IBase = IBuilder; -inline flecs::entity entity_view::get_object_for( - flecs::entity_t relation, - flecs::id_t id) const -{ - return flecs::entity(m_world, - ecs_get_object_for_id(m_world, m_id, relation, id)); -} +public: + builder(flecs::world_t *world) + : IBase(&desc_) + , desc_{} + , world_(world) { } -template -inline flecs::entity entity_view::get_object_for(flecs::entity_t relation) const { - return get_object_for(relation, _::cpp_type::id(m_world)); -} + builder(const builder& f) + : IBase(&desc_, f.term_index_) + { + world_ = f.world_; + desc_ = f.desc_; + } -template -inline flecs::entity entity_view::get_object_for(flecs::entity_t relation) const { - return get_object_for(relation, _::cpp_type::id(m_world)); -} + builder(builder&& f) noexcept + : builder(f) { } -inline flecs::entity entity_view::mut(const flecs::world& stage) const { - ecs_assert(!stage.is_readonly(), ECS_INVALID_PARAMETER, - "cannot use readonly world/stage to create mutable handle"); - return flecs::entity(m_id).set_stage(stage.c_ptr()); -} + operator TDesc*() { + return &desc_; + } -inline flecs::entity entity_view::mut(const flecs::iter& it) const { - ecs_assert(!it.world().is_readonly(), ECS_INVALID_PARAMETER, - "cannot use iterator created for readonly world/stage to create mutable handle"); - return flecs::entity(m_id).set_stage(it.world().c_ptr()); -} + T build() { + return T(world_, *static_cast(this)); + } -inline flecs::entity entity_view::mut(const flecs::entity_view& e) const { - ecs_assert(!e.world().is_readonly(), ECS_INVALID_PARAMETER, - "cannot use entity created for readonly world/stage to create mutable handle"); - return flecs::entity(m_id).set_stage(e.m_world); -} +protected: + flecs::world_t* world_v() override { return world_; } + TDesc desc_; + flecs::world_t *world_; +}; -inline flecs::entity entity_view::set_stage(world_t *stage) { - return flecs::entity(stage, m_id); -} +#undef FLECS_TBUILDER +#undef FLECS_IBUILDER -inline flecs::type entity_view::type() const { - return flecs::type(m_world, ecs_get_table(m_world, m_id)); -} +} // namespace _ +} // namespace flecs -template -inline void entity_view::each(const Func& func) const { - const ecs_vector_t *type = ecs_get_type(m_world, m_id); - if (!type) { - return; - } +/** + * @file addons/cpp/mixins/query/builder_i.hpp + * @brief Query builder interface. + */ - const ecs_id_t *ids = static_cast( - _ecs_vector_first(type, ECS_VECTOR_T(ecs_id_t))); - int32_t count = ecs_vector_count(type); +#pragma once - for (int i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - flecs::id ent(m_world, id); - func(ent); - // Case is not stored in type, so handle separately - if ((id & ECS_ROLE_MASK) == flecs::Switch) { - ent = flecs::id( - m_world, flecs::Case | ecs_get_case( - m_world, m_id, ent.second().id())); - func(ent); - } - } -} +namespace flecs +{ -template -inline void entity_view::each(flecs::id_t pred, flecs::id_t obj, const Func& func) const { - flecs::world_t *real_world = const_cast( - ecs_get_world(m_world)); +/** Query builder interface. + * + * @ingroup cpp_core_queries + */ +template +struct query_builder_i : term_builder_i { + query_builder_i(ecs_query_desc_t *desc, int32_t term_index = 0) + : term_index_(term_index) + , expr_count_(0) + , desc_(desc) { } - const ecs_table_t *table = ecs_get_table(m_world, m_id); - if (!table) { - return; + Base& query_flags(ecs_flags32_t flags) { + desc_->flags |= flags; + return *this; } - const ecs_vector_t *type = ecs_table_get_type(table); - if (!type) { - return; + Base& cache_kind(query_cache_kind_t kind) { + desc_->cache_kind = static_cast(kind); + return *this; } - flecs::id_t pattern = pred; - if (obj) { - pattern = ecs_pair(pred, obj); + Base& cached() { + return cache_kind(flecs::QueryCacheAuto); } - int32_t cur = 0; - id_t *ids = static_cast( - _ecs_vector_first(type, ECS_VECTOR_T(ecs_id_t))); - - while (-1 != (cur = ecs_search_offset(real_world, table, cur, pattern, 0))) - { - flecs::id ent(m_world, ids[cur]); - func(ent); - cur ++; + Base& detect_changes() { + desc_->flags |= EcsQueryDetectChanges; + return *this; } -} -template -inline void entity_view::each(const flecs::entity_view& rel, const Func& func) const { - return this->each(rel, flecs::Wildcard, [&](flecs::id id) { - flecs::entity obj = id.second(); - func(obj); - }); -} + Base& expr(const char *expr) { + ecs_check(expr_count_ == 0, ECS_INVALID_OPERATION, + "query_builder::expr() called more than once"); + desc_->expr = expr; + expr_count_ ++; -template ::value > > -inline bool entity_view::get(const Func& func) const { - return _::entity_with_invoker::invoke_get(m_world, m_id, func); -} + error: + return *this; + } -inline flecs::entity entity_view::lookup(const char *path) const { - auto id = ecs_lookup_path_w_sep(m_world, m_id, path, "::", "::", false); - return flecs::entity(m_world, id); -} + /* With methods */ -inline flecs::entity entity_view::clone(bool copy_value, flecs::entity_t dst_id) const { - if (!dst_id) { - dst_id = ecs_new_id(m_world); + template + Base& with() { + this->term(); + *this->term_ = flecs::term(_::type::id(this->world_v())); + this->term_->inout = static_cast( + _::type_to_inout()); + return *this; } - flecs::entity dst = flecs::entity(m_world, dst_id); - ecs_clone(m_world, dst_id, m_id, copy_value); - return dst; -} + Base& with(id_t component_id) { + this->term(); + *this->term_ = flecs::term(component_id); + return *this; + } -// Entity mixin implementation -template -inline flecs::entity world::entity(Args &&... args) const { - return flecs::entity(m_world, FLECS_FWD(args)...); -} + Base& with(const char *component_name) { + this->term(); + *this->term_ = flecs::term().first(component_name); + return *this; + } -template ::value >> -inline flecs::entity world::id(E value) const { - flecs::entity_t constant = enum_type(m_world).entity(value); - return flecs::entity(m_world, constant); -} + Base& with(const char *first, const char *second) { + this->term(); + *this->term_ = flecs::term().first(first).second(second); + return *this; + } -template -inline flecs::entity world::entity(const char *name) const { - return flecs::entity(m_world, - _::cpp_type::id_explicit(m_world, name, true, 0, false) ); -} + Base& with(entity_t first, entity_t second) { + this->term(); + *this->term_ = flecs::term(first, second); + return *this; + } -template -inline flecs::entity world::prefab(Args &&... args) const { - flecs::entity result = flecs::entity(m_world, FLECS_FWD(args)...); - result.add(flecs::Prefab); - return result; -} + Base& with(entity_t first, const char *second) { + this->term(); + *this->term_ = flecs::term(first).second(second); + return *this; + } -template -inline flecs::entity world::prefab(const char *name) const { - flecs::entity result = flecs::component(m_world, name, true); - result.add(flecs::Prefab); - return result; -} + Base& with(const char *first, entity_t second) { + this->term(); + *this->term_ = flecs::term().first(first).second(second); + return *this; + } -} + template + Base& with(entity_t second) { + return this->with(_::type::id(this->world_v()), second); + } -#pragma once + template + Base& with(const char *second) { + return this->with(_::type::id(this->world_v())).second(second); + } -namespace flecs { + template + Base& with() { + return this->with(_::type::id(this->world_v())); + } -// Component mixin implementation -template -inline flecs::component world::component(Args &&... args) const { - return flecs::component(m_world, FLECS_FWD(args)...); -} + template ::value > = 0> + Base& with(E value) { + flecs::entity_t r = _::type::id(this->world_v()); + auto o = enum_type(this->world_v()).entity(value); + return this->with(r, o); + } -template -inline flecs::untyped_component world::component(Args &&... args) const { - return flecs::untyped_component(m_world, FLECS_FWD(args)...); -} + Base& with(flecs::term& term) { + this->term(); + *this->term_ = term; + return *this; + } -} // namespace flecs + Base& with(flecs::term&& term) { + this->term(); + *this->term_ = term; + return *this; + } -#pragma once + /* Without methods, shorthand for .with(...).not_(). */ -namespace flecs { + template + Base& without(Args&&... args) { + return this->with(FLECS_FWD(args)...).not_(); + } -// Type mixin implementation -template -inline flecs::type world::type(Args &&... args) const { - return flecs::type(m_world, FLECS_FWD(args)...); -} + template + Base& without(Args&&... args) { + return this->with(FLECS_FWD(args)...).not_(); + } -template -inline flecs::type world::type(const char *name) const { - flecs::entity result = flecs::component(m_world, name, true); - return flecs::type(m_world, result); -} + template + Base& without() { + return this->with().not_(); + } -} + /* Write/read methods */ -#pragma once + Base& write() { + term_builder_i::write(); + return *this; + } -#pragma once + template + Base& write(Args&&... args) { + return this->with(FLECS_FWD(args)...).write(); + } -#pragma once + template + Base& write(Args&&... args) { + return this->with(FLECS_FWD(args)...).write(); + } -namespace flecs { -namespace _ { + template + Base& write() { + return this->with().write(); + } - template ::value > = 0> - static constexpr flecs::inout_kind_t type_to_inout() { - return flecs::In; + Base& read() { + term_builder_i::read(); + return *this; } - template ::value > = 0> - static constexpr flecs::inout_kind_t type_to_inout() { - return flecs::Out; + template + Base& read(Args&&... args) { + return this->with(FLECS_FWD(args)...).read(); } - template ::value || is_reference::value > = 0> - static constexpr flecs::inout_kind_t type_to_inout() { - return flecs::InOutDefault; + template + Base& read(Args&&... args) { + return this->with(FLECS_FWD(args)...).read(); } - template ::value > = 0> - static constexpr flecs::oper_kind_t type_to_oper() { - return flecs::Optional; + template + Base& read() { + return this->with().read(); } - template ::value > = 0> - static constexpr flecs::oper_kind_t type_to_oper() { - return flecs::And; + /* Scope_open/scope_close shorthand notation. */ + Base& scope_open() { + return this->with(flecs::ScopeOpen).entity(0); } - template - struct sig { - sig(flecs::world_t *world) - : ids({ (_::cpp_type::id(world))... }) - , inout ({ (type_to_inout())... }) - , oper ({ (type_to_oper())... }) - { (void)world; } + Base& scope_close() { + return this->with(flecs::ScopeClose).entity(0); + } - flecs::array ids; - flecs::array inout; - flecs::array oper; + /* Term notation for more complex query features */ - template - void populate(const Builder& b) { - size_t i = 0; - for (auto id : ids) { - b->term(id).inout(inout[i]).oper(oper[i]); - i ++; - } + /** Sets the current term to next one in term list. + */ + Base& term() { + if (this->term_) { + ecs_check(ecs_term_is_initialized(this->term_), + ECS_INVALID_OPERATION, + "query_builder::term() called without initializing term"); } - }; -} // namespace _ -} // namespace flecs + ecs_check(term_index_ < FLECS_TERM_COUNT_MAX, + ECS_INVALID_PARAMETER, "maximum number of terms exceeded"); + this->set_term(&desc_->terms[term_index_]); -namespace flecs -{ - -/** Term identifier builder. - * A term identifier is either the predicate (pred), subject (subj) or object - * (obj) of a term. Use the term builder to select the term identifier. */ -template -struct term_id_builder_i { - term_id_builder_i() : m_term_id(nullptr) { } + term_index_ ++; + + error: + return *this; + } - virtual ~term_id_builder_i() { } + /** Sets the current term to the one with the provided type. + * This loops over all terms to find the one with the provided type. + * For performance-critical paths, use term_at(int32_t) instead. + */ + template + Base& term_at() { + flecs::id_t term_id = _::type::id(this->world_v()); + for (int i = 0; i < term_index_; i ++) { + ecs_term_t cur_term = desc_->terms[i]; + ecs_id_t cur_term_id = cur_term.id; + ecs_id_t cur_term_pair = ecs_pair(cur_term.first.id, cur_term.second.id); + + if ((term_id == cur_term_id || (cur_term_id != 0 && term_id == ecs_get_typeid(this->world_v(), cur_term_id))) || + (term_id == cur_term_pair || (cur_term_pair != 0 && term_id == ecs_get_typeid(this->world_v(), cur_term_pair)))) { + return term_at(i); + } + } - /** Assign entity from type to currently selected term identifier. */ - template - Base& entity() { - ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); - m_term_id->entity = _::cpp_type::id(this->world_v()); + ecs_err("term not found"); return *this; } - /** Assign entity to currently selected term identifier. */ - Base& entity(flecs::id_t id) { - ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); - m_term_id->entity = id; + /** Sets the current term to the one at the provided index. + */ + Base& term_at(int32_t term_index) { + ecs_assert(term_index >= 0, ECS_INVALID_PARAMETER, NULL); + int32_t prev_index = term_index_; + term_index_ = term_index; + this->term(); + term_index_ = prev_index; + ecs_assert(ecs_term_is_initialized(this->term_), + ECS_INVALID_PARAMETER, NULL); return *this; } - /** Assign name to currently selected term identifier. - * The name will be resolved to either an entity or a variable name, - * depending on the following rules (which are the same as the query DSL): - * - * If the variable kind is flecs::VarIsDefault: - * - If the name is a single uppercase value it is parsed as a variable - * - If the name starts with an underscore it is parsed as a variable - * - In any other case the name is parsed as an entity identifier - * - * If the variable kind is flecs::VarIsEntity: - * - The name is parsed as an entity identifier - * - * If the variable kind is flecs::VarIsVariable: - * - The name is interpreted as a variable + /** Sets the current term to the one at the provided index and asserts that the type matches. */ - Base& name(const char *name) { - ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); - m_term_id->name = const_cast(name); + template + Base& term_at(int32_t term_index) { + this->term_at(term_index); +#if !defined(FLECS_NDEBUG) || defined(FLECS_KEEP_ASSERT) + flecs::id_t term_id = _::type::id(this->world_v()); + ecs_term_t cur_term = *this->term_; + ecs_id_t cur_term_id = cur_term.id; + ecs_id_t cur_term_pair = ecs_pair(cur_term.first.id, cur_term.second.id); + + ecs_assert((term_id == cur_term_id || (cur_term_id != 0 && term_id == ecs_get_typeid(this->world_v(), cur_term_id))) || + (term_id == cur_term_pair || (cur_term_pair != 0 && term_id == ecs_get_typeid(this->world_v(), cur_term_pair))), + ECS_INVALID_PARAMETER, "term type mismatch"); +#endif return *this; } - /** Set whether the currently selected term id is a variable or entity. */ - Base& var(flecs::var_kind_t var = flecs::VarIsVariable) { - m_term_id->var = static_cast(var); + /** Sort the output of a query. + * This enables sorting of entities across matched tables. As a result of this + * operation, the order of entities in the matched tables may be changed. + * Resorting happens when a query iterator is obtained, and only if the table + * data has changed. + * + * If multiple queries that match the same (down)set of tables specify different + * sorting functions, resorting is likely to happen every time an iterator is + * obtained, which can significantly slow down iterations. + * + * The sorting function will be applied to the specified component. Resorting + * only happens if that component has changed, or when the entity order in the + * table has changed. If no component is provided, resorting only happens when + * the entity order changes. + * + * @tparam T The component used to sort. + * @param compare The compare function used to sort the components. + */ + template + Base& order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { + ecs_order_by_action_t cmp = reinterpret_cast(compare); + return this->order_by(_::type::id(this->world_v()), cmp); + } + + /** Sort the output of a query. + * Same as order_by, but with component identifier. + * + * @param component The component used to sort. + * @param compare The compare function used to sort the components. + */ + Base& order_by(flecs::entity_t component, int(*compare)(flecs::entity_t, const void*, flecs::entity_t, const void*)) { + desc_->order_by_callback = reinterpret_cast(compare); + desc_->order_by = component; return *this; } - /** Set the current term id to be a variable. */ - Base& var(const char *name) { - ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); - // Const cast is safe, when the value is actually used to construct a - // query, it will be duplicated. - m_term_id->name = const_cast(name); - return var(); // Default to VarIsVariable + /** Group and sort matched tables. + * Similar to ecs_query_order_by(), but instead of sorting individual entities, this + * operation only sorts matched tables. This can be useful of a query needs to + * enforce a certain iteration order upon the tables it is iterating, for + * example by giving a certain component or tag a higher priority. + * + * The sorting function assigns a "rank" to each type, which is then used to + * sort the tables. Tables with higher ranks will appear later in the iteration. + * + * Resorting happens when a query iterator is obtained, and only if the set of + * matched tables for a query has changed. If table sorting is enabled together + * with entity sorting, table sorting takes precedence, and entities will be + * sorted within each set of tables that are assigned the same rank. + * + * @tparam T The component used to determine the group rank. + * @param group_by_action Callback that determines group id for table. + */ + template + Base& group_by(uint64_t(*group_by_action)(flecs::world_t*, flecs::table_t *table, flecs::id_t id, void* ctx)) { + ecs_group_by_action_t action = reinterpret_cast(group_by_action); + return this->group_by(_::type::id(this->world_v()), action); } - /** Assign set mask and relation. */ - Base& set(uint8_t mask, const flecs::id_t relation = flecs::IsA) - { - ecs_assert(m_term_id != NULL, ECS_INVALID_PARAMETER, NULL); - m_term_id->set.mask = mask; - m_term_id->set.relation = relation; + /** Group and sort matched tables. + * Same as group_by, but with component identifier. + * + * @param component The component used to determine the group rank. + * @param group_by_action Callback that determines group id for table. + */ + Base& group_by(flecs::entity_t component, uint64_t(*group_by_action)(flecs::world_t*, flecs::table_t *table, flecs::id_t id, void* ctx)) { + desc_->group_by_callback = reinterpret_cast(group_by_action); + desc_->group_by = component; return *this; } - /** Shorthand for .set(flecs::SuperSet | mask, relation). */ - Base& super(const flecs::id_t relation = flecs::IsA, uint8_t mask = 0) - { - ecs_assert(!(mask & flecs::SubSet), ECS_INVALID_PARAMETER, NULL); - return set(flecs::SuperSet | mask, relation); + /** Group and sort matched tables. + * Same as group_by, but with default group_by action. + * + * @tparam T The component used to determine the group rank. + */ + template + Base& group_by() { + return this->group_by(_::type::id(this->world_v()), nullptr); } - /** Shorthand for .set(flecs::SubSet | mask, relation). */ - Base& sub(const flecs::id_t relation = flecs::IsA, uint8_t mask = 0) - { - ecs_assert(!(mask & flecs::SuperSet), ECS_INVALID_PARAMETER, NULL); - return set(flecs::SubSet | mask, relation); + /** Group and sort matched tables. + * Same as group_by, but with default group_by action. + * + * @param component The component used to determine the group rank. + */ + Base& group_by(flecs::entity_t component) { + return this->group_by(component, nullptr); } - /** Set min_depth for set substitution. */ - Base& min_depth(int32_t min_depth) { - m_term_id->set.min_depth = min_depth; + /** Specify context to be passed to group_by function. + * + * @param ctx Context to pass to group_by function. + * @param ctx_free Function to cleanup context (called when query is deleted). + */ + Base& group_by_ctx(void *ctx, ecs_ctx_free_t ctx_free = nullptr) { + desc_->group_by_ctx = ctx; + desc_->group_by_ctx_free = ctx_free; return *this; } - /** Set min_depth for set substitution. */ - Base& max_depth(int32_t max_depth) { - m_term_id->set.max_depth = max_depth; + /** Specify on_group_create action. + */ + Base& on_group_create(ecs_group_create_action_t action) { + desc_->on_group_create = action; return *this; } - Base& owned(bool value = true) { - if (value) { - m_term_id->set.mask = flecs::Self; - } else { - m_term_id->set.mask = flecs::SuperSet; - m_term_id->set.relation = flecs::IsA; - } + /** Specify on_group_delete action. + */ + Base& on_group_delete(ecs_group_delete_action_t action) { + desc_->on_group_delete = action; return *this; } - ecs_term_id_t *m_term_id; - protected: - virtual flecs::world_t* world_v() = 0; + virtual flecs::world_t* world_v() override = 0; + int32_t term_index_; + int32_t expr_count_; private: operator Base&() { return *static_cast(this); } + + ecs_query_desc_t *desc_; }; -/** Term builder. A term is a single element of a query expression. */ -template -struct term_builder_i : term_id_builder_i { - term_builder_i() : m_term(nullptr) { } +} - term_builder_i(ecs_term_t *term_ptr) { - set_term(term_ptr); - } - /** Set (component) id to type id. */ - template - Base& id() { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->pred.entity = _::cpp_type::id(this->world_v()); - return *this; +namespace flecs { +namespace _ { + template + using query_builder_base = builder< + query, ecs_query_desc_t, query_builder, + query_builder_i, Components ...>; +} + +/** Query builder. + * + * @ingroup cpp_core_queries + */ +template +struct query_builder final : _::query_builder_base { + query_builder(flecs::world_t* world, flecs::entity query_entity) + : _::query_builder_base(world) + { + _::sig(world).populate(this); + this->desc_.entity = query_entity.id(); } - /** Set (component) id to id. */ - Base& id(id_t id) { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->pred.entity = id; - return *this; + query_builder(flecs::world_t* world, const char *name = nullptr) + : _::query_builder_base(world) + { + _::sig(world).populate(this); + if (name != nullptr) { + ecs_entity_desc_t entity_desc = {}; + entity_desc.name = name; + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; + this->desc_.entity = ecs_entity_init(world, &entity_desc); + } } - /** Alias for id, for compatibility with populate_filter_from_pack */ - Base& term(id_t id) { - return this->id(id); + template + void each(Func&& func) { + this->build().each(FLECS_FWD(func)); } +}; - /** Set (component) id to type. - * Type must be associated with an entity (e.g. created by world::type) and - * not an entity type (e.g. returned from entity::type). */ - Base& id(const flecs::type& type); +} - Base& id(const flecs::type& type, id_t id); - /** Set (component) id to pair derived from relation id / object id */ - Base& id(id_t r, id_t o) { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->pred.entity = r; - m_term->obj.entity = o; - return *this; - } +namespace flecs +{ - /** Set (component) id to pair derived from two types. */ - template - Base& id() { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->pred.entity = _::cpp_type::id(this->world_v()); - m_term->obj.entity = _::cpp_type::id(this->world_v()); - return *this; - } +struct query_base { + query_base() { } - /** Set (component) id to pair derived from relation type / object id. */ - template - Base& id(id_t o) { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->pred.entity = _::cpp_type::id(this->world_v()); - m_term->obj.entity = o; - return *this; - } + query_base(query_t *q) + : query_(q) { + flecs_poly_claim(q); + } - /** Set term from expression. - * The syntax for expr is the same as that of the query DSL. The expression - * must only contain a single term, for example: - * Position // correct - * Position, Velocity // incorrect - */ - Base& expr(const char *expr) { -# ifdef FLECS_PARSER - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - const char *ptr; - if ((ptr = ecs_parse_term(this->world_v(), nullptr, expr, expr, m_term)) == nullptr) { - ecs_abort(ECS_INVALID_PARAMETER, NULL); + query_base(const query_t *q) + : query_(ECS_CONST_CAST(query_t*, q)) { + flecs_poly_claim(q); } - m_term->move = true; + query_base(world_t *world, ecs_query_desc_t *desc) { + if (desc->entity && desc->terms[0].id == 0) { + const flecs::Poly *query_poly = ecs_get_pair( + world, desc->entity, EcsPoly, EcsQuery); + if (query_poly) { + query_ = static_cast(query_poly->poly); + flecs_poly_claim(query_); + return; + } + } - // Should not have more than one term - ecs_assert(ptr[0] == 0, ECS_INVALID_PARAMETER, NULL); -# else - (void)expr; - ecs_abort(ECS_UNSUPPORTED, "parser addon required for expr()"); -# endif - return *this; + query_ = ecs_query_init(world, desc); } - /** Select predicate of term. - * Use methods from term_builder to configure properties of predicate. */ - Base& pred() { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - this->m_term_id = &m_term->pred; - return *this; + query_base(const query_base& obj) { + this->query_ = obj.query_; + if (this->query_) + { + flecs_poly_claim(this->query_); + } } - /** Select subject of term. - * Use methods from term_builder to configure properties of subject. */ - Base& subj() { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - this->m_term_id = &m_term->subj; - return *this; + query_base& operator=(const query_base& obj) { + this->~query_base(); + this->query_ = obj.query_; + if (this->query_) + { + flecs_poly_claim(this->query_); + } + return *this; } - /** Select object of term. - * Use methods from term_builder to configure properties of object. Setting - * the object of a term will turn the term into a pair, and requires the - * predicate to also be set. */ - Base& obj() { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - this->m_term_id = &m_term->obj; - return *this; + query_base(query_base&& obj) noexcept { + this->query_ = obj.query_; + obj.query_ = nullptr; } - /** Select subject of term, initialize it with specified entity. */ - Base& subj(entity_t entity) { - this->subj(); - this->m_term_id->entity = entity; - return *this; + query_base& operator=(query_base&& obj) noexcept { + this->query_ = obj.query_; + obj.query_ = nullptr; + return *this; } - /** Select object of term, initialize it with specified entity. */ - Base& obj(entity_t entity) { - this->obj(); - this->m_term_id->entity = entity; - return *this; + flecs::entity entity() const { + return flecs::entity(query_->world, query_->entity); } - /** Select predicate of term, initialize it with specified name. */ - Base& pred(const char *n) { - this->pred(); - this->m_term_id->name = const_cast(n); - return *this; + const flecs::query_t* c_ptr() const { + return query_; } - /** Select subject of term, initialize it with specified name. */ - Base& subj(const char *n) { - this->subj(); - this->m_term_id->name = const_cast(n); - return *this; + operator const flecs::query_t*() const { + return query_; } - /** Select object of term, initialize it with specified name. */ - Base& obj(const char *n) { - this->obj(); - this->m_term_id->name = const_cast(n); - return *this; + operator bool() const { + return query_ != nullptr; } - /** Select subject of term, initialize it with id from specified type. */ - template - Base& subj() { - this->subj(); - this->m_term_id->entity = _::cpp_type::id(this->world_v()); - return *this; + /** Free persistent query. + * A persistent query is a query that is associated with an entity, such as + * system queries and named queries. Persistent queries must be deleted with + * destruct(), or will be deleted automatically at world cleanup. + */ + void destruct() { + ecs_assert(query_->entity != 0, ECS_INVALID_OPERATION, "destruct() " + "should only be called on queries associated with entities"); + ecs_query_fini(query_); + query_ = nullptr; + } + + ~query_base() { + /* Only free if query is not associated with entity, such as system + * queries and named queries. Named queries have to be either explicitly + * deleted with the .destruct() method, or will be deleted when the + * world is deleted. */ + if (query_ && !query_->entity) { + if (!flecs_poly_release(query_)) { + ecs_query_fini(query_); + query_ = nullptr; + } + } } - /** Select object of term, initialize it with id from specified type. */ - template - Base& obj() { - this->obj(); - this->m_term_id->entity = _::cpp_type::id(this->world_v()); - return *this; + /** Returns whether the query data changed since the last iteration. + * This operation must be invoked before obtaining the iterator, as this will + * reset the changed state. The operation will return true after: + * - new entities have been matched with + * - matched entities were deleted + * - matched components were changed + * + * @return true if entities changed, otherwise false. + */ + bool changed() const { + return ecs_query_changed(query_); } - /** Set role of term. */ - Base& role(id_t role) { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->role = role; - return *this; + /** Get info for group. + * + * @param group_id The group id for which to retrieve the info. + * @return The group info. + */ + const flecs::query_group_info_t* group_info(uint64_t group_id) const { + return ecs_query_get_group_info(query_, group_id); } - /** Set read/write access of term. */ - Base& inout(flecs::inout_kind_t inout) { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->inout = static_cast(inout); - return *this; + /** Get context for group. + * + * @param group_id The group id for which to retrieve the context. + * @return The group context. + */ + void* group_ctx(uint64_t group_id) const { + const flecs::query_group_info_t *gi = group_info(group_id); + if (gi) { + return gi->ctx; + } else { + return NULL; + } } - /** Scheduler annotation to indicate system uses add */ - Base& add() { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->inout = static_cast(flecs::Out); - m_term->subj.set.mask = flecs::Nothing; - return *this; + template + void each_term(const Func& func) { + for (int i = 0; i < query_->term_count; i ++) { + flecs::term t(query_->world, query_->terms[i]); + func(t); + t.reset(); // prevent freeing resources + } } - /** Scheduler annotation to indicate sytem uses add, remove or set */ - Base& write() { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->inout = static_cast(flecs::Out); - m_term->subj.set.mask = flecs::Nothing; - return *this; + flecs::term term(int32_t index) const { + return flecs::term(query_->world, query_->terms[index]); } - /** Scheduler annotation to indicate sytem uses get */ - Base& read() { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->inout = static_cast(flecs::In); - m_term->subj.set.mask = flecs::Nothing; - return *this; + int32_t term_count() const { + return query_->term_count; } - /** Scheduler annotation to indicate sytem uses get_mut */ - Base& read_write() { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->inout = static_cast(flecs::InOut); - m_term->subj.set.mask = flecs::Nothing; - return *this; + int32_t field_count() const { + return query_->field_count; } - /** Set operator of term. */ - Base& oper(flecs::oper_kind_t oper) { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->oper = static_cast(oper); - return *this; + int32_t find_var(const char *name) const { + return ecs_query_find_var(query_, name); } - /** Make term a singleton. */ - Base& singleton() { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - ecs_assert(m_term->id || m_term->pred.entity, ECS_INVALID_PARAMETER, NULL); - - flecs::id_t pred = m_term->id; - if (!pred) { - pred = m_term->pred.entity; - } - - ecs_assert(pred != 0, ECS_INVALID_PARAMETER, NULL); - - m_term->subj.entity = pred; - - return *this; + flecs::string str() const { + char *result = ecs_query_str(query_); + return flecs::string(result); } - flecs::id id() { - return flecs::id(this->world_v(), m_term->id); + /** Returns a string representing the query plan. + * This can be used to analyze the behavior & performance of the query. + * @see ecs_query_plan + */ + flecs::string plan() const { + char *result = ecs_query_plan(query_); + return flecs::string(result); } - ecs_term_t *m_term; - -protected: - virtual flecs::world_t* world_v() = 0; - - void set_term(ecs_term_t *term) { - m_term = term; - if (term) { - this->m_term_id = &m_term->subj; // default to subject - } else { - this->m_term_id = nullptr; - } - } + operator query<>() const; -private: - operator Base&() { - return *static_cast(this); - } -}; +# ifdef FLECS_JSON +/** Serialize query to JSON. + * + * @memberof flecs::query_base + * @ingroup cpp_addons_json + */ +flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { + ecs_iter_t it = ecs_query_iter(ecs_get_world(query_), query_); + char *json = ecs_iter_to_json(&it, desc); + return flecs::string(json); } +# endif +protected: + query_t *query_ = nullptr; +}; -namespace flecs { +template +struct query : query_base, iterable { +private: + using Fields = typename _::field_ptrs::array; -// Class that describes a term -struct term final : term_builder_i { - term(flecs::world_t *world_ptr) - : term_builder_i(&value) - , value({}) - , m_world(world_ptr) { value.move = true; } +public: + using query_base::query_base; - term(id_t id) - : term_builder_i(&value) - , value({}) - , m_world(nullptr) { - value.move = true; - this->id(id); - } + query() : query_base() { } // necessary not to confuse msvc - term(flecs::world_t *world_ptr, id_t id) - : term_builder_i(&value) - , value({}) - , m_world(world_ptr) { - value.move = true; - this->id(id); - } + query(const query& obj) : query_base(obj) { } - term(flecs::world_t *world_ptr, ecs_term_t t) - : term_builder_i(&value) - , value({}) - , m_world(world_ptr) { - value = t; - value.move = false; - this->set_term(&value); - } + query& operator=(const query& obj) { + query_base::operator=(obj); + return *this; + } - term(flecs::world_t *world_ptr, id_t r, id_t o) - : term_builder_i(&value) - , value({}) - , m_world(world_ptr) { - value.move = true; - this->id(r, o); - } + query(query&& obj) noexcept : query_base(FLECS_MOV(obj)) { } - term(flecs::world_t *world_ptr, const char *expr) - : term_builder_i(&value) - , value({}) - , m_world(world_ptr) - { - this->expr(expr); + query& operator=(query&& obj) noexcept { + query_base::operator=(FLECS_FWD(obj)); + return *this; } - term(const term& t) : term_builder_i(&value) { - m_world = t.m_world; - value = ecs_term_copy(&t.value); - this->set_term(&value); + flecs::query<> cache_query() const { + const flecs::query_t *q = ecs_query_get_cache_query(query_); + return flecs::query<>(q); } - term(term&& t) : term_builder_i(&value) { - m_world = t.m_world; - value = ecs_term_move(&t.value); - t.reset(); - this->set_term(&value); +private: + ecs_iter_t get_iter(flecs::world_t *world) const override { + ecs_assert(query_ != nullptr, ECS_INVALID_PARAMETER, + "cannot iterate invalid query"); + if (!world) { + world = query_->world; + } + return ecs_query_iter(world, query_); } - term& operator=(const term& t) { - ecs_assert(m_world == t.m_world, ECS_INVALID_PARAMETER, NULL); - ecs_term_fini(&value); - value = ecs_term_copy(&t.value); - this->set_term(&value); - return *this; + ecs_iter_next_action_t next_action() const override { + return ecs_query_next; } +}; - term& operator=(term&& t) { - ecs_assert(m_world == t.m_world, ECS_INVALID_PARAMETER, NULL); - ecs_term_fini(&value); - value = t.value; - this->set_term(&value); - t.reset(); - return *this; - } +// World mixin implementation +template +inline flecs::query world::query(Args &&... args) const { + return flecs::query_builder(world_, FLECS_FWD(args)...) + .build(); +} - ~term() { - ecs_term_fini(&value); - } +inline flecs::query<> world::query(flecs::entity query_entity) const { + ecs_query_desc_t desc = {}; + desc.entity = query_entity; + return flecs::query<>(world_, &desc); +} - void reset() { - value = {}; - this->set_term(nullptr); - } +template +inline flecs::query_builder world::query_builder(Args &&... args) const { + return flecs::query_builder(world_, FLECS_FWD(args)...); +} - int finalize() { - return ecs_term_finalize(m_world, nullptr, &value); - } +// world::each +namespace _ { - bool is_set() { - return ecs_term_is_initialized(&value); - } +// Each with entity parameter +template +struct query_delegate_w_ent; - bool is_trivial() { - return ecs_term_is_trivial(&value); +template +struct query_delegate_w_ent > +{ + query_delegate_w_ent(const flecs::world& world, Func&& func) { + auto f = world.query(); + f.each(FLECS_MOV(func)); } +}; - flecs::inout_kind_t inout() { - return static_cast(value.inout); - } +// Each without entity parameter +template +struct query_delegate_no_ent; - flecs::oper_kind_t oper() { - return static_cast(value.oper); +template +struct query_delegate_no_ent > +{ + query_delegate_no_ent(const flecs::world& world, Func&& func) { + auto f = world.query(); + f.each(FLECS_MOV(func)); } +}; - flecs::entity get_subject() { - return flecs::entity(world(), value.subj.entity); - } +// Switch between function with & without entity parameter +template +struct query_delegate; - flecs::entity get_object() { - return flecs::entity(world(), value.obj.entity); +template +struct query_delegate, flecs::entity>::value> > { + query_delegate(const flecs::world& world, Func&& func) { + query_delegate_w_ent>(world, FLECS_MOV(func)); } +}; - ecs_term_t move() { /* explicit move to ecs_term_t */ - return ecs_term_move(&value); +template +struct query_delegate, flecs::entity>::value> > { + query_delegate(const flecs::world& world, Func&& func) { + query_delegate_no_ent>(world, FLECS_MOV(func)); } - - ecs_term_t value; - -protected: - flecs::world_t* world_v() override { return m_world; } - -private: - flecs::world_t *m_world; }; -// Term mixin implementation -template -inline flecs::term world::term(Args &&... args) const { - return flecs::term(m_world, FLECS_FWD(args)...); } -template -inline flecs::term world::term(Args &&... args) const { - return flecs::term(m_world, FLECS_FWD(args)...).id(); +template +inline void world::each(Func&& func) const { + _::query_delegate f_delegate(*this, FLECS_MOV(func)); } -template -inline flecs::term world::term(Args &&... args) const { - return flecs::term(m_world, FLECS_FWD(args)...).id(); +template +inline void world::each(Func&& func) const { + ecs_iter_t it = ecs_each_id(world_, _::type::id(world_)); + + while (ecs_each_next(&it)) { + _::each_delegate(func).invoke(&it); + } } -// Builder implementation -template -inline Base& term_builder_i::id(const flecs::type& type) { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->pred.entity = type.id(); - return *this; +template +inline void world::each(flecs::id_t each_id, Func&& func) const { + ecs_iter_t it = ecs_each_id(world_, each_id); + + while (ecs_each_next(&it)) { + _::each_delegate(func).invoke(&it); + } } -template -inline Base& term_builder_i::id(const flecs::type& type, id_t o) { - ecs_assert(m_term != nullptr, ECS_INVALID_PARAMETER, NULL); - m_term->pred.entity = type.id(); - m_term->obj.entity = o; - m_term->role = ECS_PAIR; - return *this; +// query_base implementation +inline query_base::operator flecs::query<> () const { + return flecs::query<>(query_); } } +/** + * @file addons/cpp/mixins/observer/impl.hpp + * @brief Observer implementation. + */ + #pragma once +/** + * @file addons/cpp/mixins/observer/builder.hpp + * @brief Observer builder. + */ + #pragma once +/** + * @file addons/cpp/utils/node_builder.hpp + * @brief Base builder class for node objects, like systems, observers. + */ + #pragma once namespace flecs { namespace _ { // Macros for template types so we don't go cross-eyed -#define FLECS_TBUILDER template class #define FLECS_IBUILDER template class -template -struct builder : IBuilder +template +struct node_builder : IBuilder { using IBase = IBuilder; public: - builder(flecs::world_t *world) - : IBase(&m_desc) - , m_desc{} - , m_world(world) { } - - builder(const builder& f) - : IBase(&m_desc, f.m_term_index) + explicit node_builder(flecs::world_t* world, const char *name = nullptr) + : IBase(&desc_) + , desc_{} + , world_(world) { - m_world = f.m_world; - m_desc = f.m_desc; + ecs_entity_desc_t entity_desc = {}; + entity_desc.name = name; + entity_desc.sep = "::"; + entity_desc.root_sep = "::"; + desc_.entity = ecs_entity_init(world_, &entity_desc); } - builder(builder&& f) - : builder(f) { } + template + T run(Func&& func) { + using Delegate = typename _::run_delegate< + typename std::decay::type>; - operator TDesc*() { - return &m_desc; + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(func)); + desc_.run = Delegate::run; + desc_.run_ctx = ctx; + desc_.run_ctx_free = _::free_obj; + return T(world_, &desc_); } - T build() { - return T(m_world, *static_cast(this)); + template + T run(Func&& func, EachFunc&& each_func) { + using Delegate = typename _::run_delegate< + typename std::decay::type>; + + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(func)); + desc_.run = Delegate::run; + desc_.run_ctx = ctx; + desc_.run_ctx_free = _::free_obj; + return each(FLECS_FWD(each_func)); + } + + template + T each(Func&& func) { + using Delegate = typename _::each_delegate< + typename std::decay::type, Components...>; + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(func)); + desc_.callback = Delegate::run; + desc_.callback_ctx = ctx; + desc_.callback_ctx_free = _::free_obj; + return T(world_, &desc_); + } + + template + T run_each(Func&& func) { + using Delegate = typename _::each_delegate< + typename std::decay::type, Components...>; + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(func)); + desc_.run = Delegate::run_each; + desc_.run_ctx = ctx; + desc_.run_ctx_free = _::free_obj; + return T(world_, &desc_); } protected: - flecs::world_t* world_v() override { return m_world; } - TDesc m_desc; - flecs::world_t *m_world; + flecs::world_t* world_v() override { return world_; } + TDesc desc_; + flecs::world_t *world_; }; -#undef FLECS_TBUILDER #undef FLECS_IBUILDER } // namespace _ } // namespace flecs +/** + * @file addons/cpp/mixins/observer/builder_i.hpp + * @brief Observer builder interface. + */ + #pragma once -namespace flecs -{ +namespace flecs { -// Filter builder interface +/** Observer builder interface. + * + * @ingroup cpp_observers + */ template -struct filter_builder_i : term_builder_i { - filter_builder_i(ecs_filter_desc_t *desc, int32_t term_index = 0) - : m_term_index(term_index) - , m_expr_count(0) - , m_desc(desc) { } - - Base& instanced() { - m_desc->instanced = true; - return *this; - } - - Base& expr(const char *expr) { - ecs_check(m_expr_count == 0, ECS_INVALID_OPERATION, - "filter_builder::expr() called more than once"); - m_desc->expr = expr; - m_expr_count ++; - - error: - return *this; - } - - Base& term() { - if (this->m_term) { - ecs_check(ecs_term_is_initialized(this->m_term), - ECS_INVALID_OPERATION, - "filter_builder::term() called without initializing term"); - } - - if (m_term_index >= ECS_TERM_DESC_CACHE_SIZE) { - if (m_term_index == ECS_TERM_DESC_CACHE_SIZE) { - m_desc->terms_buffer = ecs_os_calloc_n( - ecs_term_t, m_term_index + 1); - ecs_os_memcpy_n(m_desc->terms_buffer, m_desc->terms, - ecs_term_t, m_term_index); - ecs_os_memset_n(m_desc->terms, 0, - ecs_term_t, ECS_TERM_DESC_CACHE_SIZE); - } else { - m_desc->terms_buffer = ecs_os_realloc_n(m_desc->terms_buffer, - ecs_term_t, m_term_index + 1); - } - - m_desc->terms_buffer_count = m_term_index + 1; - - this->set_term(&m_desc->terms_buffer[m_term_index]); - } else { - this->set_term(&m_desc->terms[m_term_index]); - } - - m_term_index ++; - - error: - return *this; - } - - Base& arg(int32_t term_index) { - ecs_assert(term_index > 0, ECS_INVALID_PARAMETER, NULL); - int32_t prev_index = m_term_index; - m_term_index = term_index - 1; - this->term(); - m_term_index = prev_index; - ecs_assert(ecs_term_is_initialized(this->m_term), - ECS_INVALID_PARAMETER, NULL); - return *this; - } - - template - Base& term() { - this->term(); - *this->m_term = flecs::term(this->world_v()).id().move(); - this->m_term->inout = static_cast(_::type_to_inout()); - return *this; - } - - Base& term(id_t id) { - this->term(); - *this->m_term = flecs::term(id).move(); - return *this; - } - - template ::value > = 0> - Base& term(E value) { - flecs::entity_t r = _::cpp_type::id(this->world_v()); - auto o = enum_type(this->world_v()).entity(value); - return term(r, o); - } - - template - Base& term() { - this->term(); - *this->m_term = flecs::term(this->world_v()).id().move(); - return *this; - } +struct observer_builder_i : query_builder_i { + using BaseClass = query_builder_i; + observer_builder_i() + : BaseClass(nullptr) + , desc_(nullptr) + , event_count_(0) { } - template - Base& term(id_t o) { - this->term(); - *this->m_term = flecs::term(this->world_v()).id(o).move(); - return *this; - } + observer_builder_i(ecs_observer_desc_t *desc) + : BaseClass(&desc->query) + , desc_(desc) + , event_count_(0) { } - Base& term(id_t r, id_t o) { - this->term(); - *this->m_term = flecs::term(this->world_v()).id(r, o).move(); + /** Specify the event(s) for when the observer should run. + * @param evt The event. + */ + Base& event(entity_t evt) { + desc_->events[event_count_ ++] = evt; return *this; } - Base& term(const flecs::type& type) { - this->term(); - *this->m_term = flecs::term(this->world_v()).id(type).move(); + /** Specify the event(s) for when the observer should run. + * @tparam E The event. + */ + template + Base& event() { + desc_->events[event_count_ ++] = _::type().id(world_v()); return *this; } - Base& term(const flecs::type& type, flecs::id_t o) { - this->term(); - *this->m_term = flecs::term(this->world_v()).id(type, o).move(); + /** Invoke observer for anything that matches its query on creation */ + Base& yield_existing(bool value = true) { + desc_->yield_existing = value; return *this; } - Base& term(const char *expr) { - this->term(); - *this->m_term = flecs::term(this->world_v(), expr).move(); - this->m_term->move = true; + /** Set observer flags */ + Base& observer_flags(ecs_flags32_t flags) { + desc_->flags_ |= flags; return *this; } - Base& term(flecs::term& term) { - this->term(); - *this->m_term = term.move(); + /** Set observer context */ + Base& ctx(void *ptr) { + desc_->ctx = ptr; return *this; } - Base& term(flecs::term&& term) { - this->term(); - *this->m_term = term.move(); + /** Set observer run callback */ + Base& run(ecs_iter_action_t action) { + desc_->run = action; return *this; } protected: - virtual flecs::world_t* world_v() = 0; - int32_t m_term_index; - int32_t m_expr_count; + virtual flecs::world_t* world_v() override = 0; private: operator Base&() { return *static_cast(this); } - ecs_filter_desc_t *m_desc; + ecs_observer_desc_t *desc_; + int32_t event_count_; }; } @@ -20951,15 +33740,19 @@ struct filter_builder_i : term_builder_i { namespace flecs { namespace _ { template - using filter_builder_base = builder< - filter, ecs_filter_desc_t, filter_builder, - filter_builder_i, Components ...>; + using observer_builder_base = node_builder< + observer, ecs_observer_desc_t, observer_builder, + observer_builder_i, Components ...>; } +/** Observer builder. + * + * @ingroup cpp_observers + */ template -struct filter_builder final : _::filter_builder_base { - filter_builder(flecs::world_t* world) - : _::filter_builder_base(world) +struct observer_builder final : _::observer_builder_base { + observer_builder(flecs::world_t* world, const char *name = nullptr) + : _::observer_builder_base(world, name) { _::sig(world).populate(this); } @@ -20971,384 +33764,462 @@ struct filter_builder final : _::filter_builder_base { namespace flecs { -struct filter_base { - filter_base() - : m_world(nullptr) - , m_filter({}) - , m_filter_ptr(nullptr) { } - - filter_base(world_t *world, const ecs_filter_t *filter) - : m_world(world) - , m_filter({}) - , m_filter_ptr(filter) { } - - filter_base(world_t *world, ecs_filter_t *filter) - : m_world(world) - , m_filter_ptr(&m_filter) { - ecs_filter_move(&m_filter, filter); - } - - filter_base(world_t *world, ecs_filter_desc_t *desc) - : m_world(world) - { - int res = ecs_filter_init(world, &m_filter, desc); - if (res != 0) { - ecs_abort(ECS_INVALID_PARAMETER, NULL); - } +struct observer final : entity +{ + using entity::entity; - if (desc->terms_buffer) { - ecs_os_free(desc->terms_buffer); - } + explicit observer() : entity() { } - m_filter_ptr = &m_filter; + observer(flecs::world_t *world, ecs_observer_desc_t *desc) { + world_ = world; + id_ = ecs_observer_init(world, desc); } - filter_base(const filter_base& obj) { - this->m_world = obj.m_world; - if (obj.m_filter_ptr) { - this->m_filter_ptr = &this->m_filter; - } else { - this->m_filter_ptr = nullptr; - } - ecs_filter_copy(&m_filter, &obj.m_filter); + void ctx(void *ctx) { + ecs_observer_desc_t desc = {}; + desc.entity = id_; + desc.ctx = ctx; + ecs_observer_init(world_, &desc); } - filter_base& operator=(const filter_base& obj) { - this->m_world = obj.m_world; - if (obj.m_filter_ptr) { - this->m_filter_ptr = &this->m_filter; - } else { - this->m_filter_ptr = nullptr; - } - ecs_filter_copy(&m_filter, &obj.m_filter); - return *this; + void* ctx() const { + return ecs_observer_get(world_, id_)->ctx; } - filter_base(filter_base&& obj) { - this->m_world = obj.m_world; - if (obj.m_filter_ptr) { - this->m_filter_ptr = &this->m_filter; - } else { - this->m_filter_ptr = nullptr; - } - ecs_filter_move(&m_filter, &obj.m_filter); + flecs::query<> query() const { + return flecs::query<>(ecs_observer_get(world_, id_)->query); } +}; - filter_base& operator=(filter_base&& obj) { - this->m_world = obj.m_world; - if (obj.m_filter_ptr) { - this->m_filter_ptr = &this->m_filter; - } else { - this->m_filter_ptr = nullptr; - } - ecs_filter_move(&m_filter, &obj.m_filter); - return *this; - } +// Mixin implementation +inline observer world::observer(flecs::entity e) const { + return flecs::observer(world_, e); +} - operator const flecs::filter_t*() const { - return m_filter_ptr; - } +template +inline observer_builder world::observer(Args &&... args) const { + return flecs::observer_builder(world_, FLECS_FWD(args)...); +} - /** Free the filter. - */ - ~filter_base() { - if ((&m_filter == m_filter_ptr) && m_filter_ptr) { - ecs_filter_fini(&m_filter); - } - } +} // namespace flecs - template - void each_term(const Func& func) { - for (int i = 0; i < m_filter_ptr->term_count; i ++) { - flecs::term t(m_world, m_filter_ptr->terms[i]); - func(t); - } - } +/** + * @file addons/cpp/mixins/event/impl.hpp + * @brief Event implementation. + */ - flecs::term term(int32_t index) { - return flecs::term(m_world, m_filter_ptr->terms[index]); - } +#pragma once - int32_t term_count() { - return m_filter_ptr->term_count; - } - flecs::string str() { - char *result = ecs_filter_str(m_world, m_filter_ptr); - return flecs::string(result); +namespace flecs +{ + +// Mixin implementation + +inline flecs::event_builder world::event(flecs::entity_t evt) const { + return flecs::event_builder(world_, evt); +} + +template +inline flecs::event_builder_typed world::event() const { + return flecs::event_builder_typed(world_, _::type().id(world_)); +} + +namespace _ { + inline void entity_observer_create( + flecs::world_t *world, + flecs::entity_t event, + flecs::entity_t entity, + ecs_iter_action_t callback, + void *callback_ctx, + ecs_ctx_free_t callback_ctx_free) + { + ecs_observer_desc_t desc = {}; + desc.events[0] = event; + desc.query.terms[0].id = EcsAny; + desc.query.terms[0].src.id = entity; + desc.callback = callback; + desc.callback_ctx = callback_ctx; + desc.callback_ctx_free = callback_ctx_free; + + flecs::entity_t o = ecs_observer_init(world, &desc); + ecs_add_pair(world, o, EcsChildOf, entity); } - operator filter<>() const; + template + struct entity_observer_factory { + template ::value> = 0> + static void create( + flecs::world_t *world, + flecs::entity_t entity, + Func&& f) + { + using Delegate = _::entity_observer_delegate; + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(f)); + entity_observer_create(world, _::type::id(world), entity, Delegate::run, ctx, _::free_obj); + } -protected: - world_t *m_world; - filter_t m_filter; - const filter_t *m_filter_ptr; -}; + template ::value> = 0> + static void create( + flecs::world_t *world, + flecs::entity_t entity, + Func&& f) + { + using Delegate = _::entity_payload_observer_delegate; + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(f)); + entity_observer_create(world, _::type::id(world), entity, Delegate::run, ctx, _::free_obj); + } + }; +} -template -struct filter : filter_base, iterable { -private: - using Terms = typename _::term_ptrs::array; +template +template +inline const Self& entity_builder::observe(flecs::entity_t evt, Func&& f) const { + using Delegate = _::entity_observer_delegate; + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(f)); -public: - using filter_base::filter_base; + _::entity_observer_create(world_, evt, id_, Delegate::run, ctx, _::free_obj); - filter() : filter_base() { } // necessary not not confuse msvc + return to_base(); +} - filter(const filter& obj) : filter_base(obj) { } +template +template +inline const Self& entity_builder::observe(Func&& f) const { + _::entity_observer_factory::template create( + world_, id_, FLECS_FWD(f)); + return to_base(); +} - filter& operator=(const filter& obj) { - filter_base::operator=(obj); - return *this; - } +template +template +inline const Self& entity_builder::observe(Func&& f) const { + return this->observe<_::event_from_func_t>(FLECS_FWD(f)); +} - filter(filter&& obj) : filter_base(FLECS_MOV(obj)) { } +inline void entity_view::emit(flecs::entity evt) const { + this->emit(evt.id()); +} - filter& operator=(filter&& obj) { - filter_base(FLECS_MOV(obj)); - return *this; - } +inline void entity_view::enqueue(flecs::entity evt) const { + this->enqueue(evt.id()); +} -private: - ecs_iter_t get_iter() const override { - return ecs_filter_iter(m_world, m_filter_ptr); - } +} // namespace flecs - ecs_iter_next_action_t next_action() const override { - return ecs_filter_next; - } +/** + * @file addons/cpp/mixins/enum/impl.hpp + * @brief Enum implementation. + */ - ecs_iter_next_action_t next_each_action() const override { - return ecs_filter_next_instanced; - } -}; +#pragma once -// World mixin implementation -template -inline flecs::filter world::filter(Args &&... args) const { - return flecs::filter_builder(m_world, FLECS_FWD(args)...) - .build(); +namespace flecs { + +template +inline E entity_view::to_constant() const { + using U = typename std::underlying_type::type; + const E* ptr = static_cast(ecs_get_id(world_, id_, + ecs_pair(flecs::Constant, _::type::id(world_)))); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, "entity is not a constant"); + return ptr[0]; } -template -inline flecs::filter_builder world::filter_builder(Args &&... args) const { - return flecs::filter_builder(m_world, FLECS_FWD(args)...); +template ::value >> +inline flecs::entity world::to_entity(E constant) const { + const auto& et = enum_type(world_); + return flecs::entity(world_, et.entity(constant)); } -// world::each +} +#ifdef FLECS_MODULE +/** + * @file addons/cpp/mixins/module/impl.hpp + * @brief Module implementation. + */ + +#pragma once + +namespace flecs { + namespace _ { -// Each with entity parameter -template -struct filter_invoker_w_ent; +template +ecs_entity_t do_import(world& world, const char *symbol) { + ecs_trace("#[magenta]import#[reset] %s", _::type_name()); + ecs_log_push(); -template -struct filter_invoker_w_ent > -{ - filter_invoker_w_ent(const flecs::world& world, Func&& func) { - auto f = world.filter(); - f.each(FLECS_MOV(func)); - } -}; + ecs_entity_t scope = ecs_set_scope(world, 0); -// Each without entity parameter -template -struct filter_invoker_no_ent; + // Initialize module component type & don't allow it to be registered as a + // tag, as this would prevent calling emplace() + auto c_ = component(world, nullptr, false); -template -struct filter_invoker_no_ent > -{ - filter_invoker_no_ent(const flecs::world& world, Func&& func) { - auto f = world.filter(); - f.each(FLECS_MOV(func)); - } -}; + // Make module component sparse so that it'll never move in memory. This + // guarantees that a module destructor can be reliably used to cleanup + // module resources. + c_.add(flecs::Sparse); + + c_.add(flecs::Singleton); -// Switch between function with & without entity parameter -template -struct filter_invoker; + ecs_set_scope(world, c_); + world.emplace(world); + ecs_set_scope(world, scope); -template -struct filter_invoker, flecs::entity>::value> > { - filter_invoker(const flecs::world& world, Func&& func) { - filter_invoker_w_ent>(world, FLECS_MOV(func)); - } -}; + ecs_add_id(world, c_, EcsModule); -template -struct filter_invoker, flecs::entity>::value> > { - filter_invoker(const flecs::world& world, Func&& func) { - filter_invoker_no_ent>(world, FLECS_MOV(func)); - } -}; + // It should now be possible to lookup the module + ecs_entity_t m = ecs_lookup_symbol(world, symbol, false, false); + ecs_assert(m != 0, ECS_MODULE_UNDEFINED, symbol); + ecs_assert(m == c_, ECS_INTERNAL_ERROR, NULL); -} + ecs_log_pop(); -template -inline void world::each(Func&& func) const { - _::filter_invoker f_invoker(*this, FLECS_MOV(func)); + return m; } -template -inline void world::each(Func&& func) const { - ecs_term_t t = {}; - t.id = _::cpp_type::id(); - ecs_iter_t it = ecs_term_iter(m_world, &t); +template +flecs::entity import(world& world) { + const char *symbol = _::symbol_name(); - while (ecs_term_next(&it)) { - _::each_invoker(func).invoke(&it); - } -} + ecs_entity_t m = ecs_lookup_symbol(world, symbol, true, false); -template -inline void world::each(flecs::id_t term_id, Func&& func) const { - ecs_term_t t = {}; - t.id = term_id; - ecs_iter_t it = ecs_term_iter(m_world, &t); + if (!_::type::registered(world)) { + /* Module is registered with world, initialize static data */ + if (m) { + _::type::init_builtin(world, m, false); + + /* Module is not yet registered, register it now */ + } else { + m = _::do_import(world, symbol); + } - while (ecs_term_next(&it)) { - _::each_invoker(func).invoke(&it); + /* Module has been registered, but could have been for another world. Import + * if module hasn't been registered for this world. */ + } else if (!m) { + m = _::do_import(world, symbol); } -} -// filter_base implementation -inline filter_base::operator flecs::filter<> () const { - flecs::filter<> f; - ecs_filter_copy(&f.m_filter, &this->m_filter); - f.m_filter_ptr = &f.m_filter; - f.m_world = this->m_world; - return f; + return flecs::entity(world, m); } } -#pragma once - +/** + * @defgroup cpp_addons_modules Modules + * @ingroup cpp_addons + * Modules organize components, systems and more in reusable units of code. + * + * @{ + */ -namespace flecs -{ +template +inline flecs::entity world::module(const char *name) const { + flecs::entity result = this->entity( + _::type::register_id(world_, nullptr, false)); + + if (name) { + flecs::entity prev_parent = result.parent(); + ecs_add_path_w_sep(world_, result, 0, name, "::", "::"); + flecs::entity parent = result.parent(); + if (prev_parent != parent) { + // Module was reparented, cleanup old parent(s) + flecs::entity cur = prev_parent, next; + while (cur) { + next = cur.parent(); + cur.destruct(); + this->set_version(cur); + cur = next; + } + } + } -// Mixin implementation + return result; +} -inline flecs::event_builder world::event(flecs::entity_t evt) const { - return flecs::event_builder(m_world, evt); +template +inline flecs::entity world::import() { + return flecs::_::import(*this); } -template -inline flecs::event_builder_typed world::event() const { - return flecs::event_builder_typed(m_world, _::cpp_type().id(m_world)); +/** @} */ + } -} // namespace flecs +#endif +#ifdef FLECS_SYSTEM +/** + * @file addons/cpp/mixins/system/impl.hpp + * @brief System module implementation. + */ #pragma once +/** + * @file addons/cpp/mixins/system/builder.hpp + * @brief System builder. + */ + #pragma once +/** + * @file addons/cpp/mixins/system/builder_i.hpp + * @brief System builder interface. + */ + #pragma once -namespace flecs { +namespace flecs +{ -// Query builder interface +/** System builder interface. + * + * @ingroup cpp_addons_systems + */ template -struct query_builder_i : filter_builder_i { +struct system_builder_i : query_builder_i { private: - using BaseClass = filter_builder_i; - + using BaseClass = query_builder_i; + public: - query_builder_i() - : BaseClass(nullptr) - , m_desc(nullptr) { } + system_builder_i(ecs_system_desc_t *desc) + : BaseClass(&desc->query) + , desc_(desc) { } - query_builder_i(ecs_query_desc_t *desc, int32_t term_index = 0) - : BaseClass(&desc->filter, term_index) - , m_desc(desc) { } + /** Specify in which phase the system should run. + * + * @param phase The phase. + */ + Base& kind(entity_t phase) { + flecs::entity_t cur_phase = ecs_get_target( + world_v(), desc_->entity, EcsDependsOn, 0); + if (cur_phase) { + ecs_remove_id(world_v(), desc_->entity, ecs_dependson(cur_phase)); + ecs_remove_id(world_v(), desc_->entity, cur_phase); + } + if (phase) { + ecs_add_id(world_v(), desc_->entity, ecs_dependson(phase)); + ecs_add_id(world_v(), desc_->entity, phase); + } + return *this; + } - /** Sort the output of a query. - * This enables sorting of entities across matched tables. As a result of this - * operation, the order of entities in the matched tables may be changed. - * Resorting happens when a query iterator is obtained, and only if the table - * data has changed. + template ::value> = 0> + Base& kind(E phase) + { + const auto& et = enum_type(this->world_v()); + flecs::entity_t target = et.entity(phase); + return this->kind(target); + } + + /** Specify in which phase the system should run. * - * If multiple queries that match the same (sub)set of tables specify different - * sorting functions, resorting is likely to happen every time an iterator is - * obtained, which can significantly slow down iterations. + * @tparam Phase The phase. + */ + template + Base& kind() { + return this->kind(_::type::id(world_v())); + } + + /** Specify whether system can run on multiple threads. * - * The sorting function will be applied to the specified component. Resorting - * only happens if that component has changed, or when the entity order in the - * table has changed. If no component is provided, resorting only happens when - * the entity order changes. + * @param value If false system will always run on a single thread. + */ + Base& multi_threaded(bool value = true) { + desc_->multi_threaded = value; + return *this; + } + + /** Specify whether system should be ran in staged context. * - * @tparam T The component used to sort. - * @param compare The compare function used to sort the components. - */ - template - Base& order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { - ecs_order_by_action_t cmp = reinterpret_cast(compare); - return this->order_by(_::cpp_type::id(this->world_v()), cmp); + * @param value If false system will always run staged. + */ + Base& immediate(bool value = true) { + desc_->immediate = value; + return *this; } - /** Sort the output of a query. - * Same as order_by, but with component identifier. + /** Set system interval. + * This operation will cause the system to be ran at the specified interval. * - * @param component The component used to sort. - * @param compare The compare function used to sort the components. - */ - Base& order_by(flecs::entity_t component, int(*compare)(flecs::entity_t, const void*, flecs::entity_t, const void*)) { - m_desc->order_by = reinterpret_cast(compare); - m_desc->order_by_component = component; + * The timer is synchronous, and is incremented each frame by delta_time. + * + * @param interval The interval value. + */ + Base& interval(ecs_ftime_t interval) { + desc_->interval = interval; return *this; } - /** Group and sort matched tables. - * Similar yo ecs_query_order_by, but instead of sorting individual entities, this - * operation only sorts matched tables. This can be useful of a query needs to - * enforce a certain iteration order upon the tables it is iterating, for - * example by giving a certain component or tag a higher priority. + /** Set system rate. + * This operation will cause the system to be ran at a multiple of the + * provided tick source. The tick source may be any entity, including + * another system. * - * The sorting function assigns a "rank" to each type, which is then used to - * sort the tables. Tables with higher ranks will appear later in the iteration. - * - * Resorting happens when a query iterator is obtained, and only if the set of - * matched tables for a query has changed. If table sorting is enabled together - * with entity sorting, table sorting takes precedence, and entities will be - * sorted within each set of tables that are assigned the same rank. + * @param tick_source The tick source. + * @param rate The multiple at which to run the system. + */ + Base& rate(const entity_t tick_source, int32_t rate) { + desc_->rate = rate; + desc_->tick_source = tick_source; + return *this; + } + + /** Set system rate. + * This operation will cause the system to be ran at a multiple of the + * frame tick frequency. If a tick source was provided, this just updates + * the rate of the system. * - * @tparam T The component used to determine the group rank. - * @param rank The rank action. + * @param rate The multiple at which to run the system. */ - template - Base& group_by(uint64_t(*rank)(flecs::world_t*, flecs::table_t *table, flecs::id_t id, void* ctx)) { - ecs_group_by_action_t rnk = reinterpret_cast(rank); - return this->group_by(_::cpp_type::id(this->world_v()), rnk); + Base& rate(int32_t rate) { + desc_->rate = rate; + return *this; } - /** Group and sort matched tables. - * Same as group_by, but with component identifier. + /** Set tick source. + * This operation sets a shared tick source for the system. * - * @param component The component used to determine the group rank. - * @param rank The rank action. + * @tparam T The type associated with the singleton tick source to use for the system. + */ + template + Base& tick_source() { + desc_->tick_source = _::type::id(world_v()); + return *this; + } + + /** Set tick source. + * This operation sets a shared tick source for the system. + * + * @param tick_source The tick source to use for the system. */ - Base& group_by(flecs::entity_t component, uint64_t(*rank)(flecs::world_t*, flecs::table_t *table, flecs::id_t id, void* ctx)) { - m_desc->group_by = reinterpret_cast(rank); - m_desc->group_by_id = component; + Base& tick_source(flecs::entity_t tick_source) { + desc_->tick_source = tick_source; return *this; - } + } + + /** Set system context */ + Base& ctx(void *ptr) { + desc_->ctx = ptr; + return *this; + } + + /** Set system run callback */ + Base& run(ecs_iter_action_t action) { + desc_->run = action; + return *this; + } - /** Specify parent query (creates subquery) */ - Base& parent(const query_base& parent); - protected: - virtual flecs::world_t* world_v() = 0; + virtual flecs::world_t* world_v() override = 0; private: operator Base&() { return *static_cast(this); } - ecs_query_desc_t *m_desc; + ecs_system_desc_t *desc_; }; } @@ -21357,309 +34228,262 @@ struct query_builder_i : filter_builder_i { namespace flecs { namespace _ { template - using query_builder_base = builder< - query, ecs_query_desc_t, query_builder, - query_builder_i, Components ...>; + using system_builder_base = node_builder< + system, ecs_system_desc_t, system_builder, + system_builder_i, Components ...>; } +/** System builder. + * + * @ingroup cpp_addons_systems + */ template -struct query_builder final : _::query_builder_base { - query_builder(flecs::world_t* world) - : _::query_builder_base(world) +struct system_builder final : _::system_builder_base { + system_builder(flecs::world_t* world, const char *name = nullptr) + : _::system_builder_base(world, name) { _::sig(world).populate(this); + +#ifdef FLECS_PIPELINE + ecs_add_id(world, this->desc_.entity, ecs_dependson(flecs::OnUpdate)); + ecs_add_id(world, this->desc_.entity, flecs::OnUpdate); +#endif } + + template + system each(Func&& func); }; } -namespace flecs { - -//////////////////////////////////////////////////////////////////////////////// -//// Persistent queries -//////////////////////////////////////////////////////////////////////////////// - -struct query_base { - query_base() - : m_world(nullptr) - , m_query(nullptr) { } - - query_base(world_t *world, query_t *query = nullptr) - : m_world(world) - , m_query(query) { } - - query_base(world_t *world, ecs_query_desc_t *desc) - : m_world(world) - { - m_query = ecs_query_init(world, desc); - - if (!m_query) { - ecs_abort(ECS_INVALID_PARAMETER, NULL); - } - - if (desc->filter.terms_buffer) { - ecs_os_free(desc->filter.terms_buffer); - } - } - - operator query_t*() const { - return m_query; - } +namespace flecs +{ - /** Returns whether the query data changed since the last iteration. - * This operation must be invoked before obtaining the iterator, as this will - * reset the changed state. The operation will return true after: - * - new entities have been matched with - * - matched entities were deleted - * - matched components were changed - * - * @return true if entities changed, otherwise false. - */ - bool changed() { - return ecs_query_changed(m_query, 0); - } +struct system_runner_fluent { + system_runner_fluent( + world_t *world, + entity_t id, + int32_t stage_current, + int32_t stage_count, + ecs_ftime_t delta_time, + void *param) + : stage_(world) + , id_(id) + , delta_time_(delta_time) + , param_(param) + , stage_current_(stage_current) + , stage_count_(stage_count) { } - /** Returns whether query is orphaned. - * When the parent query of a subquery is deleted, it is left in an orphaned - * state. The only valid operation on an orphaned query is deleting it. Only - * subqueries can be orphaned. - * - * @return true if query is orphaned, otherwise false. - */ - bool orphaned() { - return ecs_query_orphaned(m_query); + system_runner_fluent& offset(int32_t offset) { + offset_ = offset; + return *this; } - /** Free the query. - */ - void destruct() { - ecs_query_fini(m_query); - m_world = nullptr; - m_query = nullptr; + system_runner_fluent& limit(int32_t limit) { + limit_ = limit; + return *this; } - template - void each_term(const Func& func) { - const ecs_filter_t *f = ecs_query_get_filter(m_query); - ecs_assert(f != NULL, ECS_INVALID_PARAMETER, NULL); + system_runner_fluent& stage(flecs::world& stage) { + stage_ = stage.c_ptr(); + return *this; + } - for (int i = 0; i < f->term_count; i ++) { - flecs::term t(m_world, f->terms[i]); - func(t); + ~system_runner_fluent() { + if (stage_count_) { + ecs_run_worker( + stage_, id_, stage_current_, stage_count_, delta_time_, + param_); + } else { + ecs_run(stage_, id_, delta_time_, param_); } } - filter_base filter() { - return filter_base(m_world, ecs_query_get_filter(m_query)); - } +private: + world_t *stage_; + entity_t id_; + ecs_ftime_t delta_time_; + void *param_; + int32_t offset_; + int32_t limit_; + int32_t stage_current_; + int32_t stage_count_; +}; - flecs::term term(int32_t index) { - const ecs_filter_t *f = ecs_query_get_filter(m_query); - ecs_assert(f != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs::term(m_world, f->terms[index]); - } +struct system final : entity +{ + using entity::entity; - int32_t term_count() { - const ecs_filter_t *f = ecs_query_get_filter(m_query); - return f->term_count; + explicit system() { + id_ = 0; + world_ = nullptr; } - flecs::string str() { - const ecs_filter_t *f = ecs_query_get_filter(m_query); - char *result = ecs_filter_str(m_world, f); - return flecs::string(result); + explicit system(flecs::world_t *world, ecs_system_desc_t *desc) { + world_ = world; + id_ = ecs_system_init(world, desc); } - operator query<>() const; - -protected: - world_t *m_world; - query_t *m_query; -}; + void ctx(void *ctx) { + ecs_system_desc_t desc = {}; + desc.entity = id_; + desc.ctx = ctx; + ecs_system_init(world_, &desc); + } -template -struct query final : query_base, iterable { -private: - using Terms = typename _::term_ptrs::array; + void* ctx() const { + return ecs_system_get(world_, id_)->ctx; + } - ecs_iter_t get_iter() const override { - return ecs_query_iter(m_world, m_query); + flecs::query<> query() const { + return flecs::query<>(ecs_system_get(world_, id_)->query); } - ecs_iter_next_action_t next_action() const override { - return ecs_query_next; + system_runner_fluent run(ecs_ftime_t delta_time = 0.0f, void *param = nullptr) const { + return system_runner_fluent(world_, id_, 0, 0, delta_time, param); } - ecs_iter_next_action_t next_each_action() const override { - return ecs_query_next_instanced; + system_runner_fluent run_worker( + int32_t stage_current, + int32_t stage_count, + ecs_ftime_t delta_time = 0.0f, + void *param = nullptr) const + { + return system_runner_fluent( + world_, id_, stage_current, stage_count, delta_time, param); } -public: - using query_base::query_base; -}; +# ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/system_mixin.inl + * @brief Timer module system mixin. + */ -// Mixin implementation -template -inline flecs::query world::query(Args &&... args) const { - return flecs::query_builder(m_world, FLECS_FWD(args)...) - .build(); -} +/** + * @memberof flecs::system + * @ingroup cpp_addons_timer + * + * @{ + */ -template -inline flecs::query_builder world::query_builder(Args &&... args) const { - return flecs::query_builder(m_world, FLECS_FWD(args)...); -} +/** Set interval. + * @see ecs_set_interval + */ +void interval(ecs_ftime_t interval); -// Builder implementation -template -inline Base& query_builder_i::parent(const query_base& parent) { - m_desc->parent = parent; - return *static_cast(this); -} +/** Get interval. + * @see ecs_get_interval. + */ +ecs_ftime_t interval(); -// query_base implementation -inline query_base::operator query<>() const { - return flecs::query<>(m_world, m_query); -} +/** Set timeout. + * @see ecs_set_timeout + */ +void timeout(ecs_ftime_t timeout); -} // namespace flecs +/** Get timeout. + * @see ecs_get_timeout + */ +ecs_ftime_t timeout(); -#pragma once +/** Set system rate (system is its own tick source). + * @see ecs_set_rate + */ +void rate(int32_t rate); -#pragma once +/** Start timer. + * @see ecs_start_timer + */ +void start(); -#pragma once +/** Stop timer. + * @see ecs_start_timer + */ +void stop(); -namespace flecs { -namespace _ { +/** Set external tick source. + * @see ecs_set_tick_source + */ +template +void set_tick_source(); -// Macros for template types so we don't go cross-eyed -#define FLECS_IBUILDER template class +/** Set external tick source. + * @see ecs_set_tick_source + */ +void set_tick_source(flecs::entity e); -template -struct node_builder : IBuilder -{ - using IBase = IBuilder; +/** @} */ -public: - explicit node_builder(flecs::world_t* world, const char *name = nullptr) - : IBase(&m_desc) - , m_desc{} - , m_world(world) - , m_instanced(false) - { - m_desc.entity.name = name; - m_desc.entity.sep = "::"; - } +# endif - /* Iter (or each) is mandatory and always the last thing that - * is added in the fluent method chain. Create system signature from both - * template parameters and anything provided by the signature method. */ - template - T iter(Func&& func) { - using Invoker = typename _::iter_invoker< - typename std::decay::type, Components...>; - return build(FLECS_FWD(func)); - } +}; - /* Each is similar to action, but accepts a function that operates on a - * single entity */ - template - T each(Func&& func) { - using Invoker = typename _::each_invoker< - typename std::decay::type, Components...>; - m_instanced = true; - return build(FLECS_FWD(func)); - } +// Mixin implementation +inline system world::system(flecs::entity e) const { + return flecs::system(world_, e); +} -protected: - flecs::world_t* world_v() override { return m_world; } - TDesc m_desc; - flecs::world_t *m_world; - bool m_instanced; +template +inline system_builder world::system(Args &&... args) const { + return flecs::system_builder(world_, FLECS_FWD(args)...); +} -private: - template - T build(Func&& func) { - auto ctx = FLECS_NEW(Invoker)(FLECS_FWD(func)); - - m_desc.callback = Invoker::run; - m_desc.binding_ctx = ctx; - m_desc.binding_ctx_free = reinterpret_cast< - ecs_ctx_free_t>(_::free_obj); - - return T(m_world, &m_desc); - } -}; +namespace _ { -#undef FLECS_IBUILDER +inline void system_init(flecs::world& world) { + world.component("flecs::system::TickSource"); +} } // namespace _ -} // namespace flecs -#pragma once +template +template +inline system system_builder::each(Func&& func) { + // Faster version of each() that iterates the query on the C++ side. + return this->run_each(FLECS_FWD(func)); +} +} // namespace flecs -namespace flecs -{ +#endif +#ifdef FLECS_PIPELINE +/** + * @file addons/cpp/mixins/pipeline/impl.hpp + * @brief Pipeline module implementation. + */ -// Trigger builder interface -template -struct trigger_builder_i : term_builder_i { - using Class = trigger_builder_i; - using BaseClass = term_builder_i; +#pragma once - trigger_builder_i(ecs_trigger_desc_t *desc) - : BaseClass(&desc->term) - , m_desc(desc) - , m_event_count(0) { } +/** + * @file addons/cpp/mixins/pipeline/builder.hpp + * @brief Pipeline builder. + */ - /** Specify the event(s) for when the trigger should run. - * @param evt The event. - */ - Base& event(entity_t evt) { - m_desc->events[m_event_count ++] = evt; - return *this; - } +#pragma once - /** Specify the event(s) for when the trigger should run. - * @tparam E The event. - */ - template - Base& event() { - m_desc->events[m_event_count ++] = _::cpp_type().id(world_v()); - return *this; - } +/** + * @file addons/cpp/mixins/pipeline/builder_i.hpp + * @brief Pipeline builder interface. + */ - /** Invoke observer for anything that matches its filter on creation */ - Base& yield_existing(bool value = true) { - m_desc->yield_existing = value; - return *this; - } +#pragma once - /** Associate trigger with entity */ - Base& self(flecs::entity self) { - m_desc->self = self; - return *this; - } - /** Set system context */ - Base& ctx(void *ptr) { - m_desc->ctx = ptr; - return *this; - } +namespace flecs { -protected: - virtual flecs::world_t* world_v() = 0; +/** Pipeline builder interface. + * + * @ingroup cpp_pipelines + */ +template +struct pipeline_builder_i : query_builder_i { + pipeline_builder_i(ecs_pipeline_desc_t *desc, int32_t term_index = 0) + : query_builder_i(&desc->query, term_index) + , desc_(desc) { } private: - operator Base&() { - return *static_cast(this); - } - - ecs_trigger_desc_t *m_desc; - int32_t m_event_count; + ecs_pipeline_desc_t *desc_; }; } @@ -21668,899 +34492,1150 @@ struct trigger_builder_i : term_builder_i { namespace flecs { namespace _ { template - using trigger_builder_base = node_builder< - trigger, - ecs_trigger_desc_t, - trigger_builder, - trigger_builder_i, - Components ...>; + using pipeline_builder_base = builder< + pipeline, ecs_pipeline_desc_t, pipeline_builder, + pipeline_builder_i, Components ...>; } +/** Pipeline builder. + * + * @ingroup cpp_pipelines + */ template -struct trigger_builder final : _::trigger_builder_base { - trigger_builder(flecs::world_t* world, const char *name = nullptr) - : _::trigger_builder_base(world, name) +struct pipeline_builder final : _::pipeline_builder_base { + pipeline_builder(flecs::world_t* world, flecs::entity_t id = 0) + : _::pipeline_builder_base(world) { _::sig(world).populate(this); + this->desc_.entity = id; } }; } -namespace flecs -{ - -struct trigger final : entity -{ - using entity::entity; +namespace flecs { - trigger(flecs::world_t *world, ecs_trigger_desc_t *desc) - : entity(world, ecs_trigger_init(world, desc)) - { - ecs_term_fini(&desc->term); - } - - void ctx(void *ctx) { - ecs_trigger_desc_t desc = {}; - desc.entity.entity = m_id; - desc.ctx = ctx; - ecs_trigger_init(m_world, &desc); - } +template +struct pipeline : entity { + pipeline(world_t *world, ecs_pipeline_desc_t *desc) + : entity(world) + { + id_ = ecs_pipeline_init(world, desc); - void* ctx() const { - return ecs_get_trigger_ctx(m_world, m_id); + if (!id_) { + ecs_abort(ECS_INVALID_PARAMETER, NULL); + } } }; -// Mixin implementation +inline flecs::pipeline_builder<> world::pipeline() const { + return flecs::pipeline_builder<>(world_); +} -template -inline trigger_builder world::trigger(Args &&... args) const { - return flecs::trigger_builder(m_world, FLECS_FWD(args)...); +template ::value >> +inline flecs::pipeline_builder<> world::pipeline() const { + return flecs::pipeline_builder<>(world_, _::type::id(world_)); } -} // namespace flecs +inline void world::set_pipeline(const flecs::entity pip) const { + return ecs_set_pipeline(world_, pip); +} -#pragma once +template +inline void world::set_pipeline() const { + return ecs_set_pipeline(world_, _::type::id(world_)); +} -#pragma once +inline flecs::entity world::get_pipeline() const { + return flecs::entity(world_, ecs_get_pipeline(world_)); +} -#pragma once +inline bool world::progress(ecs_ftime_t delta_time) const { + return ecs_progress(world_, delta_time); +} +inline void world::run_pipeline(const flecs::entity_t pip, ecs_ftime_t delta_time) const { + return ecs_run_pipeline(world_, pip, delta_time); +} -namespace flecs { +template ::value >> +inline void world::run_pipeline(ecs_ftime_t delta_time) const { + return ecs_run_pipeline(world_, _::type::id(world_), delta_time); +} -// Observer builder interface -template -struct observer_builder_i : filter_builder_i { - using BaseClass = filter_builder_i; - observer_builder_i() - : BaseClass(nullptr) - , m_desc(nullptr) - , m_event_count(0) { } +inline void world::set_time_scale(ecs_ftime_t mul) const { + ecs_set_time_scale(world_, mul); +} - observer_builder_i(ecs_observer_desc_t *desc) - : BaseClass(&desc->filter) - , m_desc(desc) - , m_event_count(0) { } +inline void world::set_target_fps(ecs_ftime_t target_fps) const { + ecs_set_target_fps(world_, target_fps); +} - /** Specify the event(s) for when the observer should run. - * @param evt The event. - */ - Base& event(entity_t evt) { - m_desc->events[m_event_count ++] = evt; +inline void world::reset_clock() const { + ecs_reset_clock(world_); +} + +inline void world::set_threads(int32_t threads) const { + ecs_set_threads(world_, threads); +} + +inline int32_t world::get_threads() const { + return ecs_get_stage_count(world_); +} + +inline void world::set_task_threads(int32_t task_threads) const { + ecs_set_task_threads(world_, task_threads); +} + +inline bool world::using_task_threads() const { + return ecs_using_task_threads(world_); +} + +} + +#endif +#ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/impl.hpp + * @brief Timer module implementation. + */ + +#pragma once + +namespace flecs { + +// Timer class +struct timer final : entity { + using entity::entity; + + timer& interval(ecs_ftime_t interval) { + ecs_set_interval(world_, id_, interval); return *this; } - /** Specify the event(s) for when the observer should run. - * @tparam E The event. - */ - template - Base& event() { - m_desc->events[m_event_count ++] = _::cpp_type().id(world_v()); - return *this; + ecs_ftime_t interval() { + return ecs_get_interval(world_, id_); } - /** Invoke observer for anything that matches its filter on creation */ - Base& yield_existing(bool value = true) { - m_desc->yield_existing = value; + timer& timeout(ecs_ftime_t timeout) { + ecs_set_timeout(world_, id_, timeout); return *this; } - /** Associate observer with entity */ - Base& self(flecs::entity self) { - m_desc->self = self; - return *this; + ecs_ftime_t timeout() { + return ecs_get_timeout(world_, id_); } - /** Set system context */ - Base& ctx(void *ptr) { - m_desc->ctx = ptr; + timer& rate(int32_t rate, flecs::entity_t tick_source = 0) { + ecs_set_rate(world_, id_, rate, tick_source); return *this; - } - -protected: - virtual flecs::world_t* world_v() = 0; + } -private: - operator Base&() { - return *static_cast(this); + void start() { + ecs_start_timer(world_, id_); } - ecs_observer_desc_t *m_desc; - int32_t m_event_count; + void stop() { + ecs_stop_timer(world_, id_); + } }; +template +inline flecs::timer world::timer() const { + return flecs::timer(world_, _::type::id(world_)); } - -namespace flecs { -namespace _ { - template - using observer_builder_base = node_builder< - observer, ecs_observer_desc_t, observer_builder, - observer_builder_i, Components ...>; +template +inline flecs::timer world::timer(Args &&... args) const { + return flecs::timer(world_, FLECS_FWD(args)...); } -template -struct observer_builder final : _::observer_builder_base { - observer_builder(flecs::world_t* world, const char *name = nullptr) - : _::observer_builder_base(world, name) - { - _::sig(world).populate(this); - } -}; +inline void world::randomize_timers() const { + ecs_randomize_timers(world_); +} +inline void system::interval(ecs_ftime_t interval) { + ecs_set_interval(world_, id_, interval); } +inline ecs_ftime_t system::interval() { + return ecs_get_interval(world_, id_); +} -namespace flecs -{ +inline void system::timeout(ecs_ftime_t timeout) { + ecs_set_timeout(world_, id_, timeout); +} -struct observer final : entity -{ - using entity::entity; +inline ecs_ftime_t system::timeout() { + return ecs_get_timeout(world_, id_); +} - observer(flecs::world_t *world, ecs_observer_desc_t *desc) - : entity(world, ecs_observer_init(world, desc)) - { - if (desc->filter.terms_buffer) { - ecs_os_free(desc->filter.terms_buffer); - } - } +inline void system::rate(int32_t rate) { + ecs_set_rate(world_, id_, rate, 0); +} - void ctx(void *ctx) { - ecs_observer_desc_t desc = {}; - desc.entity.entity = m_id; - desc.ctx = ctx; - ecs_observer_init(m_world, &desc); - } +inline void system::start() { + ecs_start_timer(world_, id_); +} - void* ctx() const { - return ecs_get_observer_ctx(m_world, m_id); - } -}; +inline void system::stop() { + ecs_stop_timer(world_, id_); +} -// Mixin implementation +template +inline void system::set_tick_source() { + ecs_set_tick_source(world_, id_, _::type::id(world_)); +} -template -inline observer_builder world::observer(Args &&... args) const { - return flecs::observer_builder(m_world, FLECS_FWD(args)...); +inline void system::set_tick_source(flecs::entity e) { + ecs_set_tick_source(world_, id_, e); } -} // namespace flecs +namespace _ { -#ifdef FLECS_MODULE -#pragma once +inline void timer_init(flecs::world& world) { + world.component("flecs::timer::RateFilter"); + world.component("flecs::timer::Timer"); +} -namespace flecs { +} +} -template -ecs_entity_t do_import(world& world, const char *symbol) { - ecs_trace("import %s", _::type_name()); - ecs_log_push(); +#endif +#ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/impl.hpp + * @brief Doc mixin implementation. + */ - ecs_entity_t scope = ecs_get_scope(world); - ecs_set_scope(world, 0); +#pragma once - // Initialize module component type & don't allow it to be registered as a - // tag, as this would prevent calling emplace() - auto m_c = component(world, nullptr, false); - ecs_add_id(world, m_c, EcsModule); +namespace flecs { +namespace doc { - world.emplace(world); +/** Get UUID for an entity. + * + * @see ecs_doc_get_uuid() + * @see flecs::doc::set_uuid() + * @see flecs::entity_view::doc_uuid() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_uuid(const flecs::entity_view& e) { + return ecs_doc_get_uuid(e.world(), e); +} - ecs_set_scope(world, scope); +/** Get human readable name for an entity. + * + * @see ecs_doc_get_name() + * @see flecs::doc::set_name() + * @see flecs::entity_view::doc_name() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_name(const flecs::entity_view& e) { + return ecs_doc_get_name(e.world(), e); +} - // It should now be possible to lookup the module - ecs_entity_t m = ecs_lookup_symbol(world, symbol, true); - ecs_assert(m != 0, ECS_MODULE_UNDEFINED, symbol); - ecs_assert(m == m_c, ECS_INTERNAL_ERROR, NULL); +/** Get brief description for an entity. + * + * @see ecs_doc_get_brief() + * @see flecs::doc::set_brief() + * @see flecs::entity_view::doc_brief() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_brief(const flecs::entity_view& e) { + return ecs_doc_get_brief(e.world(), e); +} - ecs_log_pop(); +/** Get detailed description for an entity. + * + * @see ecs_doc_get_detail() + * @see flecs::doc::set_detail() + * @see flecs::entity_view::doc_detail() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_detail(const flecs::entity_view& e) { + return ecs_doc_get_detail(e.world(), e); +} - return m; +/** Get link to external documentation for an entity. + * + * @see ecs_doc_get_link() + * @see flecs::doc::set_link() + * @see flecs::entity_view::doc_link() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_link(const flecs::entity_view& e) { + return ecs_doc_get_link(e.world(), e); } -template -flecs::entity import(world& world) { - const char *symbol = _::symbol_name(); +/** Get color for an entity. + * + * @see ecs_doc_get_color() + * @see flecs::doc::set_color() + * @see flecs::entity_view::doc_color() + * + * @ingroup cpp_addons_doc + */ +inline const char* get_color(const flecs::entity_view& e) { + return ecs_doc_get_color(e.world(), e); +} - ecs_entity_t m = ecs_lookup_symbol(world, symbol, true); - - if (!_::cpp_type::registered()) { +/** Set UUID for an entity. + * + * @see ecs_doc_set_uuid() + * @see flecs::doc::get_uuid() + * @see flecs::entity_builder::set_doc_uuid() + * + * @ingroup cpp_addons_doc + */ +inline void set_uuid(flecs::entity& e, const char *uuid) { + ecs_doc_set_uuid(e.world(), e, uuid); +} - /* Module is registered with world, initialize static data */ - if (m) { - _::cpp_type::init(world, m, false); - - /* Module is not yet registered, register it now */ - } else { - m = do_import(world, symbol); - } +/** Set human readable name for an entity. + * + * @see ecs_doc_set_name() + * @see flecs::doc::get_name() + * @see flecs::entity_builder::set_doc_name() + * + * @ingroup cpp_addons_doc + */ +inline void set_name(flecs::entity& e, const char *name) { + ecs_doc_set_name(e.world(), e, name); +} - /* Module has been registered, but could have been for another world. Import - * if module hasn't been registered for this world. */ - } else if (!m) { - m = do_import(world, symbol); - } +/** Set brief description for an entity. + * + * @see ecs_doc_set_brief() + * @see flecs::doc::get_brief() + * @see flecs::entity_builder::set_doc_brief() + * + * @ingroup cpp_addons_doc + */ +inline void set_brief(flecs::entity& e, const char *description) { + ecs_doc_set_brief(e.world(), e, description); +} - return flecs::entity(world, m); +/** Set detailed description for an entity. + * + * @see ecs_doc_set_detail() + * @see flecs::doc::get_detail() + * @see flecs::entity_builder::set_doc_detail() + * + * @ingroup cpp_addons_doc + */ +inline void set_detail(flecs::entity& e, const char *description) { + ecs_doc_set_detail(e.world(), e, description); } -template -inline flecs::entity world::module() const { - flecs::id_t result = _::cpp_type::id(m_world); - ecs_set_scope(m_world, result); - return flecs::entity(m_world, result); +/** Set link to external documentation for an entity. + * + * @see ecs_doc_set_link() + * @see flecs::doc::get_link() + * @see flecs::entity_builder::set_doc_link() + * + * @ingroup cpp_addons_doc + */ +inline void set_link(flecs::entity& e, const char *link) { + ecs_doc_set_link(e.world(), e, link); } -template -inline flecs::entity world::import() { - return flecs::import(*this); +/** Set color for an entity. + * + * @see ecs_doc_set_color() + * @see flecs::doc::get_color() + * @see flecs::entity_builder::set_doc_color() + * + * @ingroup cpp_addons_doc + */ +inline void set_color(flecs::entity& e, const char *color) { + ecs_doc_set_color(e.world(), e, color); } +/** @private */ +namespace _ { + +/** @private */ +inline void init(flecs::world& world) { + world.component("flecs::doc::Description"); } +} // namespace _ +} // namespace doc +} // namespace flecs + #endif -#ifdef FLECS_SYSTEM -#pragma once +#ifdef FLECS_DOC +#endif +#ifdef FLECS_REST +/** + * @file addons/cpp/mixins/rest/impl.hpp + * @brief Rest module implementation. + */ #pragma once -#pragma once +namespace flecs { +namespace rest { +namespace _ { +inline void init(flecs::world& world) { + world.component("flecs::rest::Rest"); +} + +} // namespace _ +} // namespace rest +} // namespace flecs -namespace flecs -{ +#endif +#ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/impl.hpp + * @brief Meta implementation. + */ -// System builder interface -template -struct system_builder_i : query_builder_i { -private: - using BaseClass = query_builder_i; +#pragma once -public: - system_builder_i(ecs_system_desc_t *desc) - : BaseClass(&desc->query) - , m_desc(desc) { } +FLECS_ENUM_LAST(flecs::meta::type_kind_t, flecs::meta::TypeKindLast) +FLECS_ENUM_LAST(flecs::meta::primitive_kind_t, flecs::meta::PrimitiveKindLast) - /** Specify in which phase the system should run. - * - * @param phase The phase. - */ - Base& kind(entity_t phase) { - m_desc->entity.add[0] = phase; - return *this; - } +namespace flecs { +namespace meta { +namespace _ { - /** Specify in which phase the system should run. - * - * @tparam Phase The phase. - */ - template - Base& kind() { - m_desc->entity.add[0] = _::cpp_type::id(world_v()); - return *this; - } +/* Type support for entity wrappers */ +template +inline flecs::opaque flecs_entity_support(flecs::world&) { + return flecs::opaque() + .as_type(flecs::Entity) + .serialize([](const flecs::serializer *ser, const EntityType *data) { + flecs::entity_t id = data->id(); + return ser->value(flecs::Entity, &id); + }) + .assign_entity( + [](EntityType *dst, flecs::world_t *world, flecs::entity_t e) { + *dst = EntityType(world, e); + }); +} - /** Specify whether system can run on multiple threads. - * - * @param value If false system will always run on a single thread. - */ - Base& multi_threaded(bool value = true) { - m_desc->multi_threaded = value; - return *this; - } +inline void init(flecs::world& world) { + world.component("flecs::meta::bool"); + world.component("flecs::meta::char"); + world.component("flecs::meta::u8"); + world.component("flecs::meta::u16"); + world.component("flecs::meta::u32"); + world.component("flecs::meta::u64"); + world.component("flecs::meta::i8"); + world.component("flecs::meta::i16"); + world.component("flecs::meta::i32"); + world.component("flecs::meta::i64"); + world.component("flecs::meta::f32"); + world.component("flecs::meta::f64"); + + world.component("flecs::meta::type_kind"); + world.component("flecs::meta::primitive_kind"); + world.component("flecs::meta::member_t"); + world.component("flecs::meta::enum_constant"); + world.component("flecs::meta::bitmask_constant"); - /** Specify whether system should be ran in staged context. - * - * @param value If false system will always run staged. - */ - Base& no_staging(bool value = true) { - m_desc->no_staging = value; - return *this; - } + world.component("flecs::meta::type"); + world.component("flecs::meta::TypeSerializer"); + world.component("flecs::meta::primitive"); + world.component("flecs::meta::enum"); + world.component("flecs::meta::bitmask"); + world.component("flecs::meta::member"); + world.component("flecs::meta::member_ranges"); + world.component("flecs::meta::struct"); + world.component("flecs::meta::array"); + world.component("flecs::meta::vector"); - /** Set system interval. - * This operation will cause the system to be ran at the specified interval. - * - * The timer is synchronous, and is incremented each frame by delta_time. - * - * @param interval The interval value. - */ - Base& interval(FLECS_FLOAT interval) { - m_desc->interval = interval; - return *this; - } + world.component("flecs::meta::unit"); - /** Set system rate. - * This operation will cause the system to be ran at a multiple of the - * provided tick source. The tick source may be any entity, including - * another system. - * - * @param tick_source The tick source. - * @param rate The multiple at which to run the system. - */ - Base& rate(const entity_t tick_source, int32_t rate) { - m_desc->rate = rate; - m_desc->tick_source = tick_source; - return *this; - } + // To support member and member register components + // (that do not have conflicting symbols with builtin ones) for platform + // specific types. - /** Set system rate. - * This operation will cause the system to be ran at a multiple of the - * frame tick frequency. If a tick source was provided, this just updates - * the rate of the system. - * - * @param rate The multiple at which to run the system. - */ - Base& rate(int32_t rate) { - m_desc->rate = rate; - return *this; + if (!flecs::is_same() && !flecs::is_same()) { + flecs::_::type::init_builtin(world, flecs::Iptr, true); + // Remove symbol to prevent validation errors, as it doesn't match with + // the typename + ecs_remove_pair(world, flecs::Iptr, ecs_id(EcsIdentifier), EcsSymbol); } - /** Associate system with entity */ - Base& self(flecs::entity self) { - m_desc->self = self; - return *this; - } + if (!flecs::is_same() && !flecs::is_same()) { + flecs::_::type::init_builtin(world, flecs::Uptr, true); + // Remove symbol to prevent validation errors, as it doesn't match with + // the typename + ecs_remove_pair(world, flecs::Uptr, ecs_id(EcsIdentifier), EcsSymbol); + } + + // Register opaque type support for C++ entity wrappers + world.entity("::flecs::cpp").add(flecs::Module).scope([&]{ + world.component() + .opaque(flecs_entity_support); + world.component() + .opaque(flecs_entity_support); + }); +} - /** Set system context */ - Base& ctx(void *ptr) { - m_desc->ctx = ptr; - return *this; - } +} // namespace _ -protected: - virtual flecs::world_t* world_v() = 0; +} // namespace meta -private: - operator Base&() { - return *static_cast(this); - } - ecs_system_desc_t *m_desc; -}; +inline flecs::entity cursor::get_type() const { + return flecs::entity(cursor_.world, ecs_meta_get_type(&cursor_)); +} +inline flecs::entity cursor::get_unit() const { + return flecs::entity(cursor_.world, ecs_meta_get_unit(&cursor_)); } +inline flecs::entity cursor::get_entity() const { + return flecs::entity(cursor_.world, ecs_meta_get_entity(&cursor_)); +} -namespace flecs { -namespace _ { - template - using system_builder_base = node_builder< - system, ecs_system_desc_t, system_builder, - system_builder_i, Components ...>; +/** Create primitive type */ +inline flecs::entity world::primitive(flecs::meta::primitive_kind_t kind) { + ecs_primitive_desc_t desc = {}; + desc.kind = kind; + flecs::entity_t eid = ecs_primitive_init(world_, &desc); + ecs_assert(eid != 0, ECS_INVALID_OPERATION, NULL); + return flecs::entity(world_, eid); } -template -struct system_builder final : _::system_builder_base { - system_builder(flecs::world_t* world, const char *name = nullptr) - : _::system_builder_base(world, name) - { - _::sig(world).populate(this); - - this->m_desc.query.filter.instanced = this->m_instanced; +/** Create array type. */ +inline flecs::entity world::array(flecs::entity_t elem_id, int32_t array_count) { + ecs_array_desc_t desc = {}; + desc.type = elem_id; + desc.count = array_count; + flecs::entity_t eid = ecs_array_init(world_, &desc); + ecs_assert(eid != 0, ECS_INVALID_OPERATION, NULL); + return flecs::entity(world_, eid); +} -#ifdef FLECS_PIPELINE - this->m_desc.entity.add[0] = flecs::OnUpdate; -#endif - } -}; +/** Create array type. */ +template +inline flecs::entity world::array(int32_t array_count) { + return this->array(_::type::id(world_), array_count); +} +inline flecs::entity world::vector(flecs::entity_t elem_id) { + ecs_vector_desc_t desc = {}; + desc.type = elem_id; + flecs::entity_t eid = ecs_vector_init(world_, &desc); + ecs_assert(eid != 0, ECS_INVALID_OPERATION, NULL); + return flecs::entity(world_, eid); } +template +inline flecs::entity world::vector() { + return this->vector(_::type::id(world_)); +} -namespace flecs -{ +} // namespace flecs -struct system_runner_fluent { - system_runner_fluent( - world_t *world, - entity_t id, - int32_t stage_current, - int32_t stage_count, - FLECS_FLOAT delta_time, - void *param) - : m_stage(world) - , m_id(id) - , m_delta_time(delta_time) - , m_param(param) - , m_offset(0) - , m_limit(0) - , m_stage_current(stage_current) - , m_stage_count(stage_count) { } +inline int ecs_serializer_t::value(ecs_entity_t type, const void *v) const { + return this->value_(this, type, v); +} - system_runner_fluent& offset(int32_t offset) { - m_offset = offset; - return *this; - } +template +inline int ecs_serializer_t::value(const T& v) const { + return this->value(flecs::_::type::id( + const_cast(this->world)), &v); +} - system_runner_fluent& limit(int32_t limit) { - m_limit = limit; - return *this; - } +inline int ecs_serializer_t::member(const char *name) const { + return this->member_(this, name); +} - system_runner_fluent& stage(flecs::world& stage) { - m_stage = stage.c_ptr(); - return *this; - } +#endif +#ifdef FLECS_UNITS +/** + * @file addons/cpp/mixins/units/impl.hpp + * @brief Units module implementation. + */ - ~system_runner_fluent() { - if (m_stage_count) { - ecs_run_worker( - m_stage, m_id, m_stage_current, m_stage_count, m_delta_time, - m_param); - } else { - ecs_run_w_filter( - m_stage, m_id, m_delta_time, m_offset, m_limit, m_param); - } - } +#pragma once -private: - world_t *m_stage; - entity_t m_id; - FLECS_FLOAT m_delta_time; - void *m_param; - int32_t m_offset; - int32_t m_limit; - int32_t m_stage_current; - int32_t m_stage_count; -}; +namespace flecs { -struct system final : entity -{ - using entity::entity; +inline units::units(flecs::world& world) { + /* Import C module */ + FlecsUnitsImport(world); - explicit system() { - m_id = 0; - m_world = nullptr; - } + /* Bridge between C++ types and flecs.units entities */ + world.module(); - explicit system(flecs::world_t *world, ecs_system_desc_t *desc) - : entity(world, ecs_system_init(world, desc)) - { - if (desc->query.filter.terms_buffer) { - ecs_os_free(desc->query.filter.terms_buffer); - } - } + // Initialize world.entity(prefixes) scope + world.entity("::flecs::units::prefixes"); - void ctx(void *ctx) { - ecs_system_desc_t desc = {}; - desc.entity.entity = m_id; - desc.ctx = ctx; - ecs_system_init(m_world, &desc); - } + // Initialize prefixes + world.entity("::flecs::units::prefixes::Yocto"); + world.entity("::flecs::units::prefixes::Zepto"); + world.entity("::flecs::units::prefixes::Atto"); + world.entity("::flecs::units::prefixes::Femto"); + world.entity("::flecs::units::prefixes::Pico"); + world.entity("::flecs::units::prefixes::Nano"); + world.entity("::flecs::units::prefixes::Micro"); + world.entity("::flecs::units::prefixes::Milli"); + world.entity("::flecs::units::prefixes::Centi"); + world.entity("::flecs::units::prefixes::Deci"); + world.entity("::flecs::units::prefixes::Deca"); + world.entity("::flecs::units::prefixes::Hecto"); + world.entity("::flecs::units::prefixes::Kilo"); + world.entity("::flecs::units::prefixes::Mega"); + world.entity("::flecs::units::prefixes::Giga"); + world.entity("::flecs::units::prefixes::Tera"); + world.entity("::flecs::units::prefixes::Peta"); + world.entity("::flecs::units::prefixes::Exa"); + world.entity("::flecs::units::prefixes::Zetta"); + world.entity("::flecs::units::prefixes::Yotta"); + world.entity("::flecs::units::prefixes::Kibi"); + world.entity("::flecs::units::prefixes::Mebi"); + world.entity("::flecs::units::prefixes::Gibi"); + world.entity("::flecs::units::prefixes::Tebi"); + world.entity("::flecs::units::prefixes::Pebi"); + world.entity("::flecs::units::prefixes::Exbi"); + world.entity("::flecs::units::prefixes::Zebi"); + world.entity("::flecs::units::prefixes::Yobi"); - void* ctx() const { - return ecs_get_system_ctx(m_world, m_id); - } + // Initialize quantities + world.entity("::flecs::units::Duration"); + world.entity