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
-
+
+
+
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:
+
+
+
### 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