From 23a40216de0f9dc5c7dcc91ffcb9cf33c969f11c Mon Sep 17 00:00:00 2001 From: n0ffie Date: Thu, 6 Feb 2025 22:47:32 +0100 Subject: [PATCH] switching to cmake; alyson setup --- .gitignore | 3 + CMakeLists.txt | 72 + README.md | 206 +- alyson/CMakeLists.txt | 27 + alyson/includes/alyson.hpp | 17 + alyson/includes/rlyson.h | 42 + alyson/src/alyson.cpp | 43 + alyson/src/rlyson.c | 28 + cmake/utils.cmake | 33 + deps/dependee.json | 43 - deps/flecs.c | 81507 -------------------------------- deps/flecs.h | 33512 ------------- include/colysis.h | 8 +- include/colysis/bake_config.h | 24 - include/raylib.h | 1708 - include/raymath.h | 2949 -- include/rlgl.h | 5262 --- project.json | 80 - src/main.c | 27 - src/main.cpp | 16 + 20 files changed, 307 insertions(+), 125300 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 alyson/CMakeLists.txt create mode 100644 alyson/includes/alyson.hpp create mode 100644 alyson/includes/rlyson.h create mode 100644 alyson/src/alyson.cpp create mode 100644 alyson/src/rlyson.c create mode 100644 cmake/utils.cmake delete mode 100644 deps/dependee.json delete mode 100644 deps/flecs.c delete mode 100644 deps/flecs.h delete mode 100644 include/colysis/bake_config.h delete mode 100644 include/raylib.h delete mode 100644 include/raymath.h delete mode 100644 include/rlgl.h delete mode 100644 project.json delete mode 100644 src/main.c create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore index fff6aad..e29835e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ .bake_cache .DS_Store .vscode +.vs gcov bin +build +.cache \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f62ba68 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.20) + +project(colysis + LANGUAGES C CXX + VERSION 0.0.1 +) + +# Set C/C++ standards +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Set compiler flags +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Generate compile commands (useful for IDEs) +set(USE_FOLDERS ON) # Organize targets into folders (for IDEs) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) # Build static libraries by default + + +include(FetchContent) + +# Raylib 5.5 +FetchContent_Declare( + raylib + GIT_REPOSITORY https://github.com/raysan5/raylib.git + GIT_TAG 5.5 +) + +FetchContent_MakeAvailable(raylib) + +# Flecs +FetchContent_Declare( + flecs + GIT_REPOSITORY https://github.com/SanderMertens/flecs.git + GIT_TAG v4.0.4 +) + +FetchContent_MakeAvailable(flecs) + +## alyson engine (needs flecs and raylib) +set(ASSETS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/assets/") +add_subdirectory(alyson) + + +## Colysis .game + +include(cmake/utils.cmake) + +# Get files in src/ and add them to the executable + +find_files(colysis_src src cpp hpp cxx hxx c h) +add_executable(colysis + ${colysis_src} +) + +target_link_libraries(colysis + raylib + flecs::flecs_static + alyson +) + +target_include_directories(colysis PUBLIC include + #${ALYSON_INCLUDE_DIR} +) + +# put the assets folder path as a C preprocessor define +target_compile_definitions(colysis PRIVATE ASSETS_PATH="${ASSETS_PATH}") + +put_targets_into_folder(FOLDER "thid_party" TARGETS raylib flecs::flecs_static) \ No newline at end of file diff --git a/README.md b/README.md index 0cc613a..65a3ece 100644 --- a/README.md +++ b/README.md @@ -1,194 +1,36 @@ -### I do not own these libraries -_This is a template that I have made using both Raylib and Flecs libraries using the bake compiler._ +# Colysis +A little Videogame with a custom engine written in C/C++. +It uses [Flecs](https://github.com/SanderMertens/flecs) as a ECS framework and [Raylib](https://www.raylib.com/) as a 2D graphics library. -_Please note that neither Raylib (https://github.com/raysan5/raylib) nor Flecs (https://github.com/SanderMertens/flecs/tree/master) belong to me. I have only created this template for ease-of-use for any developers who desire to start using Flecs and Raylib._ +## Building +To build the project, you need to have [CMake](https://cmake.org/) installed. -# ------ SETTING UP THE PROJECT ------ -If this is your first time setting up a project, I will walk you through all of the steps for getting started. - -## MacOS or Linux -**1. First, install Brew.** - -If you're using MacOS or Linux you will first need to install **Brew**. Open a new terminal and run this command: +### Linux ```bash -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +mkdir build +cd build +cmake .. +make ``` -**2. Install Git CLI** -Once that's finished, install the **GitHub CLI** (Command Line Imterface). This is what we will use to install the **bake** compiler. +### Windows ```bash -brew install git +mkdir build +cd build +cmake -G "Visual Studio 17 2022" .. +cmake --build . --config Release ``` -> Run `git --version` to make sure it installed correctly. The result should be something like `git version 2.49.1`. -**3. Install Bake** - -Go to this link ([Bake Compiler](https://github.com/SanderMertens/bake)) and click "Code" and download the zip file. - -Once downloaded, open the folder. Open a new terminal and drag and drop `setup.sh` into the terminal window. Press return (or enter) in the terminal, enter your password if asked, and close the terminal when done. - -> Again, ensure it installed correctly with `bake --version` - -**4. Raylib and Flecs Libraries** - -Download this zip file [Unix Raylib Library](https://github.com/user-attachments/files/17962571/libraylib.a.zip) and unzip it once it's downloaded. - -Open your device root folder (for example: "Macintosh HD" in finder) then go to Users/*you*/bake/lib (or equivalent on Linux) and put this raylib library in it. - -Download the raylib source files by running, +### macOS ```bash -cd -git clone https://github.com/raysan5/raylib.git +mkdir build +cd build +cmake -G "Xcode" .. +cmake --build . --config Release ``` -Download this [flecs package](https://github.com/user-attachments/files/17963061/flecs.zip), unzip it, and run: -``` -cd /path/to/flecs -bake -``` -> Note: if this package is moved, you have to run `bake` again. +## Running +After building, you can run the game by executing the `colysis` executable. -**5. Build the Template** - -Now let's build the template. - -Simply open a terminal and go to a location where you would like to put the template. I will put mine into "~/bake/src/bake/templates". - -Once there, run these commands: -```bash -git clone https://github.com/aog05/Raylib-and-Flecs-Template -bake Raylib-and-Flecs-Template -``` - -**6. Create the Project** - -Go to a folder where you like this project to be. I put mine in a folder called, _"Raylib Projects"_ and run this command. -```bash -bake new your_project_name -t RaFT -``` - -Run the program with: `bake run`. - -If you ever want to make a new project, all you have to do is repeat step this step. - -## Windows -Windows is a slightly tougher version to build for, so if there are any problems, you can report them here or on Discord. - -**1. Install Git CLI** - -If you haven't already download the GitHub CLI (Command Line Interface) run the command below. -```powershell -winget install --id Git.Git -e --source winget -``` -> Note: this command only works on Windows 11. Go [here](https://git-scm.com/downloads/win) for previous versions. - -**2. Install Visual Studio Build Tools** - -First, follow this link ([Download Bulid Tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022)) and go down to "Tools for Visual Studio" and click download on "Build Tools for Visual Studio" at the bottom of the selection. - -Once it finishes downloading, open the Visual Studio Installer and click "Modify". After that, select "Desktop Development with C++" and click "Modify" in the bottom right. You can close the installer once it finishes installing. - -**3. Install Bake** - -Go to this link ([Bake Compiler](https://github.com/SanderMertens/bake)) and click "Code" and download the zip file. - -Once downloaded, right-click the zip folder and click "Extract All...". open the folder. Open a new terminal, type `cd`, drag and drop the folder inside (should be called "bake-master") into the terminal window, and hit enter. -```powershell -# The command should look like this -cd C:\Users\your_username\Downloads\bake-master\bake-master -``` - -Run `./setup.bat` after that. - -> Again, ensure it installed correctly with `bake --version` - -**4. Raylib and Flecs libraries** - -Download this zip file ([Raylib for x32](https://github.com/user-attachments/files/18310969/raylibdll.zip) or [Raylib for x64](https://github.com/user-attachments/files/18310976/raylibdll.zip)) and unzip it once it's downloaded. - -Open "File Explorer" and go to C:\Users\*you*\bake\lib and put this raylib library in it. - -Download the raylib source files by running, -```powershell -cd -git clone https://github.com/raysan5/raylib.git -``` - -Download this [flecs package](https://github.com/user-attachments/files/17963061/flecs.zip), unzip it, and run: -``` -cd \path\to\flecs -bake -``` -> Note: if this package is moved, you have to run `bake` again. - -**5. Build the Template** - -Now let's build the template. - -Simply open a terminal and go to a location where you would like to put the template. I will put mine into "~/bake/src/bake/templates". - -Once there, run these commands: -```powershell -git clone https://github.com/aog05/Raylib-and-Flecs-Template -bake Raylib-and-Flecs-Template -``` - -**6. Create the Project** - -Go to a folder where you like this project to be. I put mine in a folder called, _"Raylib Projects"_ and run this command. -```powershell -bake new your_project_name -t RaFT -``` - -Run the program with: `bake run`. - -If you ever want to make a new project, all you have to do is repeat this step. - -## Build for Web - -In your existing project, run these commands for your specific platform. - -**For all platforms and languages** -```powershell -bake --target em -``` - -**Web MacOS and Linux** - -Copy and paste this at the root of your project. -```bash -emcc -o bin/game.html .bake_cache/Em-debug/obj/main.o .bake_cache/Em-debug/obj/flecs.o -Wall -std=c99 -D_DEFAULT_SOURCE -Wno-missing-braces -Wunused-result --embed-file assets -Os -I ~/raylib/src -I ~/raylib/src/external -I include -I deps -s USE_GLFW=3 -s ASYNCIFY -s TOTAL_MEMORY=67108864 -s FORCE_FILESYSTEM=1 --shell-file ~/raylib/src/minshell.html ~/raylib/src/web/libraylib.a -DPLATFORM_WEB -s 'EXPORTED_FUNCTIONS=["_free","_malloc","_main"]' -s EXPORTED_RUNTIME_METHODS=ccall -``` - -**Web Windows** - -Copy and paste this at the root of your project. -```powershell -emcc -o bin/game.html .bake_cache/Em-debug/obj/main.o .bake_cache/Em-debug/obj/flecs.o -Wall -std=c99 -D_DEFAULT_SOURCE -Wno-missing-braces -Wunused-result --embed-file assets -Os -I C:/raylib/src -I C:/raylib/src/external -I include -I deps -s USE_GLFW=3 -s ASYNCIFY -s TOTAL_MEMORY=67108864 -s FORCE_FILESYSTEM=1 --shell-file C:/raylib/src/minshell.html C:/raylib/src/web/libraylib.a -DPLATFORM_WEB -s 'EXPORTED_FUNCTIONS=["_free","_malloc","_main"]' -s EXPORTED_RUNTIME_METHODS=ccall -``` - -**Test on the Web** - -This will produce an HTML file where you can run your game. - -```bash -// If you have npm, use -npx http-server /path/to/game.html - -// If you have python, use -// It may also be "py", "python3", or "py -3" depending on your OS -python -m http.server -``` - -By the way, if this is for itch.io, you have to take the HTML, JS, and WASM and compress them to a zip file. You can upload the zip file for web games on itch! - -# ------ REFERENCES ------ -Reference Articles: - -Flecs Quickstart: https://www.flecs.dev/flecs/md_docs_2Quickstart.html - -Raylib Examples: https://www.raylib.com/examples.html - -Raylib CheatSheet: https://www.raylib.com/cheatsheet/cheatsheet.html - -Bake Compiler: https://github.com/SanderMertens/bake +## License +This `colysis` is currently closed source and will not be open sourced. The engine will be licensed under the [MIT license](LICENSE) as soon as it is finished. diff --git a/alyson/CMakeLists.txt b/alyson/CMakeLists.txt new file mode 100644 index 0000000..63affd7 --- /dev/null +++ b/alyson/CMakeLists.txt @@ -0,0 +1,27 @@ +project(alyson + VERSION 0.0.1 + LANGUAGES C CXX +) + +cmake_minimum_required(VERSION 3.20) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +include(../cmake/utils.cmake) + +add_library(alyson) +target_include_directories(alyson PUBLIC includes) + +target_link_libraries(alyson PUBLIC raylib) +target_link_libraries(alyson PUBLIC flecs::flecs_static) + +target_compile_definitions(alyson PRIVATE ASSETS_PATH="${ASSETS_PATH}") + +# Add alyson files +find_files(alyson_src src cpp hpp cxx hxx c h) +target_sources(alyson PRIVATE ${alyson_src}) + +# set(ALYSON_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/alyson/include) + diff --git a/alyson/includes/alyson.hpp b/alyson/includes/alyson.hpp new file mode 100644 index 0000000..b799d8e --- /dev/null +++ b/alyson/includes/alyson.hpp @@ -0,0 +1,17 @@ +#pragma once +#ifndef ALYSON_HPP +#define ALYSON_HPP + +#ifndef ASSETS_PATH +#define ASSETS_PATH "./assets/" +#endif + +#define MANGLE_RES_PATH(path) ASSETS_PATH "/" path + +int init(); + +void update(float dt); + +void render(float dt); + +#endif // ALYSON_HPP \ No newline at end of file diff --git a/alyson/includes/rlyson.h b/alyson/includes/rlyson.h new file mode 100644 index 0000000..edcb2c7 --- /dev/null +++ b/alyson/includes/rlyson.h @@ -0,0 +1,42 @@ +#ifndef RLYSON_HPP +#define RLYSON_HPP + +#include + + +/* + the following functions are not part of the raylib API, + but are used by alyson and can be used by other projects using alyson +*/ + +typedef enum { + TEXT_ORIENTATION_LEFT, + TEXT_ORIENTATION_RIGHT, + TEXT_ORIENTATION_CENTER +} text_orientation_t; + +/* + * Draws a text with all possible parameters + * @function DrawTextFull + * @param font Font to use + * @param text Text to draw + * @param origin Position to draw the text from + * @param orientation Text orientation + * @param fontSize Font size + * @param spacing Spacing between letters + * @param lineSpacing Line spacing + * @param tint Tint of the text + */ +void DrawTextFull( + Font font, + const char *text, + Vector2 origin, + text_orientation_t orientation, + float fontSize, + float spacing, + float lineSpacing, + Color tint +); + + +#endif // RLYSON_HPP \ No newline at end of file diff --git a/alyson/src/alyson.cpp b/alyson/src/alyson.cpp new file mode 100644 index 0000000..484b854 --- /dev/null +++ b/alyson/src/alyson.cpp @@ -0,0 +1,43 @@ + +#include +#include +#include +#include +#include + +#define WINDOW_TITLE "Alyson Engine" + +int main(int argc, char *argv[]) { + + SetTargetFPS(60); + + InitWindow(900, 500, WINDOW_TITLE " -- 900x500"); + + + Texture2D logo = LoadTexture(MANGLE_RES_PATH("Raylib_logo.png")); + + std::cout << "alyson: init" << std::endl; + + int g = init(); + if(g != 0) { + std::cout << "alyson: init failed with error code " << g << std::endl; + return g; + } + + while (!WindowShouldClose()) { + if (IsWindowResized()) { + SetWindowTitle((std::string(WINDOW_TITLE) + " -- " + std::to_string(GetScreenWidth()) + "x" + std::to_string(GetScreenHeight())).c_str()); + } + + BeginDrawing(); + DrawTexture(logo, GetScreenWidth() / 2 - logo.width / 2, GetScreenHeight() / 2 - logo.height / 2, WHITE); + DrawTextFull(GetFontDefault(), WINDOW_TITLE, { GetScreenWidth() / 2.f, GetScreenHeight() - 60.f }, TEXT_ORIENTATION_CENTER, 40.f, 0.f, 0.f, WHITE); + + ClearBackground(RAYWHITE); + EndDrawing(); + } + + UnloadTexture(logo); + CloseWindow(); + return 0; +} diff --git a/alyson/src/rlyson.c b/alyson/src/rlyson.c new file mode 100644 index 0000000..72843b6 --- /dev/null +++ b/alyson/src/rlyson.c @@ -0,0 +1,28 @@ +#include + +void DrawTextFull( + Font font, + const char *text, + Vector2 origin, + text_orientation_t orientation, + float fontSize, + float spacing, + float lineSpacing, + Color tint + ) +{ + Vector2 textSize = MeasureTextEx(font, text, fontSize, spacing); + + switch (orientation) { + case TEXT_ORIENTATION_LEFT: + origin.x -= textSize.x; + break; + case TEXT_ORIENTATION_RIGHT: + origin.x += textSize.x; + break; + case TEXT_ORIENTATION_CENTER: + origin.x -= textSize.x / 2; + break; + } + DrawTextPro(font, text, textSize, origin, 0, spacing, lineSpacing, tint); +} \ No newline at end of file diff --git a/cmake/utils.cmake b/cmake/utils.cmake new file mode 100644 index 0000000..60b3175 --- /dev/null +++ b/cmake/utils.cmake @@ -0,0 +1,33 @@ + +function(put_targets_into_folder) + set(oneValueArgs FOLDER) + set(multiValueArgs TARGETS) + cmake_parse_arguments(ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + foreach(target ${ARGS_TARGETS}) + # Check if target exists + if (NOT TARGET ${target}) + message(FATAL_ERROR "${target} target not found") + endif() + + # Get the actual target (if it is aliased) + get_target_property(actual_target ${target} ALIASED_TARGET) + if (NOT actual_target) + set(actual_target ${target}) + endif() + + # Set the folder property for the target + set_target_properties(${actual_target} PROPERTIES FOLDER ${ARGS_FOLDER}) + endforeach() +endfunction() + + +function(find_files var_name path) + set(sources) + foreach(ext ${ARGN}) + file(GLOB_RECURSE files "${path}/*.${ext}") + list(APPEND sources ${files}) + endforeach() + set(${var_name} ${${var_name}} ${sources} PARENT_SCOPE) +endfunction() + diff --git a/deps/dependee.json b/deps/dependee.json deleted file mode 100644 index 02de64f..0000000 --- a/deps/dependee.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "dependee": { - "lang.c": { - "${cfg sanitize}": { - "defines": [ - "FLECS_SANITIZE" - ] - } - }, - "lang.cpp": { - "${cfg sanitize}": { - "defines": [ - "FLECS_SANITIZE" - ] - } - } - }, - "lang.c": { - "${os linux}": { - "lib": [ - "rt", - "pthread", - "m" - ], - "${cfg debug}": { - "export-symbols": true - }, - "${cfg sanitize}": { - "export-symbols": true - } - }, - "${os windows}": { - "lib": [ - "ws2_32" - ] - }, - "${cfg sanitize}": { - "defines": [ - "FLECS_SANITIZE" - ] - } - } -} \ No newline at end of file diff --git a/deps/flecs.c b/deps/flecs.c deleted file mode 100644 index 4405eb6..0000000 --- a/deps/flecs.c +++ /dev/null @@ -1,81507 +0,0 @@ -/** - * @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 - -/** - * @file private_types.h - * @brief Private types. - */ - -#ifndef FLECS_PRIVATE_TYPES_H -#define FLECS_PRIVATE_TYPES_H - -#ifndef __MACH__ -#ifndef _POSIX_C_SOURCE -#define _POSIX_C_SOURCE 200809L -#endif -#endif - -#include -#include -#include - -/** - * @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 - -#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_block_allocator_t page_allocator; - 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); - -/* Return number of entities in index */ -int32_t flecs_entity_index_count( - const ecs_entity_index_t *index); - -/* Return number of allocated entities in index */ -int32_t flecs_entity_index_size( - const ecs_entity_index_t *index); - -/* Return number of not alive entities in index */ -int32_t flecs_entity_index_not_alive_count( - const ecs_entity_index_t *index); - -/* Clear entity index */ -void flecs_entity_index_clear( - ecs_entity_index_t *index); - -/* Return number of alive entities in index */ -const uint64_t* flecs_entity_index_ids( - const ecs_entity_index_t *index); - -#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)) - -#endif - -/** - * @file bitset.h - * @brief Bitset data structure. - */ - -#ifndef FLECS_BITSET_H -#define FLECS_BITSET_H - - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct ecs_bitset_t { - uint64_t *data; - int32_t count; - ecs_size_t size; -} ecs_bitset_t; - -/** Initialize bitset. */ -FLECS_DBG_API -void flecs_bitset_init( - ecs_bitset_t *bs); - -/** Deinitialize bitset. */ -FLECS_DBG_API -void flecs_bitset_fini( - ecs_bitset_t *bs); - -/** Add n elements to bitset. */ -FLECS_DBG_API -void flecs_bitset_addn( - ecs_bitset_t *bs, - int32_t count); - -/** Ensure element exists. */ -FLECS_DBG_API -void flecs_bitset_ensure( - ecs_bitset_t *bs, - int32_t count); - -/** Set element. */ -FLECS_DBG_API -void flecs_bitset_set( - ecs_bitset_t *bs, - int32_t elem, - bool value); - -/** Get element. */ -FLECS_DBG_API -bool flecs_bitset_get( - const ecs_bitset_t *bs, - int32_t elem); - -/** Return number of elements. */ -FLECS_DBG_API -int32_t flecs_bitset_count( - const ecs_bitset_t *bs); - -/** Remove from bitset. */ -FLECS_DBG_API -void flecs_bitset_remove( - ecs_bitset_t *bs, - int32_t elem); - -/** Swap values in bitset. */ -FLECS_DBG_API -void flecs_bitset_swap( - ecs_bitset_t *bs, - int32_t elem_a, - int32_t elem_b); - -#ifdef __cplusplus -} -#endif - -#endif - -/** - * @file storage/table.h - * @brief Table storage implementation. - */ - -#ifndef FLECS_TABLE_H -#define FLECS_TABLE_H - -/** - * @file storage/table_graph.h - * @brief Table graph types and functions. - */ - -#ifndef FLECS_TABLE_GRAPH_H -#define FLECS_TABLE_GRAPH_H - -/** 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; - -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; - -/** 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; - -/** 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; - -/* 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; - -/* Table graph node */ -typedef struct ecs_graph_node_t { - /* Outgoing edges */ - ecs_graph_edges_t add; - ecs_graph_edges_t remove; - - /* Incoming edges (next = add edges, prev = remove edges) */ - ecs_graph_edge_hdr_t refs; -} ecs_graph_node_t; - -/* Find table by adding id to current table */ -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); - -/* 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); - -/* Cleanup incoming and outgoing edges for table */ -void flecs_table_clear_edges( - ecs_world_t *world, - ecs_table_t *table); - -/* 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); - -void flecs_table_diff_builder_clear( - ecs_table_diff_builder_t *builder); - -void flecs_table_diff_build_append_table( - ecs_world_t *world, - ecs_table_diff_builder_t *dst, - ecs_table_diff_t *src); - -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); - -void flecs_table_edges_add_flags( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_flags32_t flags); - -#endif - - -#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\ -} - -#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,\ -} - -#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,\ -} - -#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)) - -/* Table event type for notifying tables of world events */ -typedef enum ecs_table_eventkind_t { - EcsTableTriggersForId, - EcsTableNoTriggersForId, -} ecs_table_eventkind_t; - -typedef struct ecs_table_event_t { - ecs_table_eventkind_t kind; - - /* Component info event */ - ecs_entity_t component; - - /* Event match */ - ecs_entity_t event; - - /* 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; - -/** 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 */ - uint16_t generation; /* Used for table cleanup */ - int16_t record_count; /* Table record count including wildcards */ - - struct ecs_table_record_t *records; /* Array with table records */ - ecs_hashmap_t *name_index; /* Cached pointer to name index */ - - ecs_bitset_t *bs_columns; /* Bitset columns */ - int16_t bs_count; - int16_t bs_offset; - int16_t ft_offset; -} ecs_table__t; - -/** Table column */ -typedef struct ecs_column_t { - void *data; /* Array with component data */ - ecs_type_info_t *ti; /* Component type info */ -} ecs_column_t; - -/** Table data */ -struct ecs_data_t { - ecs_entity_t *entities; /* Entity ids */ - ecs_column_t *columns; /* Component data */ - int32_t count; - int32_t size; -}; - -/** 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) */ - ecs_type_t type; /* Vector with component ids */ - - ecs_data_t data; /* Component storage */ - ecs_graph_node_t node; /* Graph node */ - - int32_t *dirty_state; /* Keep track of changes in columns */ - int16_t *component_map; /* Get column for component id */ - int16_t *column_map; /* Map type index <-> column - * - 0..count(T): type index -> column - * - count(T)..count(C): column -> type index - */ - - ecs_table__t *_; /* Infrequently accessed table metadata */ -}; - -/* Init table */ -void flecs_table_init( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *from); - -/** Copy type. */ -ecs_type_t flecs_type_copy( - ecs_world_t *world, - const ecs_type_t *src); - -/** Free type. */ -void flecs_type_free( - ecs_world_t *world, - ecs_type_t *type); - -/** 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); - -/* Initialize columns for data */ -void flecs_table_init_data( - ecs_world_t *world, - ecs_table_t *table); - -/* 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( - ecs_world_t *world, - ecs_table_t *table); - -/* Clear all entities from the table. Do not invoke OnRemove systems */ -void flecs_table_clear_entities_silent( - ecs_world_t *world, - ecs_table_t *table); - -/* 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); - -/* Delete an entity from the table. */ -void flecs_table_delete( - ecs_world_t *world, - ecs_table_t *table, - int32_t index, - bool destruct); - -/* 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); - -/* 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); - -/* Shrink table to contents */ -bool flecs_table_shrink( - ecs_world_t *world, - ecs_table_t *table); - -/* Get dirty state for table columns */ -int32_t* flecs_table_get_dirty_state( - ecs_world_t *world, - ecs_table_t *table); - -/* Initialize root table */ -void flecs_init_root_table( - ecs_world_t *world); - -/* Unset components in table */ -void flecs_table_remove_actions( - ecs_world_t *world, - ecs_table_t *table); - -/* Free table */ -void flecs_table_fini( - ecs_world_t *world, - ecs_table_t *table); - -/* Free table */ -void flecs_table_free_type( - ecs_world_t *world, - ecs_table_t *table); - -/* 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); - -void flecs_table_swap( - ecs_world_t *world, - ecs_table_t *table, - int32_t row_1, - int32_t row_2); - -void flecs_table_mark_dirty( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t component); - -void flecs_table_notify( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_table_event_t *event); - -void flecs_table_delete_entities( - ecs_world_t *world, - ecs_table_t *table); - -/* Increase observer count of table */ -void flecs_table_traversable_add( - ecs_table_t *table, - int32_t value); - -void flecs_table_emit( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t event); - -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); - -ecs_id_t flecs_column_id( - ecs_table_t *table, - int32_t column_index); - -#endif - - -/* Used in id records to keep track of entities used with id flags */ -extern const ecs_entity_t EcsFlag; - -#define ECS_MAX_JOBS_PER_WORKER (16) -#define ECS_MAX_DEFER_STACK (8) - -/* Magic number for a flecs object */ -#define ECS_OBJECT_MAGIC (0x6563736f) - -/* 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 - -/* Mixin kinds */ -typedef enum ecs_mixin_kind_t { - EcsMixinWorld, - EcsMixinEntity, - EcsMixinObservable, - EcsMixinDtor, - EcsMixinMax -} ecs_mixin_kind_t; - -/* 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]; -}; - -/* 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; - -/* Types that have no mixins */ -#define ecs_table_t_mixins (&(ecs_mixins_t){ NULL }) - -/* Scope for flecs internals, like observers used for builtin features */ -extern const ecs_entity_t EcsFlecsInternals; - -/** 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; - -/** 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_t; - -/* World level allocators are for operations that are not multithreaded */ -typedef struct ecs_world_allocators_t { - ecs_map_params_t ptr; - ecs_map_params_t query_table_list; - ecs_block_allocator_t query_table; - ecs_block_allocator_t query_table_match; - ecs_block_allocator_t graph_edge_lo; - ecs_block_allocator_t graph_edge; - ecs_block_allocator_t id_record; - ecs_block_allocator_t id_record_chunk; - ecs_block_allocator_t table_diff; - ecs_block_allocator_t sparse_chunk; - ecs_block_allocator_t hashmap; - - /* Temporary vectors used for creating table diff id sequences */ - ecs_table_diff_builder_t diff_builder; -} ecs_world_allocators_t; - -/* Stage level allocators are for operations that can be multithreaded */ -typedef struct ecs_stage_allocators_t { - ecs_stack_t iter_stack; - ecs_stack_t deser_stack; - ecs_block_allocator_t cmd_entry_chunk; - ecs_block_allocator_t query_impl; - ecs_block_allocator_t query_cache; -} ecs_stage_allocators_t; - -/** 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; - -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; - -typedef struct ecs_cmd_n_t { - ecs_entity_t *entities; - int32_t count; -} ecs_cmd_n_t; - -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_id_record_t *idr; /* Id record (only for set/mut/emplace) */ - ecs_cmd_entry_t *entry; - ecs_entity_t entity; /* Entity id */ - - union { - ecs_cmd_1_t _1; /* Data for single entity operation */ - ecs_cmd_n_t _n; /* Data for multi entity operation */ - } is; - - ecs_entity_t system; /* System that enqueued the command */ -} ecs_cmd_t; - -/* 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; - -/** 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); - -/** 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; - - /* Unique id that identifies the stage */ - int32_t id; - - /* Zero if not deferred, positive if deferred, negative if suspended */ - int32_t defer; - - /* 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 */ - - /* 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) */ - - /* One-shot actions to be executed after the merge */ - ecs_vec_t post_frame_actions; - - /* 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 */ - - /* Running system */ - ecs_entity_t system; - - /* Thread specific allocators */ - ecs_stage_allocators_t allocators; - ecs_allocator_t allocator; - - /* Caches for query creation */ - ecs_vec_t variables; - ecs_vec_t operations; - -#ifdef FLECS_SCRIPT - /* Thread specific runtime for script execution */ - ecs_script_runtime_t *runtime; -#endif -}; - -/* Component monitor */ -typedef struct ecs_monitor_t { - ecs_vec_t queries; /* vector */ - bool is_dirty; /* Should queries be rematched? */ -} ecs_monitor_t; - -/* Component monitors */ -typedef struct ecs_monitor_set_t { - ecs_map_t monitors; /* map */ - bool is_dirty; /* Should monitors be evaluated? */ -} ecs_monitor_set_t; - -/* Data stored for id marked for deletion */ -typedef struct ecs_marked_id_t { - ecs_id_record_t *idr; - ecs_id_t id; - ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ - bool delete_id; -} ecs_marked_id_t; - -typedef struct ecs_store_t { - /* Entity lookup */ - ecs_entity_index_t entity_index; - - /* Tables */ - ecs_sparse_t tables; /* sparse */ - - /* Table lookup by hash */ - ecs_hashmap_t table_map; /* hashmap */ - - /* Root table */ - ecs_table_t root; - - /* Records cache */ - ecs_vec_t records; - - /* Stack of ids being deleted during cleanup action. */ - ecs_vec_t marked_ids; /* vector */ - - /* 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; - -/* fini actions */ -typedef struct ecs_action_elem_t { - ecs_fini_action_t action; - void *ctx; -} ecs_action_elem_t; - -typedef struct ecs_pipeline_state_t ecs_pipeline_state_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; - - /* -- Type metadata -- */ - ecs_id_record_t **id_index_lo; - ecs_map_t id_index_hi; /* map */ - ecs_map_t type_info; /* map */ - - /* -- Cached handle to id records -- */ - ecs_id_record_t *idr_wildcard; - ecs_id_record_t *idr_wildcard_wildcard; - ecs_id_record_t *idr_any; - ecs_id_record_t *idr_isa_wildcard; - ecs_id_record_t *idr_childof_0; - ecs_id_record_t *idr_childof_wildcard; - ecs_id_record_t *idr_identifier_name; - - /* -- Mixins -- */ - ecs_world_t *self; - ecs_observable_t observable; - - /* Unique id per generated event used to prevent duplicate notifications */ - int32_t event_id; - - /* Is entity range checking enabled? */ - bool range_check_enabled; - - /* -- Data storage -- */ - ecs_store_t store; - - /* Used to track when cache needs to be updated */ - ecs_monitor_set_t monitors; /* map */ - - /* -- Systems -- */ - ecs_entity_t pipeline; /* Current pipeline */ - - /* -- Identifiers -- */ - ecs_hashmap_t aliases; - ecs_hashmap_t symbols; - - /* -- Staging -- */ - ecs_stage_t **stages; /* Stages */ - int32_t stage_count; /* Number of stages */ - - /* -- Component ids -- */ - ecs_vec_t component_ids; /* World local component ids */ - - /* 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; - - /* -- 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 */ - - /* -- 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 */ - - /* -- Metrics -- */ - ecs_world_info_t info; - - /* -- World flags -- */ - ecs_flags32_t flags; - - /* -- Default query flags -- */ - ecs_flags32_t default_query_flags; - - /* Count that increases when component monitors change */ - int32_t monitor_generation; - - /* -- Allocators -- */ - ecs_world_allocators_t allocators; /* Static allocation sizes */ - ecs_allocator_t allocator; /* Dynamic allocation sizes */ - - void *ctx; /* Application context */ - void *binding_ctx; /* Binding-specific context */ - - ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ - ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ - - ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ -}; - -#endif - -/** - * @file storage/table_cache.h - * @brief Data structure for fast table iteration/lookups. - */ - -#ifndef FLECS_TABLE_CACHE_H_ -#define FLECS_TABLE_CACHE_H_ - -void ecs_table_cache_init( - ecs_world_t *world, - ecs_table_cache_t *cache); - -void ecs_table_cache_fini( - ecs_table_cache_t *cache); - -void ecs_table_cache_insert( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *result); - -void ecs_table_cache_replace( - ecs_table_cache_t *cache, - const ecs_table_t *table, - ecs_table_cache_hdr_t *elem); - -void* ecs_table_cache_remove( - ecs_table_cache_t *cache, - uint64_t table_id, - ecs_table_cache_hdr_t *elem); - -void* ecs_table_cache_get( - const ecs_table_cache_t *cache, - const ecs_table_t *table); - -#define flecs_table_cache_count(cache) (cache)->tables.count - -bool flecs_table_cache_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out); - -bool flecs_table_cache_empty_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out); - -bool flecs_table_cache_all_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out); - -ecs_table_cache_hdr_t* flecs_table_cache_next_( - ecs_table_cache_iter_t *it); - -#define flecs_table_cache_next(it, T)\ - (ECS_CAST(T*, flecs_table_cache_next_(it))) - -#endif - -/** - * @file storage/id_index.h - * @brief Index for looking up tables by (component) id. - */ - -#ifndef FLECS_ID_INDEX_H -#define FLECS_ID_INDEX_H - -/* Linked list of id records */ -typedef struct ecs_id_record_elem_t { - struct ecs_id_record_t *prev, *next; -} ecs_id_record_elem_t; - -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 NDEBUG - ecs_table_t *table; -#endif -} ecs_reachable_elem_t; - -typedef struct ecs_reachable_cache_t { - int32_t generation; - int32_t current; - ecs_vec_t ids; /* vec */ -} ecs_reachable_cache_t; - -/* Payload for id index which contains all data structures 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 */ - - /* Id of record */ - ecs_id_t id; - - /* Flags for id */ - ecs_flags32_t flags; - - /* Cached pointer to type info for id, if id contains data. */ - const ecs_type_info_t *type_info; - - /* Name lookup index (currently only used for ChildOf pairs) */ - ecs_hashmap_t *name_index; - - /* Storage for sparse components or union relationships */ - void *sparse; - - /* Lists for all id records that match a pair wildcard. The wildcard id - * record is at the head of the list. */ - ecs_id_record_elem_t first; /* (R, *) */ - ecs_id_record_elem_t second; /* (*, O) */ - ecs_id_record_elem_t trav; /* (*, O) with only traversable relationships */ - - /* Parent id record. For pair records the parent is the (R, *) record. */ - ecs_id_record_t *parent; - - /* Refcount */ - int32_t refcount; - - /* Keep alive count. This count must be 0 when the id record is deleted. If - * it is not 0, an application attempted to delete an id that was still - * queried for. */ - int32_t keep_alive; - - /* Cache for finding components that are reachable through a relationship */ - ecs_reachable_cache_t reachable; -}; - -/* Get id record for id */ -ecs_id_record_t* flecs_id_record_get( - const ecs_world_t *world, - ecs_id_t id); - -/* Ensure id record for id */ -ecs_id_record_t* flecs_id_record_ensure( - ecs_world_t *world, - ecs_id_t id); - -/* Increase refcount of id record */ -void flecs_id_record_claim( - ecs_world_t *world, - ecs_id_record_t *idr); - -/* Decrease refcount of id record, delete if 0 */ -int32_t flecs_id_record_release( - ecs_world_t *world, - ecs_id_record_t *idr); - -/* Release all empty tables in id record */ -void flecs_id_record_release_tables( - ecs_world_t *world, - ecs_id_record_t *idr); - -/* Set (component) type info for id record */ -bool flecs_id_record_set_type_info( - ecs_world_t *world, - ecs_id_record_t *idr, - const ecs_type_info_t *ti); - -/* Ensure id record has name index */ -ecs_hashmap_t* flecs_id_name_index_ensure( - ecs_world_t *world, - ecs_id_t id); - -ecs_hashmap_t* flecs_id_record_name_index_ensure( - ecs_world_t *world, - ecs_id_record_t *idr); - -/* Get name index for id record */ -ecs_hashmap_t* flecs_id_name_index_get( - const ecs_world_t *world, - ecs_id_t id); - -/* Find table record for id */ -ecs_table_record_t* flecs_table_record_get( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id); - -/* Find table record for id record */ -ecs_table_record_t* flecs_id_record_get_table( - const ecs_id_record_t *idr, - const ecs_table_t *table); - -/* Init sparse storage */ -void flecs_id_record_init_sparse( - ecs_world_t *world, - ecs_id_record_t *idr); - -/* Bootstrap cached id records */ -void flecs_init_id_records( - ecs_world_t *world); - -/* Cleanup all id records in world */ -void flecs_fini_id_records( - ecs_world_t *world); - -/* Return flags for matching id records */ -ecs_flags32_t flecs_id_flags_get( - ecs_world_t *world, - ecs_id_t id); - -#endif - - /** - * @file query/query.h - * @brief Query implementation. - */ - -/** - * @file query/compiler/compiler.h - * @brief Query compiler functions. - */ - - /** - * @file query/types.h - * @brief Internal types and functions for queries. - */ - -#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 { - EcsQueryAnd, /* And operator: find or match id against variable source */ - EcsQueryAndAny, /* And operator with support for matching Any src/id */ - EcsQueryOnlyAny, /* Dedicated instruction for _ queries where the src is unknown */ - 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 */ - 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 */ - EcsQueryUnionEq, /* Evaluate union relationship */ - EcsQueryUnionEqWith, /* Evaluate union relationship against fixed or variable source */ - EcsQueryUnionNeq, /* Evaluate union relationship */ - EcsQueryUnionEqUp, /* Evaluate union relationship w/up traversal */ - EcsQueryUnionEqSelfUp, /* Evaluate union relationship w/self|up traversal */ - 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; - - /* And context */ -typedef struct { - ecs_id_record_t *idr; - ecs_table_cache_iter_t it; - int16_t column; - int16_t remaining; -} ecs_query_and_ctx_t; - -/* Union context */ -typedef struct { - ecs_id_record_t *idr; - ecs_table_range_t range; - ecs_map_iter_t tgt_iter; - ecs_entity_t cur; - ecs_entity_t tgt; - int32_t row; -} ecs_query_union_ctx_t; - -/* Down traversal cache (for resolving up queries w/unknown source) */ -typedef struct { - ecs_table_t *table; - bool leaf; /* Table owns and inherits id (for Up queries without Self) */ -} ecs_trav_down_elem_t; - -typedef struct { - ecs_vec_t elems; /* vector */ - bool ready; -} ecs_trav_down_t; - -typedef struct { - ecs_entity_t src; - ecs_id_t id; - ecs_table_record_t *tr; - bool ready; -} ecs_trav_up_t; - -typedef enum { - EcsTravUp = 1, - EcsTravDown = 2 -} ecs_trav_direction_t; - -typedef struct { - ecs_map_t src; /* map or map */ - ecs_id_t with; - ecs_trav_direction_t dir; -} ecs_trav_up_cache_t; - -/* And up context */ -typedef struct { - union { - ecs_query_and_ctx_t and; - ecs_query_union_ctx_t union_; - } is; - ecs_table_t *table; - int32_t row; - int32_t end; - ecs_entity_t trav; - ecs_id_t with; - ecs_id_t matched; - ecs_id_record_t *idr_with; - ecs_id_record_t *idr_trav; - ecs_trav_down_t *down; - int32_t cache_elem; - ecs_trav_up_cache_t cache; -} ecs_query_up_ctx_t; - -/* 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_id_record_t *idr; - const ecs_table_record_t *tr; -} ecs_trav_elem_t; - -typedef struct { - ecs_id_t id; - ecs_id_record_t *idr; - ecs_vec_t entities; - bool up; -} ecs_trav_cache_t; - -/* 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; - - /* Eq context */ -typedef struct { - ecs_table_range_t range; - int32_t index; - int16_t name_col; - bool redo; -} ecs_query_eq_ctx_t; - - /* Each context */ -typedef struct { - int32_t row; -} ecs_query_each_ctx_t; - - /* Setthis context */ -typedef struct { - ecs_table_range_t range; -} ecs_query_setthis_ctx_t; - -/* Ids context */ -typedef struct { - ecs_id_record_t *cur; -} ecs_query_ids_ctx_t; - -/* Control flow context */ -typedef struct { - ecs_query_lbl_t op_index; - ecs_id_t field_id; - bool is_set; -} ecs_query_ctrl_ctx_t; - -/* 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; - -/* *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; - -/* 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; - -typedef struct ecs_query_op_ctx_t { - union { - 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_union_ctx_t union_; - } is; -} ecs_query_op_ctx_t; - -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 */ - - 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; - -/* 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 */ - - /* Query cache */ - struct ecs_query_cache_t *cache; /* Cache, if query contains cached terms */ - - /* User context */ - ecs_ctx_free_t ctx_free; /* Callback to free ctx */ - ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ - - /* Mixins */ - flecs_poly_dtor_t dtor; -}; - - -/* Query cache types */ - -/** Table match data. - * Each table matched by the query is represented by an ecs_query_cache_table_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. */ -struct ecs_query_cache_table_match_t { - ecs_query_cache_table_match_t *next, *prev; - ecs_table_t *table; /* The current table. */ - int32_t offset; /* Starting point in table */ - int32_t count; /* Number of entities to iterate in table */ - const ecs_table_record_t **trs; /* Information about where to find field in table */ - ecs_id_t *ids; /* Resolved (component) ids for current table */ - ecs_entity_t *sources; /* Subjects (sources) of ids */ - ecs_termset_t set_fields; /* Fields that are set */ - ecs_termset_t up_fields; /* Fields that are matched through traversal */ - uint64_t group_id; /* Value used to organize tables in groups */ - int32_t *monitor; /* Used to monitor table for changes */ - - /* Next match in cache for same table (includes empty tables) */ - ecs_query_cache_table_match_t *next_match; -}; - -/** Table record type for query table cache. A query only has one per table. */ -typedef struct ecs_query_cache_table_t { - ecs_table_cache_hdr_t hdr; /* Header for ecs_table_cache_t */ - ecs_query_cache_table_match_t *first; /* List with matches for table */ - ecs_query_cache_table_match_t *last; /* Last discovered match for table */ - uint64_t table_id; - int32_t rematch_count; /* Track whether table was rematched */ -} ecs_query_cache_table_t; - -/** Points to the beginning & ending of a query group */ -typedef struct ecs_query_cache_table_list_t { - ecs_query_cache_table_match_t *first; - ecs_query_cache_table_match_t *last; - ecs_query_group_info_t info; -} ecs_query_cache_table_list_t; - -/* Query event type for notifying queries of world events */ -typedef enum ecs_query_cache_eventkind_t { - EcsQueryTableMatch, - EcsQueryTableRematch, - EcsQueryTableUnmatch, -} ecs_query_cache_eventkind_t; - -typedef struct ecs_query_cache_event_t { - ecs_query_cache_eventkind_t kind; - ecs_table_t *table; -} ecs_query_cache_event_t; - -/* Query level block allocators have sizes that depend on query field count */ -typedef struct ecs_query_cache_allocators_t { - ecs_block_allocator_t trs; - ecs_block_allocator_t ids; - ecs_block_allocator_t sources; - ecs_block_allocator_t monitors; -} ecs_query_cache_allocators_t; - -/** Query that is automatically matched against tables */ -typedef struct ecs_query_cache_t { - /* Uncached query used to populate the cache */ - ecs_query_t *query; - - /* Observer to keep the cache in sync */ - ecs_observer_t *observer; - - /* Tables matched with query */ - ecs_table_cache_t cache; - - /* Linked list with all matched non-empty tables, in iteration order */ - ecs_query_cache_table_list_t list; - - /* Contains head/tail to nodes of query groups (if group_by is used) */ - ecs_map_t groups; - - /* 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; - - /* 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; - - /* Monitor generation */ - int32_t monitor_generation; - - 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; - - /* Zero'd out sources array, used for results that only match on $this */ - ecs_entity_t *sources; - - /* Map field indices from cache to query */ - int8_t *field_map; - - /* Query-level allocators */ - ecs_query_cache_allocators_t allocators; -} ecs_query_cache_t; - -#endif - - -/* Compile query to list of operations */ -int flecs_query_compile( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_query_impl_t *query); - -/* Compile single term */ -int flecs_query_compile_term( - ecs_world_t *world, - ecs_query_impl_t *query, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx); - -/* Compile term ref (first, second or src) */ -void flecs_query_compile_term_ref( - ecs_world_t *world, - 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); - -/* 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); - -ecs_query_op_t* flecs_query_begin_block( - ecs_query_op_kind_t kind, - ecs_query_compile_ctx_t *ctx); - -void flecs_query_end_block( - ecs_query_compile_ctx_t *ctx, - bool reset); - - -/** - * @file query/engine/engine.h - * @brief Query engine functions. - */ - - /** - * @file query/engine/cache.h - * @brief Query cache functions. - */ - - -/* Create query cache */ -ecs_query_cache_t* flecs_query_cache_init( - ecs_query_impl_t *impl, - const ecs_query_desc_t *desc); - -/* Destroy query cache */ -void flecs_query_cache_fini( - ecs_query_impl_t *impl); - -/* Notify query cache of event (separate from query observer) */ -void flecs_query_cache_notify( - ecs_world_t *world, - ecs_query_t *q, - ecs_query_cache_event_t *event); - -/* Get cache entry for table */ -ecs_query_cache_table_t* flecs_query_cache_get_table( - ecs_query_cache_t *query, - ecs_table_t *table); - -/* Sort tables (order_by implementation) */ -void flecs_query_cache_sort_tables( - ecs_world_t *world, - ecs_query_impl_t *impl); - -void flecs_query_cache_build_sorted_tables( - ecs_query_cache_t *cache); - -/* Return number of tables in cache */ -int32_t flecs_query_cache_table_count( - ecs_query_cache_t *cache); - -/* Return number of entities in cache (requires iterating tables) */ -int32_t flecs_query_cache_entity_count( - const ecs_query_cache_t *cache); - -/** - * @file query/engine/cache_iter.h - * @brief Cache iterator functions. - */ - - -/* Cache search */ -bool flecs_query_cache_search( - const ecs_query_run_ctx_t *ctx); - -/* Cache search where entire query is cached */ -bool flecs_query_is_cache_search( - const ecs_query_run_ctx_t *ctx); - -/* Cache test */ -bool flecs_query_cache_test( - const ecs_query_run_ctx_t *ctx, - bool redo); - -/* Cache test where entire query is cached */ -bool flecs_query_is_cache_test( - const ecs_query_run_ctx_t *ctx, - bool redo); - -/** - * @file query/engine/change_detection.h - * @brief Query change detection functions. - */ - - -/* Synchronize cache monitor with table dirty state */ -void flecs_query_sync_match_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match); - -/* Mark iterated out fields dirty */ -void flecs_query_mark_fields_dirty( - ecs_query_impl_t *impl, - ecs_iter_t *it); - -/* Compare cache monitor with table dirty state to detect changes */ -bool flecs_query_check_table_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_t *table, - int32_t term); - -/* Mark out fields with fixed source dirty */ -void flecs_query_mark_fixed_fields_dirty( - ecs_query_impl_t *impl, - ecs_iter_t *it); - -/* Synchronize fixed source monitor */ -bool flecs_query_update_fixed_monitor( - ecs_query_impl_t *impl); - -/* Compare fixed source monitor */ -bool flecs_query_check_fixed_monitor( - ecs_query_impl_t *impl); - -/** - * @file query/engine/trav_cache.h - * @brief Traversal cache functions - */ - - -/* Traversal cache for transitive queries. Finds all reachable entities by - * following a relationship */ - -/* 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); - -/* 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); - -/* Free traversal cache */ -void flecs_query_trav_cache_fini( - ecs_allocator_t *a, - ecs_trav_cache_t *cache); - -/* Traversal caches for up traversal. Enables searching upwards until an entity - * with the queried for id has been found. */ - -/* 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_id_record_t *idr_with, - bool self, - bool empty); - -/* Free down traversal cache */ -void flecs_query_down_cache_fini( - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache); - -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, - ecs_id_t with, - ecs_entity_t trav, - ecs_id_record_t *idr_with, - ecs_id_record_t *idr_trav); - -/* Free up traversal cache */ -void flecs_query_up_cache_fini( - ecs_trav_up_cache_t *cache); - -/** - * @file query/engine/trivial_iter.h - * @brief Trivial iterator functions. - */ - - -/* 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); - -/* 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); - -/* Trivial test for constrained $this. */ -bool flecs_query_trivial_test( - const ecs_query_run_ctx_t *ctx, - bool first, - ecs_flags64_t field_set); - - -/* Query evaluation utilities */ - -void flecs_query_set_iter_this( - ecs_iter_t *it, - const ecs_query_run_ctx_t *ctx); - -ecs_query_op_ctx_t* flecs_op_ctx_( - const ecs_query_run_ctx_t *ctx); - -#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); - -void flecs_set_source_set_flag( - ecs_iter_t *it, - int32_t field_index); - -ecs_table_range_t flecs_range_from_entity( - ecs_entity_t e, - const ecs_query_run_ctx_t *ctx); - -ecs_table_range_t flecs_query_var_get_range( - int32_t var_id, - const ecs_query_run_ctx_t *ctx); - -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); - -void flecs_query_var_narrow_range( - ecs_var_id_t var_id, - ecs_table_t *table, - int32_t offset, - int32_t count, - 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); - -void flecs_query_set_vars( - const ecs_query_op_t *op, - ecs_id_t id, - const ecs_query_run_ctx_t *ctx); - -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_entity_t flecs_get_ref_entity( - const ecs_query_ref_t *ref, - ecs_flags16_t flag, - const ecs_query_run_ctx_t *ctx); - -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_id_t flecs_query_op_get_id( - const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx); - -int16_t flecs_query_next_column( - ecs_table_t *table, - ecs_id_t id, - int32_t column); - -void flecs_query_it_set_tr( - ecs_iter_t *it, - int32_t field_index, - const ecs_table_record_t *tr); - -ecs_id_t flecs_query_it_set_id( - ecs_iter_t *it, - ecs_table_t *table, - int32_t field_index, - int32_t column); - -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); - -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); - -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); - - -/* 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); - - -/* Union evaluation */ - -bool flecs_query_union_select( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx); - -bool flecs_query_union( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx); - -bool flecs_query_union_neq( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx); - -bool flecs_query_union_with( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - bool neq); - -bool flecs_query_union_up( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx); - -bool flecs_query_union_self_up( - 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 */ - -bool flecs_query_pred_eq_match( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx); - -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); - -bool flecs_query_pred_neq( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx); - -bool flecs_query_pred_neq_name( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx); - - -/* 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; - -typedef enum ecs_query_up_select_kind_t { - FlecsQueryUpSelectDefault, - FlecsQueryUpSelectId, - FlecsQueryUpSelectUnion -} ecs_query_up_select_kind_t; - -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); - -bool flecs_query_up_with( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx); - -bool flecs_query_self_up_with( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - bool id_only); - - -/* Transitive relationship traversal */ - -bool flecs_query_trav( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx); - - -/** - * @file query/util.h - * @brief Utility functions - */ - - -/* 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; - -/* 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); - - -#ifdef FLECS_DEBUG -#define flecs_set_var_label(var, lbl) (var)->label = lbl -#else -#define flecs_set_var_label(var, lbl) -#endif - -/* Finalize query data & validate */ -int flecs_query_finalize_query( - ecs_world_t *world, - ecs_query_t *q, - const ecs_query_desc_t *desc); - -/* 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); - -/* Internal function for initializing an iterator after vars are constrained */ -void flecs_query_iter_constrain( - ecs_iter_t *it); - -/** - * @file observable.h - * @brief Functions for sending events. - */ - -#ifndef FLECS_OBSERVABLE_H -#define FLECS_OBSERVABLE_H - -/** 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 */ - - ecs_map_t observers; /* map */ - - /* Triggers for SuperSet, SubSet */ - ecs_map_t set_observers; /* map */ - - /* Triggers for Self with non-This subject */ - ecs_map_t entity_observers; /* map */ - - /* Number of active observers for (component) id */ - int32_t observer_count; -} ecs_event_id_record_t; - -typedef struct ecs_observer_impl_t { - ecs_observer_t pub; - - int32_t *last_event_id; /**< Last handled event id */ - int32_t last_event_id_storage; - - ecs_id_t register_id; /**< Id observer is registered with (single term observers only) */ - int8_t term_index; /**< Index of the term in parent observer (single term observers only) */ - - ecs_flags32_t flags; /**< Observer flags */ - uint64_t id; /**< Internal id (not entity id) */ - ecs_vec_t children; /**< If multi observer, vector stores child observers */ - - ecs_query_t *not_query; /**< Query used to populate observer data when a - term with a not operator triggers. */ - - /* Mixins */ - flecs_poly_dtor_t dtor; -} ecs_observer_impl_t; - -#define flecs_observer_impl(observer) (ECS_CONST_CAST(ecs_observer_impl_t*, observer)) - -ecs_event_record_t* flecs_event_record_get( - const ecs_observable_t *o, - ecs_entity_t event); - -ecs_event_record_t* flecs_event_record_ensure( - ecs_observable_t *o, - ecs_entity_t event); - -ecs_event_id_record_t* flecs_event_id_record_get( - const ecs_event_record_t *er, - ecs_id_t id); - -ecs_event_id_record_t* flecs_event_id_record_ensure( - ecs_world_t *world, - ecs_event_record_t *er, - ecs_id_t id); - -void flecs_event_id_record_remove( - ecs_event_record_t *er, - ecs_id_t id); - -void flecs_observable_init( - ecs_observable_t *observable); - -void flecs_observable_fini( - ecs_observable_t *observable); - -bool flecs_observers_exist( - ecs_observable_t *observable, - ecs_id_t id, - ecs_entity_t event); - -ecs_observer_t* flecs_observer_init( - ecs_world_t *world, - ecs_entity_t entity, - const ecs_observer_desc_t *desc); - -void flecs_observer_fini( - ecs_observer_t *observer); - -void flecs_emit( - ecs_world_t *world, - ecs_world_t *stage, - ecs_flags64_t set_mask, - ecs_event_desc_t *desc); - -bool flecs_default_next_callback( - ecs_iter_t *it); - -void flecs_observers_invoke( - ecs_world_t *world, - ecs_map_t *observers, - ecs_iter_t *it, - ecs_table_t *table, - ecs_entity_t trav); - -void flecs_emit_propagate_invalidate( - ecs_world_t *world, - ecs_table_t *table, - int32_t offset, - int32_t count); - -void flecs_observer_set_disable_bit( - ecs_world_t *world, - ecs_entity_t e, - ecs_flags32_t bit, - bool cond); - -#endif - -/** - * @file iter.h - * @brief Iterator utilities. - */ - -#ifndef FLECS_ITER_H -#define FLECS_ITER_H - -void flecs_iter_init( - const ecs_world_t *world, - ecs_iter_t *it, - ecs_flags8_t fields); - -void* flecs_iter_calloc( - ecs_iter_t *it, - ecs_size_t size, - ecs_size_t align); - -#define flecs_iter_calloc_t(it, T)\ - flecs_iter_calloc(it, ECS_SIZEOF(T), ECS_ALIGNOF(T)) - -#define flecs_iter_calloc_n(it, T, count)\ - flecs_iter_calloc(it, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) - -void flecs_iter_free( - void *ptr, - ecs_size_t size); - -#define flecs_iter_free_t(ptr, T)\ - flecs_iter_free(ptr, ECS_SIZEOF(T)) - -#define flecs_iter_free_n(ptr, T, count)\ - flecs_iter_free(ptr, ECS_SIZEOF(T) * count) - -#endif - -/** - * @file poly.h - * @brief Functions for managing poly objects. - */ - -#ifndef FLECS_POLY_H -#define FLECS_POLY_H - -#include - -/* Initialize poly */ -void* flecs_poly_init_( - ecs_poly_t *object, - int32_t kind, - ecs_size_t size, - ecs_mixins_t *mixins); - -#define flecs_poly_init(object, type)\ - flecs_poly_init_(object, type##_magic, sizeof(type), &type##_mixins) - -/* Deinitialize object for specified type */ -void flecs_poly_fini_( - ecs_poly_t *object, - int32_t kind); - -#define flecs_poly_fini(object, type)\ - flecs_poly_fini_(object, type##_magic) - -/* Utility functions for creating an object on the heap */ -#define flecs_poly_new(type)\ - (type*)flecs_poly_init(ecs_os_calloc_t(type), type) - -#define flecs_poly_free(obj, type)\ - flecs_poly_fini(obj, type);\ - ecs_os_free(obj) - -/* Get or create poly component for an entity */ -EcsPoly* flecs_poly_bind_( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag); - -#define flecs_poly_bind(world, entity, T) \ - flecs_poly_bind_(world, entity, T##_tag) - -void flecs_poly_modified_( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag); - -#define flecs_poly_modified(world, entity, T) \ - flecs_poly_modified_(world, entity, T##_tag) - -/* 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); - -#define flecs_poly_bind_get(world, entity, T) \ - flecs_poly_bind_get_(world, entity, T##_tag) - -ecs_poly_t* flecs_poly_get_( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag); - -#define flecs_poly_get(world, entity, T) \ - ((T*)flecs_poly_get_(world, entity, T##_tag)) - -/* 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->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, type_name);\ - ecs_assert(hdr->type == ty##_magic, ECS_INVALID_PARAMETER, type_name);\ - } while (0) -#else -#define flecs_poly_assert(object, ty) -#endif - -ecs_observable_t* ecs_get_observable( - const ecs_poly_t *object); - -flecs_poly_dtor_t* ecs_get_dtor( - const ecs_poly_t *poly); - -#endif - -/** - * @file stage.h - * @brief Stage functions. - */ - -#ifndef FLECS_STAGE_H -#define FLECS_STAGE_H - -/* Post-frame merge actions */ -void flecs_stage_merge_post_frame( - ecs_world_t *world, - ecs_stage_t *stage); - -bool flecs_defer_cmd( - ecs_stage_t *stage); - -bool flecs_defer_begin( - ecs_world_t *world, - ecs_stage_t *stage); - -bool flecs_defer_modified( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t component); - -bool flecs_defer_clone( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t src, - bool clone_value); - -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); - -bool flecs_defer_path( - ecs_stage_t *stage, - ecs_entity_t parent, - ecs_entity_t entity, - const char *name); - -bool flecs_defer_delete( - ecs_stage_t *stage, - ecs_entity_t entity); - -bool flecs_defer_clear( - ecs_stage_t *stage, - ecs_entity_t entity); - -bool flecs_defer_on_delete_action( - ecs_stage_t *stage, - ecs_id_t id, - ecs_entity_t action); - -bool flecs_defer_enable( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t component, - bool enable); - -bool flecs_defer_add( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id); - -bool flecs_defer_remove( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id); - -void* flecs_defer_set( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_cmd_kind_t op_kind, - ecs_entity_t entity, - ecs_entity_t component, - ecs_size_t size, - void *value, - bool *is_new); - -bool flecs_defer_end( - ecs_world_t *world, - ecs_stage_t *stage); - -bool flecs_defer_purge( - ecs_world_t *world, - ecs_stage_t *stage); - -void flecs_enqueue( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_event_desc_t *desc); - -ecs_entity_t flecs_stage_set_system( - ecs_stage_t *stage, - ecs_entity_t system); - -ecs_allocator_t* flecs_stage_get_allocator( - ecs_world_t *world); - -ecs_stack_t* flecs_stage_get_stack_allocator( - ecs_world_t *world); - -void flecs_commands_init( - ecs_stage_t *stage, - ecs_commands_t *cmd); - -void flecs_commands_fini( - ecs_stage_t *stage, - ecs_commands_t *cmd); - -#endif - -/** - * @file world.h - * @brief World-level API. - */ - -#ifndef FLECS_WORLD_H -#define FLECS_WORLD_H - -/* Get current stage */ -ecs_stage_t* flecs_stage_from_world( - ecs_world_t **world_ptr); - -/* Get current thread-specific stage from readonly world */ -ecs_stage_t* flecs_stage_from_readonly_world( - const ecs_world_t *world); - -/* Get component callbacks */ -const ecs_type_info_t *flecs_type_info_get( - const ecs_world_t *world, - ecs_entity_t component); - -/* Get or create component callbacks */ -ecs_type_info_t* flecs_type_info_ensure( - ecs_world_t *world, - ecs_entity_t component); - -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); - -#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__) - -void flecs_type_info_fini( - ecs_type_info_t *ti); - -void flecs_type_info_free( - ecs_world_t *world, - ecs_entity_t component); - -void flecs_eval_component_monitors( - ecs_world_t *world); - -void flecs_monitor_mark_dirty( - ecs_world_t *world, - ecs_entity_t id); - -void flecs_monitor_register( - ecs_world_t *world, - ecs_entity_t id, - ecs_query_t *query); - -void flecs_monitor_unregister( - ecs_world_t *world, - ecs_entity_t id, - ecs_query_t *query); - -void flecs_notify_tables( - ecs_world_t *world, - ecs_id_t id, - ecs_table_event_t *event); - -void flecs_register_table( - ecs_world_t *world, - ecs_table_t *table); - -void flecs_unregister_table( - ecs_world_t *world, - ecs_table_t *table); - -void flecs_delete_table( - ecs_world_t *world, - ecs_table_t *table); - -/* 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 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; - -ecs_world_t* flecs_suspend_readonly( - const ecs_world_t *world, - ecs_suspend_readonly_state_t *state); - -void flecs_resume_readonly( - ecs_world_t *world, - ecs_suspend_readonly_state_t *state); - -/* 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) - -#endif - -/** - * @file datastructures/name_index.h - * @brief Data structure for resolving 64bit keys by string (name). - */ - -#ifndef FLECS_NAME_INDEX_H -#define FLECS_NAME_INDEX_H - -void flecs_name_index_init( - ecs_hashmap_t *hm, - ecs_allocator_t *allocator); - -void flecs_name_index_init_if( - ecs_hashmap_t *hm, - ecs_allocator_t *allocator); - -bool flecs_name_index_is_init( - const ecs_hashmap_t *hm); - -ecs_hashmap_t* flecs_name_index_new( - ecs_world_t *world, - ecs_allocator_t *allocator); - -void flecs_name_index_fini( - ecs_hashmap_t *map); - -void flecs_name_index_free( - ecs_hashmap_t *map); - -ecs_hashmap_t* flecs_name_index_copy( - ecs_hashmap_t *dst); - -ecs_hashed_string_t flecs_get_hashed_string( - const char *name, - ecs_size_t length, - uint64_t hash); - -const uint64_t* flecs_name_index_find_ptr( - const ecs_hashmap_t *map, - const char *name, - ecs_size_t length, - uint64_t hash); - -uint64_t flecs_name_index_find( - const ecs_hashmap_t *map, - const char *name, - ecs_size_t length, - uint64_t hash); - -void flecs_name_index_ensure( - ecs_hashmap_t *map, - uint64_t id, - const char *name, - ecs_size_t length, - uint64_t hash); - -void flecs_name_index_remove( - ecs_hashmap_t *map, - uint64_t id, - uint64_t hash); - -void flecs_name_index_update_name( - ecs_hashmap_t *map, - uint64_t e, - uint64_t hash, - const char *name); - -#endif - - - -//////////////////////////////////////////////////////////////////////////////// -//// Bootstrap API -//////////////////////////////////////////////////////////////////////////////// - -/* Bootstrap world */ -void flecs_bootstrap( - ecs_world_t *world); - -#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_)\ - }); - -#define flecs_bootstrap_tag(world, name)\ - ecs_make_alive(world, name);\ - ecs_add_id(world, name, EcsFinal);\ - 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)]);\ - ecs_set_symbol(world, name, #name); - -#define flecs_bootstrap_trait(world, name)\ - flecs_bootstrap_tag(world, name)\ - ecs_add_id(world, name, EcsTrait) - - -/* Bootstrap functions for other parts in the code */ -void flecs_bootstrap_hierarchy(ecs_world_t *world); - - -//////////////////////////////////////////////////////////////////////////////// -//// Entity API -//////////////////////////////////////////////////////////////////////////////// - -/* 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); - -void flecs_record_add_flag( - ecs_record_t *record, - uint32_t flag); - -ecs_entity_t flecs_get_oneof( - const ecs_world_t *world, - ecs_entity_t e); - -void flecs_notify_on_remove( - 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); - -void flecs_notify_on_set( - ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - ecs_type_t *type, - bool owned); - -int32_t flecs_relation_depth( - const ecs_world_t *world, - ecs_entity_t r, - const ecs_table_t *table); - -typedef struct ecs_instantiate_ctx_t { - ecs_entity_t root_prefab; - ecs_entity_t root_instance; -} ecs_instantiate_ctx_t; - -void flecs_instantiate( - ecs_world_t *world, - ecs_entity_t base, - ecs_table_t *table, - int32_t row, - int32_t count, - const ecs_instantiate_ctx_t *ctx); - -void* flecs_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); - -void flecs_invoke_hook( - ecs_world_t *world, - ecs_table_t *table, - 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); - -void flecs_add_ids( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t *ids, - int32_t count); - -//////////////////////////////////////////////////////////////////////////////// -//// Query API -//////////////////////////////////////////////////////////////////////////////// - -void flecs_query_apply_iter_flags( - ecs_iter_t *it, - const ecs_query_t *query); - -//////////////////////////////////////////////////////////////////////////////// -//// Safe(r) integer casting -//////////////////////////////////////////////////////////////////////////////// - -#define FLECS_CONVERSION_ERR(T, value)\ - "illegal conversion from value " #value " to type " #T - -#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); - -#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))) - -#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 - -#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)) - -//////////////////////////////////////////////////////////////////////////////// -//// Utilities -//////////////////////////////////////////////////////////////////////////////// - -uint64_t flecs_hash( - const void *data, - ecs_size_t length); - -uint64_t flecs_wyhash( - const void *data, - ecs_size_t length); - -/* Get next power of 2 */ -int32_t flecs_next_pow_of_2( - int32_t n); - -/* 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); - -/* Get 64bit integer from ecs_record_t */ -uint64_t flecs_from_row( - ecs_record_t record); - -/* 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); - -/* 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); - -/* Compare function for component ids used for qsort */ -int flecs_id_qsort_cmp( - const void *a, - const void *b); - -/* Load file contents into string */ -char* flecs_load_from_file( - const char *filename); - -bool flecs_name_is_id( - const char *name); - -ecs_entity_t flecs_name_to_id( - const char *name); - -/* Convert floating point to string */ -char * ecs_ftoa( - double f, - char * buf, - int precision); - -uint64_t flecs_string_hash( - const void *ptr); - -void flecs_table_hashmap_init( - ecs_world_t *world, - ecs_hashmap_t *hm); - -void flecs_colorize_buf( - char *msg, - bool enable_colors, - ecs_strbuf_t *buf); - -int32_t flecs_search_w_idr( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t *id_out, - ecs_id_record_t *idr); - -int32_t flecs_search_relation_w_idr( - 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 *subject_out, - ecs_id_t *id_out, - struct ecs_table_record_t **tr_out, - ecs_id_record_t *idr); - -bool flecs_type_can_inherit_id( - const ecs_world_t *world, - const ecs_table_t *table, - const ecs_id_record_t *idr, - ecs_id_t id); - -int ecs_term_finalize( - const ecs_world_t *world, - ecs_term_t *term); - -int32_t flecs_query_pivot_term( - const ecs_world_t *world, - const ecs_query_t *query); - -#endif - - -/* -- Identifier Component -- */ -static ECS_DTOR(EcsIdentifier, ptr, { - ecs_os_strset(&ptr->value, NULL); -}) - -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; -}) - -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; - - src->value = NULL; - src->hash = 0; - src->index_hash = 0; - src->index = 0; - src->length = 0; -}) - -static -void ecs_on_set(EcsIdentifier)(ecs_iter_t *it) { - EcsIdentifier *ptr = ecs_field(it, EcsIdentifier, 0); - - 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_id_t pair = ecs_childof(0); - ecs_hashmap_t *index = NULL; - - 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); - ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair); - ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL); - - if (evt == EcsOnSet) { - index = flecs_id_name_index_ensure(world, pair); - } else { - index = flecs_id_name_index_get(world, pair); - } - } - - 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 (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; - } - - 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; - } - - 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); - } - } - } -} - - -/* -- Poly component -- */ - -static ECS_COPY(EcsPoly, dst, src, { - (void)dst; - (void)src; - ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied"); -}) - -static ECS_MOVE(EcsPoly, dst, src, { - if (dst->poly && (dst->poly != src->poly)) { - flecs_poly_dtor_t *dtor = ecs_get_dtor(dst->poly); - ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); - dtor[0](dst->poly); - } - - dst->poly = src->poly; - src->poly = NULL; -}) - -static ECS_DTOR(EcsPoly, ptr, { - if (ptr->poly) { - flecs_poly_dtor_t *dtor = ecs_get_dtor(ptr->poly); - ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); - dtor[0](ptr->poly); - } -}) - - -/* -- Builtin triggers -- */ - -static -void flecs_assert_relation_unused( - ecs_world_t *world, - ecs_entity_t rel, - ecs_entity_t property) -{ - if (world->flags & (EcsWorldInit|EcsWorldFini)) { - return; - } - - 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; - } - } - - bool in_use = ecs_id_in_use(world, ecs_pair(rel, EcsWildcard)); - - /* Hack to make enum unions work. C++ enum reflection registers enum - * constants right after creating the enum entity. The enum constant - * entities have a component of the enum type with the constant value, which - * is why it shows up as in use. */ - if (property != EcsUnion) { - 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, property); - - ecs_throw(ECS_ID_IN_USE, - "cannot change property '%s' for relationship '%s': already in use", - p_str, r_str); - - ecs_os_free(r_str); - ecs_os_free(p_str); - } - -error: - return; -} - -static -bool flecs_set_id_flag( - ecs_world_t *world, - ecs_id_record_t *idr, - ecs_flags32_t flag) -{ - if (!(idr->flags & flag)) { - idr->flags |= flag; - if (flag == EcsIdIsSparse) { - flecs_id_record_init_sparse(world, idr); - } - return true; - } - return false; -} - -static -bool flecs_unset_id_flag( - ecs_id_record_t *idr, - ecs_flags32_t flag) -{ - if ((idr->flags & flag)) { - idr->flags &= ~flag; - return true; - } - return false; -} - -static -void flecs_register_id_flag_for_relation( - ecs_iter_t *it, - ecs_entity_t prop, - ecs_flags32_t flag, - ecs_flags32_t not_flag, - ecs_flags32_t entity_flag) -{ - ecs_world_t *world = it->world; - ecs_entity_t event = it->event; - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - bool changed = false; - - if (event == EcsOnAdd) { - ecs_id_record_t *idr; - if (!ecs_has_id(world, e, EcsRelationship) && - !ecs_has_id(world, e, EcsTarget)) - { - idr = flecs_id_record_ensure(world, e); - changed |= flecs_set_id_flag(world, idr, flag); - } - - idr = flecs_id_record_ensure(world, ecs_pair(e, EcsWildcard)); - do { - changed |= flecs_set_id_flag(world, idr, flag); - } while ((idr = idr->first.next)); - if (entity_flag) flecs_add_flag(world, e, entity_flag); - } else if (event == EcsOnRemove) { - ecs_id_record_t *idr = flecs_id_record_get(world, e); - if (idr) changed |= flecs_unset_id_flag(idr, not_flag); - idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)); - if (idr) { - do { - changed |= flecs_unset_id_flag(idr, not_flag); - } while ((idr = idr->first.next)); - } - } - - if (changed) { - flecs_assert_relation_unused(world, e, prop); - } - } -} - -static -void flecs_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_id_record_get(world, ecs_pair(EcsIsA, e)) != NULL) { - char *e_str = ecs_get_path(world, e); - ecs_throw(ECS_ID_IN_USE, - "cannot change property 'Final' for '%s': already inherited from", - e_str); - ecs_os_free(e_str); - error: - continue; - } - } -} - -static -void flecs_register_tag(ecs_iter_t *it) { - flecs_register_id_flag_for_relation(it, EcsPairIsTag, EcsIdTag, EcsIdTag, 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_id_record_t *idr = flecs_id_record_get(world, - ecs_pair(e, EcsWildcard)); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - do { - if (idr->type_info != NULL) { - flecs_assert_relation_unused(world, e, EcsPairIsTag); - } - idr->type_info = NULL; - } while ((idr = idr->first.next)); - } - } -} - -static -void flecs_register_on_delete(ecs_iter_t *it) { - ecs_id_t id = ecs_field_id(it, 0); - flecs_register_id_flag_for_relation(it, EcsOnDelete, - ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), - EcsIdOnDeleteMask, - EcsEntityIsId); -} - -static -void flecs_register_on_delete_object(ecs_iter_t *it) { - ecs_id_t id = ecs_field_id(it, 0); - flecs_register_id_flag_for_relation(it, EcsOnDeleteTarget, - ECS_ID_ON_DELETE_TARGET_FLAG(ECS_PAIR_SECOND(id)), - EcsIdOnDeleteObjectMask, - EcsEntityIsId); -} - -static -void flecs_register_on_instantiate(ecs_iter_t *it) { - ecs_id_t id = ecs_field_id(it, 0); - flecs_register_id_flag_for_relation(it, EcsOnInstantiate, - ECS_ID_ON_INSTANTIATE_FLAG(ECS_PAIR_SECOND(id)), - 0, 0); -} - -typedef struct ecs_on_trait_ctx_t { - ecs_flags32_t flag, not_flag; -} ecs_on_trait_ctx_t; - -static -void flecs_register_trait(ecs_iter_t *it) { - ecs_on_trait_ctx_t *ctx = it->ctx; - flecs_register_id_flag_for_relation( - it, it->ids[0], ctx->flag, ctx->not_flag, 0); -} - -static -void flecs_register_trait_pair(ecs_iter_t *it) { - ecs_on_trait_ctx_t *ctx = it->ctx; - flecs_register_id_flag_for_relation( - it, ecs_pair_first(it->world, it->ids[0]), ctx->flag, ctx->not_flag, 0); -} - -static -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], EcsUnion); - } -} - -static -void flecs_on_symmetric_add_remove(ecs_iter_t *it) { - ecs_entity_t pair = ecs_field_id(it, 0); - - if (!ECS_HAS_ID_FLAG(pair, PAIR)) { - /* If relationship was not added as a pair, there's nothing to do */ - return; - } - - ecs_world_t *world = it->world; - ecs_entity_t rel = ECS_PAIR_FIRST(pair); - ecs_entity_t obj = ecs_pair_second(world, pair); - ecs_entity_t event = it->event; - - - if (obj) { - 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); - } - } - } - } -} - -static -void flecs_register_symmetric(ecs_iter_t *it) { - ecs_world_t *world = it->real_world; - - 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); - - /* 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} - }); - } -} - -static -void flecs_on_component(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsComponent *c = ecs_field(it, EcsComponent, 0); - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - - 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; - - if (it->event != EcsOnRemove) { - ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); - if (parent) { - ecs_add_id(world, parent, EcsModule); - } - } - - 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; - } - } - } -} - -static -void flecs_ensure_module_tag(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]; - ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); - if (parent) { - ecs_add_id(world, parent, EcsModule); - } - } -} - -static -void flecs_disable_observer( - ecs_iter_t *it) -{ - 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 flecs_disable_module_observers( - ecs_world_t *world, - ecs_entity_t module, - bool should_disable) -{ - 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; - - /* 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; - } - - /* Only disable observers */ - if (!ecs_table_has_id(world, table, EcsObserver)) { - continue; - } - - for (i = 0; i < child_it.count; i ++) { - flecs_observer_set_disable_bit(world, child_it.entities[i], - EcsObserverIsParentDisabled, should_disable); - } - } -} - -static -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); - } -} - -/* -- Bootstrapping -- */ - -#define flecs_bootstrap_builtin_t(world, table, name)\ - flecs_bootstrap_builtin(world, table, ecs_id(name), #name, sizeof(name),\ - ECS_ALIGNOF(name)) - -static -void flecs_bootstrap_builtin( - 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_column_t *columns = table->data.columns; - ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_record_t *record = flecs_entities_ensure(world, entity); - record->table = table; - - int32_t index = flecs_table_append(world, table, entity, false, false); - record->row = ECS_ROW_TO_RECORD(index, 0); - - EcsComponent *component = columns[0].data; - component[index].size = size; - component[index].alignment = alignment; - - const char *name = &symbol[3]; /* Strip 'Ecs' */ - ecs_size_t symbol_length = ecs_os_strlen(symbol); - ecs_size_t name_length = symbol_length - 3; - - 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; - name_col[index].index = table->_->name_index; - flecs_name_index_ensure( - table->_->name_index, entity, name, name_length, name_hash); - - 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; -} - -/** 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. */ - ecs_id_record_t *idr = flecs_id_record_ensure(world, EcsChildOf); - idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdOnInstantiateDontInherit | - EcsIdTraversable | EcsIdTag; - - /* Initialize id records cached on world */ - world->idr_childof_wildcard = flecs_id_record_ensure(world, - ecs_pair(EcsChildOf, EcsWildcard)); - world->idr_childof_wildcard->flags |= EcsIdOnDeleteObjectDelete | - EcsIdOnInstantiateDontInherit | EcsIdTraversable | EcsIdTag | EcsIdExclusive; - idr = flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsWildcard)); - idr->flags |= EcsIdOnInstantiateDontInherit; - world->idr_identifier_name = - flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsName)); - world->idr_childof_0 = flecs_id_record_ensure(world, - ecs_pair(EcsChildOf, 0)); - - ecs_id_t ids[] = { - ecs_id(EcsComponent), - EcsFinal, - ecs_pair_t(EcsIdentifier, EcsName), - ecs_pair_t(EcsIdentifier, EcsSymbol), - ecs_pair(EcsChildOf, EcsFlecsCore), - ecs_pair(EcsOnDelete, EcsPanic) - }; - - ecs_type_t array = { - .array = ids, - .count = 6 - }; - - ecs_table_t *result = flecs_table_find_or_create(world, &array); - - /* Preallocate enough memory for initial components */ - ecs_allocator_t *a = &world->allocator; - ecs_vec_t v_entities = ecs_vec_from_entities(result); - ecs_vec_init_t(a, &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(a, &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(a, &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(a, &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; - } - - result->data.entities = v_entities.array; - result->data.count = 0; - result->data.size = v_entities.size; - - return result; -} - -static -void flecs_bootstrap_entity( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - ecs_entity_t parent) -{ - char symbol[256]; - ecs_os_strcpy(symbol, "flecs.core."); - ecs_os_strcat(symbol, name); - - ecs_make_alive(world, id); - 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(world, name) == id, - ECS_INTERNAL_ERROR, NULL); - } -} - -void flecs_bootstrap( - ecs_world_t *world) -{ - ecs_log_push(); - - ecs_set_name_prefix(world, "Ecs"); - - /* Ensure builtin ids are alive */ - ecs_make_alive(world, ecs_id(EcsComponent)); - ecs_make_alive(world, EcsFinal); - ecs_make_alive(world, ecs_id(EcsIdentifier)); - ecs_make_alive(world, EcsName); - ecs_make_alive(world, EcsSymbol); - ecs_make_alive(world, EcsAlias); - ecs_make_alive(world, EcsChildOf); - ecs_make_alive(world, EcsFlecs); - ecs_make_alive(world, EcsFlecsCore); - ecs_make_alive(world, EcsFlecsInternals); - ecs_make_alive(world, EcsOnAdd); - ecs_make_alive(world, EcsOnRemove); - ecs_make_alive(world, EcsOnSet); - ecs_make_alive(world, EcsOnDelete); - ecs_make_alive(world, EcsPanic); - ecs_make_alive(world, EcsFlag); - ecs_make_alive(world, EcsIsA); - ecs_make_alive(world, EcsWildcard); - ecs_make_alive(world, EcsAny); - ecs_make_alive(world, EcsPairIsTag); - ecs_make_alive(world, EcsCanToggle); - ecs_make_alive(world, EcsTrait); - ecs_make_alive(world, EcsRelationship); - ecs_make_alive(world, EcsTarget); - ecs_make_alive(world, EcsSparse); - ecs_make_alive(world, EcsUnion); - - /* 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 - }); - - 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) - }); - - flecs_type_info_init(world, EcsPoly, { - .ctor = flecs_default_ctor, - .copy = ecs_copy(EcsPoly), - .move = ecs_move(EcsPoly), - .dtor = ecs_dtor(EcsPoly) - }); - - flecs_type_info_init(world, EcsDefaultChildComponent, { - .ctor = flecs_default_ctor, - }); - - /* Create and cache often used id records on world */ - flecs_init_id_records(world); - - /* 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); - - /* 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, EcsDefaultChildComponent); - - /* 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; - - /* Register observer for tag property before adding EcsPairIsTag */ - ecs_observer(world, { - .entity = ecs_entity(world, { .parent = EcsFlecsInternals }), - .query.terms[0] = { .id = EcsPairIsTag }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = flecs_register_tag, - .yield_existing = true - }); - - /* Populate core module */ - ecs_set_scope(world, EcsFlecsCore); - - flecs_bootstrap_tag(world, EcsName); - flecs_bootstrap_tag(world, EcsSymbol); - flecs_bootstrap_tag(world, EcsAlias); - - flecs_bootstrap_tag(world, EcsQuery); - flecs_bootstrap_tag(world, EcsObserver); - - flecs_bootstrap_tag(world, EcsModule); - flecs_bootstrap_tag(world, EcsPrivate); - 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); - - /* Initialize builtin modules */ - ecs_set_name(world, EcsFlecs, "flecs"); - ecs_add_id(world, EcsFlecs, EcsModule); - ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); - - ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); - ecs_set_name(world, EcsFlecsCore, "core"); - ecs_add_id(world, EcsFlecsCore, EcsModule); - - ecs_add_pair(world, EcsFlecsInternals, EcsChildOf, EcsFlecsCore); - ecs_set_name(world, EcsFlecsInternals, "internals"); - ecs_add_id(world, EcsFlecsInternals, EcsModule); - - /* 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; - - /* 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); - - /* Component/relationship properties */ - flecs_bootstrap_trait(world, EcsTransitive); - flecs_bootstrap_trait(world, EcsReflexive); - flecs_bootstrap_trait(world, EcsSymmetric); - flecs_bootstrap_trait(world, EcsFinal); - 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, EcsUnion); - - flecs_bootstrap_tag(world, EcsRemove); - flecs_bootstrap_tag(world, EcsDelete); - flecs_bootstrap_tag(world, EcsPanic); - - flecs_bootstrap_tag(world, EcsOverride); - flecs_bootstrap_tag(world, EcsInherit); - flecs_bootstrap_tag(world, EcsDontInherit); - - /* 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); - - /* Builtin relationships */ - flecs_bootstrap_tag(world, EcsIsA); - flecs_bootstrap_tag(world, EcsChildOf); - flecs_bootstrap_tag(world, EcsDependsOn); - - /* 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); - - /* 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); - - /* Create triggers in internals scope */ - ecs_set_scope(world, EcsFlecsInternals); - - /* Register observers for components/relationship properties. Most observers - * set flags on an id record when a property is added to a component, which - * allows for quick property testing in various operations. */ - ecs_observer(world, { - .query.terms = {{ .id = EcsFinal }}, - .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, - .events = {EcsOnAdd}, - .callback = flecs_register_final - }); - - ecs_observer(world, { - .query.terms = { - { .id = ecs_pair(EcsOnDelete, EcsWildcard) } - }, - .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = flecs_register_on_delete - }); - - ecs_observer(world, { - .query.terms = { - { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard) } - }, - .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = flecs_register_on_delete_object - }); - - ecs_observer(world, { - .query.terms = { - { .id = ecs_pair(EcsOnInstantiate, EcsWildcard) } - }, - .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, - .events = {EcsOnAdd}, - .callback = flecs_register_on_instantiate - }); - - ecs_observer(world, { - .query.terms = {{ .id = EcsSymmetric }}, - .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, - .events = {EcsOnAdd}, - .callback = flecs_register_symmetric - }); - - 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 - }); - - static ecs_on_trait_ctx_t exclusive_trait = { EcsIdExclusive, EcsIdExclusive }; - ecs_observer(world, { - .query.terms = {{ .id = EcsExclusive }}, - .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = flecs_register_trait, - .ctx = &exclusive_trait - }); - - 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 - }); - - 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 - }); - - static ecs_on_trait_ctx_t sparse_trait = { EcsIdIsSparse, 0 }; - ecs_observer(world, { - .query.terms = {{ .id = EcsSparse }}, - .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, - .events = {EcsOnAdd}, - .callback = flecs_register_trait, - .ctx = &sparse_trait - }); - - static ecs_on_trait_ctx_t union_trait = { EcsIdIsUnion, 0 }; - ecs_observer(world, { - .query.terms = {{ .id = EcsUnion }}, - .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, - .events = {EcsOnAdd}, - .callback = flecs_register_trait, - .ctx = &union_trait - }); - - /* 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 - }); - - /* 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 - }); - - /* Observer that tracks whether observers are disabled */ - ecs_observer(world, { - .query.terms = { - { .id = EcsObserver }, - { .id = EcsDisabled }, - }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = flecs_disable_observer - }); - - /* Observer that tracks whether modules are disabled */ - ecs_observer(world, { - .query.terms = { - { .id = EcsModule }, - { .id = EcsDisabled }, - }, - .events = {EcsOnAdd, EcsOnRemove}, - .callback = flecs_disable_module - }); - - /* Set scope back to flecs core */ - ecs_set_scope(world, EcsFlecsCore); - - /* 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); - - /* 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); - - /* 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, EcsUnion, EcsOnInstantiate, EcsDontInherit); - - /* Acyclic/Traversable components */ - ecs_add_id(world, EcsIsA, EcsTraversable); - ecs_add_id(world, EcsDependsOn, EcsTraversable); - ecs_add_id(world, EcsWith, EcsAcyclic); - - /* Transitive relationships */ - ecs_add_id(world, EcsIsA, EcsTransitive); - ecs_add_id(world, EcsIsA, EcsReflexive); - - /* Exclusive properties */ - ecs_add_id(world, EcsSlotOf, EcsExclusive); - ecs_add_id(world, EcsOneOf, EcsExclusive); - - /* Private properties */ - ecs_add_id(world, ecs_id(EcsPoly), EcsPrivate); - ecs_add_id(world, ecs_id(EcsIdentifier), EcsPrivate); - - /* 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_hierarchy(world); - - ecs_set_scope(world, 0); - ecs_set_name_prefix(world, NULL); - - ecs_assert(world->idr_childof_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(world->idr_isa_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_log_pop(); -} - -/** - * @file query/each.c - * @brief Simple iterator for a single component id. - */ - - -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); - - const ecs_world_t *world = ecs_get_world(stage); - - 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 - }; - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return it; - } - - ecs_each_iter_t *each_iter = &it.priv_.iter.each; - each_iter->ids = id; - each_iter->sizes = 0; - if (idr->type_info) { - each_iter->sizes = idr->type_info->size; - } - - each_iter->sources = 0; - each_iter->trs = NULL; - flecs_table_cache_iter((ecs_table_cache_t*)idr, &each_iter->it); - - return it; -error: - return (ecs_iter_t){0}; -} - -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); - it->ids = &table->type.array[next->index]; - it->trs = &each_iter->trs; - it->sources = &each_iter->sources; - it->sizes = &each_iter->sizes; - it->set_fields = 1; - - return true; - } else { - return false; - } -} - -ecs_iter_t ecs_children( - const ecs_world_t *stage, - ecs_entity_t parent) -{ - return ecs_each_id(stage, ecs_childof(parent)); -} - -bool ecs_children_next( - ecs_iter_t *it) -{ - return ecs_each_next(it); -} - -/** - * @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. - */ - -#include - -#ifdef FLECS_SCRIPT -/** - * @file addons/script/script.h - * @brief Flecs script implementation. - */ - -#ifndef FLECS_SCRIPT_PRIVATE_H -#define FLECS_SCRIPT_PRIVATE_H - - -#ifdef FLECS_SCRIPT - -#include - -typedef struct ecs_script_scope_t ecs_script_scope_t; -typedef struct ecs_script_entity_t ecs_script_entity_t; - -typedef 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; -} ecs_script_impl_t; - -typedef struct ecs_script_parser_t ecs_script_parser_t; - -#define flecs_script_impl(script) ((ecs_script_impl_t*)script) - -/** - * @file addons/script/tokenizer.h - * @brief Script tokenizer. - */ - -#ifndef FLECS_SCRIPT_TOKENIZER_H -#define FLECS_SCRIPT_TOKENIZER_H - -/* Tokenizer */ -typedef enum ecs_script_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', - 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, - EcsTokString = 113, - EcsTokNumber = 114, - EcsTokKeywordModule = 115, - EcsTokKeywordUsing = 116, - EcsTokKeywordWith = 117, - EcsTokKeywordIf = 118, - EcsTokKeywordFor = 119, - EcsTokKeywordIn = 120, - EcsTokKeywordElse = 121, - EcsTokKeywordTemplate = 122, - EcsTokKeywordProp = 130, - EcsTokKeywordConst = 131, - EcsTokKeywordMatch = 132, - EcsTokAddAssign = 133, - EcsTokMulAssign = 134, -} ecs_script_token_kind_t; - -typedef struct ecs_script_token_t { - const char *value; - ecs_script_token_kind_t kind; -} ecs_script_token_t; - -typedef struct ecs_script_tokens_t { - int32_t count; - ecs_script_token_t tokens[256]; -} ecs_script_tokens_t; - -typedef struct ecs_script_tokenizer_t { - ecs_script_tokens_t stack; - ecs_script_token_t *tokens; -} ecs_script_tokenizer_t; - -const char* flecs_script_until( - ecs_script_parser_t *parser, - const char *ptr, - ecs_script_token_t *out, - char until); - -const char* flecs_script_token_kind_str( - ecs_script_token_kind_t kind); - -const char* flecs_script_token_str( - ecs_script_token_kind_t kind); - -const char* flecs_script_token( - ecs_script_parser_t *parser, - const char *ptr, - ecs_script_token_t *out, - bool is_lookahead); - -const char* flecs_scan_whitespace( - ecs_script_parser_t *parser, - const char *pos); - -const char* flecs_script_identifier( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out); - -#endif - - -struct ecs_script_parser_t { - ecs_script_impl_t *script; - ecs_script_scope_t *scope; - const char *pos; - char *token_cur; - char *token_keep; - bool significant_newline; - bool merge_variable_members; - - /* For term parser */ - ecs_term_t *term; - ecs_oper_kind_t extra_oper; - ecs_term_ref_t *extra_args; -}; - -typedef struct ecs_function_calldata_t { - ecs_entity_t function; - ecs_function_callback_t callback; - 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, - 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; - - /* 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; - 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_script_parser_t *parser); - -ecs_script_entity_t* flecs_script_insert_entity( - ecs_script_parser_t *parser, - const char *name, - bool name_is_expr); - -ecs_script_pair_scope_t* flecs_script_insert_pair_scope( - ecs_script_parser_t *parser, - const char *first, - const char *second); - -ecs_script_with_t* flecs_script_insert_with( - ecs_script_parser_t *parser); - -ecs_script_using_t* flecs_script_insert_using( - ecs_script_parser_t *parser, - const char *name); - -ecs_script_module_t* flecs_script_insert_module( - ecs_script_parser_t *parser, - const char *name); - -ecs_script_template_node_t* flecs_script_insert_template( - ecs_script_parser_t *parser, - const char *name); - -ecs_script_annot_t* flecs_script_insert_annot( - ecs_script_parser_t *parser, - const char *name, - const char *expr); - -ecs_script_var_node_t* flecs_script_insert_var( - ecs_script_parser_t *parser, - const char *name); - -ecs_script_tag_t* flecs_script_insert_tag( - ecs_script_parser_t *parser, - const char *name); - -ecs_script_tag_t* flecs_script_insert_pair_tag( - ecs_script_parser_t *parser, - const char *first, - const char *second); - -ecs_script_component_t* flecs_script_insert_component( - ecs_script_parser_t *parser, - const char *name); - -ecs_script_component_t* flecs_script_insert_pair_component( - ecs_script_parser_t *parser, - const char *first, - const char *second); - -ecs_script_default_component_t* flecs_script_insert_default_component( - ecs_script_parser_t *parser); - -ecs_script_var_component_t* flecs_script_insert_var_component( - ecs_script_parser_t *parser, - const char *name); - -ecs_script_if_t* flecs_script_insert_if( - ecs_script_parser_t *parser); - -ecs_script_for_range_t* flecs_script_insert_for_range( - ecs_script_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 -} 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_script_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_script_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_script_token_kind_t operator; -} 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; - -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_value_node_t* flecs_expr_bool( - ecs_script_parser_t *parser, - bool value); - -ecs_expr_value_node_t* flecs_expr_int( - ecs_script_parser_t *parser, - int64_t value); - -ecs_expr_value_node_t* flecs_expr_uint( - ecs_script_parser_t *parser, - uint64_t value); - -ecs_expr_value_node_t* flecs_expr_float( - ecs_script_parser_t *parser, - double value); - -ecs_expr_value_node_t* flecs_expr_string( - ecs_script_parser_t *parser, - const char *value); - -ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( - ecs_script_parser_t *parser, - const char *value); - -ecs_expr_value_node_t* flecs_expr_entity( - ecs_script_parser_t *parser, - ecs_entity_t value); - -ecs_expr_initializer_t* flecs_expr_initializer( - ecs_script_parser_t *parser); - -ecs_expr_identifier_t* flecs_expr_identifier( - ecs_script_parser_t *parser, - const char *value); - -ecs_expr_variable_t* flecs_expr_variable( - ecs_script_parser_t *parser, - const char *value); - -ecs_expr_unary_t* flecs_expr_unary( - ecs_script_parser_t *parser); - -ecs_expr_binary_t* flecs_expr_binary( - ecs_script_parser_t *parser); - -ecs_expr_member_t* flecs_expr_member( - ecs_script_parser_t *parser); - -ecs_expr_function_t* flecs_expr_function( - ecs_script_parser_t *parser); - -ecs_expr_element_t* flecs_expr_element( - ecs_script_parser_t *parser); - -ecs_expr_match_t* flecs_expr_match( - ecs_script_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_script_token_kind_t operator); - -int flecs_value_unary( - const ecs_script_t *script, - const ecs_value_t *expr, - ecs_value_t *out, - ecs_script_token_kind_t operator); - -const char* flecs_script_parse_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out); - -const char* flecs_script_parse_initializer( - ecs_script_parser_t *parser, - const char *pos, - char until, - ecs_expr_initializer_t **node_out); - -void flecs_expr_to_str_buf( - const ecs_world_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); - -#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); - -struct ecs_script_visit_t { - ecs_script_impl_t *script; - ecs_visit_action_t visit; - ecs_script_node_t* nodes[256]; - 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,\ - (ecs_visit_action_t)visit,\ - script) - -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) - -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, - int32_t *frame_offset, - ecs_entity_t *out); - -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_eval_visitor_t *v, - ecs_script_node_t *node); - -int flecs_script_check_node( - ecs_script_eval_visitor_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); - -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; -}; - -#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_script_parser_t *parser); - -int flecs_script_visit_free( - ecs_script_t *script); - -ecs_script_vars_t* flecs_script_vars_push( - ecs_script_vars_t *parent, - ecs_stack_t *stack, - ecs_allocator_t *allocator); - -int flecs_terms_parse( - ecs_script_t *script, - ecs_term_t *terms, - int32_t *term_count_out); - -const char* flecs_id_parse( - const ecs_world_t *world, - const char *name, - const char *expr, - ecs_id_t *id); - -const char* flecs_term_parse( - ecs_world_t *world, - const char *name, - const char *expr, - ecs_term_t *term, - char *token_buffer); - -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); - -#endif // FLECS_SCRIPT -#endif // FLECS_SCRIPT_PRIVATE_H - -#endif - -static -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); - -typedef struct { - const ecs_type_info_t *ti; - void *ptr; -} flecs_component_ptr_t; - -static -flecs_component_ptr_t flecs_table_get_component( - ecs_table_t *table, - int32_t column_index, - int32_t row) -{ - ecs_check(column_index < table->column_count, ECS_NOT_A_COMPONENT, 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) - }; -error: - return (flecs_component_ptr_t){0}; -} - -static -flecs_component_ptr_t flecs_get_component_ptr( - ecs_table_t *table, - int32_t row, - ecs_id_record_t *idr) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - if (!idr) { - return (flecs_component_ptr_t){0}; - } - - if (idr->flags & EcsIdIsSparse) { - ecs_entity_t entity = ecs_table_entities(table)[row]; - return (flecs_component_ptr_t){ - .ti = idr->type_info, - .ptr = flecs_sparse_get_any(idr->sparse, 0, entity) - }; - } - - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr || (tr->column == -1)) { - return (flecs_component_ptr_t){0}; - } - - return flecs_table_get_component(table, tr->column, row); -} - -static -void* flecs_get_component( - ecs_table_t *table, - int32_t row, - ecs_id_record_t *idr) -{ - return flecs_get_component_ptr(table, row, idr).ptr; -} - -void* flecs_get_base_component( - const ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_id_record_t *idr, - int32_t recur_depth) -{ - ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, - "cycle detected in IsA relationship"); - - /* Table (and thus entity) does not have component, look for base */ - if (!(table->flags & EcsTableHasIsA)) { - return NULL; - } - - if (!(idr->flags & EcsIdOnInstantiateInherit)) { - return NULL; - } - - /* Exclude Name */ - if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { - return NULL; - } - - /* Table should always be in the table index for (IsA, *), otherwise the - * HasBase flag should not have been set */ - ecs_table_record_t *tr_isa = flecs_id_record_get_table( - world->idr_isa_wildcard, table); - ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); - - 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; - - 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; - if (!table) { - continue; - } - - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - ptr = flecs_get_base_component(world, table, id, idr, - recur_depth + 1); - } else { - if (idr->flags & EcsIdIsSparse) { - return flecs_sparse_get_any(idr->sparse, 0, 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; -} - -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_add_pair(world, instance, slot, child); - } 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); - } - - 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); - } - - ecs_add_pair(world, parent, slot, child); - break; - } - - 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); - } - } - -error: - return; -} - -static -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; -} - -static -ecs_table_t* flecs_find_table_remove( - 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_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 -int32_t flecs_child_type_insert( - ecs_type_t *type, - void **component_data, - ecs_id_t id) -{ - 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; - } - - if (cur > id) { - /* A larger id was found so id can't be part of the type. */ - break; - } - } - - /* 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*)); - } - - component_data[i] = NULL; - type->array[i] = id; - type->count ++; - - return i; -} - -static -void flecs_instantiate_children( - ecs_world_t *world, - ecs_entity_t base, - ecs_table_t *table, - int32_t row, - int32_t count, - ecs_table_t *child_table, - const ecs_instantiate_ctx_t *ctx) -{ - if (!ecs_table_count(child_table)) { - return; - } - - ecs_type_t type = child_table->type; - ecs_data_t *child_data = &child_table->data; - - ecs_entity_t slot_of = 0; - ecs_entity_t *ids = type.array; - int32_t type_count = type.count; - - /* 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 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]; - - /* 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_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & EcsIdOnInstantiateDontInherit) { - continue; - } - } - - /* 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; - } - } - - /* 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; - } - - int32_t storage_index = ecs_table_type_to_column_index(child_table, i); - if (storage_index != -1) { - component_data[diff.added.count] = - child_data->columns[storage_index].data; - } else { - component_data[diff.added.count] = NULL; - } - - diff.added.array[diff.added.count] = id; - diff.added.count ++; - diff.added_flags |= flecs_id_flags_get(world, id); - } - - /* Table must contain children of base */ - ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); - - /* 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 ++; - } - } - - /* Instantiate the prefab child table for each new instance */ - const ecs_entity_t *instances = ecs_table_entities(table); - int32_t child_count = ecs_table_count(child_table); - ecs_entity_t *child_ids = flecs_walloc_n(world, ecs_entity_t, child_count); - - for (i = row; i < count + row; i ++) { - ecs_entity_t instance = instances[i]; - ecs_table_t *i_table = NULL; - - /* Replace ChildOf element in the component array with instance id */ - diff.added.array[childof_base_index] = ecs_pair(EcsChildOf, instance); - - /* Find or create table */ - i_table = flecs_table_find_or_create(world, &diff.added); - - ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(i_table->type.count == diff.added.count, - ECS_INTERNAL_ERROR, NULL); - - /* 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); - -#ifdef FLECS_DEBUG - for (j = 0; j < child_count; j ++) { - ecs_entity_t child = children[j]; - 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 - - /* 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; - } - - for (j = 0; j < child_count; j ++) { - if ((uint32_t)children[j] < (uint32_t)ctx_cur.root_prefab) { - /* Child id is smaller than root prefab id, can't use offset */ - child_ids[j] = ecs_new(world); - continue; - } - - /* Get prefab offset, ignore lifecycle generation count */ - ecs_entity_t prefab_offset = - (uint32_t)children[j] - (uint32_t)ctx_cur.root_prefab; - ecs_assert(prefab_offset != 0, ECS_INTERNAL_ERROR, NULL); - - /* 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] = ecs_new(world); - continue; - } - - /* 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; - } - - /* Create children */ - int32_t child_row; - const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, child_ids, - &diff.added, child_count, component_data, false, &child_row, &diff); - - /* If children are slots, add slot relationships to parent */ - if (slot_of) { - for (j = 0; j < child_count; j ++) { - ecs_entity_t child = children[j]; - ecs_entity_t i_child = i_children[j]; - flecs_instantiate_slot(world, base, instance, slot_of, - child, i_child); - } - } - - /* If prefab child table has children itself, recursively instantiate */ - for (j = 0; j < child_count; j ++) { - ecs_entity_t child = children[j]; - flecs_instantiate(world, child, i_table, child_row + j, 1, &ctx_cur); - } - } - - flecs_wfree_n(world, ecs_entity_t, child_count, child_ids); -error: - return; -} - -void flecs_instantiate( - ecs_world_t *world, - ecs_entity_t base, - ecs_table_t *table, - int32_t row, - int32_t count, - const ecs_instantiate_ctx_t *ctx) -{ - ecs_record_t *record = flecs_entities_get_any(world, base); - ecs_table_t *base_table = record->table; - if (!base_table) { - return; - } - - /* If prefab has union relationships, also set them on instance */ - if (base_table->flags & EcsTableHasUnion) { - const ecs_entity_t *entities = ecs_table_entities(table); - ecs_id_record_t *union_idr = flecs_id_record_get(world, - ecs_pair(EcsWildcard, EcsUnion)); - ecs_assert(union_idr != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_table_record_t *tr = flecs_id_record_get_table( - union_idr, base_table); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t i = 0, j, union_count = 0; - do { - ecs_id_t id = base_table->type.array[i]; - if (ECS_PAIR_SECOND(id) == EcsUnion) { - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t tgt = ecs_get_target(world, base, rel, 0); - ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); - - for (j = row; j < (row + count); j ++) { - ecs_add_pair(world, entities[j], rel, tgt); - } - - union_count ++; - } - - i ++; - } while (union_count < tr->count); - } - - if (!(base_table->flags & EcsTableIsPrefab)) { - /* Don't instantiate children from base entities that aren't prefabs */ - return; - } - - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_childof(base)); - ecs_table_cache_iter_t it; - if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { - ecs_os_perf_trace_push("flecs.instantiate"); - const ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - flecs_instantiate_children( - world, base, table, row, count, tr->hdr.table, ctx); - } - ecs_os_perf_trace_pop("flecs.instantiate"); - } -} - -static -void flecs_sparse_on_add( - ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - const ecs_type_t *added, - bool construct) -{ - int32_t i, j; - for (i = 0; i < added->count; i ++) { - ecs_id_t id = added->array[i]; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr && idr->flags & EcsIdIsSparse) { - const ecs_type_info_t *ti = idr->type_info; - ecs_xtor_t ctor = ti->hooks.ctor; - ecs_iter_action_t on_add = ti->hooks.on_add; - const ecs_entity_t *entities = ecs_table_entities(table); - for (j = 0; j < count; j ++) { - ecs_entity_t e = entities[row + j]; - void *ptr = flecs_sparse_ensure(idr->sparse, 0, e); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (construct && ctor) { - ctor(ptr, 1, ti); - } - - if (on_add) { - const ecs_table_record_t *tr = - flecs_id_record_get_table(idr, table); - flecs_invoke_hook(world, table, tr, count, row, - &entities[row + j],id, ti, EcsOnAdd, on_add); - } - } - } - } -} - -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_id_record_t *idr = flecs_id_record_get(world, id); - if (idr && idr->flags & EcsIdIsSparse) { - const ecs_type_info_t *ti = idr->type_info; - const ecs_table_record_t *tr = - flecs_id_record_get_table(idr, table); - ecs_xtor_t dtor = ti->hooks.dtor; - ecs_iter_action_t on_remove = ti->hooks.on_remove; - const ecs_entity_t *entities = ecs_table_entities(table); - for (j = 0; j < count; j ++) { - ecs_entity_t e = entities[row + j]; - if (on_remove) { - flecs_invoke_hook(world, table, tr, count, row, - &entities[row + j], id, ti, EcsOnRemove, on_remove); - } - void *ptr = flecs_sparse_remove_fast(idr->sparse, 0, e); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (dtor) { - dtor(ptr, 1, ti); - } - } - } - } -} - -static -void flecs_union_on_add( - ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - const ecs_type_t *added) -{ - int32_t i, j; - for (i = 0; i < added->count; i ++) { - ecs_id_t id = added->array[i]; - if (ECS_IS_PAIR(id)) { - ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); - ecs_id_record_t *idr = flecs_id_record_get(world, wc); - if (idr && idr->flags & EcsIdIsUnion) { - const ecs_entity_t *entities = ecs_table_entities(table); - for (j = 0; j < count; j ++) { - ecs_entity_t e = entities[row + j]; - flecs_switch_set( - idr->sparse, (uint32_t)e, ecs_pair_second(world, id)); - } - } - } - } -} - -static -void flecs_union_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]; - if (ECS_IS_PAIR(id)) { - ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); - ecs_id_record_t *idr = flecs_id_record_get(world, wc); - if (idr && idr->flags & EcsIdIsUnion) { - const ecs_entity_t *entities = ecs_table_entities(table); - for (j = 0; j < count; j ++) { - ecs_entity_t e = entities[row + j]; - flecs_switch_reset(idr->sparse, (uint32_t)e); - } - } - } - } -} - -static -void flecs_notify_on_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, - ecs_flags64_t set_mask, - bool construct, - bool sparse) -{ - ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_t *added = &diff->added; - - if (added->count) { - ecs_flags32_t diff_flags = - diff->added_flags|(table->flags & EcsTableHasTraversable); - if (!diff_flags) { - return; - } - - if (sparse && (diff_flags & EcsTableHasSparse)) { - flecs_sparse_on_add(world, table, row, count, added, construct); - } - - if (diff_flags & EcsTableHasUnion) { - flecs_union_on_add(world, table, row, count, added); - } - - if (diff_flags & (EcsTableHasOnAdd|EcsTableHasTraversable)) { - flecs_emit(world, world, set_mask, &(ecs_event_desc_t){ - .event = EcsOnAdd, - .ids = added, - .table = table, - .other_table = other_table, - .offset = row, - .count = count, - .observable = world, - .flags = flags - }); - } - } -} - -void flecs_notify_on_remove( - 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_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_t *removed = &diff->removed; - ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); - - if (removed->count) { - ecs_flags32_t diff_flags = - diff->removed_flags|(table->flags & EcsTableHasTraversable); - if (!diff_flags) { - return; - } - - if (diff_flags & EcsTableHasUnion) { - flecs_union_on_remove(world, table, row, count, removed); - } - - if (diff_flags & (EcsTableHasOnRemove|EcsTableHasTraversable)) { - flecs_emit(world, world, 0, &(ecs_event_desc_t) { - .event = EcsOnRemove, - .ids = removed, - .table = table, - .other_table = other_table, - .offset = row, - .count = count, - .observable = world - }); - } - - if (diff_flags & EcsTableHasSparse) { - flecs_sparse_on_remove(world, table, row, count, removed); - } - } -} - -static -void flecs_update_name_index( - ecs_world_t *world, - ecs_table_t *src, - ecs_table_t *dst, - int32_t offset, - int32_t count) -{ - ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); - 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_hashmap_t *src_index = src->_->name_index; - ecs_hashmap_t *dst_index = dst->_->name_index; - if ((src_index == dst_index) || (!src_index && !dst_index)) { - /* If the name index didn't change, the entity still has the same parent - * so nothing needs to be done. */ - return; - } - - EcsIdentifier *names = ecs_table_get_pair(world, - dst, EcsIdentifier, EcsName, offset); - ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t i; - const ecs_entity_t *entities = &ecs_table_entities(dst)[offset]; - for (i = 0; i < count; i ++) { - ecs_entity_t e = entities[i]; - EcsIdentifier *name = &names[i]; - - uint64_t index_hash = name->index_hash; - if (index_hash) { - flecs_name_index_remove(src_index, e, index_hash); - } - const char *name_str = name->value; - if (name_str) { - 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; - } - } -} - -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); - - ecs_assert(ecs_table_count(table) > row, ECS_INTERNAL_ERROR, NULL); - flecs_notify_on_add( - world, table, NULL, row, 1, diff, evt_flags, 0, ctor, true); - ecs_assert(table == record->table, ECS_INTERNAL_ERROR, NULL); - - return record; -} - -static int commit_indent = 0; - -static -void flecs_move_entity( - ecs_world_t *world, - ecs_entity_t entity, - ecs_record_t *record, - ecs_table_t *dst_table, - ecs_table_diff_t *diff, - bool ctor, - ecs_flags32_t evt_flags) -{ - ecs_table_t *src_table = record->table; - int32_t src_row = ECS_RECORD_TO_ROW(record->row); - - 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_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, 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); - - /* 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_notify_on_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); - - /* 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_table_delete(world, src_table, src_row, false); - flecs_notify_on_add(world, dst_table, src_table, dst_row, 1, diff, - evt_flags, 0, ctor, true); - - flecs_update_name_index(world, src_table, dst_table, dst_row, 1); - - ecs_assert(record->table == dst_table, ECS_INTERNAL_ERROR, NULL); -error: - return; -} - -static -void flecs_delete_entity( - ecs_world_t *world, - ecs_record_t *record, - ecs_table_diff_t *diff) -{ - ecs_table_t *table = record->table; - int32_t row = ECS_RECORD_TO_ROW(record->row); - - /* Invoke remove actions before deleting */ - flecs_notify_on_remove(world, table, NULL, row, 1, diff); - flecs_table_delete(world, table, row, true); -} - -/* 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 -void flecs_update_component_monitor_w_array( - ecs_world_t *world, - ecs_type_t *ids) -{ - if (!ids) { - return; - } - - int i; - for (i = 0; i < ids->count; i ++) { - ecs_entity_t id = ids->array[i]; - if (ECS_HAS_ID_FLAG(id, PAIR)) { - flecs_monitor_mark_dirty(world, - ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); - } - - flecs_monitor_mark_dirty(world, id); - } -} - -static -void flecs_update_component_monitors( - ecs_world_t *world, - ecs_type_t *added, - ecs_type_t *removed) -{ - flecs_update_component_monitor_w_array(world, added); - flecs_update_component_monitor_w_array(world, removed); -} - -static -void flecs_commit( - ecs_world_t *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) -{ - ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); - flecs_journal_begin(world, EcsJournalMove, entity, - &diff->added, &diff->removed); - - ecs_table_t *src_table = NULL; - int is_trav = 0; - if (record) { - src_table = record->table; - is_trav = (record->row & EcsEntityIsTraversable) != 0; - } - - 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. */ - if (src_table && src_table->flags & EcsTableHasUnion) { - diff->added_flags |= EcsIdIsUnion; - flecs_notify_on_add(world, src_table, src_table, - ECS_RECORD_TO_ROW(record->row), 1, diff, evt_flags, 0, - construct, true); - } - flecs_journal_end(); - return; - } - - commit_indent += 2; - - ecs_os_perf_trace_push("flecs.commit"); - - if (src_table) { - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_table_traversable_add(dst_table, is_trav); - - if (dst_table->type.count) { - flecs_move_entity(world, entity, record, dst_table, diff, - construct, evt_flags); - } else { - flecs_delete_entity(world, record, diff); - record->table = NULL; - } - - flecs_table_traversable_add(src_table, -is_trav); - } else { - flecs_table_traversable_add(dst_table, is_trav); - if (dst_table->type.count) { - flecs_new_entity(world, entity, record, dst_table, diff, - construct, evt_flags); - } - } - - /* 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 ((!src_table || !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); - } - - commit_indent -=2 ; - - ecs_os_perf_trace_pop("flecs.commit"); - -error: - flecs_journal_end(); - return; -} - -static -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 **component_data, - bool is_move, - int32_t *row_out, - ecs_table_diff_t *diff) -{ - int32_t sparse_count = 0; - if (!entities) { - sparse_count = flecs_entities_count(world); - entities = flecs_entities_new_ids(world, count); - } - - if (!table) { - return entities; - } - - ecs_type_t type = table->type; - if (!type.count) { - return entities; - } - - ecs_type_t component_array = { 0 }; - if (!component_ids) { - component_ids = &component_array; - component_array.array = type.array; - component_array.count = type.count; - } - - int32_t row = flecs_table_appendn(world, table, count, entities); - - /* Update entity index. */ - int i; - for (i = 0; i < count; i ++) { - ecs_record_t *r = flecs_entities_get(world, entities[i]); - r->table = table; - r->row = ECS_ROW_TO_RECORD(row + i, 0); - } - - flecs_defer_begin(world, world->stages[0]); - - flecs_notify_on_add(world, table, NULL, row, count, diff, - (component_data == NULL) ? 0 : EcsEventNoOnSet, 0, true, true); - - 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; - } - - /* Find component in storage type */ - ecs_entity_t id = component_ids->array[c_i]; - const ecs_table_record_t *tr = flecs_table_record_get( - world, table, id); - ecs_assert(tr != NULL, ECS_INVALID_PARAMETER, - "id is not a component"); - ecs_assert(tr->column != -1, ECS_INVALID_PARAMETER, - "id is not a component"); - ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, - "ids cannot be wildcards"); - - int32_t index = tr->column; - ecs_column_t *column = &table->data.columns[index]; - ecs_type_info_t *ti = column->ti; - int32_t size = ti->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - void *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(ptr, src_ptr, size * count); - } - }; - - int32_t j, storage_count = table->column_count; - for (j = 0; j < storage_count; j ++) { - ecs_id_t id = flecs_column_id(table, j); - ecs_type_t set_type = { - .array = &id, - .count = 1 - }; - - flecs_notify_on_set(world, table, row, count, &set_type, true); - } - } - - flecs_defer_end(world, world->stages[0]); - - if (row_out) { - *row_out = row; - } - - if (sparse_count) { - entities = flecs_entities_ids(world); - return &entities[sparse_count]; - } else { - return entities; - } -} - -static -void flecs_add_id_w_record( - ecs_world_t *world, - ecs_entity_t entity, - ecs_record_t *record, - ecs_id_t id, - bool construct) -{ - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - - 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, &id, &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. */ -} - -static -void flecs_add_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_add(stage, entity, id)) { - return; - } - - 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; - ecs_table_t *dst_table = flecs_table_traverse_add( - world, src_table, &id, &diff); - - flecs_commit(world, entity, r, dst_table, &diff, true, 0); - - flecs_defer_end(world, stage); -} - -static -void flecs_remove_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_remove(stage, entity, id)) { - return; - } - - 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, &id, &diff); - - flecs_commit(world, entity, r, dst_table, &diff, true, 0); - - flecs_defer_end(world, stage); -} - -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); - - ecs_table_t *table = ecs_get_table(world, entity); - int32_t i; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - table = flecs_find_table_add(world, table, id, &diff); - } - - 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); -} - -static -flecs_component_ptr_t flecs_ensure( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t id, - ecs_record_t *r) -{ - flecs_component_ptr_t dst = {0}; - - flecs_poly_assert(world, ecs_world_t); - ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check((id & ECS_COMPONENT_MASK) == id || - ECS_HAS_ID_FLAG(id, PAIR), ECS_INVALID_PARAMETER, - "invalid component id specified for ensure"); - - ecs_id_record_t *idr = NULL; - ecs_table_t *table; - if ((table = r->table)) { - if (id < FLECS_HI_COMPONENT_ID) { - int16_t column_index = table->component_map[id]; - if (column_index > 0) { - ecs_column_t *column = &table->data.columns[column_index - 1]; - ecs_type_info_t *ti = column->ti; - dst.ptr = ECS_ELEM(column->data, ti->size, - ECS_RECORD_TO_ROW(r->row)); - dst.ti = 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]; - idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & EcsIdIsSparse) { - dst.ptr = flecs_sparse_get_any(idr->sparse, 0, entity); - dst.ti = idr->type_info; - return dst; - } - } - } else { - idr = flecs_id_record_get(world, id); - dst = flecs_get_component_ptr(table, ECS_RECORD_TO_ROW(r->row), idr); - if (dst.ptr) { - return dst; - } - } - } - - /* If entity didn't have component yet, add it */ - flecs_add_id_w_record(world, entity, r, id, true); - - /* Flush commands so the pointer we're fetching is stable */ - flecs_defer_end(world, world->stages[0]); - flecs_defer_begin(world, world->stages[0]); - - if (!idr) { - idr = flecs_id_record_get(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - } - - ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - dst = flecs_get_component_ptr(r->table, ECS_RECORD_TO_ROW(r->row), idr); -error: - return dst; -} - -void flecs_invoke_hook( - ecs_world_t *world, - ecs_table_t *table, - 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; - } - - ecs_iter_t it = { .field_count = 1}; - it.entities = entities; - - flecs_iter_init(world, &it, flecs_iter_cache_all); - it.world = world; - it.real_world = world; - it.table = table; - it.trs[0] = tr; - it.row_fields = !!(((ecs_id_record_t*)tr->hdr.cache)->flags & EcsIdIsSparse); - it.ref_fields = it.row_fields; - it.sizes = ECS_CONST_CAST(ecs_size_t*, &ti->size); - it.ids[0] = id; - 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); - ecs_iter_fini(&it); - - world->stages[0]->defer = defer; -} - -void flecs_notify_on_set( - ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - ecs_type_t *ids, - bool owned) -{ - 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); - - if (owned) { - int i; - for (i = 0; i < ids->count; i ++) { - ecs_id_t id = ids->array[i]; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *ti = idr->type_info; - ecs_iter_action_t on_set = ti->hooks.on_set; - if (!on_set) { - continue; - } - - const ecs_table_record_t *tr = - flecs_id_record_get_table(idr, table); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - - if (idr->flags & EcsIdIsSparse) { - int32_t j; - for (j = 0; j < count; j ++) { - flecs_invoke_hook(world, table, 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, tr, count, row, entities, - id, ti, EcsOnSet, on_set); - } - } - } - } - - /* Run OnSet notifications */ - if (table->flags & EcsTableHasOnSet && ids->count) { - flecs_emit(world, world, 0, &(ecs_event_desc_t) { - .event = EcsOnSet, - .ids = ids, - .table = table, - .offset = row, - .count = count, - .observable = world - }); - } -} - -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; - if (table) { - flecs_table_traversable_add(table, 1); - } - } - } - record->row |= flag; -} - -void flecs_add_flag( - ecs_world_t *world, - ecs_entity_t entity, - uint32_t flag) -{ - ecs_record_t *record = flecs_entities_get_any(world, entity); - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_record_add_flag(record, flag); -} - -/* -- Public functions -- */ - -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) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, - "commit cannot be called on stage or while world is deferred"); - - ecs_table_t *src_table = NULL; - if (!record) { - record = flecs_entities_get(world, entity); - src_table = record->table; - } - - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; - - if (added) { - diff.added = *added; - diff.added_flags = table->flags & EcsTableAddEdgeFlags; - } - if (removed) { - diff.removed = *removed; - if (src_table) { - diff.removed_flags = src_table->flags & EcsTableRemoveEdgeFlags; - } - } - - ecs_defer_begin(world); - flecs_commit(world, entity, record, table, &diff, true, 0); - ecs_defer_end(world); - - return src_table != table; -error: - return false; -} - -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; -} - -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; -} - -ecs_entity_t ecs_new( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - /* 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*, ecs_get_world(world)); - - ecs_entity_t entity; - if (unsafe_world->flags & EcsWorldMultiThreaded) { - /* When world is in multithreading mode, make sure OS API has threading - * functions initialized */ - ecs_assert(ecs_os_has_threading(), ECS_INVALID_OPERATION, - "thread safe id creation unavailable: threading API not available"); - - /* Can't atomically increase number above max int */ - ecs_assert(flecs_entities_max_id(unsafe_world) < UINT_MAX, - ECS_INVALID_OPERATION, "thread safe ids exhausted"); - entity = (ecs_entity_t)ecs_os_ainc( - (int32_t*)&flecs_entities_max_id(unsafe_world)); - } else { - entity = flecs_entities_new_id(unsafe_world); - } - - ecs_assert(!unsafe_world->info.max_id || - ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, - ECS_OUT_OF_RANGE, NULL); - - flecs_journal(world, EcsJournalNew, entity, 0, 0); - - return entity; -error: - return 0; -} - -ecs_entity_t ecs_new_low_id( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - /* 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_CONST_CAST(ecs_world_t*, ecs_get_world(world)); - if (unsafe_world->flags & EcsWorldReadonly) { - /* Can't issue new comp id while iterating when in multithreaded mode */ - ecs_check(ecs_get_stage_count(world) <= 1, - ECS_INVALID_WHILE_READONLY, NULL); - } - - ecs_entity_t id = 0; - if (unsafe_world->info.last_component_id < FLECS_HI_COMPONENT_ID) { - do { - id = unsafe_world->info.last_component_id ++; - } while (ecs_exists(unsafe_world, id) && id <= FLECS_HI_COMPONENT_ID); - } - - if (!id || id >= FLECS_HI_COMPONENT_ID) { - /* If the low component ids are depleted, return a regular entity id */ - id = ecs_new(unsafe_world); - } else { - flecs_entities_ensure(world, id); - } - - ecs_assert(ecs_get_type(world, id) == NULL, ECS_INTERNAL_ERROR, NULL); - - return id; -error: - return 0; -} - -ecs_entity_t ecs_new_w_id( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_check(world != NULL, 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); - ecs_entity_t entity = ecs_new(world); - - if (flecs_defer_add(stage, entity, id)) { - return entity; - } - - ecs_table_diff_builder_t diff_builder = ECS_TABLE_DIFF_INIT; - flecs_table_diff_builder_init(world, &diff_builder); - ecs_table_t *table = flecs_find_table_add( - world, &world->store.root, id, &diff_builder); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_table_diff_t diff; - flecs_table_diff_build_noalloc(&diff_builder, &diff); - ecs_record_t *r = flecs_entities_get(world, entity); - flecs_new_entity(world, entity, r, table, &diff, true, 0); - flecs_table_diff_builder_fini(world, &diff_builder); - - flecs_defer_end(world, stage); - - return entity; -error: - return 0; -} - -ecs_entity_t ecs_new_w_table( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); - - flecs_stage_from_world(&world); - ecs_entity_t entity = ecs_new(world); - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_flags32_t flags = table->flags & EcsTableAddEdgeFlags; - if (table->flags & EcsTableHasIsA) { - flags |= EcsTableHasOnAdd; - } - - ecs_table_diff_t table_diff = { - .added = table->type, - .added_flags = flags - }; - - flecs_new_entity(world, entity, r, table, &table_diff, true, 0); - - return entity; -error: - return 0; -} - -static -void flecs_copy_id( - ecs_world_t *world, - ecs_record_t *r, - ecs_id_t id, - size_t size, - void *dst_ptr, - void *src_ptr, - const ecs_type_info_t *ti) -{ - ecs_check(dst_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(src_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - - 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)); - } - - flecs_table_mark_dirty(world, r->table, id); - - ecs_table_t *table = r->table; - if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { - ecs_type_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set( - world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); - } -error: - return; -} - -/* 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) -{ -#ifdef FLECS_SCRIPT - const char *ptr = expr; - if (ptr) { - ecs_id_t id = 0; - while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &id))) { - if (!id) { - break; - } - - if (!ecs_id_is_valid(world, id)) { - char *idstr = ecs_id_str(world, id); - ecs_parser_error(name, expr, (ptr - expr), - "id %s is invalid for add expression", idstr); - ecs_os_free(idstr); - goto error; - } - - ecs_vec_append_t(&world->allocator, ids, ecs_id_t)[0] = id; - } - - if (!ptr) { - goto 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; -} - -/* 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) -{ -#ifdef FLECS_SCRIPT - const char *ptr = expr; - if (ptr) { - ecs_id_t id = 0; - while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &id))) { - if (!id) { - break; - } - ecs_add_id(world, entity, id); - } - } -#else - (void)world; - (void)entity; - (void)name; - (void)expr; - ecs_err("cannot parse component expression: script addon required"); -#endif -} - -/* 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) -{ - 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; - } - } - - /* 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, "%s (provided) vs. %s (existing)", - desc->symbol, sym); - } else { - ecs_set_symbol(world, result, desc->symbol); - } - } - - /* 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); - } - - /* Find existing table */ - ecs_table_t *src_table = NULL, *table = NULL; - ecs_record_t *r = flecs_entities_get(world, result); - table = r->table; - - /* Add components from the 'add' array */ - if (desc->add) { - int32_t i = 0; - ecs_id_t id; - - while ((id = desc->add[i ++])) { - table = flecs_find_table_add(world, table, id, &diff); - } - } - - /* Add components from the 'set' array */ - if (desc->set) { - int32_t i = 0; - ecs_id_t id; - - while ((id = desc->set[i ++].type)) { - table = flecs_find_table_add(world, table, id, &diff); - } - } - - /* 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); - } - } - - /* 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); - } - } - - /* 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]); - } - - /* 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]); - - 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_id_record_t *idr = flecs_id_record_get(world, v->type); - flecs_component_ptr_t ptr = flecs_get_component_ptr(table, row, idr); - ecs_check(ptr.ptr != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *ti = idr->type_info; - flecs_copy_id(world, r, v->type, - flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); - } - - 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: - flecs_table_diff_builder_fini(world, &diff); - ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); - return -1; -} - -/* 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 flecs_new_entity, - bool name_assigned) -{ - 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 (flecs_new_entity) { - if (flecs_new_entity && scope && !name && !name_assigned) { - ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); - } - - if (with) { - ecs_add_id(world, entity, with); - } - } - - /* Add components from the 'add' id array */ - if (desc->add) { - int32_t i = 0; - ecs_id_t id; - - while ((id = desc->add[i ++])) { - bool defer = true; - if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { - scope = ECS_PAIR_SECOND(id); - 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, id); - } - } - } - - /* 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_set_id(world, entity, v->type, 0, v->ptr); - } else { - ecs_add_id(world, entity, v->type); - } - } - } - - /* Add components from the 'add_expr' expression */ - if (desc->add_expr) { - flecs_defer_from_expr(world, entity, name, desc->add_expr); - } - - int32_t thread_count = ecs_get_stage_count(world); - - /* 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); - } - } - } - - /* Set name */ - if (name && !name_assigned) { - ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); - } -} - -ecs_entity_t ecs_entity_init( - ecs_world_t *world, - const ecs_entity_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_entity_desc_t was not initialized to zero"); - - 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 id; - int32_t i = 0; - while ((id = desc->add[i ++])) { - if (ECS_HAS_ID_FLAG(id, PAIR) && - (ECS_PAIR_FIRST(id) == 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 - - const char *name = desc->name; - const char *sep = desc->sep; - if (!sep) { - sep = "."; - } - - 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("name id conflicts with provided id"); - return 0; - } - name = NULL; - result = id; - } - } - - const char *root_sep = desc->root_sep; - bool flecs_new_entity = false; - bool name_assigned = false; - - /* 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; - } - } - } - - /* Parent field takes precedence over scope */ - if (desc->parent) { - scope = desc->parent; - ecs_check(ecs_is_valid(world, desc->parent), - ECS_INVALID_PARAMETER, "ecs_entity_desc_t::parent is not valid"); - } - - /* 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 (!result) { - if (desc->use_low_id) { - result = ecs_new_low_id(world); - } else { - result = ecs_new(world); - } - flecs_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_make_alive(world, result); - - 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)); - } - } - 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); - } - return 0; - } - if (!sep || sep[0]) { - ecs_os_free(path); - } - } - } - } - - ecs_assert(name_assigned == ecs_has_pair( - world, result, ecs_id(EcsIdentifier), EcsName), - ECS_INTERNAL_ERROR, NULL); - - if (ecs_is_deferred(world)) { - flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, - scope, with, flecs_new_entity, name_assigned); - } else { - if (flecs_traverse_add(world, result, name, desc, - scope, with, flecs_new_entity, name_assigned)) - { - return 0; - } - } - - return result; -error: - return 0; -} - -const ecs_entity_t* ecs_bulk_init( - ecs_world_t *world, - const ecs_bulk_desc_t *desc) -{ - 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 was not initialized to zero"); - - const ecs_entity_t *entities = desc->entities; - int32_t count = desc->count; - - 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_make_alive(world, entities[i]); - } - } - - ecs_type_t ids; - ecs_table_t *table = desc->table; - if (!table) { - ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; - flecs_table_diff_builder_init(world, &diff); - - int32_t i = 0; - ecs_id_t id; - while ((id = desc->ids[i])) { - table = flecs_find_table_add(world, table, id, &diff); - i ++; - } - - ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); - ids.count = i; - - 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 - }; - ids = (ecs_type_t){.array = diff.added.array, .count = diff.added.count}; - flecs_bulk_new(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_entities_ids(world); - return &entities[sparse_count]; - } -error: - return NULL; -} - -static -void flecs_check_component( - ecs_world_t *world, - ecs_entity_t result, - const EcsComponent *ptr, - ecs_size_t size, - ecs_size_t alignment) -{ - 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); - } -} - -ecs_entity_t ecs_component_init( - ecs_world_t *world, - const ecs_component_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_component_desc_t was not initialized to 0"); - - /* 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; - } - } - - 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 created", - ecs_get_name(world, result)); - } else { - ecs_trace("#[green]component#[reset] %s created", - ecs_get_name(world, result)); - } - } - } else { - flecs_check_component(world, result, ptr, - desc->type.size, desc->type.alignment); - } - - if (desc->type.name && new_component) { - ecs_entity(world, { .id = result, .name = desc->type.name }); - } - - 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); - } - - if (result >= world->info.last_component_id && result < FLECS_HI_COMPONENT_ID) { - world->info.last_component_id = result + 1; - } - - 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; -} - -const ecs_entity_t* ecs_bulk_new_w_id( - ecs_world_t *world, - ecs_id_t id, - int32_t count) -{ - 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; - } - - 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 (id) { - table = flecs_find_table_add(world, table, id, &diff); - } - - 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); - - return ids; -error: - return NULL; -} - -void ecs_clear( - ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_clear(stage, entity)) { - return; - } - - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_table_t *table = r->table; - if (table) { - ecs_table_diff_t diff = { - .removed = table->type, - .removed_flags = table->flags & EcsTableRemoveEdgeFlags - }; - - flecs_delete_entity(world, r, &diff); - r->table = NULL; - - if (r->row & EcsEntityIsTraversable) { - flecs_table_traversable_add(table, -1); - } - } - - flecs_defer_end(world, stage); -error: - return; -} - -static -void flecs_throw_invalid_delete( - ecs_world_t *world, - ecs_id_t id) -{ - char *id_str = NULL; - if (!(world->flags & EcsWorldQuit)) { - id_str = ecs_id_str(world, id); - ecs_err("(OnDelete, Panic) constraint violated while deleting %s", - id_str); - ecs_os_free(id_str); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } -} - -static -void flecs_marked_id_push( - ecs_world_t *world, - ecs_id_record_t* idr, - ecs_entity_t action, - bool delete_id) -{ - ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, - &world->store.marked_ids, ecs_marked_id_t); - - m->idr = idr; - m->id = idr->id; - m->action = action; - m->delete_id = delete_id; - - flecs_id_record_claim(world, idr); -} - -static -void flecs_id_mark_for_delete( - ecs_world_t *world, - ecs_id_record_t *idr, - ecs_entity_t action, - bool delete_id); - -static -void flecs_targets_mark_for_delete( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_id_record_t *idr; - const ecs_entity_t *entities = ecs_table_entities(table); - int32_t i, count = ecs_table_count(table); - for (i = 0; i < count; i ++) { - ecs_record_t *r = flecs_entities_get(world, entities[i]); - if (!r) { - continue; - } - - /* 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))) { - continue; - } - - ecs_entity_t e = entities[i]; - if (flags & EcsEntityIsId) { - if ((idr = flecs_id_record_get(world, e))) { - flecs_id_mark_for_delete(world, idr, - ECS_ID_ON_DELETE(idr->flags), true); - } - if ((idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)))) { - flecs_id_mark_for_delete(world, idr, - ECS_ID_ON_DELETE(idr->flags), true); - } - } - if (flags & EcsEntityIsTarget) { - if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) { - flecs_id_mark_for_delete(world, idr, - ECS_ID_ON_DELETE_TARGET(idr->flags), true); - } - if ((idr = flecs_id_record_get(world, ecs_pair(EcsFlag, e)))) { - flecs_id_mark_for_delete(world, idr, - ECS_ID_ON_DELETE_TARGET(idr->flags), true); - } - } - } -} - -static -bool flecs_id_is_delete_target( - ecs_id_t id, - ecs_entity_t action) -{ - 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_entity_t flecs_get_delete_action( - ecs_table_t *table, - ecs_table_record_t *tr, - ecs_entity_t action, - bool delete_target) -{ - ecs_entity_t result = action; - if (!result && delete_target) { - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_id_t id = idr->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_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; - result = ECS_ID_ON_DELETE_TARGET(idrr->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 flecs_update_monitors_for_delete( - ecs_world_t *world, - ecs_id_t id) -{ - flecs_update_component_monitors(world, NULL, &(ecs_type_t){ - .array = (ecs_id_t[]){id}, - .count = 1 - }); -} - -static -void flecs_id_mark_for_delete( - ecs_world_t *world, - ecs_id_record_t *idr, - ecs_entity_t action, - bool delete_id) -{ - if (idr->flags & EcsIdMarkedForDelete) { - return; - } - - idr->flags |= EcsIdMarkedForDelete; - flecs_marked_id_push(world, idr, action, delete_id); - - ecs_id_t id = idr->id; - - bool delete_target = flecs_id_is_delete_target(id, action); - - /* Mark all tables with the id for delete */ - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr->cache, &it)) { - 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); - } - } - } - - /* Same for empty tables */ - if (flecs_table_cache_empty_iter(&idr->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - tr->hdr.table->flags |= EcsTableMarkedForDelete; - } - } - - /* Signal query cache monitors */ - flecs_update_monitors_for_delete(world, id); - - /* If id is a wildcard pair, update cache monitors for non-wildcard ids */ - if (ecs_id_is_wildcard(id)) { - ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *cur = idr; - if (ECS_PAIR_SECOND(id) == EcsWildcard) { - while ((cur = cur->first.next)) { - flecs_update_monitors_for_delete(world, cur->id); - } - } else { - ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - while ((cur = cur->second.next)) { - flecs_update_monitors_for_delete(world, cur->id); - } - } - } -} - -static -bool flecs_on_delete_mark( - ecs_world_t *world, - ecs_id_t id, - ecs_entity_t action, - bool delete_id) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - /* If there's no id record, there's nothing to delete */ - return false; - } - - 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(idr->flags); - } - } - - if (action == EcsPanic) { - /* This id is protected from deletion */ - flecs_throw_invalid_delete(world, id); - return false; - } - - flecs_id_mark_for_delete(world, idr, action, delete_id); - - return true; -} - -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; - - /* 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_id_record_t *idr = ids[i].idr; - - if (!(tr = flecs_id_record_get_table(idr, dst_table))) { - continue; - } - - t = tr->index; - - 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, idr->id, NULL)) != -1); - } - - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - - if (!dst_table->type.count) { - /* If this removes all components, clear table */ - flecs_table_clear_entities(world, table); - } else { - /* Otherwise, merge table into dst_table */ - 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); - flecs_notify_on_remove(world, table, NULL, 0, table_count, &td); - ecs_log_pop_3(); - } - - flecs_table_merge(world, dst_table, table); - } - } - - flecs_table_diff_builder_fini(world, &diff); -} - -static -bool flecs_on_delete_clear_tables( - ecs_world_t *world) -{ - /* 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_id_record_t *idr = ids[i].idr; - ecs_entity_t action = ids[i].action; - - /* Empty all tables for id */ - { - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - - 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); - flecs_table_delete_entities(world, table); - } - } - } - } - - /* Run commands so children get notified before parent is deleted */ - if (world->stages[0]->defer) { - flecs_defer_end(world, world->stages[0]); - flecs_defer_begin(world, world->stages[0]); - } - - /* 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 -bool flecs_on_delete_clear_ids( - ecs_world_t *world) -{ - 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; - - 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_id_record_t *idr = ids[i].idr; - bool delete_id = ids[i].delete_id; - - flecs_id_record_release_tables(world, idr); - - /* Release the claim taken by flecs_marked_id_push. This may delete the - * id record as all other claims may have been released. */ - int32_t rc = flecs_id_record_release(world, idr); - 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_id_record_release(world, idr); - } else { - /* If id should not be deleted, unmark id record for deletion. This - * happens when all instances *of* an id are deleted, for example - * when calling ecs_remove_all or ecs_delete_with. */ - idr->flags &= ~EcsIdMarkedForDelete; - } - } - } - } while (-- twice); - - return true; -} - -static -void flecs_on_delete( - ecs_world_t *world, - ecs_id_t id, - ecs_entity_t action, - bool delete_id) -{ - /* 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); - - /* 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(); - - /* Empty tables with all the to be deleted ids */ - flecs_on_delete_clear_tables(world); - - /* Release remaining references to the ids */ - flecs_on_delete_clear_ids(world); - - /* Verify deleted ids are no longer in use */ -#ifdef FLECS_DEBUG - ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); - count = ecs_vec_count(&world->store.marked_ids); - for (i = 0; i < count; i ++) { - ecs_assert(!ecs_id_in_use(world, ids[i].id), - ECS_INTERNAL_ERROR, NULL); - } -#endif - ecs_assert(!ecs_id_in_use(world, id), ECS_INTERNAL_ERROR, NULL); - - /* 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(); - } -} - -void ecs_delete_with( - ecs_world_t *world, - ecs_id_t id) -{ - 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; - } - - flecs_on_delete(world, id, EcsDelete, false); - flecs_defer_end(world, stage); - - flecs_journal_end(); -} - -void ecs_remove_all( - ecs_world_t *world, - ecs_id_t id) -{ - flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { - return; - } - - flecs_on_delete(world, id, EcsRemove, false); - flecs_defer_end(world, stage); - - flecs_journal_end(); -} - -void ecs_delete( - 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_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_delete(stage, entity)) { - return; - } - - ecs_os_perf_trace_push("flecs.delete"); - - ecs_record_t *r = flecs_entities_try(world, entity); - if (r) { - flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); - - 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); - flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true); - r->idr = NULL; - } - if (row_flags & EcsEntityIsId) { - flecs_on_delete(world, entity, 0, true); - flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true); - } - if (row_flags & EcsEntityIsTraversable) { - table = r->table; - if (table) { - flecs_table_traversable_add(table, -1); - } - } - /* Merge operations before deleting entity */ - flecs_defer_end(world, stage); - flecs_defer_begin(world, stage); - } - - table = r->table; - - if (table) { - ecs_table_diff_t diff = { - .removed = table->type, - .removed_flags = table->flags & EcsTableRemoveEdgeFlags - }; - - flecs_delete_entity(world, r, &diff); - - r->row = 0; - r->table = NULL; - } - - flecs_entities_remove(world, entity); - - flecs_journal_end(); - } - - flecs_defer_end(world, stage); -error: - ecs_os_perf_trace_pop("flecs.delete"); - return; -} - -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_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - flecs_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_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id) || ecs_id_is_wildcard(id), - ECS_INVALID_PARAMETER, NULL); - flecs_remove_id(world, entity, id); -error: - return; -} - -void ecs_auto_override_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - ecs_add_id(world, entity, ECS_AUTO_OVERRIDE | id); -error: - return; -} - -ecs_entity_t ecs_clone( - ecs_world_t *world, - ecs_entity_t dst, - ecs_entity_t src, - bool copy_value) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(src != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(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(world); - } - - if (flecs_defer_clone(stage, dst, src, copy_value)) { - return dst; - } - - 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; - if (!src_table) { - goto done; - } - - 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)); - } - - ecs_type_t dst_type = dst_table->type; - ecs_table_diff_t diff = { - .added = dst_type, - .added_flags = dst_table->flags & EcsTableAddEdgeFlags - }; - ecs_record_t *dst_r = flecs_entities_get(world, dst); - /* Note 'ctor' parameter below is set to 'false' if the value will be copied, since flecs_table_move - will call a copy constructor */ - flecs_new_entity(world, dst, dst_r, dst_table, &diff, !copy_value, 0); - int32_t row = ECS_RECORD_TO_ROW(dst_r->row); - - if (copy_value) { - flecs_table_move(world, dst, src, dst_table, - row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); - int32_t i, count = dst_table->column_count; - for (i = 0; i < count; i ++) { - ecs_id_t id = flecs_column_id(dst_table, i); - ecs_type_t type = { - .array = &id, - .count = 1 - }; - flecs_notify_on_set(world, dst_table, row, 1, &type, true); - } - } - -done: - flecs_defer_end(world, stage); - return dst; -error: - return 0; -} - -#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));\ - } else if (column_index < 0) {\ - column_index = flecs_ito(int16_t, -column_index - 1);\ - const ecs_table_record_t *tr = &table->_->records[column_index];\ - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache;\ - if (idr->flags & EcsIdIsSparse) {\ - return flecs_sparse_get_any(idr->sparse, 0, entity);\ - }\ - } - -const void* ecs_get_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_table_t *table = r->table; - if (!table) { - return NULL; - } - - if (id < FLECS_HI_COMPONENT_ID) { - ecs_get_low_id(table, r, id); - if (!(table->flags & EcsTableHasIsA)) { - return NULL; - } - } - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; - } - - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return flecs_get_base_component(world, table, id, idr, 0); - } else { - if (idr->flags & EcsIdIsSparse) { - return flecs_sparse_get_any(idr->sparse, 0, entity); - } - ecs_check(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); - } - - int32_t row = ECS_RECORD_TO_ROW(r->row); - return flecs_table_get_component(table, tr->column, row).ptr; -error: - return NULL; -} - -void* ecs_get_mut_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_table_t *table = r->table; - if (!table) { - return NULL; - } - - if (id < FLECS_HI_COMPONENT_ID) { - ecs_get_low_id(table, r, id); - return NULL; - } - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - int32_t row = ECS_RECORD_TO_ROW(r->row); - return flecs_get_component_ptr(table, row, idr).ptr; -error: - return NULL; -} - -void* ecs_ensure_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_alive(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_cmd(stage)) { - return flecs_defer_set( - world, stage, EcsCmdEnsure, entity, id, 0, NULL, NULL); - } - - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - void *result = flecs_ensure(world, entity, id, r).ptr; - ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); - - flecs_defer_end(world, stage); - return result; -error: - return NULL; -} - -void* ecs_ensure_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_alive(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); - ecs_check(flecs_defer_cmd(stage), ECS_INVALID_PARAMETER, NULL); - - return flecs_defer_set(world, stage, EcsCmdSet, entity, id, 0, NULL, NULL); -error: - return NULL; -} - -static -ecs_record_t* flecs_access_begin( - ecs_world_t *stage, - ecs_entity_t entity, - bool write) -{ - ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); - - 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_table_t *table; - if (!(table = r->table)) { - return NULL; - } - - int32_t count = ecs_os_ainc(&table->_->lock); - (void)count; - if (write) { - ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); - } - - return r; -error: - return NULL; -} - -static -void flecs_access_end( - const ecs_record_t *r, - bool write) -{ - 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); - -error: - return; -} - -ecs_record_t* ecs_write_begin( - ecs_world_t *world, - ecs_entity_t entity) -{ - return flecs_access_begin(world, entity, true); -} - -void ecs_write_end( - ecs_record_t *r) -{ - flecs_access_end(r, true); -} - -const ecs_record_t* ecs_read_begin( - ecs_world_t *world, - ecs_entity_t entity) -{ - return flecs_access_begin(world, entity, false); -} - -void ecs_read_end( - const ecs_record_t *r) -{ - flecs_access_end(r, false); -} - -ecs_entity_t ecs_record_get_entity( - const ecs_record_t *record) -{ - ecs_check(record != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_table_t *table = record->table; - if (!table) { - return 0; - } - - return table->data.entities[ECS_RECORD_TO_ROW(record->row)]; -error: - return 0; -} - -const void* ecs_record_get_id( - const ecs_world_t *stage, - const ecs_record_t *r, - ecs_id_t id) -{ - const ecs_world_t *world = ecs_get_world(stage); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - return flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), idr); -} - -bool ecs_record_has_id( - ecs_world_t *stage, - const ecs_record_t *r, - ecs_id_t id) -{ - const ecs_world_t *world = ecs_get_world(stage); - if (r->table) { - return ecs_table_has_id(world, r->table, id); - } - return false; -} - -void* ecs_record_ensure_id( - ecs_world_t *stage, - ecs_record_t *r, - ecs_id_t id) -{ - const ecs_world_t *world = ecs_get_world(stage); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - return flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), idr); -} - -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); - - ecs_record_t *record = flecs_entities_get(world, entity); - ecs_check(record != NULL, ECS_INVALID_PARAMETER, - "cannot create ref for empty entity"); - - ecs_ref_t result = { - .entity = entity, - .id = id, - .record = record - }; - - ecs_table_t *table = record->table; - if (table) { - result.tr = flecs_table_record_get(world, table, id); - result.table_id = table->id; - } - - return result; -error: - return (ecs_ref_t){0}; -} - -static -bool flecs_ref_needs_sync( - ecs_ref_t *ref, - ecs_table_record_t *tr, - const ecs_table_t *table) -{ - ecs_assert(ref != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return !tr || ref->table_id != table->id || tr->hdr.table != table; -} - -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_record_t *r = ref->record; - ecs_table_t *table = r->table; - if (!table) { - return; - } - - ecs_table_record_t *tr = ref->tr; - if (flecs_ref_needs_sync(ref, tr, table)) { - tr = ref->tr = flecs_table_record_get(world, table, ref->id); - if (!tr) { - return; - } - - ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); - ref->table_id = table->id; - } -error: - return; -} - -void* ecs_ref_get_id( - const ecs_world_t *world, - ecs_ref_t *ref, - ecs_id_t id) -{ - 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, "ref not initialized"); - - ecs_record_t *r = ref->record; - ecs_table_t *table = r->table; - if (!table) { - return NULL; - } - - int32_t row = ECS_RECORD_TO_ROW(r->row); - ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); - - ecs_table_record_t *tr = ref->tr; - if (flecs_ref_needs_sync(ref, tr, table)) { - tr = ref->tr = flecs_table_record_get(world, table, id); - if (!tr) { - return NULL; - } - - ref->table_id = table->id; - ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); - } - - int32_t column = tr->column; - if (column == -1) { - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & EcsIdIsSparse) { - return flecs_sparse_get_any(idr->sparse, 0, ref->entity); - } - } - return flecs_table_get_component(table, column, row).ptr; -error: - return NULL; -} - -void* ecs_emplace_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - bool *is_new) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(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_cmd(stage)) { - return flecs_defer_set( - world, stage, EcsCmdEmplace, entity, id, 0, NULL, is_new); - } - - ecs_check(is_new || !ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, - "cannot emplace a component the entity already has"); - - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table = r->table; - flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); - flecs_defer_end(world, stage); - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - void *ptr = flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), idr); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, - "emplaced component was removed during operation, make sure to not " - "remove component T in on_add(T) hook/OnAdd(T) observer"); - - if (is_new) { - *is_new = table != r->table; - } - - return ptr; -error: - return NULL; -} - -static -void flecs_modified_id_if( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - bool owned) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(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_modified(stage, entity, id)) { - return; - } - - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table = r->table; - if (!table || !flecs_table_record_get(world, table, id)) { - flecs_defer_end(world, stage); - return; - } - - ecs_type_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, owned); - - flecs_table_mark_dirty(world, table, id); - flecs_defer_end(world, stage); -error: - return; -} - -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_alive(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_modified(stage, entity, id)) { - return; - } - - /* 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_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table = r->table; - ecs_type_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); - - flecs_table_mark_dirty(world, table, id); - flecs_defer_end(world, stage); -error: - return; -} - -static -void flecs_set_id_copy( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id, - size_t size, - void *ptr) -{ - if (flecs_defer_cmd(stage)) { - flecs_defer_set(world, stage, EcsCmdSet, entity, id, - flecs_utosize(size), ptr, NULL); - return; - } - - ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); - - flecs_copy_id(world, r, id, size, dst.ptr, ptr, dst.ti); - - flecs_defer_end(world, stage); -} - -static -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) -{ - if (flecs_defer_cmd(stage)) { - flecs_defer_set(world, stage, cmd_kind, entity, id, - flecs_utosize(size), ptr, NULL); - return; - } - - ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); - ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); - - const ecs_type_info_t *ti = dst.ti; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - 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)); - } - - flecs_table_mark_dirty(world, r->table, id); - - if (cmd_kind == EcsCmdSet) { - ecs_table_t *table = r->table; - if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { - ecs_type_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set( - world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); - } - } - - flecs_defer_end(world, stage); -error: - return; -} - -void ecs_set_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - size_t size, - const void *ptr) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(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); - - /* Safe to cast away const: function won't modify if move arg is false */ - flecs_set_id_copy(world, stage, entity, id, size, - ECS_CONST_CAST(void*, ptr)); -error: - return; -} - -#if defined(FLECS_DEBUG) || defined(FLECS_KEEP_ASSERT) -static -bool flecs_can_toggle( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return false; - } - - return (idr->flags & EcsIdCanToggle) != 0; -} -#endif - -void ecs_enable_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - bool enable) -{ - 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); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - ecs_check(flecs_can_toggle(world, id), ECS_INVALID_OPERATION, - "add CanToggle trait to component"); - - ecs_entity_t bs_id = id | ECS_TOGGLE; - ecs_add_id(world, entity, bs_id); - - if (flecs_defer_enable(stage, entity, id, enable)) { - return; - } - - 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); - - ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); - index -= table->_->bs_offset; - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); - - /* 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); - - flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); - - flecs_defer_end(world, stage); -error: - return; -} - -bool ecs_is_enabled_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); - - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = r->table; - if (!table) { - return false; - } - - ecs_entity_t bs_id = id | 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, id); - } - - 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 flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); -error: - return false; -} - -bool ecs_has_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); - - /* 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; - if (!table) { - return false; - } - - if (id < FLECS_HI_COMPONENT_ID) { - if (table->component_map[id] != 0) { - return true; - } - } else { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (tr) { - return true; - } - } - } - - if (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { - return false; - } - - if (ECS_IS_PAIR(id) && (table->flags & EcsTableHasUnion)) { - ecs_id_record_t *u_idr = flecs_id_record_get(world, - ecs_pair(ECS_PAIR_FIRST(id), EcsUnion)); - if (u_idr && u_idr->flags & EcsIdIsUnion) { - uint64_t cur = flecs_switch_get(u_idr->sparse, (uint32_t)entity); - return (uint32_t)cur == ECS_PAIR_SECOND(id); - } - } - - ecs_table_record_t *tr; - int32_t column = ecs_search_relation(world, table, 0, id, - EcsIsA, 0, 0, 0, &tr); - if (column == -1) { - return false; - } - - return true; -error: - return false; -} - -bool ecs_owns_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); - - /* 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; - if (!table) { - return false; - } - - if (id < FLECS_HI_COMPONENT_ID) { - return table->component_map[id] != 0; - } else { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (tr) { - return true; - } - } - } - -error: - return false; -} - -ecs_entity_t ecs_get_target( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel, - int32_t index) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = r->table; - if (!table) { - goto not_found; - } - - ecs_id_t wc = ecs_pair(rel, EcsWildcard); - ecs_id_record_t *idr = flecs_id_record_get(world, wc); - const ecs_table_record_t *tr = NULL; - if (idr) { - tr = flecs_id_record_get_table(idr, table); - } - if (!tr) { - if (!idr || (idr->flags & EcsIdOnInstantiateInherit)) { - goto look_in_base; - } else { - return 0; - } - } - - if (index >= tr->count) { - index -= tr->count; - goto look_in_base; - } - - ecs_entity_t result = - ecs_pair_second(world, table->type.array[tr->index + index]); - - if (result == EcsUnion) { - wc = ecs_pair(rel, EcsUnion); - ecs_id_record_t *wc_idr = flecs_id_record_get(world, wc); - ecs_assert(wc_idr != NULL, ECS_INTERNAL_ERROR, NULL); - result = flecs_switch_get(wc_idr->sparse, (uint32_t)entity); - } - - return result; -look_in_base: - if (table->flags & EcsTableHasIsA) { - ecs_table_record_t *tr_isa = flecs_id_record_get_table( - world->idr_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; - } - } - } - -not_found: -error: - return 0; -} - -ecs_entity_t ecs_get_parent( - const ecs_world_t *world, - ecs_entity_t entity) -{ - return ecs_get_target(world, entity, EcsChildOf, 0); -} - -ecs_entity_t ecs_get_target_for_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - - if (!id) { - return ecs_get_target(world, entity, rel, 0); - } - - world = ecs_get_world(world); - - 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, &subject, 0, 0); - if (column == -1) { - return 0; - } - } else { - entity = 0; /* Don't return entity if id was not found */ - - if (table) { - ecs_id_t *ids = table->type.array; - int32_t i, count = table->type.count; - - 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, id)) { - subject = ent; - break; - } - } - } - } - - if (subject == 0) { - return entity; - } else { - return subject; - } -error: - return 0; -} - -int32_t ecs_get_depth( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel) -{ - ecs_check(ecs_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)"); - - ecs_table_t *table = ecs_get_table(world, entity); - if (table) { - return ecs_table_get_depth(world, table, rel); - } - - return 0; -error: - return -1; -} - -static -const char* flecs_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_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; -} - -const char* ecs_get_name( - const ecs_world_t *world, - ecs_entity_t entity) -{ - return flecs_get_identifier(world, entity, EcsName); -} - -const char* ecs_get_symbol( - const ecs_world_t *world, - ecs_entity_t entity) -{ - 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; - } -} - -static -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(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); - - 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); - } - - 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(world, { - .name = name - }); - } - - ecs_stage_t *stage = flecs_stage_from_world(&world); - flecs_set_identifier(world, stage, entity, EcsName, name); - - return entity; -} - -ecs_entity_t ecs_set_symbol( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); -} - -void ecs_set_alias( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - flecs_set_identifier(world, NULL, entity, EcsAlias, name); -} - -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); -} - -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; - } - - /* Entity identifiers should not contain flag bits */ - if (entity & ECS_ID_FLAGS_MASK) { - return false; - } - - /* Entities should not contain data in dead zone bits */ - if (entity & ~0xFF00FFFFFFFFFFFF) { - return false; - } - - /* If id exists, it must be alive (the generation count must match) */ - return ecs_is_alive(world, entity); -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_ID_FLAGS_MASK)) { - e &= ~ECS_GENERATION_MASK; - } - - return e; -} - -bool ecs_is_alive( - 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); - - return flecs_entities_is_alive(world, entity); -error: - return false; -} - -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; - } - - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); - - if (flecs_entities_is_alive(world, entity)) { - return entity; - } - - /* 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; - } - - ecs_entity_t current = flecs_entities_get_alive(world, entity); - if (!current || !flecs_entities_is_alive(world, current)) { - return 0; - } - - return current; -error: - return 0; -} - -void ecs_make_alive( - ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - - /* Const cast is safe, function checks for threading */ - world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); - - /* 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 make entity alive while world is in multithreaded mode"); - - /* Check if a version of the provided id is alive */ - ecs_entity_t any = ecs_get_alive(world, (uint32_t)entity); - if (any == entity) { - /* If alive and equal to the argument, there's nothing left to do */ - return; - } - - /* If the id is currently alive but did not match the argument, fail */ - ecs_check(!any, ECS_INVALID_PARAMETER, - "entity is alive with different generation"); - - /* 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. */ - flecs_entities_ensure(world, entity); -error: - return; -} - -void ecs_make_alive_id( - ecs_world_t *world, - ecs_id_t id) -{ - flecs_poly_assert(world, ecs_world_t); - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t r = ECS_PAIR_FIRST(id); - ecs_entity_t o = ECS_PAIR_SECOND(id); - - ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(o != 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"); - flecs_entities_ensure(world, r); - } - if (flecs_entities_get_alive(world, o) == 0) { - ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER, - "second element of pair is not alive"); - flecs_entities_ensure(world, o); - } - } else { - flecs_entities_ensure(world, id & ECS_COMPONENT_MASK); - } -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); - - world = ecs_get_world(world); - - return flecs_entities_exists(world, entity); -error: - return false; -} - -void ecs_set_version( - ecs_world_t *world, - ecs_entity_t entity_with_generation) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, - "cannot change entity generation when world is in readonly mode"); - ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, - "cannot change entity generation while world is deferred"); - - flecs_entities_make_alive(world, entity_with_generation); - - 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; - } - } -} - -ecs_table_t* ecs_get_table( - const ecs_world_t *world, - ecs_entity_t entity) -{ - 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_record_t *record = flecs_entities_get(world, entity); - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - return record->table; -error: - return NULL; -} - -const 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; - } - - return NULL; -} - -const ecs_type_info_t* ecs_get_type_info( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr && ECS_IS_PAIR(id)) { - idr = flecs_id_record_get(world, - ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); - if (!idr || !idr->type_info) { - idr = NULL; - } - if (!idr) { - ecs_entity_t first = ecs_pair_first(world, id); - if (!first || !ecs_has_id(world, first, EcsPairIsTag)) { - idr = flecs_id_record_get(world, - ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); - if (!idr || !idr->type_info) { - idr = NULL; - } - } - } - } - - if (idr) { - return idr->type_info; - } else if (!(id & ECS_ID_FLAGS_MASK)) { - return flecs_type_info_get(world, id); - } -error: - return NULL; -} - -ecs_entity_t ecs_get_typeid( - const ecs_world_t *world, - ecs_id_t id) -{ - 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; -} - -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 Tag property */ - if (ECS_HAS_ID_FLAG(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, 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; - } - } - - return false; -} - -int32_t ecs_count_id( - const ecs_world_t *world, - ecs_entity_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!id) { - return 0; - } - - int32_t count = 0; - ecs_iter_t it = ecs_each_id(world, id); - while (ecs_each_next(&it)) { - count += it.count; - } - - return count; -error: - return 0; -} - -void ecs_enable( - ecs_world_t *world, - ecs_entity_t entity, - bool enabled) -{ - 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 id = ids[i]; - if (!(ecs_id_get_flags(world, id) & EcsIdOnInstantiateDontInherit)){ - ecs_enable(world, id, enabled); - } - } - } else { - if (enabled) { - ecs_remove_id(world, entity, EcsDisabled); - } else { - ecs_add_id(world, entity, EcsDisabled); - } - } -} - -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; -} - -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; -} - -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; -} - -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; -} - -const char* ecs_id_flag_str( - ecs_entity_t entity) -{ - 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 "UNKNOWN"; - } -} - -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); - - world = ecs_get_world(world); - - if (ECS_HAS_ID_FLAG(id, TOGGLE)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); - ecs_strbuf_appendch(buf, '|'); - } - - if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AUTO_OVERRIDE)); - ecs_strbuf_appendch(buf, '|'); - } - - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t obj = ECS_PAIR_SECOND(id); - - ecs_entity_t e; - if ((e = ecs_get_alive(world, rel))) { - rel = e; - } - if ((e = ecs_get_alive(world, obj))) { - obj = e; - } - - ecs_strbuf_appendch(buf, '('); - ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf, false); - ecs_strbuf_appendch(buf, ','); - ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf, false); - 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); - } - -error: - return; -} - -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); -} - -static -void ecs_type_str_buf( - const ecs_world_t *world, - const ecs_type_t *type, - ecs_strbuf_t *buf) -{ - ecs_entity_t *ids = type->array; - int32_t i, count = type->count; - - for (i = 0; i < count; i ++) { - ecs_entity_t id = ids[i]; - - if (i) { - ecs_strbuf_appendch(buf, ','); - ecs_strbuf_appendch(buf, ' '); - } - - if (id == 1) { - ecs_strbuf_appendlit(buf, "Component"); - } else { - ecs_id_str_buf(world, id, buf); - } - } -} - -char* ecs_type_str( - const ecs_world_t *world, - const ecs_type_t *type) -{ - if (!type) { - return ecs_os_strdup(""); - } - - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_type_str_buf(world, type, &buf); - return ecs_strbuf_get(&buf); -} - -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; - } -} - -char* ecs_entity_str( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - - 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, ']'); - - return ecs_strbuf_get(&buf); -error: - return NULL; -} - -static -void flecs_flush_bulk_new( - ecs_world_t *world, - ecs_cmd_t *cmd) -{ - ecs_entity_t *entities = cmd->is._n.entities; - - if (cmd->id) { - int i, count = cmd->is._n.count; - for (i = 0; i < count; i ++) { - flecs_entities_ensure(world, entities[i]); - flecs_add_id(world, entities[i], cmd->id); - } - } - - ecs_os_free(entities); -} - -static -void flecs_dtor_value( - ecs_world_t *world, - ecs_id_t id, - void *value, - int32_t count) -{ - 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) { - 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); - } - } -} - -static -void flecs_free_cmd_event( - ecs_world_t *world, - ecs_event_desc_t *desc) -{ - if (desc->ids) { - flecs_stack_free_n(desc->ids->array, ecs_id_t, desc->ids->count); - } - - /* 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); - - if (desc->param) { - flecs_dtor_value(world, desc->event, - /* Safe const cast, command makes copy of value */ - ECS_CONST_CAST(void*, desc->param), 1); - } -} - -static -void flecs_discard_cmd( - ecs_world_t *world, - ecs_cmd_t *cmd) -{ - 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, 1); - flecs_stack_free(value, cmd->is._1.size); - } - } -} - -static -bool flecs_remove_invalid( - ecs_world_t *world, - ecs_id_t id, - ecs_id_t *id_out) -{ - 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 obj = ECS_PAIR_SECOND(id); - if (!flecs_entities_is_valid(world, obj)) { - /* Check the relationship's policy for deleted objects */ - ecs_id_record_t *idr = flecs_id_record_get(world, - ecs_pair(rel, EcsWildcard)); - if (idr) { - ecs_entity_t action = ECS_ID_ON_DELETE_TARGET(idr->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; -} - -static -ecs_table_t* flecs_cmd_batch_add_diff( - ecs_world_t *world, - ecs_table_t *dst, - ecs_table_t *cur, - ecs_table_t *prev) -{ - int32_t p = 0, p_count = prev->type.count; - int32_t c = 0, c_count = cur->type.count; - ecs_id_t *p_ids = prev->type.array; - ecs_id_t *c_ids = cur->type.array; - - for (; c < c_count;) { - ecs_id_t c_id = c_ids[c]; - ecs_id_t p_id = p_ids[p]; - - if (p_id < c_id) { - /* Previous id no longer exists in new table, so it was removed */ - dst = ecs_table_remove_id(world, dst, p_id); - } - if (c_id < p_id) { - /* Current id didn't exist in previous table, so it was added */ - dst = ecs_table_add_id(world, dst, c_id); - } - - c += c_id <= p_id; - p += p_id <= c_id; - if (p == p_count) { - break; - } - } - - /* Remainder */ - for (; p < p_count; p ++) { - ecs_id_t p_id = p_ids[p]; - dst = ecs_table_remove_id(world, dst, p_id); - } - for (; c < c_count; c ++) { - ecs_id_t c_id = c_ids[c]; - dst = ecs_table_add_id(world, dst, c_id); - } - - return dst; -} - -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_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table = NULL; - if (r) { - table = r->table; - } - - world->info.cmd.batched_entity_count ++; - - ecs_table_t *start_table = table; - ecs_cmd_t *cmd; - int32_t next_for_entity; - ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ - int32_t cur = start; - ecs_id_t id; - - /* Mask to let observable implementation know which components were set. - * This prevents the code from overwriting components with an override if - * the batch also contains an IsA pair. */ - ecs_flags64_t set_mask = 0; - - do { - cmd = &cmds[cur]; - 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; - } - - /* 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) { - 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_cmd_kind_t kind = cmd->kind; - switch(kind) { - case EcsCmdAddModified: - /* Add is batched, but keep Modified */ - cmd->kind = EcsCmdModified; - - /* fall through */ - case EcsCmdAdd: - table = flecs_find_table_add(world, table, id, diff); - world->info.cmd.batched_command_count ++; - break; - case EcsCmdModified: - if (start_table) { - /* If a modified was inserted for an existing component, the value - * of the component could have been changed. If this is the case, - * call on_set hooks before the OnAdd/OnRemove observers are invoked - * when moving the entity to a different table. - * This ensures that if OnAdd/OnRemove observers access the modified - * component value, the on_set hook has had the opportunity to - * run first to set any computed values of the component. */ - int32_t row = ECS_RECORD_TO_ROW(r->row); - ecs_id_record_t *idr = flecs_id_record_get(world, cmd->id); - const ecs_table_record_t *tr = - flecs_id_record_get_table(idr, start_table); - if (tr) { - const ecs_type_info_t *ti = idr->type_info; - ecs_iter_action_t on_set; - if ((on_set = ti->hooks.on_set)) { - ecs_table_t *prev_table = r->table; - ecs_defer_begin(world); - flecs_invoke_hook(world, start_table, tr, 1, row, - &entity,cmd->id, ti, EcsOnSet, on_set); - ecs_defer_end(world); - - /* Don't run on_set hook twice, but make sure to still - * invoke observers. */ - cmd->kind = EcsCmdModifiedNoHook; - - /* If entity changed tables in hook, add difference to - * destination table, so we don't lose the side effects - * of the hook. */ - if (r->table != prev_table) { - table = flecs_cmd_batch_add_diff( - world, table, r->table, prev_table); - } - } - } - } - break; - case EcsCmdSet: - case EcsCmdEnsure: { - ecs_id_t *ids = diff->added.array; - uint8_t added_index = flecs_ito(uint8_t, diff->added.count); - - ecs_table_t *next = flecs_find_table_add(world, table, id, diff); - if (next != table) { - table = next; - if (diff->added.count == (added_index + 1)) { - /* Single id was added, must be at the end of the array */ - ecs_assert(ids[added_index] == id, ECS_INTERNAL_ERROR, NULL); - set_mask |= (1llu << added_index); - } else { - /* Id was already added or multiple ids got added. Do a linear - * search to find the index we need to set the set_mask. */ - int32_t i; - for (i = 0; i < diff->added.count; i ++) { - if (ids[i] == id) { - break; - } - } - - ecs_assert(i != diff->added.count, ECS_INTERNAL_ERROR, NULL); - set_mask |= (1llu << i); - } - } - - world->info.cmd.batched_command_count ++; - 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 ++; - break; - case EcsCmdClear: - if (table) { - 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 ++; - break; - case EcsCmdClone: - case EcsCmdBulkNew: - case EcsCmdPath: - case EcsCmdDelete: - case EcsCmdOnDeleteAction: - case EcsCmdEnable: - case EcsCmdDisable: - case EcsCmdEvent: - case EcsCmdSkip: - case EcsCmdModifiedNoHook: - break; - } - - /* Add, remove and clear operations can be skipped since they have no - * side effects besides adding/removing components */ - if (kind == EcsCmdAdd || kind == EcsCmdRemove || kind == EcsCmdClear) { - cmd->kind = EcsCmdSkip; - } - } while ((cur = next_for_entity)); - - /* 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 && (table->flags & EcsTableHasSparse) && 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 (set_mask) { - cur = start; - do { - cmd = &cmds[cur]; - next_for_entity = cmd->next_for_entity; - if (next_for_entity < 0) { - next_for_entity *= -1; - } - - switch(cmd->kind) { - case EcsCmdSet: - case EcsCmdEnsure: { - flecs_component_ptr_t ptr = {0}; - if (r->table) { - ecs_id_record_t *idr = flecs_id_record_get(world, cmd->id); - ptr = flecs_get_component_ptr( - r->table, ECS_RECORD_TO_ROW(r->row), idr); - } - - /* 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 (ptr.ptr) { - const ecs_type_info_t *ti = ptr.ti; - ecs_move_t move = ti->hooks.move; - if (move) { - move(ptr.ptr, cmd->is._1.value, 1, ti); - ecs_xtor_t dtor = ti->hooks.dtor; - if (dtor) { - dtor(cmd->is._1.value, 1, ti); - } - } else { - ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); - } - - flecs_stack_free(cmd->is._1.value, 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 EcsCmdClone: - case EcsCmdBulkNew: - case EcsCmdAdd: - case EcsCmdRemove: - 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)); - } - - if (added.count && r->table) { - ecs_table_diff_t add_diff = ECS_TABLE_DIFF_INIT; - add_diff.added = added; - add_diff.added_flags = diff->added_flags; - - flecs_defer_begin(world, world->stages[0]); - flecs_notify_on_add(world, r->table, start_table, - ECS_RECORD_TO_ROW(r->row), 1, &add_diff, 0, set_mask, true, false); - flecs_defer_end(world, world->stages[0]); - if (r->row & EcsEntityIsTraversable) { - /* Update monitors since we didn't do this in flecs_commit. */ - flecs_update_component_monitors(world, &added, NULL); - } - } - - diff->added.array = added.array; - diff->added.count = added.count; - - flecs_table_diff_builder_clear(diff); -} - -/* Leave safe section. Run all deferred commands. */ -bool flecs_defer_end( - ecs_world_t *world, - ecs_stage_t *stage) -{ - flecs_poly_assert(world, ecs_world_t); - flecs_poly_assert(stage, ecs_stage_t); - - 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_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); - - if (!--stage->defer && !stage->cmd_flushing) { - ecs_os_perf_trace_push("flecs.commands.merge"); - - /* 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; - } - - do { - ecs_stage_t *dst_stage = flecs_stage_from_world(&world); - ecs_commands_t *commands = stage->cmd; - ecs_vec_t *queue = &commands->queue; - - if (stage->cmd == &stage->cmd_stack[0]) { - stage->cmd = &stage->cmd_stack[1]; - } else { - stage->cmd = &stage->cmd_stack[0]; - } - - if (!ecs_vec_count(queue)) { - break; - } - - 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); - } - - ecs_cmd_t *cmds = ecs_vec_first(queue); - int32_t i, count = ecs_vec_count(queue); - - ecs_table_diff_builder_t diff; - flecs_table_diff_builder_init(world, &diff); - - 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)) { - /* Batch commands for entity to limit archetype moves */ - if (is_alive) { - 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; - } - - /* 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_id_t id = cmd->id; - - 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); - } - 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, &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; - } - 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); - 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); - } - } - - stage->cmd_flushing = false; - - flecs_stack_reset(&commands->stack); - ecs_vec_clear(queue); - flecs_table_diff_builder_fini(world, &diff); - - /* 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 false; -} - -/* Delete operations from queue without executing them. */ -bool flecs_defer_purge( - ecs_world_t *world, - ecs_stage_t *stage) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!--stage->defer) { - ecs_vec_t commands = stage->cmd->queue; - - 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]); - } - - ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); - - ecs_vec_clear(&commands); - flecs_stack_reset(&stage->cmd->stack); - flecs_sparse_clear(&stage->cmd->entries); - } - - return true; - } - -error: - return false; -} - -/** - * @file entity_name.c - * @brief Functions for working with named entities. - */ - -#include - -#define ECS_NAME_BUFFER_LENGTH (64) - -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) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_assert(sep[0] != 0, ECS_INVALID_PARAMETER, NULL); - - ecs_entity_t cur = 0; - const char *name = NULL; - ecs_size_t name_len = 0; - - if (child && ecs_is_alive(world, child)) { - ecs_record_t *r = flecs_entities_get(world, child); - bool hasName = false; - if (r && r->table) { - 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); - } - } - - const EcsIdentifier *id = ecs_get_pair( - world, child, EcsIdentifier, EcsName); - if (id) { - name = id->value; - name_len = id->length; - } - } - } - - 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)); - } - - return cur != 0; -} - -bool flecs_name_is_id( - const char *name) -{ - 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; -} - -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; -} - -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 0; -} - -static -bool flecs_is_sep( - const char **ptr, - const char *sep) -{ - ecs_size_t len = ecs_os_strlen(sep); - - if (!ecs_os_strncmp(*ptr, sep, len)) { - *ptr += len; - return true; - } else { - return false; - } -} - -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; - } - - const char *ptr; - char ch; - int32_t template_nesting = 0; - int32_t pos = 0; - ecs_size_t size = size_out ? *size_out : 0; - - 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); - - if (!template_nesting && flecs_is_sep(&ptr, sep)) { - break; - } - - 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; - } - - buffer[pos] = ch; - } - - pos ++; - } - - if (buffer) { - buffer[pos] = '\0'; - *buffer_out = buffer; - *size_out = size; - } - - if (pos || ptr[0]) { - return ptr; - } else { - return NULL; - } -error: - return NULL; -} - -static -bool flecs_is_root_path( - const char *path, - const char *prefix) -{ - if (prefix) { - return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); - } else { - return false; - } -} - -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_assert(error != NULL, ECS_INTERNAL_ERROR, NULL); - - bool start_from_root = false; - const char *path = *path_ptr; - - if (flecs_is_root_path(path, prefix)) { - path += ecs_os_strlen(prefix); - parent = 0; - start_from_root = true; - } - - 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 */ - } - - /* 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; - } - - start_from_root = true; - } - - if (!start_from_root && !parent && new_entity) { - parent = ecs_get_scope(world); - } - - *path_ptr = path; - - return parent; -} - -static -void flecs_on_set_symbol(ecs_iter_t *it) { - EcsIdentifier *n = ecs_field(it, EcsIdentifier, 0); - ecs_world_t *world = it->real_world; - - 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_bootstrap_hierarchy(ecs_world_t *world) { - ecs_observer(world, { - .entity = ecs_entity(world, { .parent = EcsFlecsInternals }), - .query.terms[0] = { - .id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol) - }, - .callback = flecs_on_set_symbol, - .events = {EcsOnSet}, - .yield_existing = true - }); -} - - -/* Public functions */ - -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) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - if (child == EcsWildcard) { - ecs_strbuf_appendch(buf, '*'); - return; - } - if (child == EcsAny) { - ecs_strbuf_appendch(buf, '_'); - return; - } - - if (!sep) { - sep = "."; - } - - if (!child || parent != child) { - flecs_path_append(world, parent, child, sep, prefix, buf, escape); - } else { - ecs_strbuf_appendstrn(buf, "", 0); - } - -error: - return; -} - -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) -{ - 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); -} - -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 (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; - } - } - - ecs_id_t pair = ecs_childof(parent); - ecs_hashmap_t *index = flecs_id_name_index_get(world, pair); - if (index) { - return flecs_name_index_find(index, name, 0, 0); - } else { - return 0; - } -error: - return 0; -} - -ecs_entity_t ecs_lookup( - const ecs_world_t *world, - const char *path) -{ - return ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true); -} - -ecs_entity_t ecs_lookup_symbol( - const ecs_world_t *world, - const char *name, - bool lookup_as_path, - bool recursive) -{ - if (!name) { - return 0; - } - - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); - - ecs_entity_t e = 0; - if (lookup_as_path) { - e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, recursive); - } - - if (!e) { - e = flecs_name_index_find(&world->symbols, name, 0, 0); - } - - return e; -error: - return 0; -} - -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) -{ - if (!path) { - return 0; - } - - 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; - } - - e = flecs_name_index_find(&world->aliases, path, 0, 0); - if (e) { - return e; - } - - 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 ++; - } - - if (!sep) { - sep = "."; - } - - bool error = false; - parent = flecs_get_parent_from_path( - stage, parent, &path, sep, prefix, true, &error); - if (error) { - return 0; - } - - if (parent && !(parent = ecs_get_alive(world, parent))) { - return 0; - } - - if (!path[0]) { - return parent; - } - - if (!sep[0]) { - return ecs_lookup_child(world, parent, path); - } - -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; - } - } - -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; - } - } - - 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 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); - - ecs_entity_t cur = stage->scope; - stage->scope = scope; - - return cur; -error: - return 0; -} - -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); - - /* Safe: application owns lookup path */ - ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); - stage->lookup_path = lookup_path; - - return cur; -error: - return NULL; -} - -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; -} - -static -void flecs_add_path( - ecs_world_t *world, - bool defer_suspend, - ecs_entity_t parent, - ecs_entity_t entity, - const char *name) -{ - 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); - } - - if (parent) { - ecs_add_pair(world, entity, EcsChildOf, parent); - } - - ecs_assert(name[0] != '#', ECS_INVALID_PARAMETER, - "path should not contain identifier with #"); - - ecs_set_name(world, entity, name); - - 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); - } -} - -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) -{ - 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); - } - - if (!sep) { - sep = "."; - } - - if (!path) { - if (!entity) { - entity = ecs_new(world); - } - - if (parent) { - ecs_add_pair(world, entity, EcsChildOf, entity); - } - - return entity; - } - - 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; - } - - 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; - - 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; - } - - 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, name); - } - - 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; -} - -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) -{ - return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); -} - -/** - * @file id.c - * @brief Id utilities. - */ - - -#ifdef FLECS_SCRIPT -#endif - -bool ecs_id_match( - ecs_id_t id, - ecs_id_t pattern) -{ - if (id == pattern) { - return true; - } - - if (ECS_HAS_ID_FLAG(pattern, PAIR)) { - if (!ECS_HAS_ID_FLAG(id, PAIR)) { - return false; - } - - 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); - - 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"); - - 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 (pattern_first == EcsWildcard) { - if (pattern_second == EcsWildcard || 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 ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { - return true; - } - } - -error: - return false; -} - -bool ecs_id_is_pair( - ecs_id_t id) -{ - return ECS_HAS_ID_FLAG(id, PAIR); -} - -bool ecs_id_is_wildcard( - ecs_id_t id) -{ - if ((id == EcsWildcard) || (id == EcsAny)) { - return true; - } - - bool is_pair = ECS_IS_PAIR(id); - if (!is_pair) { - return false; - } - - ecs_entity_t first = ECS_PAIR_FIRST(id); - ecs_entity_t second = ECS_PAIR_SECOND(id); - - return (first == EcsWildcard) || (second == EcsWildcard) || - (first == EcsAny) || (second == EcsAny); -} - -bool ecs_id_is_valid( - const ecs_world_t *world, - ecs_id_t id) -{ - if (!id) { - return false; - } - if (ecs_id_is_wildcard(id)) { - return false; - } - - if (ECS_HAS_ID_FLAG(id, PAIR)) { - if (!ECS_PAIR_FIRST(id)) { - return false; - } - if (!ECS_PAIR_SECOND(id)) { - return false; - } - } else if (id & ECS_ID_FLAGS_MASK) { - if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { - return false; - } - } - - return true; -} - -ecs_flags32_t ecs_id_get_flags( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - return idr->flags; - } else { - return 0; - } -} - -ecs_id_t ecs_id_from_str( - const ecs_world_t *world, - const char *expr) -{ -#ifdef FLECS_SCRIPT - 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_SCRIPT addon"); -#endif -} - -/** - * @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. - */ - -#include - -/* Utility macros to enforce consistency when initializing iterator fields */ - -/* If term count is smaller than cache size, initialize with inline array, - * otherwise allocate. */ -#define INIT_CACHE(it, stack, fields, f, T, count)\ - if (!it->f && (fields & flecs_iter_cache_##f) && count) {\ - it->f = flecs_stack_calloc_n(stack, T, count);\ - it->priv_.cache.used |= flecs_iter_cache_##f;\ - } - -/* If array is allocated, free it when finalizing the iterator */ -#define FINI_CACHE(it, f, T, count)\ - if (it->priv_.cache.used & flecs_iter_cache_##f) {\ - flecs_stack_free_n((void*)it->f, T, count);\ - } - -void* flecs_iter_calloc( - ecs_iter_t *it, - ecs_size_t size, - ecs_size_t align) -{ - 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); -} - -void flecs_iter_free( - void *ptr, - ecs_size_t size) -{ - flecs_stack_free(ptr, size); -} - -void flecs_iter_init( - const ecs_world_t *world, - ecs_iter_t *it, - ecs_flags8_t fields) -{ - ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INTERNAL_ERROR, NULL); - - 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_.cache.used = 0; - it->priv_.cache.allocated = 0; - it->priv_.cache.stack_cursor = flecs_stack_get_cursor(stack); - - INIT_CACHE(it, stack, fields, ids, ecs_id_t, it->field_count); - INIT_CACHE(it, stack, fields, sources, ecs_entity_t, it->field_count); - INIT_CACHE(it, stack, fields, trs, ecs_table_record_t*, it->field_count); - INIT_CACHE(it, stack, fields, variables, ecs_var_t, it->variable_count); -} - -void ecs_iter_fini( - ecs_iter_t *it) -{ - ECS_BIT_CLEAR(it->flags, EcsIterIsValid); - - if (it->fini) { - it->fini(it); - } - - ecs_world_t *world = it->world; - if (!world) { - return; - } - - FINI_CACHE(it, ids, ecs_id_t, it->field_count); - FINI_CACHE(it, sources, ecs_entity_t, it->field_count); - FINI_CACHE(it, trs, ecs_table_record_t*, it->field_count); - FINI_CACHE(it, variables, ecs_var_t, it->variable_count); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - flecs_stack_restore_cursor(&stage->allocators.iter_stack, - it->priv_.cache.stack_cursor); -} - -/* --- Public API --- */ - -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 || ecs_field_size(it, index) == size || - !ecs_field_size(it, index), - ECS_INVALID_PARAMETER, "mismatching size for field %d", index); - (void)size; - - 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_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert(!(idr->flags & EcsIdIsSparse), ECS_INVALID_OPERATION, - "use ecs_field_at to access fields for sparse components"); - (void)idr; - - 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_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - - int32_t column_index = tr->column; - ecs_assert(column_index != -1, ECS_NOT_A_COMPONENT, - "only components can be fetched with fields"); - ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); - - 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); - - if (!size) { - size = (size_t)column->ti->size; - } - - return ECS_ELEM(column->data, (ecs_size_t)size, row); -error: - return NULL; -} - -void* ecs_field_at_w_size( - const ecs_iter_t *it, - size_t size, - int8_t index, - int32_t row) -{ - 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); - - 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_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert((idr->flags & EcsIdIsSparse), ECS_INVALID_OPERATION, - "use ecs_field to access fields for non-sparse components"); - ecs_assert(it->row_fields & (1ull << index), ECS_INTERNAL_ERROR, NULL); - - ecs_entity_t src = it->sources[index]; - if (!src) { - src = ecs_table_entities(it->table)[row + it->offset]; - } - - return flecs_sparse_get_any(idr->sparse, flecs_uto(int32_t, size), src); -error: - return NULL; -} - -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()"); - 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]; - - if (term->inout == EcsIn) { - return true; - } else if (term->inout == EcsInOutDefault) { - if (!ecs_term_match_this(term)) { - return true; - } - - const ecs_term_ref_t *src = &term->src; - if (!(src->id & EcsSelf)) { - return true; - } - } -error: - return false; -} - -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; -} - -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); - - return it->set_fields & (1llu << (index)); -error: - return false; -} - -bool ecs_field_is_self( - const ecs_iter_t *it, - int8_t index) -{ - 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->sources == NULL || it->sources[index] == 0; -error: - return false; -} - -ecs_id_t ecs_field_id( - const ecs_iter_t *it, - int8_t index) -{ - 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; -} - -int32_t ecs_field_column( - const ecs_iter_t *it, - int8_t index) -{ - 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; -} - -ecs_entity_t ecs_field_src( - const ecs_iter_t *it, - int8_t index) -{ - 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; -} - -size_t ecs_field_size( - const ecs_iter_t *it, - int8_t index) -{ - 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; -} - -char* ecs_iter_str( - const ecs_iter_t *it) -{ - if (!(it->flags & EcsIterIsValid)) { - return NULL; - } - - ecs_world_t *world = it->world; - ecs_strbuf_t buf = ECS_STRBUF_INIT; - int8_t i; - - 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"); - } - - if (it->variable_count && it->variable_names) { - 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] == '_' || !strcmp(var_name, "this")) { - /* Skip anonymous variables */ - continue; - } - - ecs_var_t var = it->variables[i]; - if (!var.entity) { - /* Skip table variables */ - continue; - } - - if (!actual_count) { - ecs_strbuf_list_push(&buf, "var: ", ","); - } - - char *str = ecs_get_path(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"); - } - } - - 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 ecs_strbuf_get(&buf); -} - -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); -error: - return false; -} - -int32_t ecs_iter_count( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - - ECS_BIT_SET(it->flags, EcsIterNoData); - - int32_t count = 0; - while (ecs_iter_next(it)) { - count += it->count; - } - return count; -error: - return 0; -} - -ecs_entity_t ecs_iter_first( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - - ECS_BIT_SET(it->flags, EcsIterNoData); - - ecs_entity_t result = 0; - if (ecs_iter_next(it)) { - result = it->entities[0]; - ecs_iter_fini(it); - } - - return result; -error: - return 0; -} - -bool ecs_iter_is_true( - ecs_iter_t *it) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - - ECS_BIT_SET(it->flags, EcsIterNoData); - - bool result = ecs_iter_next(it); - if (result) { - ecs_iter_fini(it); - } - 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, - "invalid variable index %d", var_id); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, - "variable index %d out of bounds", var_id); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); - - 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 && !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; -} - -ecs_table_t* ecs_iter_get_var_as_table( - ecs_iter_t *it, - int32_t var_id) -{ - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, - "invalid variable index %d", var_id); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, - "variable index %d out of bounds", var_id); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_var_t *var = &it->variables[var_id]; - ecs_table_t *table = var->range.table; - if (!table && !var_id) { - table = it->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 = 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 (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; - } - } - -error: - return NULL; -} - -ecs_table_range_t ecs_iter_get_var_as_range( - ecs_iter_t *it, - int32_t var_id) -{ - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, - "invalid variable index %d", var_id); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, - "variable index %d out of bounds", var_id); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_table_range_t result = { 0 }; - - ecs_var_t *var = &it->variables[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 { - 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_iter_set_var( - ecs_iter_t *it, - int32_t var_id, - ecs_entity_t entity) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - 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 < it->variable_count, 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(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_var_t *var = &it->variables[var_id]; - var->entity = entity; - - 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; - } - - it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); - - /* Update iterator for constrained iterator */ - flecs_query_iter_constrain(it); - -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_CONST_CAST(ecs_table_t*, table) }; - ecs_iter_set_var_as_range(it, var_id, &range); -} - -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, - "invalid variable index %d", var_id); - ecs_check(var_id < it->variable_count, 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 = &it->variables[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; -} - -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; -} - -static -void ecs_chained_iter_fini( - ecs_iter_t *it) -{ - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_iter_fini(it->chain_it); - - it->chain_it = NULL; -} - -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); - - ecs_iter_t result = *it; - result.priv_.cache.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 }; -} - -bool ecs_page_next( - ecs_iter_t *it) -{ - 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_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, - "invalid field index %d", index); - ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); - - ecs_iter_t result = *it; - result.priv_.cache.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 }; -} - -bool ecs_worker_next( - ecs_iter_t *it) -{ - 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); - - 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; -} - -/** - * @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) -{ - union { - uint64_t u; - int64_t s; - } v; - - v.u = u; - - 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); - } - - return u; -} -#endif - -int32_t flecs_next_pow_of_2( - int32_t n) -{ - n --; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - n |= n >> 16; - n ++; - - return n; -} - -/** Convert time to double */ -double ecs_time_to_double( - ecs_time_t t) -{ - double result; - result = t.sec; - return result + (double)t.nanosec / (double)1000000000; -} - -ecs_time_t ecs_time_sub( - ecs_time_t t1, - ecs_time_t t2) -{ - 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; - } - - 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); - } -} - -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; -} - -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); -} - -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); -} - -uint64_t flecs_string_hash( - const void *ptr) -{ - const ecs_hashed_string_t *str = ptr; - ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); - return str->hash; -} - -char* flecs_vasprintf( - const char *fmt, - va_list args) -{ - ecs_size_t size = 0; - char *result = NULL; - va_list tmpa; - - va_copy(tmpa, args); - - size = vsnprintf(result, 0, fmt, tmpa); - - va_end(tmpa); - - if ((int32_t)size < 0) { - return NULL; - } - - result = (char *) ecs_os_malloc(size + 1); - - if (!result) { - return NULL; - } - - ecs_os_vsnprintf(result, size + 1, fmt, args); - - return result; -} - -char* flecs_asprintf( - const char *fmt, - ...) -{ - va_list args; - va_start(args, fmt); - char *result = flecs_vasprintf(fmt, args); - va_end(args); - return result; -} - -char* flecs_to_snake_case(const char *str) { - int32_t upper_count = 0, len = 1; - const char *ptr = str; - char ch, *out, *out_ptr; - - for (ptr = &str[1]; (ch = *ptr); ptr ++) { - if (isupper(ch)) { - upper_count ++; - } - len ++; - } - - 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 ++; - } - } - - out_ptr[0] = '\0'; - - return out; -} - -char* flecs_load_from_file( - const char *filename) -{ - FILE* file; - char* content = NULL; - int32_t bytes; - size_t size; - - /* Open file for reading */ - ecs_os_fopen(&file, filename, "r"); - if (!file) { - ecs_err("%s (%s)", ecs_os_strerror(errno), filename); - goto error; - } - - /* Determine file size */ - fseek(file, 0, SEEK_END); - bytes = (int32_t)ftell(file); - if (bytes == -1) { - goto error; - } - fseek(file, 0, SEEK_SET); - - /* 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'; - } - - fclose(file); - - return content; -error: - if (file) { - fclose(file); - } - ecs_os_free(content); - return NULL; -} - -char* flecs_chresc( - char *out, - char in, - char delimiter) -{ - 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; - } - - *(++bptr) = '\0'; - - return bptr; -} - -const char* flecs_chrparse( - const char *in, - char *out) -{ - const char *result = in + 1; - char ch; - - if (in[0] == '\\') { - result ++; - - 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]; - } - - if (out) { - *out = ch; - } - - return result; -error: - return NULL; -} - -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++; - } - } - } - - if (bptr) { - while (written < n) { - *bptr = '\0'; - bptr++; - written++; - } - } - return written; -error: - return 0; -} - -char* flecs_astresc( - char delimiter, - const char *in) -{ - 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; -} - -const char* flecs_parse_digit( - const char *ptr, - char *token) -{ - 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; - } - - tptr[0] = ch; - tptr ++; - ptr ++; - - for (; (ch = *ptr); ptr ++) { - if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { - break; - } - - tptr[0] = ch; - tptr ++; - } - - tptr[0] = '\0'; - - return ptr; -} - -const char* flecs_parse_ws_eol( - const char *ptr) -{ - while (isspace(*ptr)) { - ptr ++; - } - - return ptr; -} - -/** - * @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) -{ - flecs_sparse_init_t(&observable->events, NULL, NULL, ecs_event_record_t); - observable->on_add.event = EcsOnAdd; - observable->on_remove.event = EcsOnRemove; - observable->on_set.event = EcsOnSet; -} - -void flecs_observable_fini( - ecs_observable_t *observable) -{ - 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); - - ecs_sparse_t *events = &observable->events; - 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); - 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); - } - - flecs_sparse_fini(&observable->events); -} - -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); - - /* 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); - - /* User events */ - return flecs_sparse_try_t(&o->events, ecs_event_record_t, event); -} - -ecs_event_record_t* flecs_event_record_ensure( - ecs_observable_t *o, - ecs_entity_t event) -{ - ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_event_record_t *er = flecs_event_record_get(o, event); - if (er) { - return er; - } - er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event); - er->event = event; - return er; -} - -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); - - 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; - } - } - - return NULL; -} - -ecs_event_id_record_t* flecs_event_id_record_get( - const ecs_event_record_t *er, - ecs_id_t id) -{ - if (!er) { - return NULL; - } - - 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; - } - - if (ider->observer_count) { - return ider; - } - - return NULL; -} - -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; - } - - ider = ecs_os_calloc_t(ecs_event_id_record_t); - - 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_map_init_w_params_if(&er->event_ids, &world->allocators.ptr); - ecs_map_insert_ptr(&er->event_ids, id, ider); - return ider; -} - -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); - } - } -} - -static -int32_t flecs_event_observers_get( - const ecs_event_record_t *er, - ecs_id_t id, - ecs_event_id_record_t **iders) -{ - if (!er) { - return 0; - } - - /* Populate array with observer sets matching the id */ - int32_t count = 0; - - if (id != EcsAny) { - iders[0] = flecs_event_id_record_get_if(er, EcsAny); - count += iders[count] != 0; - } - - iders[count] = flecs_event_id_record_get_if(er, id); - count += iders[count] != 0; - - 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; - } - } - - return count; -} - -bool flecs_observers_exist( - ecs_observable_t *observable, - ecs_id_t id, - ecs_entity_t event) -{ - const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); - if (!er) { - return false; - } - - return flecs_event_id_record_get_if(er, id) != NULL; -} - -static -void flecs_emit_propagate( - ecs_world_t *world, - ecs_iter_t *it, - ecs_id_record_t *idr, - ecs_id_record_t *tgt_idr, - ecs_entity_t trav, - ecs_event_id_record_t **iders, - int32_t ider_count); - -static -void flecs_emit_propagate_id( - ecs_world_t *world, - ecs_iter_t *it, - ecs_id_record_t *idr, - ecs_id_record_t *cur, - ecs_entity_t trav, - ecs_event_id_record_t **iders, - int32_t ider_count) -{ - ecs_table_cache_iter_t idt; - if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { - return; - } - - 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; - } - - bool owned = flecs_id_record_get_table(idr, table) != NULL; - - int32_t e, entity_count = ecs_table_count(table); - it->table = table; - it->other_table = NULL; - it->offset = 0; - it->count = entity_count; - it->up_fields = 1; - if (entity_count) { - it->entities = ecs_table_entities(table); - } - - 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); - - if (!owned) { - /* Owned takes precedence */ - flecs_observers_invoke(world, &ider->self_up, it, table, trav); - } - } - - if (!table->_->traversable_count) { - continue; - } - - const ecs_entity_t *entities = ecs_table_entities(table); - for (e = 0; e < entity_count; e ++) { - ecs_record_t *r = flecs_entities_get(world, entities[e]); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *idr_t = r->idr; - if (idr_t) { - /* Only notify for entities that are used in pairs with - * traversable relationships */ - flecs_emit_propagate(world, it, idr, idr_t, trav, - iders, ider_count); - } - } - } - - it->event_cur = event_cur; - it->up_fields = 0; -} - -static -void flecs_emit_propagate( - ecs_world_t *world, - ecs_iter_t *it, - ecs_id_record_t *idr, - ecs_id_record_t *tgt_idr, - ecs_entity_t propagate_trav, - ecs_event_id_record_t **iders, - int32_t ider_count) -{ - ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); - - if (ecs_should_log_3()) { - char *idstr = ecs_id_str(world, tgt_idr->id); - ecs_dbg_3("propagate events/invalidate cache for %s", idstr); - ecs_os_free(idstr); - } - - ecs_log_push_3(); - - /* Propagate to records of traversable relationships */ - ecs_id_record_t *cur = tgt_idr; - while ((cur = cur->trav.next)) { - cur->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; - } - } - - flecs_emit_propagate_id( - world, it, idr, cur, trav, iders, ider_count); - } - - ecs_log_pop_3(); -} - -static -void flecs_emit_propagate_invalidate_tables( - ecs_world_t *world, - ecs_id_record_t *tgt_idr) -{ - ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); - - if (ecs_should_log_3()) { - char *idstr = ecs_id_str(world, tgt_idr->id); - ecs_dbg_3("invalidate reachable cache for %s", idstr); - ecs_os_free(idstr); - } - - /* Invalidate records of traversable relationships */ - ecs_id_record_t *cur = tgt_idr; - while ((cur = cur->trav.next)) { - ecs_reachable_cache_t *rc = &cur->reachable; - if (rc->current != rc->generation) { - /* Subtree is already marked invalid */ - continue; - } - - rc->generation ++; - - ecs_table_cache_iter_t idt; - if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { - continue; - } - - 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; - } - - int32_t e, entity_count = ecs_table_count(table); - const ecs_entity_t *entities = ecs_table_entities(table); - - for (e = 0; e < entity_count; e ++) { - ecs_record_t *r = flecs_entities_get(world, entities[e]); - ecs_id_record_t *idr_t = r->idr; - if (idr_t) { - /* Only notify for entities that are used in pairs with - * traversable relationships */ - flecs_emit_propagate_invalidate_tables(world, idr_t); - } - } - } - } -} - -void flecs_emit_propagate_invalidate( - ecs_world_t *world, - ecs_table_t *table, - int32_t offset, - int32_t count) -{ - const ecs_entity_t *entities = &ecs_table_entities(table)[offset]; - int32_t i; - for (i = 0; i < count; i ++) { - ecs_record_t *record = flecs_entities_get(world, entities[i]); - if (!record) { - /* If the event is emitted after a bulk operation, it's possible - * that it hasn't been populated with entities yet. */ - continue; - } - - ecs_id_record_t *idr_t = record->idr; - if (idr_t) { - /* Event is used as target in traversable relationship, propagate */ - flecs_emit_propagate_invalidate_tables(world, idr_t); - } - } -} - -static -void flecs_propagate_entities( - ecs_world_t *world, - ecs_iter_t *it, - ecs_id_record_t *idr, - 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; - } - - 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; - - int32_t i; - for (i = 0; i < count; i ++) { - ecs_record_t *record = flecs_entities_get(world, entities[i]); - if (!record) { - /* If the event is emitted after a bulk operation, it's possible - * that it hasn't been populated with entities yet. */ - continue; - } - - ecs_id_record_t *idr_t = record->idr; - if (idr_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, idr, idr_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 -void flecs_override_copy( - ecs_world_t *world, - ecs_table_t *table, - const ecs_table_record_t *tr, - const ecs_type_info_t *ti, - void *dst, - const void *src, - int32_t offset, - int32_t count) -{ - void *ptr = dst; - ecs_copy_t copy = ti->hooks.copy; - ecs_size_t size = ti->size; - int32_t i; - - if (copy) { - for (i = 0; i < count; i ++) { - copy(ptr, src, 1, ti); - ptr = ECS_OFFSET(ptr, size); - } - } else { - for (i = 0; i < count; i ++) { - ecs_os_memcpy(ptr, src, size); - ptr = ECS_OFFSET(ptr, size); - } - } - - ecs_iter_action_t on_set = ti->hooks.on_set; - if (on_set) { - const ecs_entity_t *entities = &ecs_table_entities(table)[offset]; - flecs_invoke_hook(world, table, tr, count, offset, entities, - ti->component, ti, EcsOnSet, on_set); - } -} - -static -void* flecs_override( - ecs_iter_t *it, - const ecs_type_t *emit_ids, - ecs_id_t id, - ecs_table_t *table, - ecs_id_record_t *idr) -{ - if (it->event != EcsOnAdd || (it->flags & EcsEventNoOnSet)) { - return NULL; - } - - int32_t i = 0, count = emit_ids->count; - ecs_id_t *ids = emit_ids->array; - for (i = 0; i < count; i ++) { - if (ids[i] == id) { - /* If an id was both inherited and overridden in the same event - * (like what happens during an auto override), we need to copy the - * value of the inherited component to the new component. - * Also flag to the callee that this component was overridden, so - * that an OnSet event can be emitted for it. - * Note that this is different from a component that was overridden - * after it was inherited, as this does not change the actual value - * of the component for the entity (it is copied from the existing - * overridden component), and does not require an OnSet event. */ - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - continue; - } - - int32_t index = tr->column; - ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - - ecs_column_t *column = &table->data.columns[index]; - ecs_size_t size = column->ti->size; - return ECS_ELEM(column->data, size, it->offset); - } - } - - return NULL; -} - -static -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_id_record_t *idr, - ecs_vec_t *stack, - ecs_vec_t *reachable_ids, - int32_t depth); - -static -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_id_record_t *idr, - ecs_entity_t tgt, - ecs_table_t *tgt_table, - int32_t column, - ecs_entity_t trav) -{ - ecs_id_t id = idr->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]; - - /* 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 (!may_override && (!ider_count && !ider_onset_count)) { - return; - } - - 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; - - int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column); - if (storage_i != -1) { - ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_column_t *c = &tgt_table->data.columns[storage_i]; - it->trs[0] = &tgt_table->_->records[column]; - ECS_CONST_CAST(int32_t*, it->sizes)[0] = c->ti->size; /* safe, see above */ - } - - ecs_table_record_t *tr = flecs_id_record_get_table(idr, 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); - } - } - - /* Emit OnSet events for newly inherited components */ - if (storage_i != -1) { - bool override = false; - - /* If component was added together with IsA relationship, still emit - * OnSet event, as it's a new value for the entity. */ - ecs_table_record_t *base_tr = ECS_CONST_CAST( - ecs_table_record_t*, it->trs[0]); - void *ptr = flecs_override(it, emit_ids, id, table, idr); - if (ptr) { - override = true; - } - - if (ider_onset_count) { - it->event = er_onset->event; - - 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); - } else if (override) { - ecs_entity_t src = it->sources[0]; - it->sources[0] = 0; - it->trs[0] = tr; - flecs_observers_invoke(world, &ider->self, it, table, 0); - flecs_observers_invoke(world, &ider->self_up, it, table, 0); - it->sources[0] = src; - } - } - - it->event = event; - it->trs[0] = base_tr; - } - } - - it->up_fields = 0; -} - -static -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_id_record_t *idr, - 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 = idr->id; -#ifndef 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, idr, - tgt, tgt_table, column, trav); -} - -static -int32_t flecs_emit_stack_at( - ecs_vec_t *stack, - ecs_id_record_t *idr) -{ - 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_id_record_get_table(idr, elem)) { - break; - } - } - - return sp; -} - -static -bool flecs_emit_stack_has( - ecs_vec_t *stack, - ecs_id_record_t *idr) -{ - return flecs_emit_stack_at(stack, idr) != ecs_vec_count(stack); -} - -static -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_id_record_t *rc_idr = (ecs_id_record_t*)rc_tr->hdr.cache; - ecs_record_t *rc_record = rc_elem->record; - - ecs_assert(rc_idr->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); - - if (flecs_emit_stack_has(stack, rc_idr)) { - continue; - } - - flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, - it, table, rc_idr, rc_elem->src, - rc_record, rc_record->table, rc_tr, rc_tr->index, - reachable_ids, trav); - } -} - -static -void flecs_emit_dump_cache( - ecs_world_t *world, - const ecs_vec_t *vec) -{ - 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); - ecs_dbg_3("- id: %s (%u), src: %s (%u), table: %p", - idstr, (uint32_t)elem->id, - estr, (uint32_t)elem->src, - elem->table); - ecs_os_free(idstr); - ecs_os_free(estr); - } - if (!ecs_vec_count(vec)) { - ecs_dbg_3("- no entries"); - } -} - -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_id_record_t *tgt_idr, - 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_idr is out of sync but is not the current id record being updated, - * keep track so that we can update two records for the cost of one. */ - ecs_reachable_cache_t *rc = &tgt_idr->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_idr->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_idr->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_id_record_t *idr = (ecs_id_record_t*)tgt_tr->hdr.cache; - if (inherit && !(idr->flags & EcsIdOnInstantiateInherit)) { - continue; - } - - if (idr == tgt_idr) { - char *idstr = ecs_id_str(world, idr->id); - ecs_assert(idr != tgt_idr, ECS_CYCLE_DETECTED, idstr); - ecs_os_free(idstr); - return; - } - - /* Id has the same relationship, traverse to find ids for forwarding */ - if (ECS_PAIR_FIRST(id) == trav || ECS_PAIR_FIRST(id) == EcsIsA) { - ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, - ecs_table_t*); - t[0] = tgt_table; - - ecs_reachable_cache_t *idr_rc = &idr->reachable; - if (idr_rc->current == idr_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); - } - ecs_log_push_3(); - flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it, - table, idr_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, idr, stack, reachable_ids, depth); - if (++i >= id_count) { - break; - } - - id = ids[i]; - if (ECS_PAIR_FIRST(id) != trav) { - break; - } - } while (true); - } - - ecs_vec_remove_last(stack); - continue; - } - - int32_t stack_at = flecs_emit_stack_at(stack, idr); - if (parent_revalidate && (stack_at == (stack_count - 1))) { - /* If parent id 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 = idr->id; -#ifndef NDEBUG - elem->table = tgt_table; -#endif - } - - /* Skip id if it's masked by a lower table in the tree */ - if (stack_at != stack_count) { - continue; - } - - flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, - table, idr, tgt, tgt_record, tgt_table, tgt_tr, i, - reachable_ids, trav); - } - - 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); - - /* 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); - } - - rc->current = rc->generation; - - if (ecs_should_log_3()) { - char *idstr = ecs_id_str(world, tgt_idr->id); - ecs_dbg_3("cache revalidated for %s:", idstr); - ecs_os_free(idstr); - flecs_emit_dump_cache(world, &rc->ids); - } - } - - ecs_log_pop_3(); -} - -static -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_id_record_t *idr, - ecs_vec_t *stack, - ecs_vec_t *reachable_ids, - int32_t depth) -{ - if (depth >= FLECS_DAG_DEPTH_MAX) { - char *idstr = ecs_id_str(world, idr->id); - ecs_assert(depth < FLECS_DAG_DEPTH_MAX, ECS_CYCLE_DETECTED, idstr); - ecs_os_free(idstr); - return; - } - - ecs_id_t id = idr->id; - ecs_entity_t tgt = ECS_PAIR_SECOND(id); - tgt = flecs_entities_get_alive(world, tgt); - ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); - ecs_record_t *tgt_record = flecs_entities_try(world, tgt); - ecs_table_t *tgt_table; - if (!tgt_record || !(tgt_table = tgt_record->table)) { - return; - } - - flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, - tgt, tgt_table, tgt_record, idr, stack, reachable_ids, depth + 1); -} - -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_id_record_t *idr) -{ - ecs_reachable_cache_t *rc = &idr->reachable; - - 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, idr->id); - ecs_dbg_3("reachable cache miss for %s", idstr); - ecs_os_free(idstr); - } - ecs_log_push_3(); - - 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, - idr, &stack, &rc->ids, 0); - it->sources[0] = 0; - ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); - - 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; - } - - if (ecs_should_log_3()) { - ecs_dbg_3("cache after rebuild:"); - flecs_emit_dump_cache(world, &rc->ids); - } - - 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, idr->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(idr->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_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_record_t *r = elem->record; - - ecs_assert(rc_idr->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); - - flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, - rc_idr, elem->src, r->table, tr->index, trav); - } - } - - /* 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->idr) { - break; - } - } - - 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_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_record_t *r = elem->record; - - ecs_assert(rc_idr->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; - - /* If entities already have the component, don't propagate */ - if (flecs_id_record_get_table(rc_idr, it->table)) { - continue; - } - - ecs_event_id_record_t *iders[5] = {0}; - int32_t ider_count = flecs_event_observers_get( - er, rc_idr->id, iders); - - flecs_propagate_entities(world, it, rc_idr, it->entities, - it->count, elem->src, iders, ider_count); - } - } - } -} - -/* 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_world_t *stage, - ecs_flags64_t set_mask, - ecs_event_desc_t *desc) -{ - 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); - - ecs_os_perf_trace_push("flecs.emit"); - - ecs_time_t t = {0}; - bool measure_time = world->flags & EcsWorldMeasureSystemTime; - if (measure_time) { - ecs_time_measure(&t); - } - - 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; - - /* Deferring cannot be suspended for observers */ - int32_t defer = world->stages[0]->defer; - if (defer < 0) { - world->stages[0]->defer *= -1; - } - - /* 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; - } - - /* 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_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; - - 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_observable_t *observable = ecs_get_observable(desc->observable); - ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); - - /* 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); - - ecs_data_t *storage = NULL; - ecs_column_t *columns = NULL; - if (count) { - storage = &table->data; - columns = storage->columns; - it.entities = &ecs_table_entities(table)[offset]; - } - - int32_t id_count = ids->count; - ecs_id_t *id_array = ids->array; - - /* If a table has IsA relationships, OnAdd/OnRemove events can trigger - * (un)overriding a component. When a component is overridden its value is - * initialized with the value of the overridden component. */ - bool can_override = count && (table_flags & EcsTableHasIsA) && ( - (event == EcsOnAdd) || (event == EcsOnRemove)); - - /* 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 = event != EcsOnSet; - - /* Does table has observed entities */ - bool has_observed = table_flags & EcsTableHasTraversable; - - ecs_event_id_record_t *iders[5] = {0}; - - if (count && can_forward && has_observed) { - flecs_emit_propagate_invalidate(world, table, offset, count); - } - -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_record_t *idr = NULL; - const ecs_type_info_t *ti = NULL; - ecs_id_t id = id_array[i]; - ecs_assert(id == EcsAny || !ecs_id_is_wildcard(id), - ECS_INVALID_PARAMETER, "cannot emit wildcard ids"); - int32_t ider_i, ider_count = 0; - bool is_pair = ECS_IS_PAIR(id); - void *override_ptr = NULL; - bool override_base_added = false; - ecs_table_record_t *base_tr = NULL; - ecs_entity_t base = 0; - bool id_can_override = can_override; - ecs_flags64_t id_bit = 1llu << i; - if (id_bit & set_mask) { - /* Component is already set, so don't override with prefab value */ - id_can_override = false; - } - - /* 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 && is_pair) || id_can_override) { - idr = flecs_id_record_get(world, id); - if (!idr) { - /* Possible for union ids */ - continue; - } - - ecs_flags32_t idr_flags = idr->flags; - - if (is_pair && (idr_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; - flecs_instantiate(world, tgt, table, offset, count, NULL); - world->stages[0]->base = 0; - } - - /* Adding an IsA relationship will emit OnSet events for - * any new reachable components. */ - er_fwd = er_onset; - } - } - - /* Forward events for components from pair target */ - flecs_emit_forward(world, er, er_fwd, ids, &it, table, idr); - ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); - } - - if (id_can_override && !(idr_flags & EcsIdOnInstantiateDontInherit)) { - /* Initialize overridden components with value from base */ - ti = idr->type_info; - if (ti) { - int32_t base_column = ecs_search_relation(world, table, - 0, id, EcsIsA, EcsUp, &base, NULL, &base_tr); - if (base_column != -1) { - /* Base found with component */ - ecs_table_t *base_table = base_tr->hdr.table; - if (idr->flags & EcsIdIsSparse) { - override_ptr = flecs_sparse_get_any( - idr->sparse, 0, base); - } else { - base_column = base_tr->column; - ecs_assert(base_column != -1, ECS_INTERNAL_ERROR, NULL); - ecs_record_t *base_r = flecs_entities_get(world, base); - ecs_assert(base_r != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t base_row = ECS_RECORD_TO_ROW(base_r->row); - override_ptr = base_table->data.columns[base_column].data; - override_ptr = ECS_ELEM(override_ptr, ti->size, base_row); - } - - /* For ids with override policy, check if base was added - * in same operation. This will determine later on - * whether we need to emit an OnSet event. */ - if (!(idr->flags & - (EcsIdOnInstantiateInherit|EcsIdOnInstantiateDontInherit))) { - int32_t base_i; - for (base_i = 0; base_i < id_count; base_i ++) { - ecs_id_t base_id = id_array[base_i]; - if (!ECS_IS_PAIR(base_id)) { - continue; - } - if (ECS_PAIR_FIRST(base_id) != EcsIsA) { - continue; - } - if (ECS_PAIR_SECOND(base_id) == (uint32_t)base) { - override_base_added = true; - } - } - } - } - } - } - } - - 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); - idr = idr ? idr : flecs_id_record_get(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - } - - if (!ider_count && !override_ptr) { - /* If nothing more to do for this id, early out */ - continue; - } - - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - 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; - } - - int32_t storage_i; - it.trs[0] = tr; - ECS_CONST_CAST(int32_t*, it.sizes)[0] = 0; /* safe, owned by observer */ - it.event_id = id; - it.ids[0] = id; - - if (count) { - storage_i = tr->column; - bool is_sparse = idr->flags & EcsIdIsSparse; - - if (!ecs_id_is_wildcard(id) && (storage_i != -1 || is_sparse)) { - void *ptr; - ecs_size_t size = idr->type_info->size; - - if (is_sparse) { - ecs_assert(count == 1, ECS_UNSUPPORTED, - "events for multiple entities are currently unsupported" - " for sparse components"); - ecs_entity_t e = ecs_table_entities(table)[offset]; - ptr = flecs_sparse_get(idr->sparse, 0, e); - } else{ - ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_column_t *c = &columns[storage_i]; - ptr = ECS_ELEM(c->data, size, offset); - } - - /* Safe, owned by observer */ - ECS_CONST_CAST(int32_t*, it.sizes)[0] = size; - - if (override_ptr) { - if (event == EcsOnAdd) { - /* If this is a new override, initialize the component - * with the value of the overridden component. */ - flecs_override_copy(world, table, tr, ti, ptr, - override_ptr, offset, count); - - /* If the base for this component got added in the same - * operation, generate an OnSet event as this is the - * first time this value is observed for the entity. */ - if (override_base_added) { - 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); - 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); - } - } - } else if (er_onset && it.other_table) { - /* If an override was removed, this re-exposes the - * overridden component. Because this causes the actual - * (now inherited) value of the component to change, an - * OnSet event must be emitted for the base component.*/ - ecs_assert(event == EcsOnRemove, ECS_INTERNAL_ERROR, NULL); - 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) { - /* Set the source temporarily to the base and base - * component pointer. */ - it.sources[0] = base; - it.trs[0] = base_tr; - it.up_fields = 1; - - 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); - } - - it.sources[0] = 0; - it.trs[0] = tr; - } - } - } - } - } - - /* 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); - } - - 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, idr, it.entities, count, 0, iders, ider_count); - } - - can_override = false; /* Don't override twice */ - can_forward = false; /* Don't forward twice */ - - if (wcer && er != wcer) { - /* Repeat event loop for Wildcard event */ - er = wcer; - it.event = event; - goto repeat_event; - } - -error: - world->stages[0]->defer = defer; - - ecs_os_perf_trace_pop("flecs.emit"); - - if (measure_time) { - world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); - } - return; -} - -void ecs_emit( - ecs_world_t *stage, - ecs_event_desc_t *desc) -{ - 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"); - - 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); - ecs_table_t *table; - if (!r || !(table = r->table)) { - /* Empty entities can't trigger observers */ - return; - } - desc->table = table; - desc->offset = ECS_RECORD_TO_ROW(r->row); - desc->count = 1; - } - - if (!desc->observable) { - desc->observable = world; - } - - ecs_type_t default_ids = (ecs_type_t){ - .count = 1, - .array = (ecs_id_t[]){ EcsAny } - }; - - if (!desc->ids || !desc->ids->count) { - desc->ids = &default_ids; - } - - if (desc->const_param) { - desc->param = ECS_CONST_CAST(void*, desc->const_param); - desc->const_param = NULL; - } - - ecs_defer_begin(world); - flecs_emit(world, stage, 0, desc); - ecs_defer_end(world); - - if (desc->ids == &default_ids) { - desc->ids = NULL; - } -error: - return; -} - -void ecs_enqueue( - ecs_world_t *world, - ecs_event_desc_t *desc) -{ - if (!ecs_is_deferred(world)) { - ecs_emit(world, desc); - return; - } - - ecs_stage_t *stage = flecs_stage_from_world(&world); - flecs_enqueue(world, stage, desc); -} - -/** - * @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. - */ - -#include - -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->oper == EcsNot) { - if (event == EcsOnAdd || event == EcsOnSet) { - event = EcsOnRemove; - } else if (event == EcsOnRemove) { - event = EcsOnAdd; - } - } - - return event; -} - -static -ecs_flags32_t flecs_id_flag_for_event( - ecs_entity_t e) -{ - 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| - EcsIdHasOnTableFill|EcsIdHasOnTableEmpty| - EcsIdHasOnTableCreate|EcsIdHasOnTableDelete; - } - return 0; -} - -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) -{ - 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 - }); - - ecs_flags32_t flags = flecs_id_flag_for_event(event); - if (flags) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - idr->flags |= flags; - } - } - } else if (result == 0) { - /* Ditto, but the reverse */ - flecs_notify_tables(world, id, &(ecs_table_event_t){ - .kind = EcsTableNoTriggersForId, - .event = event - }); - - ecs_flags32_t flags = flecs_id_flag_for_event(event); - if (flags) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - idr->flags &= ~flags; - } - } - - flecs_event_id_record_remove(evt, id); - ecs_os_free(idt); - } -} - -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); - } - } - - return id; -} - -static -void flecs_register_observer_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *o, - size_t offset) -{ - ecs_observer_impl_t *impl = flecs_observer_impl(o); - ecs_id_t term_id = flecs_observer_id(impl->register_id); - ecs_term_t *term = &o->query->terms[0]; - ecs_entity_t trav = term->trav; - - int i; - for (i = 0; i < o->event_count; i ++) { - ecs_entity_t event = flecs_get_observer_event( - term, o->events[i]); - - /* Get observers for event */ - ecs_event_record_t *er = flecs_event_record_ensure(observable, event); - ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); - - /* 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); - - ecs_map_t *observers = ECS_OFFSET(idt, offset); - ecs_map_init_w_params_if(observers, &world->allocators.ptr); - ecs_map_insert_ptr(observers, impl->id, o); - - flecs_inc_observer_count(world, event, er, term_id, 1); - if (trav) { - flecs_inc_observer_count(world, event, er, - ecs_pair(trav, EcsWildcard), 1); - } - } -} - -static -void flecs_uni_observer_register( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *o) -{ - ecs_term_t *term = &o->query->terms[0]; - ecs_flags64_t flags = ECS_TERM_REF_FLAGS(&term->src); - - if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { - flecs_register_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, self_up)); - } else if (flags & EcsSelf) { - flecs_register_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, self)); - } 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)); - } -} - -static -void flecs_unregister_observer_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *o, - size_t offset) -{ - ecs_observer_impl_t *impl = flecs_observer_impl(o); - ecs_id_t term_id = flecs_observer_id(impl->register_id); - ecs_term_t *term = &o->query->terms[0]; - ecs_entity_t trav = term->trav; - - int i; - for (i = 0; i < o->event_count; i ++) { - ecs_entity_t event = flecs_get_observer_event( - term, o->events[i]); - - /* Get observers for event */ - ecs_event_record_t *er = flecs_event_record_get(observable, event); - ecs_assert(er != NULL, ECS_INTERNAL_ERROR, 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) { - flecs_inc_observer_count(world, event, er, - ecs_pair(trav, EcsWildcard), -1); - } - } -} - -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); - if (o->query->term_count == 0) { - return; - } - - ecs_term_t *term = &o->query->terms[0]; - ecs_flags64_t flags = ECS_TERM_REF_FLAGS(&term->src); - - if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { - flecs_unregister_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, self_up)); - } else if (flags & EcsSelf) { - flecs_unregister_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, self)); - } else if (flags & EcsUp) { - flecs_unregister_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, up)); - } -} - -static -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_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; - } - - if (impl->flags & (EcsObserverIsDisabled|EcsObserverIsParentDisabled)) { - return true; - } - - ecs_flags32_t table_flags = table->flags, query_flags = o->query->flags; - - bool result = (table_flags & EcsTableIsPrefab) && - !(query_flags & EcsQueryMatchPrefab); - result = result || ((table_flags & EcsTableIsDisabled) && - !(query_flags & EcsQueryMatchDisabled)); - - return result; -} - -static -void flecs_observer_invoke( - ecs_world_t *world, - ecs_iter_t *it, - ecs_observer_t *o, - ecs_iter_action_t callback, - int32_t term_index) -{ - ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL); - - if (ecs_should_log_3()) { - char *path = ecs_get_path(world, it->system); - ecs_dbg_3("observer: invoke %s", path); - ecs_os_free(path); - } - - ecs_log_push_3(); - - ecs_entity_t old_system = flecs_stage_set_system( - world->stages[0], o->entity); - world->info.observers_ran_frame ++; - - ecs_query_t *query = o->query; - ecs_assert(term_index < query->term_count, ECS_INTERNAL_ERROR, NULL); - ecs_term_t *term = &query->terms[term_index]; - if (it->table && (term->oper != EcsNot)) { - ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), - ECS_INTERNAL_ERROR, NULL); - } - - ecs_termset_t row_fields = it->row_fields; - it->row_fields = query->row_fields; - - bool match_this = query->flags & EcsQueryMatchThis; - if (match_this) { - callback(it); - ecs_os_inc(&query->eval_count); - } else { - ecs_entity_t observer_src = ECS_TERM_REF_ID(&term->src); - if (observer_src && !(term->src.id & EcsIsEntity)) { - observer_src = 0; - } - - const ecs_entity_t *entities = it->entities; - int32_t i, count = it->count; - ecs_entity_t src = it->sources[0]; - it->count = 1; - for (i = 0; i < count; i ++) { - ecs_entity_t e = entities[i]; - it->entities = &e; - if (!observer_src) { - callback(it); - ecs_os_inc(&query->eval_count); - } else if (observer_src == e) { - ecs_entity_t dummy = 0; - it->entities = &dummy; - if (!src) { - it->sources[0] = e; - } - - callback(it); - ecs_os_inc(&query->eval_count); - it->sources[0] = src; - break; - } - } - - it->entities = entities; - it->count = count; - } - - it->row_fields = row_fields; - - flecs_stage_set_system(world->stages[0], old_system); - - ecs_log_pop_3(); -} - -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; - - if (ecs_should_log_3()) { - char *path = ecs_get_path(it->world, it->system); - ecs_dbg_3("observer %s", path); - ecs_os_free(path); - } - - ecs_log_push_3(); - flecs_observer_invoke(it->real_world, it, o, o->callback, 0); - ecs_log_pop_3(); -} - -static -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) -{ - ecs_query_t *query = o->query; - ecs_term_t *term = &query->terms[0]; - if (flecs_ignore_observer(o, table, it)) { - return; - } - - ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); - if (trav && term->trav != trav) { - return; - } - - ecs_observer_impl_t *impl = flecs_observer_impl(o); - bool is_filter = term->inout == EcsInOutNone; - ECS_BIT_COND(it->flags, EcsIterNoData, is_filter); - 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; - it->query = query; - it->ref_fields = query->fixed_fields | query->row_fields; - it->row_fields = query->row_fields; - - ecs_entity_t event = it->event; - int32_t event_cur = it->event_cur; - it->event = flecs_get_observer_event(term, event); - - if (o->run) { - it->next = flecs_default_next_callback; - it->callback = flecs_default_uni_observer_run_callback; - it->ctx = o; - o->run(it); - } else { - ecs_iter_action_t callback = o->callback; - it->callback = callback; - flecs_observer_invoke(world, it, o, callback, 0); - } - - it->event = event; - it->event_cur = event_cur; -} - -void flecs_observers_invoke( - ecs_world_t *world, - ecs_map_t *observers, - ecs_iter_t *it, - ecs_table_t *table, - ecs_entity_t trav) -{ - if (ecs_map_is_init(observers)) { - ecs_table_lock(it->world, table); - - 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); - } - - ecs_table_unlock(it->world, table); - } -} - -static -void flecs_multi_observer_invoke( - ecs_iter_t *it) -{ - ecs_observer_t *o = it->ctx; - flecs_poly_assert(o, ecs_observer_t); - - 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; - } - - 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; - } - - table = table ? table : &world->store.root; - prev_table = prev_table ? prev_table : &world->store.root; - - ecs_iter_t user_it; - - 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. */ - 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 - }; - - match = ecs_query_has_range(o->query, &range, &user_it); - } - - 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)) { - ecs_iter_fini(&table_it); - ecs_iter_fini(&user_it); - goto done; - } - } - - impl->last_event_id[0] = it->event_cur; - - /* 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; - - 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; - - 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); - } - - ecs_iter_fini(&user_it); - - 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 --; - } - -done: - return; -} - -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); - - ecs_world_t *world = it->real_world; - ecs_table_t *table = it->table; - ecs_iter_t user_it = *it; - - 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); - } - - ecs_table_unlock(it->world, table); - flecs_stage_set_system(world->stages[0], old_system); -} - -/* For convenience, so applications can (in theory) use a single run callback - * that uses ecs_iter_next to iterate results */ -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; - } -} - -/* 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; - - 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; - } - } - - flecs_multi_observer_invoke(it); -} - -static -void flecs_observer_yield_existing( - ecs_world_t *world, - ecs_observer_t *o, - bool yield_on_remove) -{ - ecs_run_action_t run = o->run; - if (!run) { - run = flecs_multi_observer_invoke_no_query; - } - - ecs_defer_begin(world); - - /* 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]; - - /* 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; - } - } - - ecs_iter_t it = ecs_query_iter(world, o->query); - 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 (ecs_query_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; - } - } - - ecs_defer_end(world); -} - -static -int flecs_uni_observer_init( - ecs_world_t *world, - ecs_observer_t *o, - const ecs_observer_desc_t *desc) -{ - ecs_observer_impl_t *impl = flecs_observer_impl(o); - ecs_term_t *term = &o->query->terms[0]; - impl->last_event_id = desc->last_event_id; - if (!impl->last_event_id) { - impl->last_event_id = &impl->last_event_id_storage; - } - impl->register_id = term->id; - term->field_index = flecs_ito(int8_t, desc->term_index_); - - if (ecs_id_is_tag(world, term->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; - } - } - } - } - - flecs_uni_observer_register(world, o->observable, o); - - return 0; -} - -static -int flecs_observer_add_child( - ecs_world_t *world, - ecs_observer_t *o, - const ecs_observer_desc_t *child_desc) -{ - 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; - } - - 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 -int flecs_multi_observer_init( - ecs_world_t *world, - ecs_observer_t *o, - const ecs_observer_desc_t *desc) -{ - ecs_observer_impl_t *impl = flecs_observer_impl(o); - - /* 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); - - /* 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; - } - } - - if ((query->terms[i].oper == EcsNot) && - (query->terms[i].inout != EcsInOutFilter)) - { - has_not = true; - } - } - - /* 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]; - - 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; - } - } - } - } - - /* AndFrom & OrFrom terms insert multiple observers */ - if (oper == EcsAndFrom || oper == EcsOrFrom) { - const ecs_type_t *type = ecs_get_type(world, id); - if (!type) { - continue; - } - - 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_id_record_t *idr = flecs_id_record_get(world, ti_id); - if (idr->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; - } - } - continue; - } - - /* 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; - } - } - - if (flecs_observer_add_child(world, o, &child_desc)) { - goto error; - } - - if (optional_only) { - break; - } - } - - /* 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; - - 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; -} - -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 entity, - const ecs_observer_desc_t *desc) -{ - 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; - 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; - - /* Create query */ - ecs_query_t *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); - - ecs_check(o->query->term_count > 0, ECS_INVALID_PARAMETER, - "observer must have at least one term"); - - ecs_observable_t *observable = desc->observable; - if (!observable) { - observable = ecs_get_observable(world); - } - - 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 = observable; - o->entity = entity; - impl->term_index = desc->term_index_; - impl->flags = desc->flags_; - - 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. */ - int i; - 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 ++; - } - - /* 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) { - if (flecs_uni_observer_init(world, o, desc)) { - goto error; - } - } else { - if (flecs_multi_observer_init(world, o, desc)) { - goto error; - } - } - - if (impl->flags & EcsObserverYieldOnCreate) { - flecs_observer_yield_existing(world, o, false); - } - - return o; -error: - return NULL; -} - -ecs_entity_t ecs_observer_init( - ecs_world_t *world, - const ecs_observer_desc_t *desc) -{ - 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"); - - entity = desc->entity; - if (!entity) { - entity = ecs_entity(world, {0}); - } - - EcsPoly *poly = flecs_poly_bind(world, entity, ecs_observer_t); - if (!poly->poly) { - ecs_observer_t *o = flecs_observer_init(world, entity, desc); - ecs_assert(o->entity == entity, ECS_INTERNAL_ERROR, NULL); - poly->poly = o; - - 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; - - if (o->ctx_free) { - if (o->ctx && o->ctx != desc->ctx) { - o->ctx_free(o->ctx); - } - } - - 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; - } - } - - 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; - } - } - - if (desc->run) { - o->run = desc->run; - if (!desc->callback) { - o->callback = NULL; - } - } - - if (desc->callback) { - o->callback = desc->callback; - if (!desc->run) { - o->run = NULL; - } - } - - if (desc->ctx) { - o->ctx = desc->ctx; - } - - if (desc->callback_ctx) { - o->callback_ctx = desc->callback_ctx; - } - - if (desc->run_ctx) { - o->run_ctx = desc->run_ctx; - } - - if (desc->ctx_free) { - o->ctx_free = desc->ctx_free; - } - - if (desc->callback_ctx_free) { - o->callback_ctx_free = desc->callback_ctx_free; - } - - if (desc->run_ctx_free) { - o->run_ctx_free = desc->run_ctx_free; - } - } - - 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) -{ - return flecs_poly_get(world, observer, ecs_observer_t); -} - -void flecs_observer_fini( - ecs_observer_t *o) -{ - ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(o->query != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_world_t *world = o->query->world; - ecs_observer_impl_t *impl = flecs_observer_impl(o); - - if (impl->flags & EcsObserverYieldOnDelete) { - flecs_observer_yield_existing(world, o, true); - } - - 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 { - if (o->query->term_count) { - flecs_unregister_observer(world, o->observable, o); - } else { - /* Observer creation failed while creating query */ - } - } - - ecs_vec_fini_t(&world->allocator, &impl->children, ecs_observer_t*); - - /* Cleanup queries */ - ecs_query_fini(o->query); - 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); -} - -void flecs_observer_set_disable_bit( - ecs_world_t *world, - ecs_entity_t e, - ecs_flags32_t bit, - bool cond) -{ - 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 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. - */ - -#include -#include - -void ecs_os_api_impl(ecs_os_api_t *api); - -static bool ecs_os_api_initialized = false; -static bool ecs_os_api_initializing = false; -static int ecs_os_api_init_count = 0; - -ecs_os_api_t ecs_os_api = { - .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, - .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ -}; - -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; - } -} - -ecs_os_api_t ecs_os_get_api(void) { - return ecs_os_api; -} - -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_(); - } - } -} - -void ecs_os_fini(void) { - if (!--ecs_os_api_init_count) { - if (ecs_os_api.fini_) { - ecs_os_api.fini_(); - } - } -} - -/* 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__) -#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 - -#if HAVE_EXECINFO -#include -#define ECS_BT_BUF_SIZE 100 - -void flecs_dump_backtrace( - void *stream) -{ - 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) { - return; - } - - for (int j = 1; j < nptrs; j++) { - fprintf(stream, "%s\n", strings[j]); - } - - free(strings); -} -#else -void flecs_dump_backtrace( - void *stream) -{ - (void)stream; -} -#endif -#undef HAVE_EXECINFO_H - -static -void flecs_log_msg( - int32_t level, - const char *file, - int32_t line, - const char *msg) -{ - FILE *stream = ecs_os_api.log_out_; - if (!stream) { - stream = stdout; - } - - 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 (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 { - fputs(" ", stream); - } - } - - 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); - } - - 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'; - - fputs(indent, stream); - } - } - - if (level < 0) { - if (file) { - const char *file_ptr = strrchr(file, '/'); - if (!file_ptr) { - file_ptr = strrchr(file, '\\'); - } - - if (file_ptr) { - file = file_ptr + 1; - } - - fputs(file, stream); - fputs(": ", stream); - } - - if (line) { - fprintf(stream, "%d: ", line); - } - } - - fputs(msg, stream); - - fputs("\n", stream); - - if (level == -4) { - flecs_dump_backtrace(stream); - } -} - -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); - } -} - -void ecs_os_trace( - const char *file, - int32_t line, - const char *msg) -{ - if (ecs_os_api.log_) { - ecs_os_api.log_(0, file, line, msg); - } -} - -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); - } -} - -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); - } -} - -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); - } -} - -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); - - time->sec = (uint32_t)sec; - time->nanosec = (uint32_t)(now - sec * 1000000000); -} - -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); -} - -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 -void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { - ecs_assert(size > 0, ECS_INVALID_PARAMETER, 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); - } - - return realloc(ptr, (size_t)size); -} - -static -void ecs_os_api_free(void *ptr) { - if (ptr) { - ecs_os_linc(&ecs_os_api_free_count); - } - 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; - } -} - -void ecs_os_strset(char **str, const char *value) { - char *old = str[0]; - str[0] = ecs_os_strdup(value); - ecs_os_free(old); -} - -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 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); - } -} - -/* 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; - } - } - - return base; -} - -static -char* ecs_os_api_module_to_dl(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; - - /* 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 (ecs_os_api_initializing != 0) { - return; - } - - 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; - - /* Strings */ - ecs_os_api.strdup_ = ecs_os_api_strdup; - - /* Time */ - ecs_os_api.get_time_ = ecs_os_gettime; - - /* Logging */ - ecs_os_api.log_ = flecs_log_msg; - - /* Modules */ - if (!ecs_os_api.module_to_dl_) { - ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; - } - - if (!ecs_os_api.module_to_etc_) { - ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; - } - - 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; -} - -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); -} - -bool ecs_os_has_time(void) { - return - (ecs_os_api.get_time_ != NULL) && - (ecs_os_api.sleep_ != NULL) && - (ecs_os_api.now_ != NULL); -} - -bool ecs_os_has_logging(void) { - return (ecs_os_api.log_ != 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), - } -}; - -ecs_mixins_t ecs_stage_t_mixins = { - .type_name = "ecs_stage_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_stage_t, world) - } -}; - -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 -void* assert_mixin( - const ecs_poly_t *poly, - ecs_mixin_kind_t kind) -{ - 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, - "invalid/freed pointer to flecs object detected"); - - const ecs_mixins_t *mixins = hdr->mixins; - ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); - - 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; - - /* 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); - - ecs_header_t *hdr = poly; - ecs_os_memset(poly, 0, size); - - hdr->magic = ECS_OBJECT_MAGIC; - hdr->type = type; - hdr->refcount = 1; - hdr->mixins = mixins; - - return poly; -} - -void flecs_poly_fini_( - ecs_poly_t *poly, - int32_t type) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - (void)type; - - ecs_header_t *hdr = poly; - - /* Don't deinit poly that wasn't initialized */ - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, - "invalid/freed pointer to flecs object detected"); - ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, - "incorrect function called to free flecs object"); - hdr->magic = 0; -} - -int32_t flecs_poly_claim_( - ecs_poly_t *poly) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_header_t *hdr = poly; - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, 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; - } -} - -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->magic == ECS_OBJECT_MAGIC, 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; - } -} - -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->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, - "invalid/freed pointer to flecs object detected"); - return hdr->refcount; -} - -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); - } - - /* Never defer creation of a poly object */ - bool deferred = false; - if (ecs_is_deferred(world)) { - deferred = true; - ecs_defer_suspend(world); - } - - /* 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); - - if (deferred) { - ecs_defer_resume(world); - } - - return result; -} - -void flecs_poly_modified_( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag) -{ - ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); -} - -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); -} - -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; -} - -bool flecs_poly_is_( - const ecs_poly_t *poly, - int32_t type) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - - const ecs_header_t *hdr = poly; - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, - "invalid/freed pointer to flecs object detected"); - return hdr->type == type; -} - -ecs_observable_t* ecs_get_observable( - const ecs_poly_t *poly) -{ - return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); -} - -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; - } - return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); -} - -ecs_entity_t ecs_get_entity( - const ecs_poly_t *poly) -{ - return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); -} - -flecs_poly_dtor_t* ecs_get_dtor( - const ecs_poly_t *poly) -{ - return (flecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); -} - -/** - * @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. - */ - - -static -int32_t flecs_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->index; - if (tr_out) tr_out[0] = tr; - if (id_out) { - id_out[0] = ids[r]; - } - return r; - } - - return -1; -} - -static -int32_t flecs_type_offset_search( - int32_t offset, - ecs_id_t id, - ecs_id_t *ids, - int32_t count, - ecs_id_t *id_out) -{ - 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); - - 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; - } - } - - return -1; -} - -bool flecs_type_can_inherit_id( - const ecs_world_t *world, - const ecs_table_t *table, - const ecs_id_record_t *idr, - ecs_id_t id) -{ - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - if (idr->flags & EcsIdOnInstantiateDontInherit) { - return false; - } - - if (idr->flags & EcsIdExclusive) { - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t er = ECS_PAIR_FIRST(id); - if (flecs_table_record_get( - world, table, ecs_pair(er, EcsWildcard))) - { - return false; - } - } - } - return true; -} - -static -int32_t flecs_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, - bool self, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - ecs_table_record_t **tr_out) -{ - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - int32_t count = type.count; - - if (self) { - if (offset) { - int32_t r = flecs_type_offset_search(offset, id, ids, count, id_out); - if (r != -1) { - return r; - } - } else { - int32_t r = flecs_type_search(table, idr, ids, id_out, tr_out); - if (r != -1) { - return r; - } - } - } - - ecs_flags32_t flags = table->flags; - if ((flags & EcsTableHasPairs) && rel) { - bool is_a = rel == ecs_pair(EcsIsA, EcsWildcard); - if (is_a) { - if (!(flags & EcsTableHasIsA)) { - return -1; - } - idr_r = world->idr_isa_wildcard; - - if (!flecs_type_can_inherit_id(world, table, idr, id)) { - return -1; - } - } - - if (!idr_r) { - idr_r = flecs_id_record_get(world, rel); - if (!idr_r) { - return -1; - } - } - - ecs_id_t id_r; - int32_t r, r_column; - if (offset) { - r_column = flecs_type_offset_search(offset, rel, ids, count, &id_r); - } else { - r_column = flecs_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 = flecs_entities_get_any(world, obj); - ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_table_t *obj_table = rec->table; - if (obj_table) { - ecs_assert(obj_table != table, ECS_CYCLE_DETECTED, NULL); - - r = flecs_type_search_relation(world, obj_table, 0, id, idr, - rel, idr_r, true, subject_out, id_out, tr_out); - if (r != -1) { - if (subject_out && !subject_out[0]) { - subject_out[0] = ecs_get_alive(world, obj); - } - return r_column; - } - - if (!is_a) { - r = flecs_type_search_relation(world, obj_table, 0, id, idr, - ecs_pair(EcsIsA, EcsWildcard), world->idr_isa_wildcard, - true, subject_out, id_out, tr_out); - if (r != -1) { - if (subject_out && !subject_out[0]) { - subject_out[0] = ecs_get_alive(world, obj); - } - return r_column; - } - } - } - - r_column = flecs_type_offset_search( - r_column + 1, rel, ids, count, &id_r); - } - } - - return -1; -} - -int32_t flecs_search_relation_w_idr( - 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 *subject_out, - ecs_id_t *id_out, - struct ecs_table_record_t **tr_out, - ecs_id_record_t *idr) -{ - if (!table) return -1; - - flecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - - flags = flags ? flags : (EcsSelf|EcsUp); - - if (!idr) { - idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; - } - } - - if (subject_out) subject_out[0] = 0; - if (!(flags & EcsUp)) { - if (offset) { - return ecs_search_offset(world, table, offset, id, id_out); - } else { - return flecs_type_search( - table, idr, table->type.array, id_out, tr_out); - } - } - - int32_t result = flecs_type_search_relation(world, table, offset, id, idr, - ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, - id_out, tr_out); - - 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, - ecs_flags64_t flags, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - struct ecs_table_record_t **tr_out) -{ - if (!table) return -1; - - flecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - - flags = flags ? flags : (EcsSelf|EcsUp); - - if (subject_out) subject_out[0] = 0; - if (!(flags & EcsUp)) { - return ecs_search_offset(world, table, offset, id, id_out); - } - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; - } - - int32_t result = flecs_type_search_relation(world, table, offset, id, idr, - ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, - id_out, tr_out); - - return result; -} - -int32_t flecs_search_w_idr( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t *id_out, - ecs_id_record_t *idr) -{ - if (!table) return -1; - - flecs_poly_assert(world, ecs_world_t); - (void)world; - - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - return flecs_type_search(table, idr, ids, id_out, 0); -} - -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; - - flecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; - } - - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - return flecs_type_search(table, idr, ids, id_out, 0); -} - -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) { - flecs_poly_assert(world, ecs_world_t); - return ecs_search(world, table, id, id_out); - } - - if (!table) return -1; - - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - int32_t count = type.count; - return flecs_type_offset_search(offset, id, ids, count, id_out); -} - -static -int32_t flecs_relation_depth_walk( - const ecs_world_t *world, - const ecs_id_record_t *idr, - const ecs_table_t *first, - const ecs_table_t *table) -{ - int32_t result = 0; - - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return 0; - } - - 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]); - ecs_assert(o != 0, ECS_INTERNAL_ERROR, NULL); - - 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, idr, 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) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); - if (!idr) { - return 0; - } - - return flecs_relation_depth_walk(world, idr, table, table); -} - -/** - * @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. - */ - - -static -ecs_cmd_t* flecs_cmd_new( - ecs_stage_t *stage) -{ - 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 -ecs_cmd_t* flecs_cmd_new_batched( - ecs_stage_t *stage, - ecs_entity_t e) -{ - ecs_vec_t *cmds = &stage->cmd->queue; - ecs_cmd_entry_t *entry = flecs_sparse_get_any_t( - &stage->cmd->entries, ecs_cmd_entry_t, e); - - int32_t cur = ecs_vec_count(cmds); - ecs_cmd_t *cmd = flecs_cmd_new(stage); - 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); - ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL); - 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 { - cmd->entry = entry = flecs_sparse_ensure_fast_t( - &stage->cmd->entries, ecs_cmd_entry_t, e); - entry->first = cur; - } - - entry->last = cur; - - return cmd; -} - -static -void flecs_stage_merge( - ecs_world_t *world) -{ - bool is_stage = flecs_poly_is(world, ecs_stage_t); - ecs_stage_t *stage = flecs_stage_from_world(&world); - - bool measure_frame_time = ECS_BIT_IS_SET(ecs_world_get_flags(world), - EcsWorldMeasureFrameTime); - - ecs_time_t t_start = {0}; - if (measure_frame_time) { - ecs_os_get_time(&t_start); - } - - 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 { - /* 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); - } - } - - flecs_eval_component_monitors(world); - - if (measure_frame_time) { - world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); - } - - world->info.merge_count_total ++; - - /* If stage is unmanaged, deferring is always enabled */ - if (stage->id == -1) { - flecs_defer_begin(world, stage); - } - - ecs_log_pop_3(); -} - -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); - (void)world; - if (stage->defer < 0) return false; - return (++ stage->defer) == 1; -} - -bool flecs_defer_cmd( - ecs_stage_t *stage) -{ - if (stage->defer) { - return (stage->defer > 0); - } - - stage->defer ++; - return false; -} - -bool flecs_defer_modified( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - if (flecs_defer_cmd(stage)) { - ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); - if (cmd) { - cmd->kind = EcsCmdModified; - cmd->id = id; - cmd->entity = entity; - } - return true; - } - return false; -} - -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; -} - -bool flecs_defer_path( - ecs_stage_t *stage, - ecs_entity_t parent, - ecs_entity_t entity, - const char *name) -{ - 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; -} - -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; -} - -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; -} - -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; -} - -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; -} - -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)); - - /* Use ecs_new_id as this is thread safe */ - int i; - for (i = 0; i < count; i ++) { - ids[i] = ecs_new(world); - } - - *ids_out = ids; - - /* 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; -} - -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; -} - -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; - return true; - } - return false; -} - -void* flecs_defer_set( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_cmd_kind_t cmd_kind, - ecs_entity_t entity, - ecs_id_t id, - ecs_size_t size, - void *value, - bool *is_new) -{ - ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); - - /* Find type info for id */ - const ecs_type_info_t *ti = NULL; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - /* If idr doesn't exist yet, create it but only if the - * application is not multithreaded. */ - if (world->flags & EcsWorldMultiThreaded) { - ti = ecs_get_type_info(world, id); - } else { - /* When not in multi threaded mode, it's safe to find or - * create the id record. */ - idr = flecs_id_record_ensure(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Get type_info from id record. We could have called - * ecs_get_type_info directly, but since this function can be - * expensive for pairs, creating the id record ensures we can - * find the type_info quickly for subsequent operations. */ - ti = idr->type_info; - } - } else { - ti = idr->type_info; - } - - /* If the id isn't associated with a type, we can't set anything */ - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, - "provided component is not a type"); - - /* Make sure the size of the value equals the type size */ - ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, - "mismatching size specified for component in ensure/emplace/set"); - size = ti->size; - - /* Find existing component. Make sure it's owned, so that we won't use the - * component of a prefab. */ - void *existing = NULL; - ecs_table_t *table = NULL; - if (idr) { - /* Entity can only have existing component if id record exists */ - ecs_record_t *r = flecs_entities_get(world, entity); - table = r->table; - if (r && table) { - const ecs_table_record_t *tr = flecs_id_record_get_table( - idr, table); - if (tr) { - if (tr->column != -1) { - /* Entity has the component */ - existing = table->data.columns[tr->column].data; - existing = ECS_ELEM(existing, size, - ECS_RECORD_TO_ROW(r->row)); - } else { - ecs_assert(idr->flags & EcsIdIsSparse, - ECS_NOT_A_COMPONENT, NULL); - existing = flecs_sparse_get_any(idr->sparse, 0, entity); - } - } - } - } - - /* Get existing value from storage */ - void *cmd_value = existing; - bool emplace = cmd_kind == EcsCmdEmplace; - - /* If the component does not yet exist, create a temporary value. This is - * necessary so we can store a component value in the deferred command, - * without adding the component to the entity which is not allowed in - * deferred mode. */ - if (!existing) { - ecs_stack_t *stack = &stage->cmd->stack; - cmd_value = flecs_stack_alloc(stack, size, ti->alignment); - - /* If the component doesn't yet exist, construct it and move the - * provided value into the component, if provided. Don't construct if - * this is an emplace operation, in which case the application is - * responsible for constructing. */ - if (value) { - if (emplace) { - ecs_move_t move = ti->hooks.move_ctor; - if (move) { - move(cmd_value, value, 1, ti); - } else { - ecs_os_memcpy(cmd_value, value, size); - } - } else { - ecs_copy_t copy = ti->hooks.copy_ctor; - if (copy) { - copy(cmd_value, value, 1, ti); - } else { - ecs_os_memcpy(cmd_value, value, size); - } - } - } else if (!emplace) { - /* If the command is not an emplace, construct the temp storage */ - - /* Check if entity inherits component */ - void *base = NULL; - if (table && (table->flags & EcsTableHasIsA)) { - base = flecs_get_base_component(world, table, id, idr, 0); - } - - if (!base) { - /* Normal ctor */ - ecs_xtor_t ctor = ti->hooks.ctor; - if (ctor) { - ctor(cmd_value, 1, ti); - } - } else { - /* Override */ - ecs_copy_t copy = ti->hooks.copy_ctor; - if (copy) { - copy(cmd_value, base, 1, ti); - } else { - ecs_os_memcpy(cmd_value, base, size); - } - } - } - } else if (value) { - /* If component exists and value is provided, copy */ - ecs_copy_t copy = ti->hooks.copy; - if (copy) { - copy(existing, value, 1, ti); - } else { - ecs_os_memcpy(existing, value, size); - } - } - - if (!cmd) { - /* If cmd is NULL, entity was already deleted. Check if we need to - * insert a command into the queue. */ - if (!ti->hooks.dtor) { - /* If temporary memory does not need to be destructed, it'll get - * freed when the stack allocator is reset. This prevents us - * from having to insert a command when the entity was - * already deleted. */ - return cmd_value; - } - cmd = flecs_cmd_new(stage); - } - - if (!existing) { - /* If component didn't exist yet, insert command that will create it */ - cmd->kind = cmd_kind; - cmd->id = id; - cmd->idr = idr; - cmd->entity = entity; - cmd->is._1.size = size; - cmd->is._1.value = cmd_value; - - if (is_new) { - *is_new = true; - } - } else { - /* If component already exists, still insert an Add command to ensure - * that any preceding remove commands won't remove the component. If the - * operation is a set, also insert a Modified command. */ - if (cmd_kind == EcsCmdSet) { - cmd->kind = EcsCmdAddModified; - } else { - cmd->kind = EcsCmdAdd; - } - cmd->id = id; - cmd->entity = entity; - - if (is_new) { - *is_new = false; - } - } - - return cmd_value; -error: - return NULL; -} - -void flecs_enqueue( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_event_desc_t *desc) -{ - 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; - } - - cmd->is._1.value = desc_cmd; - cmd->is._1.size = ECS_SIZEOF(ecs_event_desc_t); - - 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"); - - 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"); - - 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); - } - } - - desc_cmd->param = param_cmd; - desc_cmd->const_param = NULL; - } -} - -void flecs_stage_merge_post_frame( - ecs_world_t *world, - ecs_stage_t *stage) -{ - /* 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); - } - - ecs_vec_clear(&stage->post_frame_actions); -} - -void flecs_commands_init( - ecs_stage_t *stage, - ecs_commands_t *cmd) -{ - 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); -} - -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); - - flecs_stack_fini(&cmd->stack); - ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); - flecs_sparse_fini(&cmd->entries); -} - -ecs_entity_t flecs_stage_set_system( - ecs_stage_t *stage, - ecs_entity_t system) -{ - ecs_entity_t old = stage->system; - stage->system = system; - return old; -} - -static -ecs_stage_t* flecs_stage_new( - ecs_world_t *world) -{ - 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); - - stage->world = world; - stage->thread_ctx = world; - - flecs_stack_init(&stage->allocators.iter_stack); - flecs_stack_init(&stage->allocators.deser_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); - - ecs_allocator_t *a = &stage->allocator; - ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); - - int32_t i; - for (i = 0; i < 2; i ++) { - flecs_commands_init(stage, &stage->cmd_stack[i]); - } - - stage->cmd = &stage->cmd_stack[0]; - return stage; -} - -static -void flecs_stage_free( - ecs_world_t *world, - ecs_stage_t *stage) -{ - (void)world; - flecs_poly_assert(world, ecs_world_t); - flecs_poly_assert(stage, ecs_stage_t); - - flecs_poly_fini(stage, ecs_stage_t); - - 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); - - int32_t i; - for (i = 0; i < 2; i ++) { - flecs_commands_fini(stage, &stage->cmd_stack[i]); - } - -#ifdef FLECS_SCRIPT - if (stage->runtime) { - ecs_script_runtime_free(stage->runtime); - } -#endif - - flecs_stack_fini(&stage->allocators.iter_stack); - flecs_stack_fini(&stage->allocators.deser_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); - - ecs_os_free(stage); -} - -ecs_allocator_t* flecs_stage_get_allocator( - ecs_world_t *world) -{ - ecs_stage_t *stage = flecs_stage_from_world( - ECS_CONST_CAST(ecs_world_t**, &world)); - return &stage->allocator; -} - -ecs_stack_t* flecs_stage_get_stack_allocator( - ecs_world_t *world) -{ - ecs_stage_t *stage = flecs_stage_from_world( - ECS_CONST_CAST(ecs_world_t**, &world)); - return &stage->allocators.iter_stack; -} - -ecs_world_t* ecs_stage_new( - ecs_world_t *world) -{ - ecs_stage_t *stage = flecs_stage_new(world); - stage->id = -1; - - flecs_defer_begin(world, stage); - - return (ecs_world_t*)stage; -} - -void ecs_stage_free( - ecs_world_t *world) -{ - 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; -} - -void ecs_set_stage_count( - ecs_world_t *world, - int32_t stage_count) -{ - flecs_poly_assert(world, ecs_world_t); - - /* World must have at least one default stage */ - ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), - ECS_INTERNAL_ERROR, NULL); - - const ecs_entity_t *lookup_path = NULL; - if (world->stage_count >= 1) { - lookup_path = world->stages[0]->lookup_path; - } - - 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 (stage_count) { - world->stages = ecs_os_realloc_n( - world->stages, ecs_stage_t*, stage_count); - - for (i = count; i < stage_count; i ++) { - ecs_stage_t *stage = world->stages[i] = flecs_stage_new(world); - stage->id = i; - - /* 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; - } - - /* 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; - } - - 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; -} - -int32_t ecs_stage_get_id( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (flecs_poly_is(world, ecs_stage_t)) { - ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world); - - /* Index 0 is reserved for main stage */ - return stage->id; - } else if (flecs_poly_is(world, ecs_world_t)) { - return 0; - } else { - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } -error: - return 0; -} - -ecs_world_t* ecs_get_stage( - const ecs_world_t *world, - int32_t stage_id) -{ - 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; -} - -bool ecs_readonly_begin( - ecs_world_t *world, - bool multi_threaded) -{ - flecs_poly_assert(world, ecs_world_t); - - ecs_dbg_3("#[bold]readonly"); - ecs_log_push_3(); - - 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); - } - - bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); - - /* 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); - - return is_readonly; -} - -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"); - - /* After this it is safe again to mutate the world directly */ - ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); - ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); - - ecs_log_pop_3(); - - flecs_stage_merge(world); -error: - return; -} - -void ecs_merge( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(flecs_poly_is(world, ecs_world_t) || - flecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); - flecs_stage_merge(world); -error: - return; -} - -bool ecs_stage_is_readonly( - const ecs_world_t *stage) -{ - const ecs_world_t *world = ecs_get_world(stage); - - 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; - } - } - - if (world->flags & EcsWorldReadonly) { - if (flecs_poly_is(stage, ecs_world_t)) { - return true; - } - } else { - if (flecs_poly_is(stage, ecs_stage_t)) { - return true; - } - } - - return false; -} - -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; -} - -/** - * @file value.c - * @brief Utility functions to work with non-trivial pointers of user types. - */ - - -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; - - ecs_xtor_t ctor; - if ((ctor = ti->hooks.ctor)) { - ctor(ptr, 1, ti); - } else { - ecs_os_memset(ptr, 0, ti->size); - } - - return 0; -error: - return -1; -} - -int ecs_value_init( - const 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"); - return ecs_value_init_w_type_info(world, ti, ptr); -error: - return -1; -} - -void* ecs_value_new_w_type_info( - ecs_world_t *world, - const ecs_type_info_t *ti) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; - - 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; - } - - return result; -error: - return NULL; -} - -void* ecs_value_new( - ecs_world_t *world, - ecs_entity_t type) -{ - 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_new_w_type_info(world, ti); -error: - return NULL; -} - -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; - - ecs_xtor_t dtor; - if ((dtor = ti->hooks.dtor)) { - dtor(ptr, 1, ti); - } - - return 0; -error: - return -1; -} - -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; -} - -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; - } - - flecs_free(&world->allocator, ti->size, ptr); - - return 0; -error: - return -1; -} - -int ecs_value_copy_w_type_info( - const ecs_world_t *world, - const ecs_type_info_t *ti, - void* dst, - const void *src) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; - - 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; -} - -int ecs_value_copy( - const ecs_world_t *world, - ecs_entity_t type, - void* dst, - const 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_copy_w_type_info(world, ti, dst, src); -error: - return -1; -} - -int ecs_value_move_w_type_info( - const ecs_world_t *world, - const ecs_type_info_t *ti, - void* dst, - void *src) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; - - ecs_move_t move; - if ((move = ti->hooks.move)) { - move(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, ti->size); - } - - return 0; -error: - return -1; -} - -int ecs_value_move( - 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; -} - -int ecs_value_move_ctor_w_type_info( - const ecs_world_t *world, - const ecs_type_info_t *ti, - void* dst, - void *src) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; - - ecs_move_t move; - if ((move = ti->hooks.move_ctor)) { - move(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, ti->size); - } - - return 0; -error: - return -1; -} - -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; -} - -/** - * @file world.c - * @brief World-level API. - */ - - -/* 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); - -/** 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; - -/* Poly target components */ -const ecs_entity_t EcsQuery = 5; -const ecs_entity_t EcsObserver = 6; -const ecs_entity_t EcsSystem = 7; - -/* Core scopes & entities */ -const ecs_entity_t EcsWorld = FLECS_HI_COMPONENT_ID + 0; -const ecs_entity_t EcsFlecs = FLECS_HI_COMPONENT_ID + 1; -const ecs_entity_t EcsFlecsCore = FLECS_HI_COMPONENT_ID + 2; -const ecs_entity_t EcsFlecsInternals = FLECS_HI_COMPONENT_ID + 3; -const ecs_entity_t EcsModule = FLECS_HI_COMPONENT_ID + 4; -const ecs_entity_t EcsPrivate = FLECS_HI_COMPONENT_ID + 5; -const ecs_entity_t EcsPrefab = FLECS_HI_COMPONENT_ID + 6; -const ecs_entity_t EcsDisabled = FLECS_HI_COMPONENT_ID + 7; -const ecs_entity_t EcsNotQueryable = FLECS_HI_COMPONENT_ID + 8; - -const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 9; -const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 10; - -/* Marker entities for query encoding */ -const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 11; -const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 12; -const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 13; -const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 14; - -/* Traits */ -const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 15; -const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 16; -const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 17; -const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 18; -const ecs_entity_t EcsOnInstantiate = FLECS_HI_COMPONENT_ID + 19; -const ecs_entity_t EcsOverride = FLECS_HI_COMPONENT_ID + 20; -const ecs_entity_t EcsInherit = FLECS_HI_COMPONENT_ID + 21; -const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 22; -const ecs_entity_t EcsPairIsTag = FLECS_HI_COMPONENT_ID + 23; -const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 24; -const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 25; -const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 26; -const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 27; -const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 28; -const ecs_entity_t EcsCanToggle = FLECS_HI_COMPONENT_ID + 29; -const ecs_entity_t EcsTrait = FLECS_HI_COMPONENT_ID + 30; -const ecs_entity_t EcsRelationship = FLECS_HI_COMPONENT_ID + 31; -const ecs_entity_t EcsTarget = FLECS_HI_COMPONENT_ID + 32; - - -/* Builtin relationships */ -const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 33; -const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 34; -const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 35; - -/* Identifier tags */ -const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 36; -const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 37; -const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 38; - -/* Events */ -const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 39; -const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 40; -const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 41; -const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 43; -const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 44; -const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 45; -const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 46; - -/* Timers */ -const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 49; -const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 50; -const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 51; - -/* Actions */ -const ecs_entity_t EcsRemove = FLECS_HI_COMPONENT_ID + 52; -const ecs_entity_t EcsDelete = FLECS_HI_COMPONENT_ID + 53; -const ecs_entity_t EcsPanic = FLECS_HI_COMPONENT_ID + 54; - -/* Storage */ -const ecs_entity_t EcsSparse = FLECS_HI_COMPONENT_ID + 55; -const ecs_entity_t EcsUnion = FLECS_HI_COMPONENT_ID + 56; - -/* Misc */ -const ecs_entity_t ecs_id(EcsDefaultChildComponent) = FLECS_HI_COMPONENT_ID + 57; - -/* Builtin predicate ids (used by query engine) */ -const ecs_entity_t EcsPredEq = FLECS_HI_COMPONENT_ID + 58; -const ecs_entity_t EcsPredMatch = FLECS_HI_COMPONENT_ID + 59; -const ecs_entity_t EcsPredLookup = FLECS_HI_COMPONENT_ID + 60; -const ecs_entity_t EcsScopeOpen = FLECS_HI_COMPONENT_ID + 61; -const ecs_entity_t EcsScopeClose = FLECS_HI_COMPONENT_ID + 62; - -/* Systems */ -const ecs_entity_t EcsMonitor = FLECS_HI_COMPONENT_ID + 63; -const ecs_entity_t EcsEmpty = FLECS_HI_COMPONENT_ID + 64; -const ecs_entity_t ecs_id(EcsPipeline) = FLECS_HI_COMPONENT_ID + 65; -const ecs_entity_t EcsOnStart = FLECS_HI_COMPONENT_ID + 66; -const ecs_entity_t EcsPreFrame = FLECS_HI_COMPONENT_ID + 67; -const ecs_entity_t EcsOnLoad = FLECS_HI_COMPONENT_ID + 68; -const ecs_entity_t EcsPostLoad = FLECS_HI_COMPONENT_ID + 69; -const ecs_entity_t EcsPreUpdate = FLECS_HI_COMPONENT_ID + 70; -const ecs_entity_t EcsOnUpdate = FLECS_HI_COMPONENT_ID + 71; -const ecs_entity_t EcsOnValidate = FLECS_HI_COMPONENT_ID + 72; -const ecs_entity_t EcsPostUpdate = FLECS_HI_COMPONENT_ID + 73; -const ecs_entity_t EcsPreStore = FLECS_HI_COMPONENT_ID + 74; -const ecs_entity_t EcsOnStore = FLECS_HI_COMPONENT_ID + 75; -const ecs_entity_t EcsPostFrame = FLECS_HI_COMPONENT_ID + 76; -const ecs_entity_t EcsPhase = FLECS_HI_COMPONENT_ID + 77; - -/* 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 + 80; -const ecs_entity_t ecs_id(ecs_char_t) = FLECS_HI_COMPONENT_ID + 81; -const ecs_entity_t ecs_id(ecs_byte_t) = FLECS_HI_COMPONENT_ID + 82; -const ecs_entity_t ecs_id(ecs_u8_t) = FLECS_HI_COMPONENT_ID + 83; -const ecs_entity_t ecs_id(ecs_u16_t) = FLECS_HI_COMPONENT_ID + 84; -const ecs_entity_t ecs_id(ecs_u32_t) = FLECS_HI_COMPONENT_ID + 85; -const ecs_entity_t ecs_id(ecs_u64_t) = FLECS_HI_COMPONENT_ID + 86; -const ecs_entity_t ecs_id(ecs_uptr_t) = FLECS_HI_COMPONENT_ID + 87; -const ecs_entity_t ecs_id(ecs_i8_t) = FLECS_HI_COMPONENT_ID + 88; -const ecs_entity_t ecs_id(ecs_i16_t) = FLECS_HI_COMPONENT_ID + 89; -const ecs_entity_t ecs_id(ecs_i32_t) = FLECS_HI_COMPONENT_ID + 90; -const ecs_entity_t ecs_id(ecs_i64_t) = FLECS_HI_COMPONENT_ID + 91; -const ecs_entity_t ecs_id(ecs_iptr_t) = FLECS_HI_COMPONENT_ID + 92; -const ecs_entity_t ecs_id(ecs_f32_t) = FLECS_HI_COMPONENT_ID + 93; -const ecs_entity_t ecs_id(ecs_f64_t) = FLECS_HI_COMPONENT_ID + 94; -const ecs_entity_t ecs_id(ecs_string_t) = FLECS_HI_COMPONENT_ID + 95; -const ecs_entity_t ecs_id(ecs_entity_t) = FLECS_HI_COMPONENT_ID + 96; -const ecs_entity_t ecs_id(ecs_id_t) = FLECS_HI_COMPONENT_ID + 97; - -/** Meta module component ids */ -const ecs_entity_t ecs_id(EcsType) = FLECS_HI_COMPONENT_ID + 98; -const ecs_entity_t ecs_id(EcsTypeSerializer) = FLECS_HI_COMPONENT_ID + 99; -const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 100; -const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 101; -const ecs_entity_t ecs_id(EcsBitmask) = 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(EcsUnit) = FLECS_HI_COMPONENT_ID + 109; -const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 110; -const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 111; -const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 112; -#endif - -/* Doc module components */ -#ifdef FLECS_DOC -const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 113; -const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 114; -const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 115; -const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 116; -const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 117; -const ecs_entity_t EcsDocUuid = FLECS_HI_COMPONENT_ID + 118; -#endif - -/* REST module components */ -#ifdef FLECS_REST -const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 119; -#endif - -/* Max static id: - * #define EcsFirstUserEntityId (FLECS_HI_COMPONENT_ID + 128) */ - -/* Default lookup path */ -static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; - -/* 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 - -/* -- Private functions -- */ - -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); - - 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 NULL; -} - -ecs_stage_t* flecs_stage_from_world( - ecs_world_t **world_ptr) -{ - ecs_world_t *world = *world_ptr; - - ecs_assert(flecs_poly_is(world, ecs_world_t) || - flecs_poly_is(world, ecs_stage_t), - ECS_INTERNAL_ERROR, - NULL); - - if (flecs_poly_is(world, ecs_world_t)) { - return world->stages[0]; - } - - *world_ptr = ((ecs_stage_t*)world)->world; - return ECS_CONST_CAST(ecs_stage_t*, world); -} - -ecs_world_t* flecs_suspend_readonly( - const ecs_world_t *stage_world, - ecs_suspend_readonly_state_t *state) -{ - ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_world_t *world = - ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); - flecs_poly_assert(world, ecs_world_t); - - 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); - - if (!is_readonly && !stage->defer) { - state->is_readonly = false; - state->is_deferred = false; - return world; - } - - ecs_dbg_3("suspending readonly mode"); - - /* Cannot suspend when running with multiple threads */ - ecs_assert(!(world->flags & EcsWorldReadonly) || - !(world->flags & EcsWorldMultiThreaded), - ECS_INVALID_WHILE_READONLY, NULL); - - state->is_readonly = is_readonly; - state->is_deferred = stage->defer != 0; - state->cmd_flushing = stage->cmd_flushing; - - /* Silence readonly checks */ - world->flags &= ~EcsWorldReadonly; - stage->cmd_flushing = false; - - /* 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; - - flecs_commands_init(stage, &stage->cmd_stack[0]); - flecs_commands_init(stage, &stage->cmd_stack[1]); - stage->cmd = &stage->cmd_stack[0]; - - state->scope = stage->scope; - state->with = stage->with; - stage->defer = 0; - - return world; -} - -void flecs_resume_readonly( - ecs_world_t *world, - ecs_suspend_readonly_state_t *state) -{ - flecs_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); - - if (state->is_readonly || state->is_deferred) { - ecs_dbg_3("resuming readonly mode"); - - ecs_run_aperiodic(world, 0); - - /* 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; - } -} - -/* 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 flecs_eval_component_monitor( - ecs_world_t *world) -{ - flecs_poly_assert(world, ecs_world_t); - - if (!world->monitors.is_dirty) { - return; - } - - world->info.eval_comp_monitors_total ++; - - ecs_os_perf_trace_push("flecs.component_monitor.eval"); - - world->monitors.is_dirty = false; - - 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; - } - - m->is_dirty = false; - - 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_cache_notify(world, q, &(ecs_query_cache_event_t) { - .kind = EcsQueryTableRematch - }); - } - } - - ecs_os_perf_trace_pop("flecs.component_monitor.eval"); -} - -void flecs_monitor_mark_dirty( - ecs_world_t *world, - ecs_entity_t id) -{ - 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_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; - } - } -} - -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; -} - -void flecs_monitor_unregister( - 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; - if (!ecs_map_is_init(monitors)) { - return; - } - - 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 (!count) { - ecs_vec_fini_t(&world->allocator, &m->queries, ecs_query_t*); - ecs_map_remove_free(monitors, id); - } - - if (!ecs_map_count(monitors)) { - ecs_map_fini(monitors); - } -} - -static -void flecs_init_store( - ecs_world_t *world) -{ - 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); - - /* Initialize root table */ - flecs_init_root_table(world); -} - -static -void flecs_clean_tables( - ecs_world_t *world) -{ - 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_t(&world->store.tables, - ecs_table_t, 0); - (void)first; - - 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); - } - - /* 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); - } - - /* Clear the root table */ - if (count) { - flecs_table_reset(world, &world->store.root); - } -} - -static -void flecs_fini_root_tables( - ecs_world_t *world, - ecs_id_record_t *idr, - bool fini_targets) -{ - 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(&idr->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) { - 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)) { - 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; - } - } - } -} - -static -void flecs_fini_roots( - ecs_world_t *world) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); - - /* 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, idr, true); - flecs_defer_end(world, world->stages[0]); - - flecs_defer_begin(world, world->stages[0]); - flecs_fini_root_tables(world, idr, false); - flecs_defer_end(world, world->stages[0]); -} - -static -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); - - 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); - - 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); -} - -static -void flecs_world_allocators_init( - ecs_world_t *world) -{ - ecs_world_allocators_t *a = &world->allocators; - - flecs_allocator_init(&world->allocator); - - ecs_map_params_init(&a->ptr, &world->allocator); - ecs_map_params_init(&a->query_table_list, &world->allocator); - - flecs_ballocator_init_t(&a->query_table, ecs_query_cache_table_t); - flecs_ballocator_init_t(&a->query_table_match, ecs_query_cache_table_match_t); - 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->id_record, ecs_id_record_t); - flecs_ballocator_init_n(&a->id_record_chunk, ecs_id_record_t, FLECS_SPARSE_PAGE_SIZE); - 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_ballocator_init_t(&a->hashmap, ecs_hashmap_t); - flecs_table_diff_builder_init(world, &world->allocators.diff_builder); -} - -static -void flecs_world_allocators_fini( - ecs_world_t *world) -{ - ecs_world_allocators_t *a = &world->allocators; - - ecs_map_params_fini(&a->ptr); - ecs_map_params_fini(&a->query_table_list); - flecs_ballocator_fini(&a->query_table); - flecs_ballocator_fini(&a->query_table_match); - flecs_ballocator_fini(&a->graph_edge_lo); - flecs_ballocator_fini(&a->graph_edge); - flecs_ballocator_fini(&a->id_record); - flecs_ballocator_fini(&a->id_record_chunk); - flecs_ballocator_fini(&a->table_diff); - flecs_ballocator_fini(&a->sparse_chunk); - flecs_ballocator_fini(&a->hashmap); - flecs_table_diff_builder_fini(world, &world->allocators.diff_builder); - - flecs_allocator_fini(&world->allocator); -} - -#define ECS_STRINGIFY_INNER(x) #x -#define ECS_STRINGIFY(x) ECS_STRINGIFY_INNER(x) - -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 - -static const char *flecs_addons_info[] = { -#ifdef FLECS_CPP - "FLECS_CPP", -#endif -#ifdef FLECS_MODULE - "FLECS_MODULE", -#endif -#ifdef FLECS_SCRIPT - "FLECS_SCRIPT", -#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_SCRIPT - "FLECS_SCRIPT", -#endif -#ifdef FLECS_HTTP - "FLECS_HTTP", -#endif -#ifdef FLECS_REST - "FLECS_REST", -#endif -NULL -}; - -static const ecs_build_info_t flecs_build_info = { - .compiler = flecs_compiler_info, - .addons = flecs_addons_info, -#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 -}; - -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); - - ecs_trace("flecs version %s", bi->version); - - ecs_trace("addons included in build:"); - ecs_log_push(); - - const char **addon = bi->addons; - do { - ecs_trace(addon[0]); - } while ((++ addon)[0]); - ecs_log_pop(); - - 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"); - } - - ecs_trace("compiled with %s", bi->compiler); -} - -/* -- Public functions -- */ - -const ecs_build_info_t* ecs_get_build_info(void) { - return &flecs_build_info; -} - -const ecs_world_info_t* ecs_get_world_info( - const ecs_world_t *world) -{ - world = ecs_get_world(world); - return &world->info; -} - -ecs_world_t *ecs_mini(void) { -#ifdef FLECS_OS_API_IMPL - ecs_set_os_api_impl(); -#endif - ecs_os_init(); - - ecs_trace("#[bold]bootstrapping world"); - ecs_log_push(); - - ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); - - if (!ecs_os_has_heap()) { - ecs_abort(ECS_MISSING_OS_API, NULL); - } - - if (!ecs_os_has_threading()) { - ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); - } - - if (!ecs_os_has_time()) { - ecs_trace("time management not available"); - } - - flecs_log_build_info(); - - 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); - - world->flags |= EcsWorldInit; - - flecs_world_allocators_init(world); - ecs_allocator_t *a = &world->allocator; - - ecs_map_init(&world->type_info, a); - ecs_map_init_w_params(&world->id_index_hi, &world->allocators.ptr); - world->id_index_lo = ecs_os_calloc_n( - ecs_id_record_t*, FLECS_HI_ID_RECORD_ID); - flecs_observable_init(&world->observable); - - 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); - - world->info.time_scale = 1.0; - if (ecs_os_has_time()) { - ecs_os_get_time(&world->world_start_time); - } - - 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); - - flecs_bootstrap(world); - - world->flags &= ~EcsWorldInit; - - ecs_trace("world ready!"); - ecs_log_pop(); - - return world; -} - -ecs_world_t *ecs_init(void) { - ecs_world_t *world = ecs_mini(); - -#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 - return world; -} - -ecs_world_t* ecs_init_w_args( - int argc, - char *argv[]) -{ - ecs_world_t *world = ecs_init(); - - (void)argc; - (void)argv; - -#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 - - return world; -} - -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; -} - -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; -} - -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 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_t(tables, ecs_table_t, i); - flecs_table_notify(world, table, id, event); - } - - /* If id is specified, only broadcast to tables with id */ - } else { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return; - } - - ecs_table_cache_iter_t it; - const ecs_table_record_t *tr; - - flecs_table_cache_all_iter(&idr->cache, &it); - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - flecs_table_notify(world, tr->hdr.table, id, event); - } - } -} - -void flecs_default_ctor( - void *ptr, - int32_t count, - const ecs_type_info_t *ti) -{ - ecs_os_memset(ptr, 0, ti->size * count); -} - -static -void flecs_default_copy_ctor(void *dst_ptr, const void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - const ecs_type_hooks_t *cl = &ti->hooks; - cl->ctor(dst_ptr, count, ti); - cl->copy(dst_ptr, src_ptr, count, ti); -} - -static -void flecs_default_move_ctor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - const ecs_type_hooks_t *cl = &ti->hooks; - cl->ctor(dst_ptr, count, ti); - cl->move(dst_ptr, src_ptr, count, ti); -} - -static -void flecs_default_ctor_w_move_w_dtor(void *dst_ptr, void *src_ptr, - int32_t count, const ecs_type_info_t *ti) -{ - 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 -void flecs_default_move_ctor_w_dtor(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_ctor(dst_ptr, src_ptr, count, ti); - cl->dtor(src_ptr, count, ti); -} - -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); -} - -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); -} - -static -void flecs_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 ecs_type_hooks_t *cl = &ti->hooks; - cl->move(dst_ptr, src_ptr, count, ti); - cl->dtor(src_ptr, count, ti); -} - -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); -} - -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); -} - -ECS_NORETURN static -void flecs_copy_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 assignment for %s", ti->name); -} - -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); -} - -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); -} - -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); -} - -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); - - /* TODO: Refactor to enforce flags consistency: */ - ecs_flags32_t flags = h->flags; - flags &= ~((ecs_flags32_t)ECS_TYPE_HOOKS); - - /* TODO: enable asserts once RTT API is updated */ - /* - ecs_check(!(h->flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) || !h->ctor, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) || !h->dtor, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_COPY_ILLEGAL) || !h->copy, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) || !h->move, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL) || !h->copy_ctor, - ECS_INVALID_PARAMETER, - "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL) || !h->move_ctor, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL) || - !h->ctor_move_dtor, ECS_INVALID_PARAMETER, - "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) || !h->move_dtor, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - */ - - flecs_stage_from_world(&world); - - /* 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)); - - ecs_type_info_t *ti = flecs_type_info_ensure(world, component); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_check(!ti->component || ti->component == component, - ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - - if (!ti->size) { - const EcsComponent *component_ptr = ecs_get( - world, component, EcsComponent); - - /* Cannot register lifecycle actions for things that aren't a component */ - ecs_check(component_ptr != NULL, ECS_INVALID_PARAMETER, - "provided entity is not a component"); - ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, - "cannot register type hooks for type with size 0"); - - 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->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->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) { - if(flags & ECS_TYPE_HOOK_COPY_ILLEGAL || flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) { - flags |= ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL; - } else if(h->copy) { - ti->hooks.copy_ctor = flecs_default_copy_ctor; - } - } - - if (!h->move_ctor) { - if(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL || flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) { - flags |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; - } else 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 { - /* 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 (!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 { - 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; - } - } - - ti->hooks.flags = flags; - - 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(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_COPY_CTOR_ILLEGAL) { - ti->hooks.copy_ctor = flecs_copy_ctor_illegal; - } - - if(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL) { - ti->hooks.move_ctor = flecs_move_ctor_illegal; - } - - if(ti->hooks.flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL) { - ti->hooks.ctor_move_dtor = flecs_move_ctor_illegal; - } - - if(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) { - ti->hooks.ctor_move_dtor = flecs_move_ctor_illegal; - } - -error: - return; -} - -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; -} - -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); - - 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); - - 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_check((world->flags & EcsWorldFrameInProgress), ECS_INVALID_OPERATION, - "cannot register post frame action while frame is not in progress"); - - 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; -} - -/* Unset data in tables */ -static -void flecs_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_t(tables, ecs_table_t, i); - flecs_table_remove_actions(world, table); - } -} - -/* 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_vec_fini_t(NULL, &world->fini_actions, ecs_action_elem_t); -} - -/* Cleanup remaining type info elements */ -static -void flecs_fini_type_info( - ecs_world_t *world) -{ - 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); -} - -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 { - return ecs_get_target(world, e, EcsOneOf, 0); - } - } else { - return 0; - } -} - -/* The destroyer of worlds */ -int ecs_fini( - ecs_world_t *world) -{ - 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"); - - ecs_trace("#[bold]shutting down world"); - ecs_log_push(); - - world->flags |= EcsWorldQuit; - - /* 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(); - - world->flags |= EcsWorldFini; - - /* 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(); - - ecs_dbg_1("#[bold]cleanup remaining entities"); - ecs_log_push_1(); - - /* Operations invoked during OnRemove/destructors are deferred and - * will be discarded after world cleanup */ - flecs_defer_begin(world, world->stages[0]); - - /* Run OnRemove actions for components while the store is still - * unmodified by cleanup. */ - flecs_fini_unset_tables(world); - - /* This will destroy all entities and components. */ - flecs_fini_store(world); - - /* 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(); - - /* 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); - - /* 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); - } - - /* After this point no more user code is invoked */ - - ecs_dbg_1("#[bold]cleanup world data structures"); - ecs_log_push_1(); - flecs_entities_fini(world); - flecs_fini_id_records(world); - flecs_fini_type_info(world); - flecs_observable_fini(&world->observable); - flecs_name_index_fini(&world->aliases); - flecs_name_index_fini(&world->symbols); - ecs_set_stage_count(world, 0); - ecs_vec_fini_t(&world->allocator, &world->component_ids, ecs_id_t); - ecs_log_pop_1(); - - flecs_world_allocators_fini(world); - - /* End of the world */ - flecs_poly_free(world, ecs_world_t); - ecs_os_fini(); - - ecs_trace("world destroyed, bye!"); - ecs_log_pop(); - - return 0; -} - -bool ecs_is_fini( - const ecs_world_t *world) -{ - ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return ECS_BIT_IS_SET(world->flags, EcsWorldFini); -} - -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); -} - -void flecs_eval_component_monitors( - ecs_world_t *world) -{ - 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; -} - -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; -} - -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_measure_frame_time(world, true); - world->info.target_fps = fps; -error: - return; -} - -void ecs_set_default_query_flags( - ecs_world_t *world, - ecs_flags32_t flags) -{ - flecs_poly_assert(world, ecs_world_t); - world->default_query_flags = flags; -} - -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; -} - -void* ecs_get_binding_ctx( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); - return world->binding_ctx; -error: - return NULL; -} - -void ecs_set_ctx( - ecs_world_t *world, - void *ctx, - ecs_ctx_free_t ctx_free) -{ - 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; - } - - uint32_t start = (uint32_t)id_start; - uint32_t end = (uint32_t)id_end; - - flecs_entities_max_id(world) = start - 1; - - world->info.min_id = start; - world->info.max_id = end; -error: - return; -} - -bool ecs_enable_range_check( - ecs_world_t *world, - bool enable) -{ - 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; -} - -const ecs_type_info_t* flecs_type_info_get( - const ecs_world_t *world, - ecs_entity_t component) -{ - flecs_poly_assert(world, ecs_world_t); - - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); - - return ecs_map_get_deref(&world->type_info, ecs_type_info_t, component); -} - -ecs_type_info_t* flecs_type_info_ensure( - ecs_world_t *world, - ecs_entity_t component) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - - 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 { - ti_mut = ECS_CONST_CAST(ecs_type_info_t*, ti); - } - - 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); - } - } - } - - return ti_mut; -} - -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; - - flecs_entities_ensure(world, component); - - 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); - } - } - - /* Set type info for id record of component */ - ecs_id_record_t *idr = flecs_id_record_ensure(world, component); - changed |= flecs_id_record_set_type_info(world, idr, ti); - bool is_tag = idr->flags & EcsIdTag; - - /* All id records with component as relationship inherit type info */ - idr = flecs_id_record_ensure(world, ecs_pair(component, EcsWildcard)); - do { - if (is_tag) { - changed |= flecs_id_record_set_type_info(world, idr, NULL); - } else if (ti) { - changed |= flecs_id_record_set_type_info(world, idr, ti); - } else if ((idr->type_info != NULL) && - (idr->type_info->component == component)) - { - changed |= flecs_id_record_set_type_info(world, idr, NULL); - } - } while ((idr = idr->first.next)); - - /* All non-tag id records with component as object inherit type info, - * if relationship doesn't have type info */ - idr = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, component)); - do { - if (!(idr->flags & EcsIdTag) && !idr->type_info) { - changed |= flecs_id_record_set_type_info(world, idr, ti); - } - } while ((idr = idr->first.next)); - - /* Type info of (*, component) should always point to component */ - ecs_assert(flecs_id_record_get(world, ecs_pair(EcsWildcard, component))-> - type_info == ti, ECS_INTERNAL_ERROR, NULL); - - return changed; -} - -void flecs_type_info_fini( - ecs_type_info_t *ti) -{ - if (ti->hooks.ctx_free) { - ti->hooks.ctx_free(ti->hooks.ctx); - } - 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; - } - - ti->size = 0; - ti->alignment = 0; -} - -void flecs_type_info_free( - ecs_world_t *world, - ecs_entity_t component) -{ - flecs_poly_assert(world, ecs_world_t); - - 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; - } - - 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); - } -} - -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 4 times smaller than the time one frame - * should take. */ - ecs_ftime_t sleep_time = sleep / (ecs_ftime_t)4.0; - - 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; -} - -void flecs_delete_table( - ecs_world_t *world, - ecs_table_t *table) -{ - flecs_poly_assert(world, ecs_world_t); - flecs_table_fini(world, table); -} - -static -void flecs_process_empty_queries( - ecs_world_t *world) -{ - flecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t *idr = flecs_id_record_get(world, - ecs_pair(ecs_id(EcsPoly), EcsQuery)); - if (!idr) { - return; - } - - /* Make sure that we defer adding the inactive tags until after iterating - * the query */ - flecs_defer_begin(world, world->stages[0]); - - ecs_table_cache_iter_t it; - const ecs_table_record_t *tr; - if (flecs_table_cache_iter(&idr->cache, &it)) { - 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); - int32_t i, count = ecs_table_count(table); - - for (i = 0; i < count; i ++) { - ecs_query_t *query = queries[i].poly; - const ecs_entity_t *entities = ecs_table_entities(table); - if (!ecs_query_is_true(query)) { - ecs_add_id(world, entities[i], EcsEmpty); - } - } - } - } - - flecs_defer_end(world, world->stages[0]); -} - -bool ecs_id_in_use( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return false; - } - - return (flecs_table_cache_count(&idr->cache) != 0); -} - -void ecs_run_aperiodic( - ecs_world_t *world, - ecs_flags32_t flags) -{ - flecs_poly_assert(world, ecs_world_t); - - if ((flags & EcsAperiodicEmptyQueries)) { - flecs_process_empty_queries(world); - } - - if (!flags || (flags & EcsAperiodicComponentMonitors)) { - flecs_eval_component_monitors(world); - } -} - -int32_t ecs_delete_empty_tables( - ecs_world_t *world, - const ecs_delete_empty_tables_desc_t *desc) -{ - flecs_poly_assert(world, ecs_world_t); - - ecs_os_perf_trace_push("flecs.delete_empty_tables"); - - ecs_time_t start = {0}, cur = {0}; - int32_t delete_count = 0; - bool time_budget = false; - - ecs_id_t id = desc->id; - uint16_t clear_generation = desc->clear_generation; - uint16_t delete_generation = desc->delete_generation; - int32_t min_id_count = desc->min_id_count; - double time_budget_seconds = desc->time_budget_seconds; - - if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { - ecs_time_measure(&start); - } - - if (ECS_NEQZERO(time_budget_seconds)) { - time_budget = true; - } - - if (!id) { - id = EcsAny; /* Iterate all empty tables */ - } - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - ecs_table_cache_iter_t it; - if (idr && flecs_table_cache_empty_iter((ecs_table_cache_t*)idr, &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; - } - } - - ecs_table_t *table = tr->hdr.table; - ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); - - if (table->type.count < min_id_count) { - continue; - } - - uint16_t gen = ++ table->_->generation; - if (delete_generation && (gen > delete_generation)) { - flecs_table_fini(world, table); - delete_count ++; - } else if (clear_generation && (gen > clear_generation)) { - flecs_table_shrink(world, table); - } - } - } - -done: - ecs_os_perf_trace_pop("flecs.delete_empty_tables"); - - return delete_count; -} - -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; -} - -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; - } -} - -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; - } -} - -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); - - if (index >= ecs_vec_count(&world->component_ids)) { - return 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; - } - - 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; - } - - return result; -} - -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); - - 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; -} - -/** - * @file addons/alerts.c - * @brief Alerts addon. - */ - - -#ifdef FLECS_ALERTS - -ECS_COMPONENT_DECLARE(FlecsAlerts); - -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); -}) - -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); -}) - -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}; - - 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}; - - 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; -}) - -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); -}) - -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}; -}) - -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; -}) - -static -ECS_COPY(EcsAlertInstance, dst, src, { - ecs_os_free(dst->message); - dst->message = ecs_os_strdup(src->message); -}) - -static -void flecs_alerts_add_alert_to_src( - ecs_world_t *world, - ecs_entity_t source, - ecs_entity_t alert, - ecs_entity_t alert_instance) -{ - EcsAlertsActive *active = ecs_ensure( - world, source, EcsAlertsActive); - ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); - - 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 *ptr = ecs_map_ensure(&active->alerts, alert); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ptr[0] = alert_instance; - ecs_modified(world, source, EcsAlertsActive); -} - -static -void flecs_alerts_remove_alert_from_src( - ecs_world_t *world, - ecs_entity_t source, - ecs_entity_t alert) -{ - EcsAlertsActive *active = ecs_ensure( - world, source, EcsAlertsActive); - ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_remove(&active->alerts, alert); - - 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 --; - } - - if (!ecs_map_count(&active->alerts)) { - ecs_remove(world, source, EcsAlertsActive); - } else { - ecs_modified(world, source, EcsAlertsActive); - } -} - -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_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; - } - } - } - } - - return 0; -} - -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; - } - - bool has_error = ECS_NEQ(ranges->error.min, ranges->error.max); - bool has_warning = ECS_NEQ(ranges->warning.min, ranges->warning.max); - - 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; - } -} - -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); - - 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; - } - - 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_iter_t rit = ecs_query_iter(world, q); - rit.flags |= EcsIterNoData; - - 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); - } - } - } - } - } - } -} - -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; - } - - 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; - } - - flecs_poly_assert(query, ecs_query_t); - - ecs_id_t member_id = alert->id; - const EcsMemberRanges *ranges = NULL; - if (member_id) { - ranges = ecs_ref_get(world, &alert->ranges, EcsMemberRanges); - } - - 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; - - /* 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; - } - - /* 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); - - if (ecs_query_next(&rit)) { - bool match = true; - - /* 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); - } - - 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; - } - } - } - - 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 = rit.variable_count > 1; - } - } - - 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); - } - - /* 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; - } - } - - /* 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_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_query_desc_t private_desc = desc->query; - private_desc.entity = result; - - ecs_query_t *q = ecs_query_init(world, &private_desc); - if (!q) { - ecs_err("failed to create alert filter"); - return 0; - } - - if (!(q->flags & EcsQueryMatchThis)) { - ecs_err("alert filter must have at least one '$this' term"); - ecs_query_fini(q); - return 0; - } - - /* 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; - - /* 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; - } - } - } - } - - /* 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_id_record_t *idr = flecs_id_record_ensure(world, alert->id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - if (!idr->type_info) { - ecs_err("ecs_alert_desc_t::id must be a component"); - goto error; - } - - ecs_entity_t type = idr->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 = idr->type_info->size; - alert->kind = pr->kind; - alert->ranges = ecs_ref_init(world, desc->member, EcsMemberRanges); - alert->var_id = var_id; - } - - ecs_modified(world, result, EcsAlert); - - /* 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 - } - - 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 result; -error: - if (result) { - ecs_delete(world, result); - } - return 0; -} - -int32_t ecs_get_alert_count( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t alert) -{ - 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; - } - - if (alert) { - return ecs_map_get(&active->alerts, alert) != NULL; - } - - return ecs_map_count(&active->alerts); -error: - return 0; -} - -ecs_entity_t ecs_get_alert( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t alert) -{ - 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_entity_t *ptr = ecs_map_get(&active->alerts, alert); - if (ptr) { - return ptr[0]; - } - -error: - return 0; -} - -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_DOC - ECS_IMPORT(world, FlecsDoc); -#endif - - 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); - - ecs_set_name_prefix(world, "EcsAlert"); - ECS_COMPONENT_DEFINE(world, EcsAlertInstance); - ECS_COMPONENT_DEFINE(world, EcsAlertTimeout); - - ECS_TAG_DEFINE(world, EcsAlertInfo); - ECS_TAG_DEFINE(world, EcsAlertWarning); - ECS_TAG_DEFINE(world, EcsAlertError); - ECS_TAG_DEFINE(world, EcsAlertCritical); - - ecs_add_id(world, ecs_id(EcsAlert), EcsPairIsTag); - ecs_add_id(world, ecs_id(EcsAlert), EcsExclusive); - ecs_add_id(world, ecs_id(EcsAlertsActive), EcsPrivate); - - ecs_struct(world, { - .entity = ecs_id(EcsAlertInstance), - .members = { - { .name = "message", .type = ecs_id(ecs_string_t) } - } - }); - - ecs_set_hooks(world, EcsAlert, { - .ctor = ecs_ctor(EcsAlert), - .dtor = ecs_dtor(EcsAlert), - .move = ecs_move(EcsAlert) - }); - - ecs_set_hooks(world, EcsAlertsActive, { - .ctor = ecs_ctor(EcsAlertsActive), - .dtor = ecs_dtor(EcsAlertsActive), - .move = ecs_move(EcsAlertsActive) - }); - - ecs_set_hooks(world, EcsAlertInstance, { - .ctor = flecs_default_ctor, - .dtor = ecs_dtor(EcsAlertInstance), - .move = ecs_move(EcsAlertInstance), - .copy = ecs_copy(EcsAlertInstance) - }); - - 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) } - } - }); - - ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, - Alert, - (Poly, Query)); - - ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance, - flecs.metrics.Source, - flecs.metrics.Value, - ?Timeout, - ?Disabled); - - ecs_system(world, { - .entity = ecs_id(MonitorAlerts), - .immediate = true, - .interval = (ecs_ftime_t)0.5 - }); - - ecs_system(world, { - .entity = ecs_id(MonitorAlertInstances), - .interval = (ecs_ftime_t)0.5 - }); -} - -#endif - -/** - * @file addons/app.c - * @brief App addon. - */ - - -#ifdef FLECS_APP - -static -int flecs_default_run_action( - ecs_world_t *world, - ecs_app_desc_t *desc) -{ - if (desc->init) { - desc->init(world); - } - - 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) { } - } - - /* 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 - - if (result == 1) { - return 0; /* Normal exit */ - } else { - return result; /* Error code */ - } -} - -static -int flecs_default_frame_action( - ecs_world_t *world, - const ecs_app_desc_t *desc) -{ - return !ecs_progress(world, desc->delta_time); -} - -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; - -/* Serve REST API from wasm image when running in emscripten */ -#ifdef ECS_TARGET_EM -#include - -ecs_http_server_t *flecs_wasm_rest_server = NULL; - -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"); - - 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); - } - } -} -#endif - -int ecs_app_run( - ecs_world_t *world, - ecs_app_desc_t *desc) -{ - ecs_app_desc = *desc; - -#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 - - /* 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 - } - - /* 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 run_action(world, &ecs_app_desc); -} - -int ecs_app_run_frame( - ecs_world_t *world, - const ecs_app_desc_t *desc) -{ - return frame_action(world, desc); -} - -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; - } - - run_action = callback; - - return 0; -} - -int ecs_app_set_frame_action( - ecs_app_frame_action_t callback) -{ - if (frame_action != flecs_default_frame_action && frame_action != callback) { - ecs_err("frame action already set"); - return -1; - } - - frame_action = callback; - - return 0; -} - -#endif - -/** - * @file addons/doc.c - * @brief Doc addon. - */ - - -#ifdef FLECS_DOC - -static ECS_COPY(EcsDocDescription, dst, src, { - ecs_os_strset((char**)&dst->value, src->value); - -}) - -static ECS_MOVE(EcsDocDescription, dst, src, { - ecs_os_free((char*)dst->value); - dst->value = src->value; - src->value = NULL; -}) - -static ECS_DTOR(EcsDocDescription, ptr, { - ecs_os_free((char*)ptr->value); -}) - -static -void flecs_doc_set( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t kind, - const char *value) -{ - 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); - } -} - -void ecs_doc_set_uuid( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - flecs_doc_set(world, entity, EcsDocUuid, name); -} - -void ecs_doc_set_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - flecs_doc_set(world, entity, EcsName, name); -} - -void ecs_doc_set_brief( - ecs_world_t *world, - ecs_entity_t entity, - const char *brief) -{ - flecs_doc_set(world, entity, EcsDocBrief, brief); -} - -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); -} - -void ecs_doc_set_color( - ecs_world_t *world, - ecs_entity_t entity, - const char *color) -{ - flecs_doc_set(world, entity, EcsDocColor, color); -} - -const char* ecs_doc_get_uuid( - const ecs_world_t *world, - ecs_entity_t entity) -{ - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocUuid); - if (ptr) { - return ptr->value; - } else { - return NULL; - } -} - -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); - } -} - -const char* ecs_doc_get_brief( - const ecs_world_t *world, - ecs_entity_t entity) -{ - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocBrief); - if (ptr) { - return ptr->value; - } else { - return NULL; - } -} - -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; - } -} - -const char* ecs_doc_get_link( - const ecs_world_t *world, - ecs_entity_t entity) -{ - const EcsDocDescription *ptr = ecs_get_pair( - world, entity, EcsDocDescription, EcsDocLink); - if (ptr) { - return ptr->value; - } else { - return NULL; - } -} - -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; - } -} - -/* Doc definitions for core components */ -static -void flecs_doc_import_core_definitions( - ecs_world_t *world) -{ - 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, "Module with builtin components"); - ecs_doc_set_brief(world, EcsFlecsInternals, "Module with internal entities"); - - ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); - - ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to 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, EcsPrivate, "Tag that is added to private components"); - ecs_doc_set_brief(world, EcsFlag, "Internal tag for tracking ids with special id flags"); - ecs_doc_set_brief(world, ecs_id(EcsPoly), "Internal component that stores pointer to poly objects"); - - ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); - ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to store entity name"); - ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to store entity symbol"); - ecs_doc_set_brief(world, EcsAlias, "Tag used with EcsIdentifier to store entity alias"); - - ecs_doc_set_brief(world, EcsQuery, "Tag added to query entities"); - ecs_doc_set_brief(world, EcsObserver, "Tag added to observer entities"); - - ecs_doc_set_brief(world, EcsTransitive, "Trait that enables transitive evaluation of relationships"); - ecs_doc_set_brief(world, EcsReflexive, "Trait that enables reflexive evaluation of relationships"); - ecs_doc_set_brief(world, EcsFinal, "Trait that indicates an entity cannot be inherited from"); - ecs_doc_set_brief(world, EcsDontInherit, "Trait that indicates it should not be inherited"); - ecs_doc_set_brief(world, EcsPairIsTag, "Trait that ensures a pair cannot contain a value"); - ecs_doc_set_brief(world, EcsAcyclic, "Trait that indicates a relationship is acyclic"); - ecs_doc_set_brief(world, EcsTraversable, "Trait that indicates a relationship is traversable"); - ecs_doc_set_brief(world, EcsExclusive, "Trait that ensures a relationship can only have one target"); - ecs_doc_set_brief(world, EcsSymmetric, "Trait that causes a relationship to be two-way"); - ecs_doc_set_brief(world, EcsWith, "Trait for adding additional components when a component is added"); - ecs_doc_set_brief(world, EcsOneOf, "Trait that enforces target of relationship is a child of "); - ecs_doc_set_brief(world, EcsOnDelete, "Cleanup trait for specifying what happens when component is deleted"); - ecs_doc_set_brief(world, EcsOnDeleteTarget, "Cleanup trait for specifying what happens when pair target is deleted"); - ecs_doc_set_brief(world, EcsRemove, "Cleanup action used with OnDelete/OnDeleteTarget"); - ecs_doc_set_brief(world, EcsDelete, "Cleanup action used with OnDelete/OnDeleteTarget"); - ecs_doc_set_brief(world, EcsPanic, "Cleanup action used with OnDelete/OnDeleteTarget"); - ecs_doc_set_brief(world, ecs_id(EcsDefaultChildComponent), "Sets default component hint for children of entity"); - ecs_doc_set_brief(world, EcsIsA, "Relationship used for expressing inheritance"); - ecs_doc_set_brief(world, EcsChildOf, "Relationship used for expressing hierarchies"); - ecs_doc_set_brief(world, EcsDependsOn, "Relationship used for expressing dependencies"); - ecs_doc_set_brief(world, EcsSlotOf, "Relationship used for expressing prefab slots"); - ecs_doc_set_brief(world, EcsOnAdd, "Event emitted when component is added"); - ecs_doc_set_brief(world, EcsOnRemove, "Event emitted when component is removed"); - ecs_doc_set_brief(world, EcsOnSet, "Event emitted when component is set"); - ecs_doc_set_brief(world, EcsMonitor, "Marker used to create monitor observers"); - ecs_doc_set_brief(world, EcsOnTableCreate, "Event emitted when table is created"); - ecs_doc_set_brief(world, EcsOnTableDelete, "Event emitted when table is deleted"); - - ecs_doc_set_brief(world, EcsThis, "Query marker to express $this variable"); - ecs_doc_set_brief(world, EcsWildcard, "Query marker to express match all wildcard"); - ecs_doc_set_brief(world, EcsAny, "Query marker to express match at least one wildcard"); - - ecs_doc_set_brief(world, EcsPredEq, "Query marker to express == operator"); - ecs_doc_set_brief(world, EcsPredMatch, "Query marker to express ~= operator"); - ecs_doc_set_brief(world, EcsPredLookup, "Query marker to express by-name lookup"); - ecs_doc_set_brief(world, EcsScopeOpen, "Query marker to express scope open"); - ecs_doc_set_brief(world, EcsScopeClose, "Query marker to express scope close"); - ecs_doc_set_brief(world, EcsEmpty, "Tag used to indicate a query has no results"); -} - -/* Doc definitions for doc components */ -static -void flecs_doc_import_doc_definitions( - ecs_world_t *world) -{ - ecs_entity_t doc = ecs_lookup(world, "flecs.doc"); - ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); - - ecs_doc_set_brief(world, EcsDocBrief, "Brief description"); - ecs_doc_set_brief(world, EcsDocDetail, "Detailed description"); - ecs_doc_set_brief(world, EcsDocLink, "Link to additional documentation"); - ecs_doc_set_brief(world, EcsDocColor, "Color hint for entity"); - - 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"); -} - -void FlecsDocImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsDoc); - - ecs_set_name_prefix(world, "EcsDoc"); - - 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) - }); - - ecs_add_pair(world, ecs_id(EcsDocDescription), EcsOnInstantiate, EcsDontInherit); - ecs_add_id(world, ecs_id(EcsDocDescription), EcsPrivate); - - flecs_doc_import_core_definitions(world); - flecs_doc_import_doc_definitions(world); -} - -#endif - -/** - * @file addons/flecs_cpp.c - * @brief Utilities for C++ addon. - */ - -#include - -/* 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 " - -#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 -ecs_size_t ecs_cpp_strip_prefix( - char *typeName, - ecs_size_t len, - const char *prefix, - ecs_size_t prefix_len) -{ - 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; -} - -static -void ecs_cpp_trim_type_name( - char *typeName) -{ - ecs_size_t len = ecs_os_strlen(typeName); - - 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'; - } - - /* 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'; - } - 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; - } - } - } -} - -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; - } - } - - symbol_name[i] = '\0'; - - return symbol_name; -} - -static -const char* flecs_cpp_func_rchr( - const char *func_name, - ecs_size_t func_name_len, - ecs_size_t func_back_len, - char ch) -{ - const char *r = strrchr(func_name, ch); - if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, func_back_len))) { - return NULL; - } - return r; -} - -static -const char* flecs_cpp_func_max( - const char *a, - const char *b) -{ - if (a > b) return a; - return b; -} - -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; -} - -// 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; - } - - 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 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_os_free(path); - - return type_name; -} - -ecs_entity_t ecs_cpp_component_find( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - const char *symbol, - size_t size, - size_t alignment, - bool implicit_name, - bool *existing_out) -{ - (void)size; - (void)alignment; - - ecs_assert(existing_out != NULL, ECS_INTERNAL_ERROR, NULL); - - /* 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); - *existing_out = ent != 0 && ecs_has(world, ent, EcsComponent); - } - ecs_set_scope(world, prev_scope); - - /* If entity exists, compare symbol name to ensure that the component - * we are trying to register under this name is the same */ - if (ent) { - const EcsComponent *component = ecs_get(world, ent, EcsComponent); - if (component != NULL) { - const char *sym = ecs_get_symbol(world, ent); - if (sym && ecs_os_strcmp(sym, 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 (not symbol). - * - * 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, ent); - if (ecs_os_strcmp(type_path, 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); - } - - 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); - } - - ecs_os_free(type_path); - } else if (!sym) { - ecs_set_symbol(world, ent, symbol); - } - } - - /* If no entity is found, lookup symbol to check if the component was - * registered under a different name. */ - } else if (!implicit_name) { - ent = ecs_lookup_symbol(world, symbol, false, false); - ecs_assert(ent == 0 || (ent == id), - ECS_INCONSISTENT_COMPONENT_ID, symbol); - } - - return ent; -} - -ecs_entity_t ecs_cpp_component_register( - 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, - bool *existing_out) -{ - char *existing_name = NULL; - - ecs_assert(existing_out != NULL, ECS_INTERNAL_ERROR, 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 (!id) { - if (!name) { - // If no name was provided first check if a type with the provided - // symbol was already registered. - id = ecs_lookup_symbol(world, symbol, false, false); - if (id) { - existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::"); - name = existing_name; - *existing_out = true; - } else { - // If type is not yet known, derive from type name - name = ecs_cpp_trim_module(world, type_name); - } - } - } 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_entity_t entity; - if (is_component || size != 0) { - entity = ecs_entity(world, { - .id = s_id, - .name = name, - .sep = "::", - .root_sep = "::", - .symbol = symbol, - .use_low_id = true - }); - ecs_assert(entity != 0, ECS_INVALID_OPERATION, - "registration failed for component %s", name); - - entity = ecs_component_init(world, &(ecs_component_desc_t){ - .entity = entity, - .type.size = flecs_uto(int32_t, size), - .type.alignment = flecs_uto(int32_t, alignment) - }); - ecs_assert(entity != 0, ECS_INVALID_OPERATION, - "registration failed for component %s", name); - } else { - entity = ecs_entity(world, { - .id = s_id, - .name = name, - .sep = "::", - .root_sep = "::", - .symbol = symbol, - .use_low_id = true - }); - } - - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); - ecs_os_free(existing_name); - - return entity; -} - -void ecs_cpp_enum_init( - ecs_world_t *world, - ecs_entity_t id, - ecs_entity_t underlying_type) -{ - (void)world; - (void)id; - (void)underlying_type; -#ifdef FLECS_META - ecs_suspend_readonly_state_t readonly_state; - world = flecs_suspend_readonly(world, &readonly_state); - ecs_set(world, id, EcsEnum, { .underlying_type = underlying_type }); - flecs_resume_readonly(world, &readonly_state); -#endif -} - -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) -{ -#ifdef FLECS_META - ecs_suspend_readonly_state_t readonly_state; - world = flecs_suspend_readonly(world, &readonly_state); - - 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 ++; - } - } - - 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); - -#ifdef FLECS_DEBUG - const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); - ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); -#endif - - ecs_set_id(world, id, ecs_pair(EcsConstant, value_type), value_size, value); - - flecs_resume_readonly(world, &readonly_state); - - 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); - } - - return id; -#else - (void)world; - (void)parent; - (void)id; - (void)name; - (void)value; - (void)value_type; - (void)value_size; - ecs_err("enum reflection not supported without FLECS_META addon"); - return 0; -#endif -} - -#ifdef FLECS_META -const 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; - } - - 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 m; -} -#endif - -#endif - -/** - * @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. - */ - - -#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) - -/* 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; - -/* 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; - -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 = 0; - 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); - 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 == '%') { - 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)) - { - 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; - if (len >= 1024) { - reqstr = ecs_os_malloc(len); - } - - len += 3; - - 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/journal.c - * @brief Journal addon. - */ - - -#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; -} - -static -char* flecs_journal_idstr( - ecs_world_t *world, - ecs_id_t id) -{ - 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); - } -} - -static int flecs_journal_sp = 0; - -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 ++; - - if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { - return; - } - - 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, "#[magenta]#ifndef #[normal]_var_%s", var_id); - ecs_print(4, "#[magenta]#define #[normal]_var_%s", var_id); - ecs_print(4, "#[green]ecs_entity_t %s;", var_id); - ecs_print(4, "#[magenta]#endif"); - ecs_print(4, "%s = #[cyan]ecs_new_id#[reset](world); " - "#[grey] // %s = new()", var_id, path); - } - 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, "#[cyan]ecs_add_id#[reset](world, %s, %s); " - "#[grey] // add(%s, %s)", var_id, jidstr, - path, idstr); - ecs_os_free(idstr); - ecs_os_free(jidstr); - } - } - 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, "#[cyan]ecs_remove_id#[reset](world, %s, %s); " - "#[grey] // remove(%s, %s)", var_id, jidstr, - path, idstr); - ecs_os_free(idstr); - ecs_os_free(jidstr); - } - } - if (kind == EcsJournalClear) { - ecs_print(4, "#[cyan]ecs_clear#[reset](world, %s); " - "#[grey] // clear(%s)", var_id, path); - } else if (kind == EcsJournalDelete) { - ecs_print(4, "#[cyan]ecs_delete#[reset](world, %s); " - "#[grey] // delete(%s)", var_id, path); - } else if (kind == EcsJournalDeleteWith) { - ecs_print(4, "#[cyan]ecs_delete_with#[reset](world, %s); " - "#[grey] // delete_with(%s)", var_id, path); - } else if (kind == EcsJournalRemoveAll) { - ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " - "#[grey] // remove_all(%s)", var_id, path); - } - ecs_os_free(var_id); - ecs_os_free(path); - ecs_log_push(); -} - -void flecs_journal_end(void) { - flecs_journal_sp --; - ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_log_pop(); -} - -#endif - -/** - * @file addons/log.c - * @brief Log addon. - */ - - -#ifdef FLECS_LOG - -#include - -void flecs_colorize_buf( - char *msg, - bool enable_colors, - ecs_strbuf_t *buf) -{ - ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL); - - 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_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; - } - - 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; - } - - if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { - if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); - isVar = false; - } - - if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { - if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); - isVar = true; - } - } - - if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { - bool isColor = true; - overrideColor = true; - - /* 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; - } - - if (isColor) { - ptr += 2; - while ((ch = *ptr) != ']') ptr ++; - dontAppend = true; - } - if (!autoColor) { - overrideColor = true; - } - } - - 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; - } - } - - if (!dontAppend) { - ecs_strbuf_appendstrn(buf, ptr, 1); - } - - if (!overrideColor) { - if (((ch == '\'') || (ch == '"')) && !isStr) { - if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); - } - } - - prev = ch; - } - - if (isNum || isStr || isVar || overrideColor) { - if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); - } -} - -void ecs_printv_( - 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; - - /* 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); - - 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, ""); - } -} - -void ecs_print_( - int level, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - va_list args; - va_start(args, fmt); - ecs_printv_(level, file, line, fmt, args); - va_end(args); -} - -void ecs_logv_( - int level, - const char *file, - int32_t line, - const char *fmt, - va_list args) -{ - if (level > ecs_os_api.log_level_) { - return; - } - - ecs_printv_(level, file, line, fmt, args); -} - -void ecs_log_( - int level, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - if (level > ecs_os_api.log_level_) { - return; - } - - va_list args; - va_start(args, fmt); - ecs_printv_(level, file, line, fmt, args); - va_end(args); -} - - -void ecs_log_push_( - int32_t level) -{ - if (level <= ecs_os_api.log_level_) { - ecs_os_api.log_indent_ ++; - } -} - -void ecs_log_pop_( - int32_t level) -{ - if (level <= ecs_os_api.log_level_) { - ecs_os_api.log_indent_ --; - ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL); - } -} - -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 (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; - } - - int32_t column = flecs_itoi32(column_arg); - - if (ecs_os_api.log_level_ >= -2) { - ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; - - /* 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); - } - - ecs_strbuf_vappend(&msg_buf, fmt, args); - - if (expr) { - ecs_strbuf_appendch(&msg_buf, '\n'); - - /* 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 --; - } - - while (ptr[0] != '\n' && ptr > expr) { - ptr --; - } - - if (ptr[0] == '\n') { - ptr ++; - } - - if (ptr == expr) { - /* ptr is already at start of line */ - } else { - column -= (int32_t)(ptr - expr); - expr = ptr; - } - } - - /* 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); - } - - ecs_strbuf_appendch(&msg_buf, '\n'); - - if (column != -1) { - int32_t c; - for (c = 0; c < column; c ++) { - ecs_strbuf_appendch(&msg_buf, ' '); - } - ecs_strbuf_appendch(&msg_buf, '^'); - } - } - - char *msg = ecs_strbuf_get(&msg_buf); - if (is_warning) { - ecs_os_warn(name, 0, msg); - } else { - ecs_os_err(name, 0, msg); - } - ecs_os_free(msg); - } -} - -void ecs_parser_errorv_( - const char *name, - const char *expr, - int64_t column_arg, - const char *fmt, - va_list args) -{ - flecs_parser_errorv(name, expr, column_arg, fmt, args, false); -} - -void ecs_parser_warningv_( - const char *name, - const char *expr, - int64_t column_arg, - const char *fmt, - va_list args) -{ - flecs_parser_errorv(name, expr, column_arg, fmt, args, true); -} - -void ecs_parser_error_( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) -{ - 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); - } -} - -void ecs_parser_warning_( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) -{ - 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); - } -} - -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, "%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; -} - -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, "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; -} - -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 - - return level <= ecs_os_api.log_level_; -} - -#define ECS_ERR_STR(code) case code: return &(#code[4]) - -const char* ecs_strerror( - int32_t error_code) -{ - 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_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); - ECS_ERR_STR(ECS_ID_IN_USE); - } - - return "unknown error code"; -} - -#else - -/* Empty bodies for when logging is disabled */ - -void ecs_log_( - int32_t level, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - (void)level; - (void)file; - (void)line; - (void)fmt; -} - -void ecs_parser_error_( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) -{ - (void)name; - (void)expr; - (void)column; - (void)fmt; -} - -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; -} - - -void ecs_parser_warning_( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - ...) -{ - (void)name; - (void)expr; - (void)column; - (void)fmt; -} - -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; -} - -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; -} - -void ecs_assert_log_( - int32_t error_code, - const char *condition_str, - const char *file, - int32_t line, - const char *fmt, - ...) -{ - (void)error_code; - (void)condition_str; - (void)file; - (void)line; - (void)fmt; -} - -#endif - -int ecs_log_get_level(void) { - return ecs_os_api.log_level_; -} - -int ecs_log_set_level( - int level) -{ - int prev = ecs_os_api.log_level_; - ecs_os_api.log_level_ = level; - return prev; -} - -bool ecs_log_enable_colors( - bool enabled) -{ - bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; - ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); - return prev; -} - -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; -} - -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; -} - -int ecs_log_last_error(void) -{ - int result = ecs_os_api.log_last_error_; - ecs_os_api.log_last_error_ = 0; - return result; -} - -/** - * @file addons/metrics.c - * @brief Metrics addon. - */ - - -#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_id_record_t *idr; /**< Id 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_id_record_t *idr; /**< Id 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_id_record_t *idr; /**< Id 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); - } -}) - -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); - } -}) - -static ECS_MOVE(EcsMetricCountTargets, dst, src, { - *dst = *src; - src->ctx = NULL; -}) - -/** 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); - - 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); - - 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); - } -} - -/** 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; - - 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); - - 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); - } -} - -/** 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; - } - - 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); - } -} - -/** 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); - - 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); - } - } -} -#endif - -/** 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); - - 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]); - } - } -} - -/** 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; - } - - 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 { - m[i].value += - ecs_meta_ptr_to_float(ctx->type_kind, ptr) * (double)dt; - } - } else { - ecs_delete(it->world, it->entities[i]); - } - } -} - -static void UpdateGaugeMemberInstance(ecs_iter_t *it) { - UpdateMemberInstance(it, false); -} - -static void UpdateCounterMemberInstance(ecs_iter_t *it) { - UpdateMemberInstance(it, false); -} - -static void UpdateCounterIncrementMemberInstance(ecs_iter_t *it) { - UpdateMemberInstance(it, true); -} - -/** 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; - - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_record_t *r = mi[i].r; - if (!r) { - continue; - } - - ecs_table_t *table = r->table; - if (!table) { - ecs_delete(it->world, it->entities[i]); - continue; - } - - ecs_id_metric_ctx_t *ctx = mi[i].ctx; - ecs_id_record_t *idr = ctx->idr; - if (flecs_search_w_idr(world, table, NULL, idr) != -1) { - if (!counter) { - m[i].value = 1.0; - } else { - m[i].value += 1.0 * (double)dt; - } - } else { - ecs_delete(it->world, it->entities[i]); - } - } -} - -static void UpdateGaugeIdInstance(ecs_iter_t *it) { - UpdateIdInstance(it, false); -} - -static void UpdateCounterIdInstance(ecs_iter_t *it) { - UpdateIdInstance(it, true); -} - -/** 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; - - 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; - } - - ecs_table_t *mtable = r->table; - - double *value = ECS_ELEM(m, ctx->size, i); - if (!counter) { - ecs_os_memset(value, 0, ctx->size); - } - - if (!mtable) { - ecs_delete(it->world, it->entities[i]); - continue; - } - - ecs_id_record_t *idr = ctx->idr; - ecs_id_t id; - if (flecs_search_w_idr(world, mtable, &id, idr) == -1) { - ecs_delete(it->world, it->entities[i]); - continue; - } - - 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; - } - - value = ECS_OFFSET(value, *offset); - - if (!counter) { - *value = 1.0; - } else { - *value += 1.0 * (double)dt; - } - } -} - -static void UpdateGaugeOneOfInstance(ecs_iter_t *it) { - UpdateOneOfInstance(it, false); -} - -static void UpdateCounterOneOfInstance(ecs_iter_t *it) { - UpdateOneOfInstance(it, true); -} - -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_id_record_t *cur = ctx->idr; - while ((cur = cur->first.next)) { - 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; - } - - EcsMetricValue *value = ecs_ensure(world, mi[0], EcsMetricValue); - value->value += (double)ecs_count_id(world, cur->id) * - (double)it->delta_system_time; - } - } -} - -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); - - 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 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; - } - - 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; - } - - type = ecs_get_typeid(world, desc->id); - - 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; - } - 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; - } - id = desc->id; - } - - member = desc->member; - member_type = m->type; - offset = flecs_ito(uintptr_t, m->offset); - } - - 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; - } - - 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; -} - -/** Update id metric */ -static -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->idr = flecs_id_record_ensure(world, desc->id); - ecs_check(ctx->idr != 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_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; -} - -/** Update oneof metric */ -static -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->idr = flecs_id_record_ensure(world, desc->id); - ecs_check(ctx->idr != 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; - } - - char *to_snake_case = flecs_to_snake_case(name); - - ecs_entity_t mbr = ecs_entity(world, { - .name = to_snake_case, - .parent = metric - }); - - 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); - } - } - - ctx->size = flecs_uto(ecs_size_t, offset); - - 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_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; -} - -static -int flecs_count_id_targets_metric_init( - ecs_world_t *world, - ecs_entity_t metric, - const ecs_metric_desc_t *desc) -{ - 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->idr = flecs_id_record_ensure(world, desc->id); - ecs_check(ctx->idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_init(&ctx->targets, NULL); - - 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 flecs_count_ids_metric_init( - ecs_world_t *world, - ecs_entity_t metric, - const ecs_metric_desc_t *desc) -{ - ecs_set(world, metric, EcsMetricCountIds, { .id = desc->id }); - ecs_set(world, metric, EcsMetricValue, { .value = 0 }); - return 0; -} - -ecs_entity_t ecs_metric_init( - ecs_world_t *world, - const ecs_metric_desc_t *desc) -{ - 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); - - ecs_entity_t result = desc->entity; - if (!result) { - result = ecs_new(world); - } - - ecs_entity_t kind = desc->kind; - if (!kind) { - ecs_err("missing metric kind"); - goto error; - } - - if (kind != EcsGauge && - kind != EcsCounter && - kind != EcsCounterId && - kind != EcsCounterIncrement) - { - ecs_err("invalid metric kind %s", ecs_get_path(world, kind)); - goto error; - } - - if (kind == EcsCounterIncrement && !desc->member && !desc->dotmember) { - ecs_err("CounterIncrement can only be used in combination with member"); - goto error; - } - - if (kind == EcsCounterId && (desc->member || desc->dotmember)) { - ecs_err("CounterId cannot be used in combination with member"); - goto error; - } - - 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 - } - - if (desc->member || desc->dotmember) { - if (flecs_member_metric_init(world, result, desc)) { - 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; - } - } - } - } else { - ecs_err("missing source specified for metric"); - goto error; - } - - return result; -error: - if (result && result != desc->entity) { - ecs_delete(world, result); - } - return 0; -} - -void FlecsMetricsImport(ecs_world_t *world) { - ECS_MODULE_DEFINE(world, FlecsMetrics); - - ECS_IMPORT(world, FlecsPipeline); - ECS_IMPORT(world, FlecsMeta); - ECS_IMPORT(world, FlecsUnits); - - 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); - - 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_add_id(world, ecs_id(EcsMetricMemberInstance), EcsPrivate); - ecs_add_id(world, ecs_id(EcsMetricIdInstance), EcsPrivate); - ecs_add_id(world, ecs_id(EcsMetricOneOfInstance), EcsPrivate); - - ecs_struct(world, { - .entity = ecs_id(EcsMetricValue), - .members = { - { .name = "value", .type = ecs_id(ecs_f64_t) } - } - }); - - ecs_struct(world, { - .entity = ecs_id(EcsMetricSource), - .members = { - { .name = "entity", .type = ecs_id(ecs_entity_t) } - } - }); - - ecs_set_hooks(world, EcsMetricMember, { - .ctor = flecs_default_ctor, - .dtor = ecs_dtor(EcsMetricMember), - .move = ecs_move(EcsMetricMember) - }); - - ecs_set_hooks(world, EcsMetricId, { - .ctor = flecs_default_ctor, - .dtor = ecs_dtor(EcsMetricId), - .move = ecs_move(EcsMetricId) - }); - - ecs_set_hooks(world, EcsMetricOneOf, { - .ctor = flecs_default_ctor, - .dtor = ecs_dtor(EcsMetricOneOf), - .move = ecs_move(EcsMetricOneOf) - }); - - ecs_set_hooks(world, EcsMetricCountTargets, { - .ctor = flecs_default_ctor, - .dtor = ecs_dtor(EcsMetricCountTargets), - .move = ecs_move(EcsMetricCountTargets) - }); - - ecs_add_id(world, EcsMetric, EcsOneOf); - -#ifdef FLECS_DOC - ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, - Source); -#endif - - ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore, - [in] Source); - - ECS_SYSTEM(world, UpdateGaugeMemberInstance, EcsPreStore, - [out] Value, - [in] MemberInstance, - [none] (Metric, Gauge)); - - 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 - -#include - -char* flecs_module_path_from_c( - const char *c_name) -{ - 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); - } - } - - ecs_strbuf_appendstrn(&str, &ch, 1); - } - - return ecs_strbuf_get(&str); -} - -ecs_entity_t ecs_import( - ecs_world_t *world, - ecs_module_action_t module, - const char *module_name) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(!(world->flags & EcsWorldReadonly), - ECS_INVALID_WHILE_READONLY, NULL); - - ecs_entity_t old_scope = ecs_set_scope(world, 0); - const char *old_name_prefix = world->info.name_prefix; - - 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(); - - /* 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(); - } - - /* Restore to previous state */ - ecs_set_scope(world, old_scope); - world->info.name_prefix = old_name_prefix; - - return e; -error: - return 0; -} - -ecs_entity_t ecs_import_c( - ecs_world_t *world, - ecs_module_action_t module, - const char *c_name) -{ - 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 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"); - } - - 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_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); - - 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); - } - - /* Do not free id, as it will be stored as the component identifier */ - ecs_entity_t result = ecs_import(world, action, module); - - if (import_func != module_name) { - ecs_os_free(import_func); - } - - if (module != module_name) { - ecs_os_free(module); - } - - ecs_os_free(library_filename); - - return result; -error: - return 0; -} - -ecs_entity_t ecs_module_init( - ecs_world_t *world, - const char *c_name, - const ecs_component_desc_t *desc) -{ - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_poly_assert(world, ecs_world_t); - - ecs_entity_t old_scope = ecs_set_scope(world, 0); - - 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); - - ecs_component_desc_t private_desc = *desc; - private_desc.entity = e; - - 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; - } - - ecs_set_scope(world, old_scope); - - return e; -error: - return 0; -} - -#endif - -/** - * @file addons/rest.c - * @brief Rest addon. - */ - - -/** - * @file addons/pipeline/pipeline.h - * @brief Internal functions/types for pipeline addon. - */ - -#ifndef FLECS_PIPELINE_PRIVATE_H -#define FLECS_PIPELINE_PRIVATE_H - - -/** 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; - -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 */ - ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */ - 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 */ -}; - -typedef struct EcsPipeline { - /* Stable ptr so threads can safely access while entity/components move */ - ecs_pipeline_state_t *state; -} EcsPipeline; - -//////////////////////////////////////////////////////////////////////////////// -//// Pipeline API -//////////////////////////////////////////////////////////////////////////////// - -bool flecs_pipeline_update( - ecs_world_t *world, - ecs_pipeline_state_t *pq, - bool start_of_frame); - -void flecs_run_pipeline( - ecs_world_t *world, - ecs_pipeline_state_t *pq, - ecs_ftime_t delta_time); - -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); - -//////////////////////////////////////////////////////////////////////////////// -//// Worker API -//////////////////////////////////////////////////////////////////////////////// - -void flecs_workers_progress( - ecs_world_t *world, - ecs_pipeline_state_t *pq, - ecs_ftime_t delta_time); - -void flecs_create_worker_threads( - ecs_world_t *world); - -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); - -#endif - - -#ifdef FLECS_REST - -/* Retain captured commands for one minute at 60 FPS */ -#define FLECS_REST_COMMAND_RETAIN_COUNT (60 * 60) - -static ECS_TAG_DECLARE(EcsRestPlecs); - -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; - -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_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; - } - - 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; - } - - void *ptr = ecs_ensure_id(world, e, id); - if (!ptr) { - flecs_reply_error(reply, "failed to create component '%s'", component); - reply->code = 500; - return true; - } - - 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) -{ - (void)world; - (void)req; - (void)reply; -#ifdef FLECS_SCRIPT - ecs_entity_t script = flecs_rest_entity_from_path(world, reply, path); - if (!script) { - script = ecs_entity(world, { .name = path }); - } - - 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) { - flecs_reply_error(reply, "missing code parameter"); - if (!try) { - reply->code = 400; - } - return true; - } - - bool prev_color = ecs_log_enable_colors(false); - ecs_os_api_log_t prev_log = ecs_os_api.log_; - flecs_set_prev_log(ecs_os_api.log_, try); - ecs_os_api.log_ = flecs_rest_capture_log; - - script = ecs_script(world, { - .entity = script, - .code = code - }); - - if (!script) { - char *err = flecs_rest_get_captured_log(); - char *escaped_err = flecs_astresc('"', err); - if (escaped_err) { - flecs_reply_error(reply, "%s", escaped_err); - } else { - flecs_reply_error(reply, "error parsing script"); - } - if (!try) { - reply->code = 400; /* bad request */ - } - ecs_os_free(escaped_err); - ecs_os_free(err); - } - - ecs_os_api.log_ = prev_log; - ecs_log_enable_colors(prev_color); - - return true; -#else - return false; -#endif -} - -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); - flecs_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_SCRIPT - 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_ = rest_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_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_entity_t *systems = ecs_vec_first_t(&p->state->systems, ecs_entity_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_entity_t system = systems[s]; - - 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_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); - ecs_strbuf_appendch(reply, '"'); - ecs_id_str_buf(world, ids[i], reply); - ecs_strbuf_appendch(reply, '"'); - } - ecs_strbuf_list_pop(reply, "]"); -} - -static -void flecs_rest_reply_table_append_memory( - ecs_strbuf_t *reply, - const ecs_table_t *table) -{ - int32_t used = 0, allocated = 0; - int32_t count = ecs_table_count(table), size = ecs_table_size(table); - - used += count * ECS_SIZEOF(ecs_entity_t); - allocated += size * ECS_SIZEOF(ecs_entity_t); - - int32_t i, storage_count = table->column_count; - ecs_column_t *columns = table->data.columns; - - for (i = 0; i < storage_count; i ++) { - used += count * columns[i].ti->size; - allocated += size * columns[i].ti->size; - } - - ecs_strbuf_list_push(reply, "{", ","); - ecs_strbuf_list_appendlit(reply, "\"used\":"); - ecs_strbuf_appendint(reply, used); - ecs_strbuf_list_appendlit(reply, "\"allocated\":"); - ecs_strbuf_appendint(reply, allocated); - ecs_strbuf_list_pop(reply, "}"); -} - -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_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, "\"memory\":"); - flecs_rest_reply_table_append_memory(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); - - /* 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]); - } - } 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; - srv_ctx->srv = srv; - 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); -#ifdef FLECS_SCRIPT - ECS_IMPORT(world, FlecsScript); -#endif -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsRest), - "Module that implements Flecs REST API"); -#endif - - 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 - }); - - ecs_set_name_prefix(world, "EcsRest"); - ECS_TAG_DEFINE(world, EcsRestPlecs); - - /* 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_intern( - 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 AddTickSource(ecs_iter_t *it) { - int32_t i; - for (i = 0; i < it->count; i ++) { - ecs_set(it->world, it->entities[i], EcsTickSource, {0}); - } -} - -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); -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsTimer), - "Module that implements system timers (used by .interval)"); -#endif - - 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 - }); - - /* Add EcsTickSource to timers and rate filters */ - ecs_system(world, { - .entity = ecs_entity(world, {.name = "AddTickSource", .add = ecs_ids( ecs_dependson(EcsPreFrame) )}), - .query.terms = { - { .id = ecs_id(EcsTimer), .oper = EcsOr }, - { .id = ecs_id(EcsRateFilter), .oper = EcsAnd }, - { .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut} - }, - .callback = AddTickSource - }); - - /* 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); - -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsUnits), - "Module with (amongst others) SI units for annotating component members"); -#endif - - 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. - */ - - -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; -} - -void flecs_allocator_init( - ecs_allocator_t *a) -{ - 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); -} - -void flecs_allocator_fini( - ecs_allocator_t *a) -{ - 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); -} - -ecs_block_allocator_t* flecs_allocator_get( - ecs_allocator_t *a, - ecs_size_t size) -{ - 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_any_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; -} - -char* flecs_strdup( - ecs_allocator_t *a, - const char* str) -{ - 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; -} - -void flecs_strfree( - ecs_allocator_t *a, - char* str) -{ - ecs_size_t len = ecs_os_strlen(str); - flecs_free_n(a, char, len + 1, str); -} - -void* flecs_dup( - ecs_allocator_t *a, - ecs_size_t size, - const void *src) -{ - ecs_block_allocator_t *ba = flecs_allocator_get(a, size); - if (ba) { - void *dst = flecs_balloc(ba); - ecs_os_memcpy(dst, src, size); - return dst; - } else { - return NULL; - } -} - -/** - * @file datastructures/bitset.c - * @brief Bitset data structure. - * - * Simple bitset implementation. The bitset allows for storage of arbitrary - * numbers of bits. - */ - - -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); - } -} - -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; - ensure(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; - ensure(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 - -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; - if (!allocator->block_tail) { - ecs_assert(!allocator->block_head, ECS_INTERNAL_ERROR, 0); - block->next = NULL; - allocator->block_head = block; - allocator->block_tail = block; - } else { - block->next = NULL; - allocator->block_tail->next = block; - allocator->block_tail = 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; -#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); -#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; - ba->block_tail = NULL; -} - -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); - -#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; -} - -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; -#ifdef FLECS_USE_OS_ALLOC - result = ecs_os_malloc(ba->data_size); -#else - - if (!ba) return NULL; - - 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)); -#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; - } - -#ifdef FLECS_SANITIZE - memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); - 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->chunk_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 -#include -#include -#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; -} - -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; - flecs_ballocator_init_t(&map->bucket_allocator, ecs_hm_bucket_t); - 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); -#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) - flecs_bfree(&map->bucket_allocator, bucket); -#endif - } - - flecs_ballocator_fini(&map->bucket_allocator); - 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_balloc(&dst->bucket_allocator); - 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_bcalloc(&map->bucket_allocator); - } - - 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_bfree(&map->bucket_allocator, 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) - -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(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_block_allocator_t *allocator, - ecs_bucket_t *bucket, - ecs_map_key_t key) -{ - ecs_bucket_entry_t *new_entry = flecs_balloc(allocator); - 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; - flecs_bfree(map->entry_allocator, entry); - map->count --; - return value; - } - } - - return 0; -} - -/* Free contents of bucket */ -static -void flecs_map_bucket_clear( - ecs_block_allocator_t *allocator, - ecs_bucket_t *bucket) -{ - ecs_bucket_entry_t *entry = bucket->first; - while(entry) { - ecs_bucket_entry_t *next = entry->next; - flecs_bfree(allocator, 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; - } - ecs_assert(count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); - - int32_t old_count = map->bucket_count; - ecs_bucket_t *buckets = map->buckets, *b, *end = ECS_BUCKET_END(buckets, old_count); - - if (map->allocator) { - map->buckets = flecs_calloc_n(map->allocator, ecs_bucket_t, count); - } else { - map->buckets = ecs_os_calloc_n(ecs_bucket_t, count); - } - map->bucket_count = count; - map->bucket_shift = flecs_map_get_bucket_shift(count); - - /* 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( - map->bucket_shift, entry->key); - ecs_bucket_t *bucket = &map->buckets[bucket_index]; - entry->next = bucket->first; - bucket->first = entry; - entry = next; - } - } - - if (map->allocator) { - flecs_free_n(map->allocator, ecs_bucket_t, old_count, buckets); - } else { - ecs_os_free(buckets); - } -} - -void ecs_map_params_init( - ecs_map_params_t *params, - ecs_allocator_t *allocator) -{ - params->allocator = allocator; - flecs_ballocator_init_t(¶ms->entry_allocator, ecs_bucket_entry_t); -} - -void ecs_map_params_fini( - ecs_map_params_t *params) -{ - flecs_ballocator_fini(¶ms->entry_allocator); -} - -void ecs_map_init_w_params( - ecs_map_t *result, - ecs_map_params_t *params) -{ - ecs_os_zeromem(result); - - result->allocator = params->allocator; - - if (params->entry_allocator.chunk_size) { - result->entry_allocator = ¶ms->entry_allocator; - result->shared_allocator = true; - } else { - result->entry_allocator = flecs_ballocator_new_t(ecs_bucket_entry_t); - } - - flecs_map_rehash(result, 0); -} - -void ecs_map_init_w_params_if( - ecs_map_t *result, - ecs_map_params_t *params) -{ - if (!ecs_map_is_init(result)) { - ecs_map_init_w_params(result, params); - } -} - -void ecs_map_init( - ecs_map_t *result, - ecs_allocator_t *allocator) -{ - ecs_map_init_w_params(result, &(ecs_map_params_t) { - .allocator = allocator - }); -} - -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; - } - - bool sanitize = false; -#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) - sanitize = true; -#endif - - /* Free buckets in sanitized mode, so we can replace the allocator with - * regular malloc/free and use asan/valgrind to find memory errors. */ - ecs_allocator_t *a = map->allocator; - ecs_block_allocator_t *ea = map->entry_allocator; - if (map->shared_allocator || sanitize) { - ecs_bucket_t *bucket = map->buckets, *end = &bucket[map->bucket_count]; - while (bucket != end) { - flecs_map_bucket_clear(ea, bucket); - bucket ++; - } - } - - if (ea && !map->shared_allocator) { - flecs_ballocator_free(ea); - map->entry_allocator = NULL; - } - if (a) { - flecs_free_n(a, ecs_bucket_t, map->bucket_count, map->buckets); - } else { - ecs_os_free(map->buckets); - } - - map->bucket_shift = 0; -} - -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->entry_allocator, bucket, key)[0] = value; -} - -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->entry_allocator, bucket, key); - *v = 0; - 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) -{ - return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); -} - -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->entry_allocator, &map->buckets[i]); - } - if (map->allocator) { - flecs_free_n(map->allocator, ecs_bucket_t, count, map->buckets); - } else { - ecs_os_free(map->buckets); - } - map->buckets = NULL; - map->bucket_count = 0; - map->count = 0; - flecs_map_rehash(map, 2); -} - -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 - }; - } else { - return (ecs_map_iter_t){ 0 }; - } -} - -bool ecs_map_next( - ecs_map_iter_t *iter) -{ - const ecs_map_t *map = iter->map; - ecs_bucket_t *end; - if (!map || (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; - - 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_world_t *world, - ecs_allocator_t *allocator) -{ - ecs_hashmap_t *result = flecs_bcalloc(&world->allocators.hashmap); - flecs_name_index_init(result, allocator); - result->hashmap_allocator = &world->allocators.hashmap; - 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) { - flecs_name_index_fini(map); - flecs_bfree(map->hashmap_allocator, map); - } -} - -ecs_hashmap_t* flecs_name_index_copy( - ecs_hashmap_t *map) -{ - ecs_hashmap_t *result = flecs_bcalloc(map->hashmap_allocator); - result->hashmap_allocator = map->hashmap_allocator; - 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 id registered with name '%s'", name); - } - } - - 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)) - -typedef struct ecs_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_page_t; - -static -ecs_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_page_t *pages; - - if (count <= page_index) { - ecs_vec_set_count_t(a, &sparse->pages, ecs_page_t, page_index + 1); - pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); - ecs_os_memset_n(&pages[count], 0, ecs_page_t, (1 + page_index - count)); - } else { - pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); - } - - ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_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. */ - result->data = a ? flecs_calloc(a, sparse->size * FLECS_SPARSE_PAGE_SIZE) - : ecs_os_calloc(sparse->size * FLECS_SPARSE_PAGE_SIZE); - - ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); - - return result; -} - -static -void flecs_sparse_page_free( - ecs_sparse_t *sparse, - ecs_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); - } -} - -static -ecs_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_page_t, page_index); -} - -static -ecs_page_t* flecs_sparse_get_or_create_page( - ecs_sparse_t *sparse, - int32_t page_index) -{ - ecs_page_t *page = flecs_sparse_get_page(sparse, page_index); - if (page && page->sparse) { - 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 -uint64_t flecs_sparse_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; -} - -static -void flecs_sparse_assign_index( - ecs_page_t * page, - 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 .*/ - page->sparse[FLECS_SPARSE_OFFSET(index)] = dense; - dense_array[dense] = index; -} - -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); -} - -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 index = flecs_sparse_inc_id(sparse); - flecs_sparse_grow_dense(sparse); - - ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); - ecs_assert(page->sparse[FLECS_SPARSE_OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); - - uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); - flecs_sparse_assign_index(page, dense_array, index, dense); - - return index; -} - -/* 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 index) -{ - flecs_sparse_strip_generation(&index); - ecs_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_page_t * page_a, - int32_t a, - int32_t b) -{ - uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); - uint64_t index_a = dense_array[a]; - uint64_t index_b = dense_array[b]; - - ecs_page_t *page_b = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index_b)); - flecs_sparse_assign_index(page_a, dense_array, index_a, b); - flecs_sparse_assign_index(page_b, dense_array, index_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_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_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_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_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); - for (i = 0; i < count; i ++) { - flecs_sparse_page_free(sparse, &pages[i]); - } - - ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_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 index = flecs_sparse_new_index(sparse); - ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); - ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); - return DATA(page->data, size, FLECS_SPARSE_OFFSET(index)); -} - -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_ensure( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) -{ - 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; - - uint64_t gen = flecs_sparse_strip_generation(&index); - ecs_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] = index | gen; - } else { - /* Dense is already alive, nothing to be done */ - } - - /* 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. */ -#ifdef FLECS_DEBUG - uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); - ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); -#endif - } 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_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, index, count); - dense_array[count] |= gen; - } - - return DATA(page->data, sparse->size, offset); -} - -void* flecs_sparse_ensure_fast( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index_long) -{ - 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)index_long; - ecs_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); -} - -void* flecs_sparse_remove_fast( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; - - ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); - if (!page || !page->sparse) { - return NULL; - } - - flecs_sparse_strip_generation(&index); - 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 */ - return DATA(page->data, sparse->size, offset); - } else { - /* Element is not paired and thus not alive, nothing to be done */ - return NULL; - } -} - - -void flecs_sparse_remove( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; - - ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); - if (!page || !page->sparse) { - return; - } - - uint64_t gen = flecs_sparse_strip_generation(&index); - int32_t offset = FLECS_SPARSE_OFFSET(index); - int32_t dense = page->sparse[offset]; - - if (dense) { - uint64_t *dense_array = ecs_vec_first_t(&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; - } - - /* Increase generation */ - dense_array[dense] = index | flecs_sparse_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 */ - flecs_sparse_swap_dense(sparse, page, dense, count - 1); - sparse->count --; - } else { - /* Element is not alive, nothing to be done */ - return; - } - - /* Reset memory to zero on remove */ - void *ptr = DATA(page->data, sparse->size, offset); - ecs_os_memset(ptr, 0, size); - } else { - /* Element is not paired and thus not alive, nothing to be done */ - return; - } -} - -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 index) -{ - ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); - if (!page || !page->sparse) { - return false; - } - - int32_t offset = FLECS_SPARSE_OFFSET(index); - int32_t dense = page->sparse[offset]; - if (!dense || (dense >= sparse->count)) { - return false; - } - - uint64_t gen = flecs_sparse_strip_generation(&index); - uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); - uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; - - if (cur_gen != gen) { - return false; - } - - ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); - return true; -} - -void* flecs_sparse_try( - const ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; - ecs_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]; - if (!dense || (dense >= sparse->count)) { - return NULL; - } - - uint64_t gen = flecs_sparse_strip_generation(&index); - uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); - uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; - if (cur_gen != gen) { - return NULL; - } - - ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); - return DATA(page->data, sparse->size, offset); -} - -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(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; - - ecs_page_t *page = ecs_vec_get_t(&sparse->pages, ecs_page_t, FLECS_SPARSE_PAGE(index)); - int32_t offset = FLECS_SPARSE_OFFSET(index); - int32_t dense = page->sparse[offset]; - ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); - - uint64_t gen = flecs_sparse_strip_generation(&index); - uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); - uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; - (void)cur_gen; (void)gen; - - ecs_assert(cur_gen == gen, ECS_INVALID_PARAMETER, NULL); - ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); - ecs_assert(dense < sparse->count, ECS_INTERNAL_ERROR, NULL); - return DATA(page->data, sparse->size, offset); -} - -void* flecs_sparse_get_any( - const ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; - - flecs_sparse_strip_generation(&index); - ecs_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); -} - -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 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. - */ - - -#define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) - -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 + ECS_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); - - ecs_stack_page_t *page = stack->tail_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); - void *result = NULL; - - if (next_sp > ECS_STACK_PAGE_SIZE) { - if (size > ECS_STACK_PAGE_SIZE) { - result = ecs_os_malloc(size); /* Too large for page */ - goto done; - } - - 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 > ECS_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; - 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; - stack->first->sp = 0; - stack->tail_cursor = NULL; -} - -void flecs_stack_init( - ecs_stack_t *stack) -{ - ecs_os_zeromem(stack); - stack->first = flecs_stack_page_new(0); - stack->first->sp = 0; - stack->tail_page = stack->first; -} - -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->sp == 0, ECS_LEAK_DETECTED, - FLECS_STACK_LEAK_MSG); - - 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 - -/** - * 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; - - if (result == b->small_string) { - result = ecs_os_memdup_n(result, char, b->length + 1); - } - - 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; -} - - -static -ecs_switch_page_t* flecs_switch_page_ensure( - ecs_switch_t* sw, - uint32_t elem) -{ - int32_t page_index = FLECS_SPARSE_PAGE(elem); - ecs_vec_set_min_count_zeromem_t( - sw->hdrs.allocator, &sw->pages, ecs_switch_page_t, page_index + 1); - - ecs_switch_page_t *page = ecs_vec_get_t( - &sw->pages, ecs_switch_page_t, page_index); - if (!ecs_vec_count(&page->nodes)) { - ecs_vec_init_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t, - FLECS_SPARSE_PAGE_SIZE); - ecs_vec_init_t(sw->hdrs.allocator, &page->values, uint64_t, - FLECS_SPARSE_PAGE_SIZE); - ecs_vec_set_min_count_zeromem_t(sw->hdrs.allocator, &page->nodes, - ecs_switch_node_t, FLECS_SPARSE_PAGE_SIZE); - ecs_vec_set_min_count_zeromem_t(sw->hdrs.allocator, &page->values, - uint64_t, FLECS_SPARSE_PAGE_SIZE); - } - - return page; -} - -static -ecs_switch_page_t* flecs_switch_page_get( - const ecs_switch_t* sw, - uint32_t elem) -{ - int32_t page_index = FLECS_SPARSE_PAGE(elem); - if (page_index >= ecs_vec_count(&sw->pages)) { - return NULL; - } - - ecs_switch_page_t *page = ecs_vec_get_t( - &sw->pages, ecs_switch_page_t, page_index); - if (!ecs_vec_count(&page->nodes)) { - return NULL; - } - - return page; -} - -static -void flecs_switch_page_fini( - ecs_switch_t* sw, - ecs_switch_page_t *page) -{ - if (ecs_vec_count(&page->nodes)) { - ecs_vec_fini_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t); - ecs_vec_fini_t(sw->hdrs.allocator, &page->values, uint64_t); - } -} - -static -ecs_switch_node_t* flecs_switch_get_node( - ecs_switch_t* sw, - uint32_t element) -{ - if (!element) { - return NULL; - } - - ecs_switch_page_t *page = flecs_switch_page_ensure(sw, element); - int32_t page_offset = FLECS_SPARSE_OFFSET(element); - return ecs_vec_get_t(&page->nodes, ecs_switch_node_t, page_offset); -} - -void flecs_switch_init( - ecs_switch_t* sw, - ecs_allocator_t *allocator) -{ - ecs_map_init(&sw->hdrs, allocator); - ecs_vec_init_t(allocator, &sw->pages, ecs_switch_page_t, 0); -} - -void flecs_switch_fini( - ecs_switch_t* sw) -{ - int32_t i, count = ecs_vec_count(&sw->pages); - ecs_switch_page_t *pages = ecs_vec_first(&sw->pages); - for (i = 0; i < count; i ++) { - flecs_switch_page_fini(sw, &pages[i]); - } - ecs_vec_fini_t(sw->hdrs.allocator, &sw->pages, ecs_switch_page_t); - ecs_map_fini(&sw->hdrs); -} - -bool flecs_switch_set( - ecs_switch_t *sw, - uint32_t element, - uint64_t value) -{ - ecs_switch_page_t *page = flecs_switch_page_ensure(sw, element); - int32_t page_offset = FLECS_SPARSE_OFFSET(element); - - uint64_t *elem = ecs_vec_get_t(&page->values, uint64_t, page_offset); - if (elem[0] == value) { - return false; - } - - ecs_switch_node_t *node = ecs_vec_get_t( - &page->nodes, ecs_switch_node_t, page_offset); - - uint64_t prev_value = elem[0]; - if (prev_value) { - ecs_switch_node_t *prev = flecs_switch_get_node(sw, node->prev); - if (prev) { - prev->next = node->next; - } - - ecs_switch_node_t *next = flecs_switch_get_node(sw, node->next); - if (next) { - next->prev = node->prev; - } - - if (!prev) { - uint64_t *hdr = ecs_map_get(&sw->hdrs, prev_value); - ecs_assert(hdr[0] == (uint64_t)element, ECS_INTERNAL_ERROR, NULL); - hdr[0] = (uint64_t)node->next; - } - } - - elem[0] = value; - - if (value) { - uint64_t *hdr = ecs_map_ensure(&sw->hdrs, value); - - if (!hdr[0]) { - hdr[0] = (uint64_t)element; - node->next = 0; - } else { - ecs_switch_node_t *head = flecs_switch_get_node(sw, (uint32_t)hdr[0]); - ecs_assert(head->prev == 0, ECS_INTERNAL_ERROR, NULL); - head->prev = element; - - node->next = (uint32_t)hdr[0]; - hdr[0] = (uint64_t)element; - ecs_assert(node->next != element, ECS_INTERNAL_ERROR, NULL); - } - - node->prev = 0; - } - - return true; -} - -bool flecs_switch_reset( - ecs_switch_t *sw, - uint32_t element) -{ - return flecs_switch_set(sw, element, 0); -} - -uint64_t flecs_switch_get( - const ecs_switch_t *sw, - uint32_t element) -{ - ecs_switch_page_t *page = flecs_switch_page_get(sw, element); - if (!page) { - return 0; - } - - int32_t page_offset = FLECS_SPARSE_OFFSET(element); - uint64_t *elem = ecs_vec_get_t(&page->values, uint64_t, page_offset); - return elem[0]; -} - -uint32_t flecs_switch_first( - const ecs_switch_t *sw, - uint64_t value) -{ - uint64_t *hdr = ecs_map_get(&sw->hdrs, value); - if (!hdr) { - return 0; - } - - return (uint32_t)hdr[0]; -} - -FLECS_DBG_API -uint32_t flecs_switch_next( - const ecs_switch_t *sw, - uint32_t previous) -{ - ecs_switch_page_t *page = flecs_switch_page_get(sw, previous); - if (!page) { - return 0; - } - - int32_t offset = FLECS_SPARSE_OFFSET(previous); - ecs_switch_node_t *elem = ecs_vec_get_t( - &page->nodes, ecs_switch_node_t, offset); - return elem->next; -} - -ecs_map_iter_t flecs_switch_targets( - const ecs_switch_t *sw) -{ - return ecs_map_iter(&sw->hdrs); -} - -/** - * @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); -} - -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 = NULL; - v->count = 0; - if (elem_count) { - if (allocator) { - v->array = flecs_alloc_w_dbg_info( - allocator, size * elem_count, type_name); - } else { - v->array = ecs_os_malloc(size * elem_count); - } - } - 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) { - if (allocator) { - v->array = flecs_realloc( - allocator, size * count, size * v->size, v->array); - } else { - v->array = ecs_os_realloc(v->array, size * count); - } - 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_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_count( - 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->count != elem_count) { - if (v->size < elem_count) { - ecs_vec_set_size(allocator, v, size, elem_count); - } - - 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 --; -} - -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. - */ - -#include - -/* 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 group_order_by = desc->group_by || desc->group_by_callback || - desc->order_by || desc->order_by_callback; - - /* If caching policy is default, try to pick a policy that does the right - * thing in most cases. */ - if (kind == EcsQueryCacheDefault) { - if (desc->entity || group_order_by) { - /* 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 (desc->group_by || desc->order_by) { - ecs_err("cannot create uncached query with group_by/order_by"); - 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. */ - impl->pub.cache_kind = EcsQueryCacheAll; - } 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 (!group_order_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->flags_ & EcsTermIsCacheable) { - cacheable_terms ++; - if (term->oper == EcsNot || term->oper == EcsOptional) { - not_optional_terms ++; - } - } - } - } - - if (group_order_by || 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) && !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; -} - -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; - int i, count = q->term_count; - for (i = 0; i < count; i ++) { - ecs_term_t *term = &q->terms[i]; - if (!(term->flags_ & EcsTermKeepAlive)) { - continue; - } - - ecs_id_record_t *idr = flecs_id_record_get(q->real_world, term->id); - if (idr) { - if (!(ecs_world_get_flags(q->world) & EcsWorldQuit)) { - if (ecs_os_has_threading()) { - int32_t idr_keep_alive = ecs_os_adec(&idr->keep_alive); - ecs_assert(idr_keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); - (void)idr_keep_alive; - } else { - idr->keep_alive --; - ecs_assert(idr->keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); - } - } - } - } - - 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_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_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) { - /* 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 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); - } - - return &result->pub; -error: - result->pub.entity = 0; - ecs_query_fini(&result->pub); - return NULL; -} - -bool ecs_query_has( - 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( - 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); - - *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( - ecs_query_t *q, - ecs_table_range_t *range, - ecs_iter_t *it) -{ - flecs_poly_assert(q, ecs_query_t); - - if (q->flags & EcsQueryMatchThis) { - if (range->table) { - if ((range->offset + range->count) > ecs_table_count(range->table)) { - 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}; - - if (!(q->flags & EcsQueryMatchThis)) { - return result; - } - - 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); - } - - 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; - } -} - - /** - * @file query/util.c - * @brief Query utilities. - */ - - -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; - } -} - -const char* flecs_query_op_str( - uint16_t kind) -{ - switch(kind) { - case EcsQueryAnd: return "and "; - case EcsQueryAndAny: return "andany "; - case EcsQueryTriv: return "triv "; - case EcsQueryCache: return "cache "; - case EcsQueryIsCache: return "xcache "; - case EcsQueryOnlyAny: return "any "; - case EcsQueryUp: return "up "; - case EcsQuerySelfUp: return "selfup "; - case EcsQueryWith: return "with "; - 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 EcsQueryUnionEq: return "union "; - case EcsQueryUnionEqWith: return "union_w "; - case EcsQueryUnionNeq: return "unionneq "; - case EcsQueryUnionEqUp: return "union_up "; - case EcsQueryUnionEqSelfUp: return "union_sup "; - 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 "; - } -} - -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); - // ecs_query_impl_t *impl = flecs_query_impl(q); - // if (impl->cache) { - // ecs_strbuf_appendch(&buf, '\n'); - // flecs_query_plan_w_profile(impl->cache->query, 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); -} - -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 *src = &term->src; - const ecs_term_ref_t *first = &term->first; - const ecs_term_ref_t *second = &term->second; - - ecs_entity_t src_id = ECS_TERM_REF_ID(src); - 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, "("); - if (term->src.id & EcsIsEntity && src_id == first_id) { - ecs_strbuf_appendlit(buf, "$"); - } else { - 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; -} - -int32_t flecs_query_pivot_term( - const ecs_world_t *world, - const ecs_query_t *query) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - - const ecs_term_t *terms = query->terms; - int32_t i, term_count = query->term_count; - int32_t pivot_term = -1, min_count = -1, self_pivot_term = -1; - - for (i = 0; i < term_count; i ++) { - const ecs_term_t *term = &terms[i]; - ecs_id_t id = term->id; - - if ((term->oper != EcsAnd) || (i && (term[-1].oper == EcsOr))) { - continue; - } - - if (!ecs_term_match_this(term)) { - continue; - } - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - /* If one of the terms does not match with any data, iterator - * should not return anything */ - return -2; /* -2 indicates query doesn't match anything */ - } - - int32_t table_count = flecs_table_cache_count(&idr->cache); - if (min_count == -1 || table_count < min_count) { - min_count = table_count; - pivot_term = i; - if ((term->src.id & EcsTraverseFlags) == EcsSelf) { - self_pivot_term = i; - } - } - } - - if (self_pivot_term != -1) { - pivot_term = self_pivot_term; - } - - return pivot_term; -error: - return -2; -} - -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); -} - -/** - * @file query/validator.c - * @brief Validate and finalize queries. - */ - -#include - -#ifdef FLECS_SCRIPT -#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) -{ - if ((ref->id & EcsIsEntity) && (ref->id & EcsIsVariable)) { - flecs_query_validator_error(ctx, "cannot set both IsEntity and IsVariable"); - return -1; - } - - if (ref->name && ref->name[0] == '$') { - if (!ref->name[1]) { - if (!(ref->id & EcsIsName)) { - if (ref->id & ~EcsTermRefFlags) { - flecs_query_validator_error(ctx, - "conflicting values for .name and .id"); - return -1; - } - - ref->id |= EcsVariable; - ref->id |= EcsIsVariable; - } - } 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 -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 -ecs_id_t flecs_wildcard_to_any(ecs_id_t id) { - ecs_id_t flags = id & EcsTermRefFlags; - - if (ECS_IS_PAIR(id)) { - ecs_entity_t first = ECS_PAIR_FIRST(id); - ecs_entity_t second = ECS_PAIR_SECOND(id); - if (first == EcsWildcard) id = ecs_pair(EcsAny, second); - if (second == EcsWildcard) id = ecs_pair(ECS_PAIR_FIRST(id), EcsAny); - } else if ((id & ~EcsTermRefFlags) == EcsWildcard) { - id = EcsAny; - } - - return id | flags; -} - -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)) { - return -1; - } - - if (flecs_term_ref_finalize_flags(first, ctx)) { - return -1; - } - - if (flecs_term_ref_finalize_flags(second, ctx)) { - 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; - } - - /* If operator is Not, automatically convert wildcard queries to any */ - if (term->oper == EcsNot) { - if (ECS_TERM_REF_ID(first) == EcsWildcard) { - first->id = EcsAny | ECS_TERM_REF_FLAGS(first); - } - - if (ECS_TERM_REF_ID(second) == EcsWildcard) { - second->id = EcsAny | ECS_TERM_REF_FLAGS(second); - } - - term->id = flecs_wildcard_to_any(term->id); - } - - 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; - - 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 { - /* (ChildOf, 0) is allowed so query can be used to efficiently - * query for root entities */ - } - } - } 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); - } - } - - 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.id_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) { - 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)) - { - char *pred_str = ecs_get_path(world, term->first.id); - flecs_query_validator_error(ctx, "term with acyclic relationship" - " '%s' cannot have same subject and object", pred_str); - ecs_os_free(pred_str); - 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; -} - -static -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 and first.id have competing values"); - return -1; - } - if (src->name && (src->id & ~EcsTermRefFlags)) { - flecs_query_validator_error(ctx, - "src.name and src.id have competing values"); - return -1; - } - if (second->name && (second->id & ~EcsTermRefFlags)) { - flecs_query_validator_error(ctx, - "second.name and second.id have competing values"); - 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)) { - /* 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; - } - } - - ecs_entity_t first_entity = 0; - if ((first->id & EcsIsEntity)) { - first_entity = first_id; - } - - ecs_id_record_t *idr = flecs_id_record_get(world, term->id); - ecs_flags32_t id_flags = 0; - if (idr) { - id_flags = idr->flags; - } else if (ECS_IS_PAIR(term->id)) { - ecs_id_record_t *wc_idr = flecs_id_record_get( - world, ecs_pair(ECS_PAIR_FIRST(term->id), EcsWildcard)); - if (wc_idr) { - id_flags = wc_idr->flags; - } - } - - if (src_id || src->name) { - if (!(term->src.id & EcsTraverseFlags)) { - if (id_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, ".trav specified for " - "'%s' which can't be inherited", 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 (!(id_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', add the (OnInstantiate, Inherit) trait", idstr); - ecs_os_free(idstr); - return -1; - } - } - - if (first_entity) { - /* 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_id_record_get(world, ecs_pair(EcsIsA, first->id))) { - if (!((first_flags & EcsTraverseFlags) == EcsSelf)) { - term->flags_ |= EcsTermIdInherited; - } - } - - /* If component id is final, don't attempt component inheritance */ - ecs_record_t *first_record = flecs_entities_get(world, first_entity); - ecs_table_t *first_table = first_record ? first_record->table : NULL; - 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 (ecs_table_has_id(world, first_table, EcsTransitive)) { - term->flags_ |= EcsTermTransitive; - } - - if (ecs_table_has_id(world, first_table, EcsReflexive)) { - term->flags_ |= EcsTermReflexive; - } - } - } - } - - /* Check if term is union */ - if (ecs_table_has_id(world, first_table, EcsUnion)) { - /* Any wildcards don't need special handling as they just return - * (Rel, *). */ - if (ECS_IS_PAIR(term->id) && ECS_PAIR_SECOND(term->id) != EcsAny) { - term->flags_ |= EcsTermIsUnion; - } - } - } - } - - /* Check if term has toggleable component */ - if (id_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_id_is_wildcard(term->id)) { - if (!(id_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; - 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_ & EcsTermIsUnion) { - 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 flecs_identifier_is_0( - const char *id) -{ - return id[0] == '#' && id[1] == '0' && !id[2]; -} - -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; -} - -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)); -} - -int ecs_term_finalize( - const ecs_world_t *world, - ecs_term_t *term) -{ - ecs_query_validator_ctx_t ctx = {0}; - ctx.world = world; - ctx.term = term; - return flecs_term_finalize(world, term, &ctx); -} - -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( - const 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; - 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; - ctx.term_index = i; - - if (flecs_term_finalize(world, term, &ctx)) { - return -1; - } - - 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) { - if (ECS_TERM_REF_ID(&term[-1].src) != ECS_TERM_REF_ID(&term->src)) { - flecs_query_validator_error(&ctx, "mismatching src.id for OR terms"); - return -1; - } - if (term->oper != EcsOr && term->oper != EcsAnd) { - flecs_query_validator_error(&ctx, - "term after OR operator must use AND operator"); - return -1; - } - } else { - field_count ++; - } - - term->field_index = flecs_ito(int8_t, field_count - 1); - - if (ecs_id_is_wildcard(term->id)) { - q->flags |= EcsQueryMatchWildcards; - } else if (!(term->flags_ & EcsTermIsOr)) { - ECS_TERMSET_SET(q->static_id_fields, 1u << term->field_index); - } - - 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) == 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_ & EcsTermIsUnion) { - 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); - } - } - - 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); - } - - bool is_sparse = false; - - ecs_id_record_t *idr = flecs_id_record_get(world, term->id); - if (idr) { - if (ecs_os_has_threading()) { - ecs_os_ainc(&idr->keep_alive); - } else { - idr->keep_alive ++; - } - - term->flags_ |= EcsTermKeepAlive; - - if (idr->flags & EcsIdIsSparse) { - is_sparse = true; - } - } else { - ecs_entity_t type = ecs_get_typeid(world, term->id); - if (type && ecs_has_id(world, type, EcsSparse)) { - is_sparse = true; - } - } - - 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 (term->flags_ & EcsTermIsOr) { - if (flecs_query_or_other_type(q, i)) { - q->sizes[field] = 0; - q->ids[field] = 0; - continue; - } - } - - ecs_id_record_t *idr = flecs_id_record_get(world, term->id); - if (idr) { - if (!ECS_IS_PAIR(idr->id) || ECS_PAIR_FIRST(idr->id) != EcsWildcard) { - if (idr->type_info) { - q->sizes[field] = idr->type_info->size; - q->ids[field] = idr->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); - - 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 (!(term->flags_ & EcsTermIsTrivial)) { - break; - } - } - - if (term_count && (i == term_count)) { - 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); - - /* 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_SCRIPT - ecs_script_impl_t script = { - .pub.world = world, - .pub.name = desc->entity ? ecs_get_name(world, desc->entity) : NULL, - .pub.code = expr - }; - - /* Allocate buffer that's large enough to tokenize the query string */ - script.token_buffer_size = ecs_os_strlen(expr) * 2 + 1; - script.token_buffer = flecs_alloc( - &flecs_query_impl(q)->stage->allocator, script.token_buffer_size); - - if (flecs_terms_parse(&script.pub, &q->terms[term_count], - &term_count)) - { - flecs_free(&stage->allocator, - script.token_buffer_size, script.token_buffer); - goto error; - } - - /* Store on query object so we can free later */ - flecs_query_impl(q)->tokens = script.token_buffer; - flecs_query_impl(q)->tokens_len = - flecs_ito(int16_t, script.token_buffer_size); - #else - (void)world; - (void)stage; - ecs_err("cannot parse query expression: script addon required"); - goto error; - #endif - } - - q->term_count = flecs_ito(int8_t, term_count); - - return 0; -error: - return -1; -} - -#ifndef FLECS_SANITIZE -static -bool flecs_query_finalize_simple( - ecs_world_t *world, - ecs_query_t *q, - const ecs_query_desc_t *desc) -{ - /* 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 ++) { - if (!ecs_term_is_initialized(&desc->terms[i])) { - break; - } - - ecs_id_t id = desc->terms[i].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 term = { .id = desc->terms[i].id }; - if (ecs_os_memcmp_t(&term, &desc->terms[i], 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); - - /* Simple query that only queries for component ids */ - - /* Populate terms */ - 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; - } - - term->field_index = i; - term->first.id = first | EcsIsEntity | EcsSelf; - term->src.id = EcsThis | EcsIsVariable | EcsSelf; - - q->ids[i] = id; - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - idr->keep_alive ++; - term->flags_ |= EcsTermKeepAlive; - } - - if (!idr && ECS_IS_PAIR(id)) { - idr = flecs_id_record_get(world, - ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); - } - - bool cacheable = true, trivial = true; - if (idr) { - if (idr->type_info) { - q->sizes[i] = idr->type_info->size; - q->flags |= EcsQueryHasOutTerms; - q->data_fields |= (ecs_termset_t)(1llu << i); - } - - if (idr->flags & EcsIdOnInstantiateInherit) { - term->src.id |= EcsUp; - term->trav = EcsIsA; - up_count ++; - } - - if (idr->flags & EcsIdCanToggle) { - term->flags_ |= EcsTermIsToggle; - trivial = false; - } - - if (ECS_IS_PAIR(id)) { - if (idr->flags & EcsIdIsUnion) { - term->flags_ |= EcsTermIsUnion; - trivial = false; - cacheable = false; - } - } - - if (idr->flags & EcsIdIsSparse) { - term->flags_ |= EcsTermIsSparse; - cacheable = false; trivial = false; - q->row_fields |= flecs_uto(uint32_t, 1llu << i); - } - } - - if (ECS_IS_PAIR(id)) { - 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_id_record_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 ++; - } - } - - /* 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); - q->flags |= EcsQueryMatchThis|EcsQueryMatchOnlyThis|EcsQueryHasTableThisVar; - - 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; -} -#endif - -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; - - /* 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)) { - return 0; - } - #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)); - - return 0; -error: - return -1; -} - - -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 = flecs_bcalloc(&index->page_allocator); - 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); - flecs_ballocator_init(&index->page_allocator, - ECS_SIZEOF(ecs_entity_index_page_t)); -} - -void flecs_entity_index_fini( - ecs_entity_index_t *index) -{ - ecs_vec_fini_t(index->allocator, &index->dense, uint64_t); -#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) - 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 ++) { - flecs_bfree(&index->page_allocator, pages[i]); - } -#endif - ecs_vec_fini_t(index->allocator, &index->pages, ecs_entity_index_page_t*); - flecs_ballocator_fini(&index->page_allocator); -} - -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, NULL); - 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->idr = 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) { - return ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0]; - } else { - 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, - "max id %u exceeds 32 bits", index->max_id); - - /* 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, - "max id %u exceeds 32 bits", index->max_id); - - /* 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; -} - -const uint64_t* flecs_entity_index_ids( - const ecs_entity_index_t *index) -{ - return ecs_vec_get_t(&index->dense, uint64_t, 1); -} - -/** - * @file storage/id_index.c - * @brief Index for looking up tables by (component) id. - * - * An id record stores the administration for an in use (component) id, that is - * an id that has been used in tables. - * - * An id 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_id_record_elem( - ecs_id_record_t *head, - ecs_id_record_elem_t *list, - ecs_id_record_t *idr) -{ - return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head); -} - -static -void flecs_id_record_elem_insert( - ecs_id_record_t *head, - ecs_id_record_t *idr, - ecs_id_record_elem_t *elem) -{ - ecs_id_record_elem_t *head_elem = flecs_id_record_elem(idr, elem, head); - ecs_id_record_t *cur = head_elem->next; - elem->next = cur; - elem->prev = head; - if (cur) { - ecs_id_record_elem_t *cur_elem = flecs_id_record_elem(idr, elem, cur); - cur_elem->prev = idr; - } - head_elem->next = idr; -} - -static -void flecs_id_record_elem_remove( - ecs_id_record_t *idr, - ecs_id_record_elem_t *elem) -{ - ecs_id_record_t *prev = elem->prev; - ecs_id_record_t *next = elem->next; - ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_id_record_elem_t *prev_elem = flecs_id_record_elem(idr, elem, prev); - prev_elem->next = next; - if (next) { - ecs_id_record_elem_t *next_elem = flecs_id_record_elem(idr, elem, next); - next_elem->prev = prev; - } -} - -static -void flecs_insert_id_elem( - ecs_world_t *world, - ecs_id_record_t *idr, - ecs_id_t wildcard, - ecs_id_record_t *widr) -{ - ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); - if (!widr) { - widr = flecs_id_record_ensure(world, wildcard); - } - ecs_assert(widr != NULL, ECS_INTERNAL_ERROR, NULL); - - if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { - ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - flecs_id_record_elem_insert(widr, idr, &idr->first); - } else { - ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - flecs_id_record_elem_insert(widr, idr, &idr->second); - - if (idr->flags & EcsIdTraversable) { - flecs_id_record_elem_insert(widr, idr, &idr->trav); - } - } -} - -static -void flecs_remove_id_elem( - ecs_id_record_t *idr, - ecs_id_t wildcard) -{ - ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); - - if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { - ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - flecs_id_record_elem_remove(idr, &idr->first); - } else { - ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - flecs_id_record_elem_remove(idr, &idr->second); - - if (idr->flags & EcsIdTraversable) { - flecs_id_record_elem_remove(idr, &idr->trav); - } - } -} - -static -ecs_id_t flecs_id_record_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 o = ECS_PAIR_SECOND(id); - if (r == EcsAny) { - r = EcsWildcard; - } - if (o == EcsAny) { - o = EcsWildcard; - } - id = ecs_pair(r, o); - } - return id; -} - -void flecs_id_record_init_sparse( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - if (!idr->sparse) { - if (idr->flags & EcsIdIsSparse) { - ecs_assert(!(idr->flags & EcsIdIsUnion), ECS_CONSTRAINT_VIOLATED, - "cannot mix union and sparse traits"); - ecs_assert(idr->type_info != NULL, ECS_INVALID_OPERATION, - "only components can be marked as sparse"); - idr->sparse = flecs_walloc_t(world, ecs_sparse_t); - flecs_sparse_init(idr->sparse, NULL, NULL, idr->type_info->size); - } else - if (idr->flags & EcsIdIsUnion) { - idr->sparse = flecs_walloc_t(world, ecs_switch_t); - flecs_switch_init(idr->sparse, &world->allocator); - } - } -} - -static -void flecs_id_record_fini_sparse( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - if (idr->sparse) { - if (idr->flags & EcsIdIsSparse) { - ecs_assert(flecs_sparse_count(idr->sparse) == 0, - ECS_INTERNAL_ERROR, NULL); - flecs_sparse_fini(idr->sparse); - flecs_wfree_t(world, ecs_sparse_t, idr->sparse); - } else - if (idr->flags & EcsIdIsUnion) { - flecs_switch_fini(idr->sparse); - flecs_wfree_t(world, ecs_switch_t, idr->sparse); - } else { - ecs_abort(ECS_INTERNAL_ERROR, "unknown sparse storage"); - } - } -} - -static -ecs_flags32_t flecs_id_record_event_flags( - ecs_world_t *world, - ecs_id_t id) -{ - 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; - return result; -} - -static -ecs_id_record_t* flecs_id_record_new( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr, *idr_t = NULL; - ecs_id_t hash = flecs_id_record_hash(id); - idr = flecs_bcalloc(&world->allocators.id_record); - - if (hash >= FLECS_HI_ID_RECORD_ID) { - ecs_map_insert_ptr(&world->id_index_hi, hash, idr); - } else { - world->id_index_lo[hash] = idr; - } - - ecs_table_cache_init(world, &idr->cache); - - idr->id = id; - idr->refcount = 1; - idr->reachable.current = -1; - - bool is_wildcard = ecs_id_is_wildcard(id); - bool is_pair = ECS_IS_PAIR(id); - - ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK; - if (is_pair) { - // rel = ecs_pair_first(world, id); - rel = ECS_PAIR_FIRST(id); - rel = flecs_entities_get_alive(world, rel); - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - - /* Relationship object can be 0, as tables without a ChildOf - * relationship are added to the (ChildOf, 0) id record */ - tgt = ECS_PAIR_SECOND(id); - -#ifdef FLECS_DEBUG - /* Check constraints */ - if (tgt) { - tgt = flecs_entities_get_alive(world, 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)) - { - char *idstr = ecs_id_str(world, id); - char *tgtstr = ecs_id_str(world, tgt); - ecs_err("constraint violated: relationship '%s' cannot be used" - " as target in pair '%s'", tgtstr, idstr); - ecs_os_free(tgtstr); - ecs_os_free(idstr); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } - } - } - - if (ecs_has_id(world, rel, EcsTarget)) { - char *idstr = ecs_id_str(world, id); - char *relstr = ecs_id_str(world, rel); - ecs_err("constraint violated: " - "%s: target '%s' cannot be used as relationship", - idstr, relstr); - ecs_os_free(relstr); - ecs_os_free(idstr); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } - - if (tgt && !ecs_id_is_wildcard(tgt) && tgt != EcsUnion) { - /* 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)) { - char *idstr = ecs_id_str(world, id); - char *tgtstr = ecs_get_path(world, tgt); - char *oneofstr = ecs_get_path(world, oneof); - ecs_err("OneOf constraint violated: " - "%s: '%s' is not a child of '%s'", - idstr, tgtstr, oneofstr); - ecs_os_free(oneofstr); - ecs_os_free(tgtstr); - ecs_os_free(idstr); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } - } - - /* Check if we're not trying to inherit from a final target */ - if (rel == EcsIsA) { - if (ecs_has_id(world, tgt, EcsFinal)) { - char *idstr = ecs_id_str(world, id); - char *tgtstr = ecs_get_path(world, tgt); - ecs_err("Final constraint violated: " - "%s: cannot inherit from final entity '%s'", - idstr, tgtstr); - ecs_os_free(tgtstr); - ecs_os_free(idstr); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } - } - } -#endif - - if (!is_wildcard && (rel != EcsFlag)) { - /* Inherit flags from (relationship, *) record */ - ecs_id_record_t *idr_r = flecs_id_record_ensure( - world, ecs_pair(rel, EcsWildcard)); - idr->parent = idr_r; - idr->flags = idr_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, idr, ecs_pair(rel, EcsWildcard), idr_r); - - idr_t = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, tgt)); - flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, tgt), idr_t); - } - } else { - rel = id & ECS_COMPONENT_MASK; - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - - /* Can't use relationship outside of a pair */ -#ifdef FLECS_DEBUG - rel = flecs_entities_get_alive(world, rel); - bool is_tgt = false; - if (ecs_has_id(world, rel, EcsRelationship) || - (is_tgt = ecs_has_id(world, rel, EcsTarget))) - { - char *idstr = ecs_id_str(world, id); - char *relstr = ecs_id_str(world, rel); - ecs_err("constraint violated: " - "%s: relationship%s '%s' cannot be used as component", - idstr, is_tgt ? " target" : "", relstr); - ecs_os_free(relstr); - ecs_os_free(idstr); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } -#endif - } - - /* Initialize type info if id is not a tag */ - if (!is_wildcard && (!role || is_pair)) { - if (!(idr->flags & EcsIdTag)) { - const ecs_type_info_t *ti = flecs_type_info_get(world, rel); - if (!ti && tgt) { - ti = flecs_type_info_get(world, tgt); - } - idr->type_info = ti; - } - } - - /* 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, EcsEntityIsId); - if (tgt) { - /* Flag for OnDeleteTarget policies */ - ecs_record_t *tgt_r = flecs_entities_get_any(world, tgt); - ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_record_add_flag(tgt_r, EcsEntityIsTarget); - if (idr->flags & EcsIdTraversable) { - /* Flag used to determine if object should be traversed when - * propagating events or with super/subset queries */ - flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); - - /* Add reference to (*, tgt) id record to entity record */ - tgt_r->idr = idr_t; - } - - /* If second element of pair determines the type, check if the pair - * should be stored as a sparse component. */ - if (idr->type_info && idr->type_info->component == tgt) { - if (ecs_has_id(world, tgt, EcsSparse)) { - idr->flags |= EcsIdIsSparse; - } - } - } - - idr->flags |= flecs_id_record_event_flags(world, id); - - if (idr->flags & EcsIdIsSparse) { - flecs_id_record_init_sparse(world, idr); - } else if (idr->flags & EcsIdIsUnion) { - if (ECS_IS_PAIR(id) && ECS_PAIR_SECOND(id) == EcsUnion) { - flecs_id_record_init_sparse(world, idr); - } - } - - 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); - } - - /* Update counters */ - world->info.id_create_total ++; - world->info.component_id_count += idr->type_info != NULL; - world->info.tag_id_count += idr->type_info == NULL; - world->info.pair_id_count += is_pair; - - return idr; -} - -static -void flecs_id_record_assert_empty( - ecs_id_record_t *idr) -{ - (void)idr; - ecs_assert(flecs_table_cache_count(&idr->cache) == 0, - ECS_INTERNAL_ERROR, NULL); -} - -static -void flecs_id_record_free( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t id = idr->id; - - flecs_id_record_assert_empty(idr); - - /* Id is still in use by a query */ - ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0), - ECS_ID_IN_USE, "cannot delete id that is queried for"); - - 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(idr, ecs_pair(rel, EcsWildcard)); - flecs_remove_id_elem(idr, 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_id_record_t *cur, *next = idr->second.next; - while ((cur = next)) { - flecs_id_record_assert_empty(cur); - next = cur->second.next; - flecs_id_record_release(world, cur); - } - } else { - /* Iterate (Relationship, *) list */ - ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *cur, *next = idr->first.next; - while ((cur = next)) { - flecs_id_record_assert_empty(cur); - next = cur->first.next; - flecs_id_record_release(world, cur); - } - } - - ecs_log_pop_2(); - } - } - - /* Cleanup sparse storage */ - flecs_id_record_fini_sparse(world, idr); - - /* Update counters */ - world->info.id_delete_total ++; - world->info.pair_id_count -= ECS_IS_PAIR(id); - world->info.component_id_count -= idr->type_info != NULL; - world->info.tag_id_count -= idr->type_info == NULL; - - /* Unregister the id record from the world & free resources */ - ecs_table_cache_fini(&idr->cache); - flecs_name_index_free(idr->name_index); - ecs_vec_fini_t(&world->allocator, &idr->reachable.ids, ecs_reachable_elem_t); - - ecs_id_t hash = flecs_id_record_hash(id); - if (hash >= FLECS_HI_ID_RECORD_ID) { - ecs_map_remove(&world->id_index_hi, hash); - } else { - world->id_index_lo[hash] = NULL; - } - - flecs_bfree(&world->allocators.id_record, idr); - - 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); - } -} - -ecs_id_record_t* flecs_id_record_ensure( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - idr = flecs_id_record_new(world, id); - } - return idr; -} - -ecs_id_record_t* flecs_id_record_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->idr_isa_wildcard; - } else if (id == ecs_pair(EcsChildOf, EcsWildcard)) { - return world->idr_childof_wildcard; - } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { - return world->idr_identifier_name; - } - - ecs_id_t hash = flecs_id_record_hash(id); - ecs_id_record_t *idr = NULL; - if (hash >= FLECS_HI_ID_RECORD_ID) { - idr = ecs_map_get_deref(&world->id_index_hi, ecs_id_record_t, hash); - } else { - idr = world->id_index_lo[hash]; - } - - return idr; -} - -void flecs_id_record_claim( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - (void)world; - idr->refcount ++; -} - -int32_t flecs_id_record_release( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - int32_t rc = -- idr->refcount; - ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); - - if (!rc) { - flecs_id_record_free(world, idr); - } - - return rc; -} - -void flecs_id_record_release_tables( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - ecs_table_cache_iter_t it; - if (flecs_table_cache_all_iter(&idr->cache, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - /* Release current table */ - flecs_table_fini(world, tr->hdr.table); - } - } -} - -bool flecs_id_record_set_type_info( - ecs_world_t *world, - ecs_id_record_t *idr, - const ecs_type_info_t *ti) -{ - bool is_wildcard = ecs_id_is_wildcard(idr->id); - if (!is_wildcard) { - if (ti) { - if (!idr->type_info) { - world->info.tag_id_count --; - world->info.component_id_count ++; - } - } else { - if (idr->type_info) { - world->info.tag_id_count ++; - world->info.component_id_count --; - } - } - } - - bool changed = idr->type_info != ti; - idr->type_info = ti; - - return changed; -} - -ecs_hashmap_t* flecs_id_record_name_index_ensure( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - ecs_hashmap_t *map = idr->name_index; - if (!map) { - map = idr->name_index = flecs_name_index_new(world, &world->allocator); - } - - return map; -} - -ecs_hashmap_t* flecs_id_name_index_ensure( - ecs_world_t *world, - ecs_id_t id) -{ - flecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - - return flecs_id_record_name_index_ensure(world, idr); -} - -ecs_hashmap_t* flecs_id_name_index_get( - const ecs_world_t *world, - ecs_id_t id) -{ - flecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; - } - - return idr->name_index; -} - -ecs_table_record_t* flecs_table_record_get( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) -{ - flecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t* idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; - } - - return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); -} - -ecs_table_record_t* flecs_id_record_get_table( - const ecs_id_record_t *idr, - const ecs_table_t *table) -{ - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); -} - -void flecs_init_id_records( - ecs_world_t *world) -{ - /* Cache often used id records on world */ - world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard); - world->idr_wildcard_wildcard = flecs_id_record_ensure(world, - ecs_pair(EcsWildcard, EcsWildcard)); - world->idr_any = flecs_id_record_ensure(world, EcsAny); - world->idr_isa_wildcard = flecs_id_record_ensure(world, - ecs_pair(EcsIsA, EcsWildcard)); -} - -void flecs_fini_id_records( - 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_id_record_release(world, ecs_map_ptr(&it)); - } - - int32_t i; - for (i = 0; i < FLECS_HI_ID_RECORD_ID; i ++) { - ecs_id_record_t *idr = world->id_index_lo[i]; - if (idr) { - flecs_id_record_release(world, idr); - } - } - - 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) -{ - const ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - ecs_flags32_t extra_flags = 0; - if (idr->flags & EcsIdOnInstantiateInherit) { - extra_flags |= EcsIdHasOnAdd|EcsIdHasOnRemove; - } - return idr->flags|extra_flags; - } - return flecs_id_record_event_flags(world, id); -} - -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; -} - -/** - * @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_world_t *world, - 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(world, 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_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - const ecs_type_info_t *ti = idr->type_info; - - if (!ti || (idr->flags & EcsIdIsSparse)) { - 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_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_id_t id = idr->id; - - if (ecs_id_is_wildcard(id)) { - ecs_table_record_t *first_tr = &records[tr->index]; - tr->column = first_tr->column; - } - } -} - -/* 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; - - int32_t i; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - - if (id <= EcsLastInternalComponentId) { - table->flags |= EcsTableHasBuiltins; - } - - if (id == EcsModule) { - table->flags |= EcsTableHasBuiltins; - 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 (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; - 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; - } - } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { - table->flags |= EcsTableHasName; - } else if (r == ecs_id(EcsPoly)) { - table->flags |= EcsTableHasBuiltins; - } - } 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_id_record_t *idr = flecs_id_record_ensure(world, id); - ecs_table_record_t *tr = (ecs_table_record_t*)flecs_id_record_get_table( - idr, 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(&idr->cache, table, &tr->hdr); - } else { - tr->count ++; - } - - ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); -} - -void flecs_table_emit( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t event) -{ - ecs_defer_begin(world); - flecs_emit(world, world, 0, &(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_id_record_t *idr, *childof_idr = 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; - } - } - - /* 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]; - - idr = NULL; - - if (dst_id == src_id) { - ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL); - idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache; - } else if (dst_id < src_id) { - idr = flecs_id_record_ensure(world, dst_id); - } - if (idr) { - tr = ecs_vec_append_t(a, records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)idr; - 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); - idr = flecs_id_record_ensure(world, dst_id); - tr->hdr.cache = (ecs_table_cache_t*)idr; - ecs_assert(tr->hdr.cache != 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); - } - - /* 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; - 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_id_record_t *p_idr = (ecs_id_record_t*)tr->hdr.cache; - r = ECS_PAIR_FIRST(dst_id); - if (r == EcsChildOf) { - childof_idr = p_idr; - } - - idr = p_idr->parent; /* (R, *) */ - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - - tr = ecs_vec_append_t(a, records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)idr; - 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]; - 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.cache = (ecs_table_cache_t*)world->idr_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.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard; - tr->index = flecs_ito(int16_t, first_pair); - tr->count = flecs_ito(int16_t, last_pair - first_pair); - } - if (dst_count) { - tr = ecs_vec_append_t(a, records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)world->idr_any; - tr->index = 0; - tr->count = 1; - } - if (dst_count && !has_childof) { - tr = ecs_vec_append_t(a, records, ecs_table_record_t); - childof_idr = world->idr_childof_0; - tr->hdr.cache = (ecs_table_cache_t*)childof_idr; - tr->index = 0; - tr->count = 1; - } - - /* 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; - - /* Register & patch up records */ - for (i = 0; i < dst_record_count; i ++) { - tr = &dst_tr[i]; - idr = (ecs_id_record_t*)dst_tr[i].hdr.cache; - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - - if (ecs_table_cache_get(&idr->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 */ - ecs_table_cache_replace(&idr->cache, table, &tr->hdr); - } else { - /* Other records are not registered yet */ - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_cache_insert(&idr->cache, table, &tr->hdr); - } - - /* Claim id record so it stays alive as long as the table exists */ - flecs_id_record_claim(world, idr); - - /* Initialize event flags */ - table->flags |= idr->flags & EcsIdEventMask; - - /* Initialize column index (will be overwritten by init_columns) */ - tr->column = -1; - - if (ECS_ID_ON_INSTANTIATE(idr->flags) == EcsOverride) { - table->flags |= EcsTableHasOverrides; - } - - if ((i < table->type.count) && (idr->type_info != NULL)) { - if (!(idr->flags & EcsIdIsSparse)) { - column_count ++; - } - } - } - - table->component_map = flecs_wcalloc_n( - world, int16_t, FLECS_HI_COMPONENT_ID); - - if (column_count) { - table->column_map = flecs_walloc_n(world, int16_t, - dst_count + column_count); - } - - table->column_count = flecs_ito(int16_t, column_count); - flecs_table_init_data(world, table); - - if (table->flags & EcsTableHasName) { - ecs_assert(childof_idr != NULL, ECS_INTERNAL_ERROR, NULL); - table->_->name_index = - flecs_id_record_name_index_ensure(world, childof_idr); - ecs_assert(table->_->name_index != NULL, ECS_INTERNAL_ERROR, NULL); - } - - 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_table_cache_t *cache = tr->hdr.cache; - ecs_id_t id = ((ecs_id_record_t*)cache)->id; - - ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_id_record_get(world, id) == (ecs_id_record_t*)cache, - ECS_INTERNAL_ERROR, NULL); - (void)id; - - ecs_table_cache_remove(cache, table_id, &tr->hdr); - flecs_id_record_release(world, (ecs_id_record_t*)cache); - } - - 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_notify_on_remove(world, table, NULL, 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]; - flecs_invoke_hook(world, table, tr, count, row, entities, - table->type.array[type_index], column->ti, event, callback); -} - -/* Construct components */ -static -void flecs_table_invoke_ctor( - 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 ctor = ti->hooks.ctor; - if (ctor) { - void *ptr = ECS_ELEM(column->data, ti->size, row); - ctor(ptr, 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, - ecs_column_t *column, - ecs_entity_t *entities, - int32_t row, - int32_t count, - bool construct) -{ - 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(column, 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); - } -} - -/* Destruct all components and/or delete all entities in table in range */ -static -void flecs_table_dtor_all( - ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - bool is_delete) -{ - const ecs_entity_t *entities = ecs_table_entities(table); - int32_t column_count = table->column_count; - int32_t i, c, end = row + count; - - ecs_assert(!column_count || table->data.columns != NULL, - ECS_INTERNAL_ERROR, NULL); - - if (is_delete && table->_->traversable_count) { - /* If table contains monitored entities with traversable relationships, - * make sure to invalidate observer cache */ - flecs_emit_propagate_invalidate(world, table, row, count); - } - - /* If table has components with destructors, iterate component columns */ - if (table->flags & EcsTableHasDtors) { - /* Throw up a lock just to be sure */ - table->_->lock = true; - - /* 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[row], row, count); - } - } - - /* Destruct components */ - for (c = 0; c < column_count; c++) { - flecs_table_invoke_dtor(&table->data.columns[c], row, 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 = row; i < end; 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); - - if (is_delete) { - flecs_entities_remove(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 - ecs_record_t *record = flecs_entities_get(world, e); - record->table = NULL; - record->row = 0; - } - } - - table->_->lock = false; - - /* If table does not have destructors, just update entity index */ - } else { - 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); - flecs_entities_remove(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_record_t *record = flecs_entities_get(world, e); - record->table = NULL; - record->row = record->row & ECS_ROW_FLAGS_MASK; - (void)e; - } - } - } -} - -#define FLECS_LOCKED_STORAGE_MSG \ - "to fix, defer operations 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 is_delete, - bool deallocate) -{ - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - - if (do_on_remove) { - flecs_table_notify_on_remove(world, table); - } - - int32_t count = ecs_table_count(table); - if (count) { - flecs_table_dtor_all(world, table, 0, count, is_delete); - } - - 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(&world->allocator, &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(&world->allocator, &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) -{ - return table->data.entities; -} - -/* Cleanup, no OnRemove, delete from entity index, deactivate table, retain allocations */ -void ecs_table_clear_entities( - ecs_world_t* world, - ecs_table_t* table) -{ - flecs_table_fini_data(world, table, true, true, false); -} - -/* Cleanup, no OnRemove, clear entity index, deactivate table, free allocations */ -void flecs_table_clear_entities_silent( - ecs_world_t *world, - ecs_table_t *table) -{ - flecs_table_fini_data(world, table, false, false, true); -} - -/* Cleanup, run OnRemove, clear entity index, deactivate table, free allocations */ -void flecs_table_clear_entities( - ecs_world_t *world, - ecs_table_t *table) -{ - flecs_table_fini_data(world, table, true, false, true); -} - -/* Cleanup, run OnRemove, delete from entity index, deactivate table, free allocations */ -void flecs_table_delete_entities( - ecs_world_t *world, - ecs_table_t *table) -{ - flecs_table_fini_data(world, table, true, 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) -{ - (void)world; - 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); - - bool is_root = table == &world->store.root; - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - 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, delete from entity index, don't deactivate */ - flecs_table_fini_data(world, table, false, true, 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_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); - flecs_wfree_n(world, int16_t, table->column_count + table->type.count, - table->column_map); - flecs_wfree_n(world, int16_t, FLECS_HI_COMPONENT_ID, 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_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); - 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) -{ - (void)world; - if (table->dirty_state) { - table->dirty_state[index] ++; - } -} - -/* Mark table component dirty */ -void flecs_table_mark_dirty( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t component) -{ - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - 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_id_record_t *idr = flecs_id_record_get(world, component); - if (!idr) { - return; - } - - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, 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] ++; - } -} - -/* 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_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; - void *result = NULL; - - 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_xtor_t ctor = ti->hooks.ctor; - ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Create vector */ - ecs_vec_t dst; - ecs_vec_init(&world->allocator, &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) */ - result = ECS_ELEM(dst_buffer, elem_size, count); - ctor(result, to_add, ti); - } - - /* Free old vector */ - ecs_vec_fini(&world->allocator, 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(&world->allocator, column, elem_size, dst_size); - } - - result = ecs_vec_grow(&world->allocator, column, elem_size, to_add); - - ecs_xtor_t ctor; - if (construct && (ctor = ti->hooks.ctor)) { - /* If new elements need to be constructed and component has a - * constructor, construct */ - ctor(result, 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); - - 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(&world->allocator, &v_entities, ecs_entity_t, size); - - ecs_entity_t *e = ecs_vec_last_t(&v_entities, ecs_entity_t) + 1; - 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 (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); - } - - /* 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, &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; - flecs_table_invoke_add_hooks(world, table, column, 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); - - /* 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_world_t *world, - 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(&world->allocator, &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); - - flecs_table_check_sanity(world, 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(&world->allocator, - &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); - ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); - - /* Fast path: no switch columns, no lifecycle actions */ - if (!(table->flags & EcsTableIsComplex)) { - flecs_table_fast_append(world, table); - table->data.count = v_entities.count; - table->data.size = v_entities.size; - return count; - } - - 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, &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(world, 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); - - flecs_table_check_sanity(world, 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(world, 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(world, 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); - ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - - ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); - - flecs_table_check_sanity(world, dst_table); - flecs_table_check_sanity(world, src_table); - - if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { - flecs_table_fast_move(dst_table, dst_index, src_table, src_index); - flecs_table_check_sanity(world, dst_table); - flecs_table_check_sanity(world, src_table); - return; - } - - flecs_table_move_bitset_columns( - dst_table, dst_index, src_table, src_index, 1, false); - - /* If the source and destination entities are the same, move component - * between tables. If the entities are not the same (like when cloning) use - * a copy. */ - bool same_entity = dst_entity == src_entity; - - /* 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); - - if (same_entity) { - 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 { - ecs_copy_t copy = ti->hooks.copy_ctor; - if (copy) { - copy(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, - dst_column, &dst_entity, dst_index, 1, construct); - } else if (same_entity) { - 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, &dst_columns[i_new], - &dst_entity, dst_index, 1, construct); - } - - if (same_entity) { - 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(world, dst_table); - flecs_table_check_sanity(world, 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); - - flecs_table_check_sanity(world, 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(world, 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_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - (void)world; - - flecs_table_check_sanity(world, table); - - bool has_payload = table->data.entities != NULL; - ecs_vec_t v_entities = ecs_vec_from_entities(table); - ecs_vec_reclaim_t(&world->allocator, &v_entities, ecs_entity_t); - - int32_t i, count = table->column_count; - for (i = 0; i < count; i ++) { - ecs_column_t *column = &table->data.columns[i]; - const ecs_type_info_t *ti = column->ti; - ecs_vec_t v_column = ecs_vec_from_column(column, table, ti->size); - ecs_vec_reclaim(&world->allocator, &v_column, ti->size); - column->data = v_column.array; - } - - table->data.count = v_entities.count; - table->data.size = v_entities.size; - table->data.entities = v_entities.array; - - flecs_table_check_sanity(world, 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); - ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); - - flecs_table_check_sanity(world, 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(world, 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(world, table); -} - -static -void flecs_table_merge_vec( - ecs_world_t *world, - 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(&world->allocator, 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(&world->allocator, - dst, size, elem_size); - } - ecs_vec_set_count(&world->allocator, - 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(&world->allocator, 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(&world->allocator, 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, 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(&world->allocator, 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(world, &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; - ecs_allocator_t *a = &world->allocator; - - 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(a, &dst_vec, dst_elem_size, column_size); - dst_column->data = dst_vec.array; - flecs_table_invoke_ctor(dst_column, 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(a, &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(a, &vec, elem_size, column_size); - column->data = vec.array; - flecs_table_invoke_ctor(column, 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(a, &vec, elem_size); - column->data = vec.array; - } - - /* Mark entity column as dirty */ - flecs_table_mark_table_dirty(world, dst_table, 0); - - 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); - ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - - flecs_table_check_sanity(world, src_table); - flecs_table_check_sanity(world, 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(world, src_table); - flecs_table_check_sanity(world, 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]; -} - -/* -- 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_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; - } - - ecs_table_record_t *tr = flecs_id_record_get_table(idr, 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_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; - } - - ecs_table_record_t *tr = flecs_id_record_get_table(idr, 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, NULL); - - 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; -} - -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; -} - -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); -} - -int32_t flecs_table_observed_count( - const ecs_table_t *table) -{ - return table->_->traversable_count; -} - -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; -} - -/** - * @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 id 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_w_params(&cache->index, &world->allocators.ptr); -} - -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->cache = cache; - result->table = ECS_CONST_CAST(ecs_table_t*, table); - - flecs_table_cache_list_insert(cache, result); - - if (table) { - 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); - if (table) { - if (ecs_map_is_init(&cache->index)) { - return ecs_map_get_deref(&cache->index, void**, table->id); - } - return NULL; - } else { - ecs_table_cache_hdr_t *elem = cache->tables.first; - ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL); - return elem; - } -} - -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(table_id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_assert(elem->cache == 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( - 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( - 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( - 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; -} - -ecs_table_cache_hdr_t* flecs_table_cache_next_( - ecs_table_cache_iter_t *it) -{ - 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 (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) { - dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); - } - - 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; - - 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 */ -static -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; - } -} - -/* 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, 256); - ecs_vec_init_t(a, &builder->removed, ecs_id_t, 256); - 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_w_params(edges->hi, &world->allocators.ptr); - } - - 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); - 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) -{ - 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 -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) -{ - ecs_type_t node_type = node->type; - ecs_type_t next_type = next->type; - - if (ECS_IS_PAIR(id)) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair( - ECS_PAIR_FIRST(id), EcsWildcard)); - if (idr->flags & EcsIdIsUnion) { - if (node != next) { - id = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); - } else { - ecs_table_diff_t *diff = flecs_bcalloc( - &world->allocators.table_diff); - diff->added.count = 1; - diff->added.array = flecs_wdup_n(world, ecs_id_t, 1, &id); - diff->added_flags = EcsTableHasUnion; - edge->diff = diff; - 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); - - /* 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; - - 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; - } else { - ecs_table_record_t *tr = &base_table->_->records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (ECS_ID_ON_INSTANTIATE(idr->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_id_record_t *idr = flecs_id_record_get(world, wc); - if (idr) { - exclusive = (idr->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) { - ecs_table_record_t *tr = flecs_id_record_get_table( - world->idr_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_id_record_t *idr_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); - if (!table) { - return; - } - - ecs_table_record_t *tr = flecs_id_record_get_table( - idr_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, idr_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_id_record_t *idr = NULL; - ecs_entity_t r = 0, o = 0; - - if (ECS_IS_PAIR(with)) { - r = ECS_PAIR_FIRST(with); - o = ECS_PAIR_SECOND(with); - idr = flecs_id_record_ensure(world, ecs_pair(r, EcsWildcard)); - if (idr->flags & EcsIdIsUnion) { - with = ecs_pair(r, EcsUnion); - } else if (idr->flags & EcsIdExclusive) { - /* Relationship is exclusive, check if table already has it */ - ecs_table_record_t *tr = flecs_id_record_get_table(idr, 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 { - idr = flecs_id_record_ensure(world, with); - r = with; - } - - /* 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 (idr->flags & EcsIdWith) { - ecs_id_record_t *idr_with_wildcard = flecs_id_record_get(world, - ecs_pair(EcsWith, EcsWildcard)); - /* If id has With property, add targets to type */ - flecs_add_with_property(world, idr_with_wildcard, &dst_type, r, o); - } - - 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) -{ - if (ECS_IS_PAIR(without)) { - ecs_entity_t r = 0; - ecs_id_record_t *idr = NULL; - r = ECS_PAIR_FIRST(without); - idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); - if (idr && idr->flags & EcsIdIsUnion) { - without = ecs_pair(r, EcsUnion); - } - } - - /* 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 */ - } - - 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 & EcsTableHasUnion)) { - /* 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); - } -} - -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) { - /* 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); - } -} - -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); - - node = node ? node : &world->store.root; - - /* 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) { - 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); - - node = node ? node : &world->store.root; - - /* 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; - if (diff->added_flags & EcsIdIsUnion) { - if (diff->added.count == 1) { - diff->added.array = id_ptr; - diff->added.count = 1; - diff->removed.count = 0; - } - } - } 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(); -} - -/* 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; - 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; - 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/json/deserialize.c - * @brief Deserialize JSON strings into (component) values. - */ - -/** - * @file addons/json/json.h - * @brief Internal functions for JSON addon. - */ - -#ifndef FLECS_JSON_PRIVATE_H -#define FLECS_JSON_PRIVATE_H - - -#ifdef FLECS_JSON - -/* 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_id_record_t *idr_doc_name; - ecs_id_record_t *idr_doc_color; - ecs_json_value_ser_ctx_t value_ctx[64]; -} ecs_json_ser_ctx_t; - -typedef struct ecs_json_this_data_t { - const ecs_entity_t *ids; - const EcsIdentifier *names; - 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); - -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); - -const char* flecs_json_expect( - const char *json, - ecs_json_token_t token_kind, - char *token, - const ecs_from_json_desc_t *desc); - -const char* flecs_json_expect_string( - const char *json, - char *token, - char **out, - const ecs_from_json_desc_t *desc); - -const char* flecs_json_expect_member( - const char *json, - char *token, - const ecs_from_json_desc_t *desc); - -const char* flecs_json_expect_next_member( - const char *json, - char *token, - const ecs_from_json_desc_t *desc); - -const char* flecs_json_expect_member_name( - const char *json, - char *token, - const char *member_name, - const ecs_from_json_desc_t *desc); - -const char* flecs_json_skip_object( - const char *json, - char *token, - const ecs_from_json_desc_t *desc); - -const char* flecs_json_skip_array( - const char *json, - char *token, - const ecs_from_json_desc_t *desc); - -/* Serialize to JSON */ -void flecs_json_next( - ecs_strbuf_t *buf); - -void flecs_json_number( - ecs_strbuf_t *buf, - double value); - -void flecs_json_u32( - ecs_strbuf_t *buf, - uint32_t value); - -void flecs_json_true( - ecs_strbuf_t *buf); - -void flecs_json_false( - ecs_strbuf_t *buf); - -void flecs_json_bool( - ecs_strbuf_t *buf, - bool value); - -void flecs_json_null( - ecs_strbuf_t *buf); - -void flecs_json_array_push( - ecs_strbuf_t *buf); - -void flecs_json_array_pop( - ecs_strbuf_t *buf); - -void flecs_json_object_push( - ecs_strbuf_t *buf); - -void flecs_json_object_pop( - ecs_strbuf_t *buf); - -void flecs_json_string( - ecs_strbuf_t *buf, - const char *value); - -void flecs_json_string_escape( - ecs_strbuf_t *buf, - const char *value); - -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_type_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, - 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); - -void 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); - -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); - -#endif - -#endif /* FLECS_JSON_PRIVATE_H */ - -#include - -#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, - ecs_map_t *anonymous_ids) -{ - ecs_entity_t e = 0; - - 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, &term, token_buffer)) { - 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); - } - - lah = flecs_json_parse(json, &token_kind, token); - if (token_kind != JsonNull) { - ecs_entity_t type = ecs_get_typeid(world, id); - if (!type) { - flecs_json_missing_reflection(world, id, json, ctx, desc); - if (desc->strict) { - goto error; - } - - json = flecs_json_skip_object(json + 1, token, desc); - if (!json) { - goto error; - } - } else { - void *ptr = ecs_ensure_id(world, e, id); - - lah = flecs_json_parse(json, &token_kind, token); - if (token_kind != JsonNull) { - 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_json_skip_object(json + 1, token, desc); - if (!json) { - goto error; - } - } else { - json = next; - ecs_modified_id(world, e, id); - } - } 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 (!e) { - e = flecs_json_lookup(world, parent, str, desc); - } else { - ecs_set_name(world, e, str); - } - - if (str[0] != '#') { - 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; - } - - if (!e) { - 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 */ - } - - json = flecs_json_parse_next_member(json, token, &token_kind, desc); - if (!json) { - goto error; - } - 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_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 = (ecs_entity_t(*)( - const ecs_world_t*, const char*, void*))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 = (ecs_entity_t(*)( - const ecs_world_t*, const char*, void*))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. - */ - -#include - -#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; - - 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 (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 (ecs_meta_dotmember(&cur, token) != 0) { - goto error; - } - } 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. - */ - -#include - -#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)); - 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) -{ - 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, '"'); - 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_type_op_kind_t kind) -{ - return kind - EcsOpPrimitive; -} - -#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 ecs_meta_type_serialized_init( - ecs_iter_t *it); - -void ecs_meta_dtor_serialized( - EcsTypeSerializer *ptr); - -ecs_meta_type_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); - -#endif - -#endif - - -#ifdef FLECS_JSON - -int ecs_entity_to_json_buf( - const 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 id record for flecs.doc ids */ - ecs_json_ser_ctx_t ser_ctx; - ecs_os_zeromem(&ser_ctx); -#ifdef FLECS_DOC - ser_ctx.idr_doc_name = flecs_id_record_get(world, - ecs_pair_t(EcsDocDescription, EcsName)); - ser_ctx.idr_doc_color = flecs_id_record_get(world, - ecs_pair_t(EcsDocDescription, EcsDocColor)); -#endif - - 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 - }; - - /* 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 - }; - - if (flecs_json_serialize_iter_result( - world, &it, buf, &iter_desc, &ser_ctx)) - { - return -1; - } - - return 0; -} - -char* ecs_entity_to_json( - const 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 (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 id record for flecs.doc ids */ - ecs_json_ser_ctx_t ser_ctx; - ecs_os_zeromem(&ser_ctx); -#ifdef FLECS_DOC - ser_ctx.idr_doc_name = flecs_id_record_get(world, - ecs_pair_t(EcsDocDescription, EcsName)); - ser_ctx.idr_doc_color = flecs_id_record_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); - } - - 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); - return -1; - } - } - - 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) -{ - char **variable_names = it->variable_names; - int32_t var_count = it->variable_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 = it->variables[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_id_record_t *idr = flecs_id_record_get(world, - ecs_pair_t(EcsPoly, EcsQuery)); - - if (idr) { - ecs_table_cache_iter_t it; - if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &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 (!qit.variables) { - 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_idr( - const ecs_world_t *world, - ecs_strbuf_t *buf, - ecs_id_record_t *idr) -{ - char *id_str = ecs_id_str(world, ecs_pair_first(world, idr->id)); - - flecs_json_member(buf, id_str); - ecs_os_free(id_str); - - flecs_json_array_push(buf); - - ecs_table_cache_iter_t it; - if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &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_id_record_t *idr = flecs_id_record_get(world, - ecs_pair(relationship, entity)); - - if (idr) { - if (relationship == EcsWildcard) { - ecs_id_record_t *cur = idr; - while ((cur = cur->second.next)) { - flecs_json_serialize_refs_idr(world, buf, cur); - } - } else { - flecs_json_serialize_refs_idr(world, buf, idr); - } - } - - 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; - } -} - -void 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_assert(row < it->count, ECS_INTERNAL_ERROR, NULL); - - if (parent_path) { - flecs_json_memberl(buf, "parent"); - flecs_json_string(buf, parent_path); - } - - flecs_json_memberl(buf, "name"); - 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 (desc && desc->serialize_entity_ids) { - flecs_json_memberl(buf, "id"); - flecs_json_u32(buf, (uint32_t)this_data->ids[row]); - } - -#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); - } -} - -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 = {0}; - - 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_table_record_get( - world, table, ecs_pair(EcsChildOf, EcsWildcard)); - 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, ".", ""); - } - - /* 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); - } - - /* 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 { - /* Very rare case, but could happen if someone's using an iterator - * to return empty entities. */ - } - } - - if (desc && desc->serialize_table) { - if (flecs_json_serialize_iter_result_table(world, it, buf, - desc, 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 ++) { - flecs_json_next(buf); - flecs_json_object_push(buf); - - if (has_this) { - flecs_json_serialize_iter_this( - it, parent_path, this_data, i, buf, desc); - } - - 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_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_id_t id = table->type.array[i]; - if (!(idr->flags & EcsIdIsSparse) && - (!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 = idr->type_info; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - - 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_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - - if (src_table) { - if (!(idr->flags & EcsIdOnInstantiateInherit)) { - continue; - } - } - if (idr->flags & EcsIdIsSparse) { - 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; - - 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_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - - if (src_table) { - if (!(idr->flags & EcsIdOnInstantiateInherit)) { - continue; - } - } - if (idr->flags & EcsIdIsSparse) { - 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); - } - - ecs_entity_t second = flecs_entities_get_alive( - world, ECS_PAIR_SECOND(id)); - - 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 (second == EcsUnion) { - ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); - ecs_entity_t e = ecs_table_entities(table)[row]; - second = ecs_get_target(world, e, first, 0); - } - - 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 || !desc->serialize_builtin) { - if (flecs_json_is_builtin(id)) { - continue; - } - } - - void *ptr; - const ecs_table_record_t *tr = &table->_->records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - - if (src_table) { - if (!(idr->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 (!(idr->flags & EcsIdIsSparse)) { - continue; - } - ecs_entity_t e = ecs_table_entities(table)[row]; - ptr = flecs_sparse_get_any(idr->sparse, 0, e); - ti = idr->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 -bool 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) -{ - if (!(table->flags & EcsTableHasIsA)) { - return false; - } - - const ecs_table_record_t *tr = flecs_id_record_get_table( - world->idr_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; - flecs_json_serialize_table_inherited_type(world, base_table, buf, desc); - - 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; - flecs_json_serialize_table_components( - world, base_table, table, buf, NULL, desc, - ECS_RECORD_TO_ROW(base_record->row), &component_count); - - if (desc->serialize_type_info) { - flecs_json_serialize_table_type_info( - world, base_table, buf, desc); - } - - flecs_json_object_pop(buf); - } - - return true; -} - -static -bool 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) -{ - if (!(table->flags & EcsTableHasIsA)) { - return false; - } - - flecs_json_memberl(buf, "inherited"); - flecs_json_object_push(buf); - flecs_json_serialize_table_inherited_type(world, table, buf, desc); - flecs_json_object_pop(buf); - return true; -} - -static -bool 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 = false; - ecs_strbuf_list_push(buf, "", ","); - result |= flecs_json_serialize_table_tags(world, table, NULL, buf, desc); - result |= flecs_json_serialize_table_pairs(world, table, NULL, row, buf, desc); - result |= flecs_json_serialize_vars(world, it, buf, desc); - if (desc->serialize_inherited) { - result |= flecs_json_serialize_table_inherited(world, table, buf, desc); - } - - if (desc->serialize_type_info) { - /* If we're serializing tables and are requesting type info, it must be - * added to each result. */ - result |= flecs_json_serialize_table_type_info(world, table, buf, desc); - } - - ecs_strbuf_list_pop(buf, ""); - if (!result) { - ecs_strbuf_reset(buf); - } - return result; -} - -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, - 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, - * except when table has union pairs, which can be different for each - * entity. */ - ecs_strbuf_t tags_pairs_vars_buf = ECS_STRBUF_INIT; - int32_t tags_pairs_vars_len = 0; - char *tags_pairs_vars = NULL; - bool has_union = table->flags & EcsTableHasUnion; - - if (!has_union) { - if (flecs_json_serialize_table_tags_pairs_vars( - world, it, table, 0, &tags_pairs_vars_buf, desc)) - { - 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 ++) { - flecs_json_next(buf); - flecs_json_object_push(buf); - - if (has_this) { - ecs_json_this_data_t this_data_cpy = *this_data; - flecs_json_serialize_iter_this( - it, parent_path, &this_data_cpy, i - it->offset, buf, desc); - } - - if (has_union) { - if (flecs_json_serialize_table_tags_pairs_vars( - world, it, table, i, &tags_pairs_vars_buf, desc)) - { - tags_pairs_vars_len = ecs_strbuf_written(&tags_pairs_vars_buf); - tags_pairs_vars = ecs_strbuf_get(&tags_pairs_vars_buf); - } - } - - if (tags_pairs_vars) { - ecs_strbuf_list_appendstrn(buf, - tags_pairs_vars, tags_pairs_vars_len); - } - - if (has_union) { - ecs_os_free(tags_pairs_vars); - tags_pairs_vars = NULL; - } - - 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 json_typeinfo_ser_type( - const ecs_world_t *world, - ecs_entity_t type, - ecs_strbuf_t *buf); - -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; - case EcsId: - flecs_json_string(str, "id"); - break; - default: - return -1; - } - - return 0; -} - -static -void 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 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); -} - -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); -} - -static -int json_typeinfo_ser_array( - const ecs_world_t *world, - ecs_entity_t elem_type, - int32_t count, - ecs_strbuf_t *str) -{ - ecs_strbuf_list_appendstr(str, "\"array\""); - - flecs_json_next(str); - if (json_typeinfo_ser_type(world, elem_type, str)) { - goto error; - } - - ecs_strbuf_list_append(str, "%u", count); - return 0; -error: - return -1; -} - -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; - } - - return 0; -error: - return -1; -} - -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); - - ecs_strbuf_list_appendstr(str, "\"vector\""); - - flecs_json_next(str); - if (json_typeinfo_ser_type(world, arr->type, str)) { - goto error; - } - - return 0; -error: - return -1; -} - -/* Serialize unit information */ -static -int 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 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); -} - -/* 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, - const EcsStruct *st) -{ - if (op->kind == EcsOpOpaque) { - const EcsOpaque *ct = ecs_get(world, op->type, - EcsOpaque); - ecs_assert(ct != NULL, ECS_INTERNAL_ERROR, NULL); - return json_typeinfo_ser_type(world, ct->as_type, str); - } - - flecs_json_array_push(str); - - switch(op->kind) { - case EcsOpPush: - case EcsOpPop: - /* Should not be parsed as single op */ - ecs_throw(ECS_INVALID_PARAMETER, - "unexpected push/pop serializer instruction"); - 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; - case EcsOpOpaque: - /* Can't happen, already handled above */ - ecs_throw(ECS_INTERNAL_ERROR, NULL); - 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 (json_typeinfo_ser_primitive( - flecs_json_op_to_primitive_kind(op->kind), str)) - { - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } - break; - case EcsOpScope: - case EcsOpPrimitive: - default: - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } - - if (st) { - 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_next(str); - flecs_json_object_push(str); - - if (unit) { - json_typeinfo_ser_unit(world, str, unit); - } - if (value_range) { - json_typeinfo_ser_range(str, "range", &m->range); - } - if (error_range) { - json_typeinfo_ser_range(str, "error_range", &m->error_range); - } - if (warning_range) { - 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; -} - -/* 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, - const EcsStruct *st) -{ - const EcsStruct *stack[64] = {st}; - int32_t sp = 1; - - for (int i = 0; i < op_count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; - - if (op != ops) { - if (op->name) { - flecs_json_member(str, op->name); - } - } - - int32_t elem_count = op->count; - if (elem_count > 1) { - 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; - } - - switch(op->kind) { - case EcsOpPush: - flecs_json_object_push(str); - ecs_assert(sp < 63, ECS_INVALID_OPERATION, "type nesting too deep"); - stack[sp ++] = ecs_get(world, op->type, EcsStruct); - break; - case EcsOpPop: { - 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); - json_typeinfo_ser_unit(world, str, unit); - flecs_json_object_pop(str); - flecs_json_array_pop(str); - } - - flecs_json_object_pop(str); - sp --; - break; - } - case EcsOpArray: - case EcsOpVector: - case EcsOpEnum: - case EcsOpBitmask: - 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: - case EcsOpOpaque: - if (json_typeinfo_ser_type_op(world, op, str, stack[sp - 1])) { - goto error; - } - break; - case EcsOpPrimitive: - case EcsOpScope: - default: - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } - } - - return 0; -error: - return -1; -} - -static -int 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; - } - - const EcsStruct *st = ecs_get(world, type, EcsStruct); - ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); - int32_t count = ecs_vec_count(&ser->ops); - - if (json_typeinfo_ser_type_ops(world, ops, count, buf, st)) { - 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 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_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 in_array); - -static -int flecs_json_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str); - -/* Serialize enumeration */ -static -int flecs_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); - - int32_t value = *(const 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_deref(&enum_type->constants, - ecs_enum_constant_t, (ecs_map_key_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_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); - - 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(&bitmask_type->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; -} - -/* Serialize elements of a contiguous array */ -static -int flecs_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, - bool is_array) -{ - flecs_json_array_push(str); - - const void *ptr = base; - - int i; - for (i = 0; i < elem_count; i ++) { - ecs_strbuf_list_next(str); - if (flecs_json_ser_type_ops(world, ops, op_count, ptr, str, is_array)) { - return -1; - } - ptr = ECS_OFFSET(ptr, elem_size); - } - - flecs_json_array_pop(str); - - return 0; -} - -static -int flecs_json_ser_type_elements( - const ecs_world_t *world, - ecs_entity_t type, - const void *base, - int32_t elem_count, - ecs_strbuf_t *str, - bool is_array) -{ - const EcsTypeSerializer *ser = ecs_get( - world, type, EcsTypeSerializer); - 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_vec_first_t(&ser->ops, ecs_meta_type_op_t); - int32_t op_count = ecs_vec_count(&ser->ops); - - return flecs_json_ser_elements( - world, ops, op_count, base, elem_count, comp->size, str, is_array); -} - -/* 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); - - return flecs_json_ser_type_elements( - world, a->type, ptr, a->count, str, true); -} - -/* 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) -{ - const ecs_vec_t *value = base; - const EcsVector *v = ecs_get(world, op->type, EcsVector); - ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t count = ecs_vec_count(value); - void *array = ecs_vec_first(value); - - /* Serialize contiguous buffer of vector */ - return flecs_json_ser_type_elements(world, v->type, array, count, str, false); -} - -typedef struct json_serializer_ctx_t { - ecs_strbuf_t *str; - bool is_collection; - bool is_struct; -} json_serializer_ctx_t; - -static -int json_ser_custom_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 json_ser_custom_member( - const ecs_serializer_t *ser, - const char *name) -{ - json_serializer_ctx_t *json_ser = ser->ctx; - if (!json_ser->is_struct) { - ecs_err("serializer::member can only be called for structs"); - return -1; - } - flecs_json_member(json_ser->str, name); - return 0; -} - -static -int json_ser_custom_type( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str) -{ - const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); - ecs_assert(ct != NULL, ECS_INVALID_OPERATION, - "entity %s in opaque type serializer instruction is not an opaque type", - ecs_get_name(world, op->type)); - ecs_assert(ct->as_type != 0, ECS_INVALID_OPERATION, - "opaque type %s has not populated as_type field", - ecs_get_name(world, op->type)); - ecs_assert(ct->serialize != NULL, ECS_INVALID_OPERATION, - "opaque type %s does not have serialize interface", - ecs_get_name(world, op->type)); - - const EcsType *pt = ecs_get(world, ct->as_type, EcsType); - ecs_assert(pt != NULL, ECS_INVALID_OPERATION, - "opaque type %s is missing flecs.meta.Type component", - ecs_get_name(world, op->type)); - - ecs_type_kind_t kind = pt->kind; - bool is_collection = false; - bool is_struct = false; - - if (kind == EcsStructType) { - flecs_json_object_push(str); - is_struct = true; - } else if (kind == EcsArrayType || kind == EcsVectorType) { - flecs_json_array_push(str); - is_collection = true; - } - - json_serializer_ctx_t json_ser = { - .str = str, - .is_struct = is_struct, - .is_collection = is_collection - }; - - ecs_serializer_t ser = { - .world = world, - .value = json_ser_custom_value, - .member = json_ser_custom_member, - .ctx = &json_ser - }; - - if (ct->serialize(&ser, base)) { - return -1; - } - - if (kind == EcsStructType) { - flecs_json_object_pop(str); - } else if (kind == EcsArrayType || kind == EcsVectorType) { - flecs_json_array_pop(str); - } - - return 0; -} - -/* Forward serialization to the different type kinds */ -static -int flecs_json_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str) -{ - void *vptr = ECS_OFFSET(ptr, op->offset); - bool large_int = false; - if (op->kind == EcsOpI64) { - if (*(int64_t*)vptr >= 2147483648) { - large_int = true; - } - } else if (op->kind == EcsOpU64) { - if (*(uint64_t*)vptr >= 2147483648) { - large_int = true; - } - } - - if (large_int) { - ecs_strbuf_appendch(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)*(const ecs_f32_t*)vptr, '"'); - break; - case EcsOpF64: - ecs_strbuf_appendflt(str, - *(ecs_f64_t*)vptr, '"'); - break; - case EcsOpEnum: - if (flecs_json_ser_enum(world, op, vptr, str)) { - goto error; - } - break; - case EcsOpBitmask: - if (flecs_json_ser_bitmask(world, op, vptr, str)) { - goto error; - } - break; - case EcsOpArray: - if (json_ser_array(world, op, vptr, str)) { - goto error; - } - break; - case EcsOpVector: - if (json_ser_vector(world, op, vptr, str)) { - goto error; - } - break; - case EcsOpOpaque: - if (json_ser_custom_type(world, op, vptr, str)) { - goto error; - } - break; - case EcsOpEntity: { - ecs_entity_t e = *(const ecs_entity_t*)vptr; - 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*)vptr; - 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), - ECS_OFFSET(ptr, op->offset), str, true)) - { - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } - break; - - case EcsOpPrimitive: - case EcsOpScope: - default: - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } - - if (large_int) { - ecs_strbuf_appendch(str, '"'); - } - - return 0; -error: - return -1; -} - -/* Iterate over a slice of the type ops array */ -static -int flecs_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, - int32_t in_array) -{ - for (int i = 0; i < op_count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; - - if (in_array <= 0) { - if (op->name) { - flecs_json_member(str, op->name); - } - - int32_t elem_count = op->count; - if (elem_count > 1) { - /* Serialize inline array */ - if (flecs_json_ser_elements(world, op, op->op_count, base, - elem_count, op->size, str, true)) - { - return -1; - } - - i += op->op_count - 1; - continue; - } - } - - switch(op->kind) { - case EcsOpPush: - flecs_json_object_push(str); - in_array --; - break; - case EcsOpPop: - flecs_json_object_pop(str); - in_array ++; - break; - case EcsOpArray: - case EcsOpVector: - 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: - case EcsOpString: - case EcsOpOpaque: - if (flecs_json_ser_type_op(world, op, base, str)) { - goto error; - } - break; - default: - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } - } - - 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_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); - int32_t count = ecs_vec_count(v_ops); - return flecs_json_ser_type_ops(world, ops, count, base, str, 0); -} - -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) -{ - 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 - }; - - 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/api.c - * @brief API for creating entities with reflection data. - */ - - -#ifdef FLECS_META - -static -bool flecs_type_is_number( - ecs_world_t *world, - ecs_entity_t type) -{ - const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); - if (!p) { - return false; - } - - 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; - - case EcsBool: - case EcsByte: - case EcsUPtr: - case EcsIPtr: - case EcsString: - case EcsEntity: - case EcsId: - return false; - default: - ecs_abort(ECS_INVALID_PARAMETER, NULL); - } -} - -/* 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_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); - - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - ecs_set(world, t, EcsPrimitive, { desc->kind }); - - flecs_resume_readonly(world, &rs); - return t; -} - -ecs_entity_t ecs_enum_init( - ecs_world_t *world, - const ecs_enum_desc_t *desc) -{ - ecs_suspend_readonly_state_t rs; - world = flecs_suspend_readonly(world, &rs); - - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - ecs_entity_t underlying = desc->underlying_type; - - if (!underlying) { - underlying = ecs_id(ecs_i32_t); - } - - ecs_assert(ecs_is_valid(world, underlying), ECS_INVALID_PARAMETER, - "invalid underlying type for enum"); - - 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; - } - - 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_set(world, t, EcsEnum, { .underlying_type = underlying }); - - 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; - } - - 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 { - void *ptr = ecs_ensure_id(world, c, - ecs_pair(EcsConstant, underlying)); - 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); - } - - 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; - } - - ecs_modified_id(world, c, ecs_pair(EcsConstant, underlying)); - } - } - - ecs_set_scope(world, old_scope); - flecs_resume_readonly(world, &rs); - - if (i == 0) { - ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; - } - - return t; -} - -ecs_entity_t ecs_bitmask_init( - ecs_world_t *world, - const ecs_bitmask_desc_t *desc) -{ - ecs_suspend_readonly_state_t rs; - world = flecs_suspend_readonly(world, &rs); - - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - 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 - }); - - 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); - - if (i == 0) { - ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); - ecs_delete(world, t); - return 0; - } - - return t; -} - -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); - - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - ecs_set(world, t, EcsArray, { - .type = desc->type, - .count = desc->count - }); - - flecs_resume_readonly(world, &rs); - - return t; -} - -ecs_entity_t ecs_vector_init( - ecs_world_t *world, - const ecs_vector_desc_t *desc) -{ - ecs_suspend_readonly_state_t rs; - world = flecs_suspend_readonly(world, &rs); - - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - ecs_set(world, t, EcsVector, { - .type = desc->type - }); - - flecs_resume_readonly(world, &rs); - - return t; -} - -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; - } - - if (ECS_EQ(range->min, range->max)) { - return false; - } - - if (range->min < with->min || - range->max > with->max) - { - return true; - } - - return false; -} - -ecs_entity_t ecs_struct_init( - ecs_world_t *world, - const ecs_struct_desc_t *desc) -{ - ecs_suspend_readonly_state_t rs; - world = flecs_suspend_readonly(world, &rs); - - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - ecs_entity_t old_scope = ecs_set_scope(world, t); - - 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 (!m_desc->name) { - ecs_err("member %d of struct '%s' does not have a name", i, - ecs_get_name(world, t)); - goto error; - } - - ecs_entity_t m = ecs_entity(world, { - .name = m_desc->name - }); - - ecs_set(world, m, EcsMember, { - .type = m_desc->type, - .count = m_desc->count, - .offset = m_desc->offset, - .unit = m_desc->unit, - .use_offset = m_desc->use_offset - }); - - EcsMemberRanges *ranges = NULL; - const ecs_member_value_range_t *range = &m_desc->range; - const ecs_member_value_range_t *error = &m_desc->error_range; - const ecs_member_value_range_t *warning = &m_desc->warning_range; - if (ECS_NEQ(range->min, range->max)) { - ranges = ecs_ensure(world, m, EcsMemberRanges); - if (range->min > range->max) { - char *member_name = ecs_get_path(world, m); - ecs_err("member '%s' has an invalid value range [%f..%f]", - member_name, range->min, range->max); - ecs_os_free(member_name); - goto error; - } - ranges->value.min = range->min; - ranges->value.max = range->max; - } - if (ECS_NEQ(error->min, error->max)) { - if (error->min > error->max) { - char *member_name = ecs_get_path(world, m); - ecs_err("member '%s' has an invalid error range [%f..%f]", - member_name, error->min, error->max); - ecs_os_free(member_name); - goto error; - } - if (flecs_member_range_overlaps(error, range)) { - char *member_name = ecs_get_path(world, m); - ecs_err("error range of member '%s' overlaps with value range", - member_name); - ecs_os_free(member_name); - goto error; - } - if (!ranges) { - ranges = ecs_ensure(world, m, EcsMemberRanges); - } - ranges->error.min = error->min; - ranges->error.max = error->max; - } - - if (ECS_NEQ(warning->min, warning->max)) { - if (warning->min > warning->max) { - char *member_name = ecs_get_path(world, m); - ecs_err("member '%s' has an invalid warning range [%f..%f]", - member_name, warning->min, warning->max); - ecs_os_free(member_name); - goto error; - } - if (flecs_member_range_overlaps(warning, range)) { - char *member_name = ecs_get_path(world, m); - ecs_err("warning range of member '%s' overlaps with value " - "range", member_name); - ecs_os_free(member_name); - goto error; - } - if (flecs_member_range_overlaps(warning, error)) { - char *member_name = ecs_get_path(world, m); - ecs_err("warning range of member '%s' overlaps with error " - "range", member_name); - ecs_os_free(member_name); - goto error; - } - - if (!ranges) { - ranges = ecs_ensure(world, m, EcsMemberRanges); - } - ranges->warning.min = warning->min; - ranges->warning.max = warning->max; - } - - if (ranges && !flecs_type_is_number(world, m_desc->type)) { - char *member_name = ecs_get_path(world, m); - ecs_err("member '%s' has an value/error/warning range, but is not a " - "number", member_name); - ecs_os_free(member_name); - goto error; - } - - if (ranges) { - ecs_modified(world, m, EcsMemberRanges); - } - } - - ecs_set_scope(world, old_scope); - flecs_resume_readonly(world, &rs); - - if (i == 0) { - ecs_err("struct '%s' has no members", ecs_get_name(world, t)); - goto error; - } - - if (!ecs_has(world, t, EcsStruct)) { - goto error; - } - - return t; -error: - flecs_resume_readonly(world, &rs); - if (t) { - ecs_delete(world, t); - } - return 0; -} - -ecs_entity_t ecs_opaque_init( - ecs_world_t *world, - const ecs_opaque_desc_t *desc) -{ - ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL); - - ecs_suspend_readonly_state_t rs; - world = flecs_suspend_readonly(world, &rs); - - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - ecs_set_ptr(world, t, EcsOpaque, &desc->type); - - flecs_resume_readonly(world, &rs); - - return t; -} - -ecs_entity_t ecs_unit_init( - ecs_world_t *world, - const ecs_unit_desc_t *desc) -{ - ecs_suspend_readonly_state_t rs; - world = flecs_suspend_readonly(world, &rs); - - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - 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; - } - - ecs_add_pair(world, t, EcsQuantity, desc->quantity); - } else { - ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); - } - - 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); - - 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; -} - -ecs_entity_t ecs_unit_prefix_init( - ecs_world_t *world, - const ecs_unit_prefix_desc_t *desc) -{ - ecs_suspend_readonly_state_t rs; - world = flecs_suspend_readonly(world, &rs); - - ecs_entity_t t = desc->entity; - if (!t) { - t = ecs_new_low_id(world); - } - - ecs_set(world, t, EcsUnitPrefix, { - .symbol = ECS_CONST_CAST(char*, desc->symbol), - .translation = desc->translation - }); - - flecs_resume_readonly(world, &rs); - - return t; -} - -ecs_entity_t ecs_quantity_init( - ecs_world_t *world, - const ecs_entity_desc_t *desc) -{ - ecs_suspend_readonly_state_t rs; - world = flecs_suspend_readonly(world, &rs); - - ecs_entity_t t = ecs_entity_init(world, desc); - if (!t) { - return 0; - } - - ecs_add_id(world, t, EcsQuantity); - - flecs_resume_readonly(world, &rs); - - return t; -} - -#endif - -/** - * @file addons/meta/c_utils.c - * @brief C utilities for meta addon. - */ - - -#ifdef FLECS_META - -#include - -#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 = 1; - 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 != 1) { - 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_entity_t old_scope = ecs_set_scope(world, t); - - while ((ptr = flecs_meta_utils_parse_member(ptr, &token, &ctx)) && ptr[0]) { - ecs_entity_t m = ecs_entity(world, { - .name = token.name - }); - - ecs_entity_t type = flecs_meta_utils_lookup( - world, &token.type, ptr, 1, &ctx); - if (!type) { - goto error; - } - - ecs_set(world, m, EcsMember, { - .type = type, - .count = (ecs_size_t)token.count - }); - } - - ecs_set_scope(world, old_scope); - - return 0; -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_set(world, t, EcsEnum, { .underlying_type = ecs_id(ecs_i32_t) }); - 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 -#include - -#ifdef FLECS_META -#ifdef FLECS_SCRIPT -#endif - -static -const char* flecs_meta_op_kind_str( - ecs_meta_type_op_kind_t kind) -{ - switch(kind) { - - case EcsOpEnum: return "Enum"; - case EcsOpBitmask: return "Bitmask"; - case EcsOpArray: return "Array"; - case EcsOpVector: return "Vector"; - case EcsOpOpaque: return "Opaque"; - 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"; - case EcsOpId: return "Id"; - case EcsOpScope: return "Scope"; - default: return "<< invalid kind >>"; - } -} - -/* Get current scope */ -static -ecs_meta_scope_t* flecs_meta_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_meta_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_type_op_t* flecs_meta_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->op_cur]; -} - -/* Get component for type in current scope */ -static -const EcsComponent* get_ecs_component( - 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); - } - 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) -{ - return get_ecs_component(world, scope)->size; -} - -static -int32_t get_elem_count( - ecs_meta_scope_t *scope) -{ - const EcsOpaque *opaque = scope->opaque; - - if (scope->vector) { - return ecs_vec_count(scope->vector); - } else if (opaque && opaque->count) { - return flecs_uto(int32_t, opaque->count(scope[-1].ptr)); - } - - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - return op->count; -} - -/* Get pointer to current field/element */ -static -ecs_meta_type_op_t* flecs_meta_cursor_get_ptr( - const ecs_world_t *world, - ecs_meta_scope_t *scope) -{ - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - ecs_size_t size = get_size(world, scope); - const EcsOpaque *opaque = scope->opaque; - - if (scope->vector) { - ecs_vec_set_min_count(NULL, scope->vector, size, scope->elem_cur + 1); - scope->ptr = ecs_vec_first(scope->vector); - } else if (opaque) { - if (scope->is_collection) { - 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; - - void *opaque_ptr = opaque->ensure_element( - scope->ptr, flecs_ito(size_t, scope->elem_cur)); - ecs_assert(opaque_ptr != NULL, ECS_INVALID_OPERATION, - "ensure_element returned NULL"); - return opaque_ptr; - } 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; - } - } - - return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); -} - -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_type_op_t), - .op_count = ecs_vec_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) -{ - 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, - flecs_meta_cursor_get_scope(cursor)); -} - -int ecs_meta_next( - ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); - scope = flecs_meta_cursor_restore_scope(cursor, scope); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - - if (scope->is_collection) { - scope->elem_cur ++; - scope->op_cur = 0; - - if (scope->opaque) { - return 0; - } - - if (scope->elem_cur >= get_elem_count(scope)) { - ecs_err("out of collection bounds (%d elements vs. size %d)", - scope->elem_cur + 1, get_elem_count(scope)); - return -1; - } - return 0; - } - - scope->op_cur += op->op_count; - - if (scope->op_cur >= scope->op_count) { - ecs_err("out of bounds"); - return -1; - } - - return 0; -} - -int ecs_meta_elem( - ecs_meta_cursor_t *cursor, - int32_t elem) -{ - ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); - if (!scope->is_collection) { - ecs_err("ecs_meta_elem can be used for collections only"); - return -1; - } - - scope->elem_cur = elem; - scope->op_cur = 0; - - if (scope->elem_cur >= get_elem_count(scope) || (scope->elem_cur < 0)) { - ecs_err("out of collection bounds (%d)", scope->elem_cur); - return -1; - } - - return 0; -} - -int ecs_meta_member( - ecs_meta_cursor_t *cursor, - const char *name) -{ - if (cursor->depth == 0) { - ecs_err("cannot move to member in root scope"); - return -1; - } - - ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); - scope = flecs_meta_cursor_restore_scope(cursor, scope); - - ecs_hashmap_t *members = scope->members; - const ecs_world_t *world = cursor->world; - - if (!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(members, name, 0, 0); - if (!cur_ptr) { - char *path = ecs_get_path(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]); - - const EcsOpaque *opaque = scope->opaque; - if (opaque) { - 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 0; -} - -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); - ecs_os_memcpy(token_out, start, len); - token_out[len] = '\0'; - if (ch == '.') { - ptr ++; - } - - return ptr; -} - -int ecs_meta_dotmember( - ecs_meta_cursor_t *cursor, - const char *name) -{ - ecs_meta_scope_t *cur_scope = flecs_meta_cursor_get_scope(cursor); - flecs_meta_cursor_restore_scope(cursor, cur_scope); - - int32_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 (ecs_meta_member(cursor, token)) { - goto error; - } - - if (!ptr[0]) { - break; - } - - dotcount ++; - } - - cur_scope = flecs_meta_cursor_get_scope(cursor); - if (dotcount) { - cur_scope->prev_depth = prev_depth; - } - - return 0; -error: - return -1; -} - -int ecs_meta_push( - ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - const ecs_world_t *world = cursor->world; - - if (cursor->depth == 0) { - if (!cursor->is_primitive_scope) { - bool is_primitive = false; - if ((op->kind > EcsOpScope) && (op->count <= 1)) { - is_primitive = true; - } else if (op->kind == EcsOpOpaque) { - const EcsOpaque *t = ecs_get(world, op->type, EcsOpaque); - ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); - if (ecs_has(world, t->as_type, EcsPrimitive)) { - is_primitive = true; - } - } - - if ((cursor->is_primitive_scope = is_primitive)) { - return 0; - } - } - } - - void *ptr = flecs_meta_cursor_get_ptr(world, scope); - cursor->depth ++; - ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, - ECS_INVALID_PARAMETER, NULL); - - ecs_meta_scope_t *next_scope = flecs_meta_cursor_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 - }; - - /* With 'is_inline_array' set to true we ensure that we can never push - * the same inline array twice */ - return 0; - } - - /* Operation-specific switch behavior */ - switch(op->kind) { - - /* Struct push: this happens when pushing a struct member. */ - case EcsOpPush: { - const EcsOpaque *opaque = scope->opaque; - if (opaque) { - /* If this is a nested push for an opaque type, push the type of the - * element instead of the next operation. This ensures that we won't - * use flattened offsets for nested members. */ - if (flecs_meta_cursor_push_type( - world, next_scope, op->type, ptr) != 0) - { - goto error; - } - - /* Strip the Push operation since we already pushed */ - next_scope->members = next_scope->ops[0].members; - next_scope->ops = &next_scope->ops[1]; - next_scope->op_count --; - break; - } - - /* The ops array contains a flattened list for all members and nested - * members of a struct, so we can use (ops + 1) to initialize the ops - * array of the next scope. */ - 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, - .members = op->members - }; - break; - } - - /* Array push for an array type. Arrays can be encoded in 2 ways: either by - * setting the EcsMember::count member to a value >1, or by specifying an - * array type as member type. This is the latter case. */ - case EcsOpArray: { - if (flecs_meta_cursor_push_type(world, next_scope, op->type, ptr) != 0) { - goto error; - } - - const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); - next_scope->type = type_ptr->type; - next_scope->is_collection = true; - break; - } - - /* Vector push */ - case EcsOpVector: { - next_scope->vector = ptr; - if (flecs_meta_cursor_push_type(world, next_scope, op->type, NULL) != 0) { - goto error; - } - - const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); - next_scope->type = type_ptr->type; - next_scope->is_collection = true; - break; - } - - /* Opaque type push. Depending on the type the opaque type represents the - * scope will be pushed as a struct or collection type. The type information - * of the as_type is retained, as this is important for type checking and - * for nested opaque type support. */ - case EcsOpOpaque: { - const EcsOpaque *type_ptr = ecs_get(world, op->type, EcsOpaque); - ecs_entity_t as_type = type_ptr->as_type; - const EcsType *mtype_ptr = ecs_get(world, as_type, EcsType); - - /* Check what kind of type the opaque type represents */ - switch(mtype_ptr->kind) { - - /* Opaque vector support */ - case EcsVectorType: { - const EcsVector *vt = ecs_get(world, type_ptr->as_type, EcsVector); - next_scope->type = vt->type; - - /* Push the element type of the vector type */ - if (flecs_meta_cursor_push_type( - world, next_scope, vt->type, NULL) != 0) - { - goto error; - } - - /* This tracks whether any data was assigned inside the scope. When - * the scope is popped, and is_empty_scope is still true, the vector - * will be resized to 0. */ - next_scope->is_empty_scope = true; - next_scope->is_collection = true; - break; - } - - /* Opaque array support */ - case EcsArrayType: { - const EcsArray *at = ecs_get(world, type_ptr->as_type, EcsArray); - next_scope->type = at->type; - - /* Push the element type of the array type */ - if (flecs_meta_cursor_push_type( - world, next_scope, at->type, NULL) != 0) - { - goto error; - } - - /* Arrays are always a fixed size */ - next_scope->is_empty_scope = false; - next_scope->is_collection = true; - break; - } - - /* Opaque struct support */ - case EcsStructType: - /* Push struct type that represents the opaque type. This ensures - * that the deserializer retains information about members and - * member types, which is necessary for nested opaque types, and - * allows for error checking. */ - if (flecs_meta_cursor_push_type( - world, next_scope, as_type, NULL) != 0) - { - goto error; - } - - /* Strip push op, since we already pushed */ - next_scope->members = next_scope->ops[0].members; - next_scope->ops = &next_scope->ops[1]; - next_scope->op_count --; - break; - - case EcsPrimitiveType: - case EcsEnumType: - case EcsBitmaskType: - case EcsOpaqueType: - default: - break; - } - - next_scope->ptr = ptr; - next_scope->opaque = type_ptr; - break; - } - - case EcsOpPop: - 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 EcsOpString: - case EcsOpEntity: - case EcsOpId: { - char *path = ecs_get_path(world, scope->type); - ecs_err("invalid push for type '%s'", path); - ecs_os_free(path); - goto error; - } - default: - ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); - } - - if (scope->is_collection && !scope->opaque) { - next_scope->ptr = ECS_OFFSET(next_scope->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 = flecs_meta_cursor_get_scope(cursor); - scope = flecs_meta_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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_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 = flecs_meta_cursor_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 */ - } else if (op->kind == EcsOpOpaque) { - const EcsOpaque *opaque = scope->opaque; - if (scope->is_collection) { - const EcsType *mtype = ecs_get(cursor->world, - opaque->as_type, EcsType); - ecs_assert(mtype != NULL, ECS_INTERNAL_ERROR, NULL); - - /* 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. */ - if (mtype->kind != EcsArrayType) { - 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->is_empty_scope) { - /* If no values were serialized for scope, resize - * collection to 0 elements. */ - ecs_assert(!scope->elem_cur, ECS_INTERNAL_ERROR, NULL); - 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, scope->elem_cur + 1)); - } - } - } else { - /* Opaque struct type, nothing to be done */ - } - } 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); - } - } - - return 0; -} - -bool ecs_meta_is_collection( - const ecs_meta_cursor_t *cursor) -{ - ecs_meta_scope_t *scope = flecs_meta_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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_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_meta_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_type_op_t *op = flecs_meta_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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_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_meta_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_type_op_t *op = flecs_meta_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_T_checked(EcsOpEnum, ecs_i32_t, dst, src, bounds) - -#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_type_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'", from, path); - ecs_os_free(path); - } -} - -int ecs_meta_set_bool( - ecs_meta_cursor_t *cursor, - bool value) -{ - ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, 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 EcsOpOpaque: { - const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); - if (ot && ot->assign_bool) { - ot->assign_bool(ptr, value); - break; - } - } - /* fall through */ - case EcsOpArray: - case EcsOpVector: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, 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 EcsOpOpaque: { - 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_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 EcsOpArray: - case EcsOpVector: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, 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_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 EcsOpOpaque: { - 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_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 EcsOpArray: - case EcsOpVector: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); - - 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 EcsOpOpaque: { - 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_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 EcsOpArray: - case EcsOpVector: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); - - 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 EcsOpOpaque: { - 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_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 EcsOpArray: - case EcsOpVector: - case EcsOpPush: - case EcsOpPop: - 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); - 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: return ecs_meta_set_string(cursor, *(char**)value->ptr); - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, 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_type_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_type_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( - 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) -{ - ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); - - 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 EcsOpArray: - case EcsOpVector: - case EcsOpOpaque: - case EcsOpPush: - case EcsOpPop: - 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: { - ecs_assert(*(ecs_string_t*)ptr != value, ECS_INVALID_PARAMETER, NULL); - 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; - } - - const ecs_i32_t *v = ecs_get_pair_second( - cursor->world, c, EcsConstant, ecs_i32_t); - if (v == NULL) { - 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; - } - - set_T(ecs_i32_t, ptr, v[0]); - 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_SCRIPT - 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 EcsOpOpaque: { - 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 EcsOpArray: - case EcsOpVector: - case EcsOpPush: - case EcsOpScope: - case EcsOpPrimitive: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, 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 EcsOpArray: - case EcsOpVector: - case EcsOpOpaque: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, 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 EcsOpOpaque: { - 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 EcsOpArray: - case EcsOpVector: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, 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 EcsOpOpaque: { - 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 EcsOpArray: - case EcsOpVector: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); - switch (op->kind) { - case EcsOpString: - ecs_os_free(*(char**)ptr); - set_T(ecs_string_t, ptr, NULL); - break; - case EcsOpOpaque: { - const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); - if (ot && ot->assign_null) { - ot->assign_null(ptr); - break; - } - } - /* fall through */ - case EcsOpArray: - case EcsOpVector: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_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_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 EcsOpArray: - case EcsOpVector: - case EcsOpOpaque: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpChar: - return *(ecs_char_t*)ptr != 0; - case EcsOpArray: - case EcsOpVector: - case EcsOpOpaque: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, 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 EcsOpArray: - case EcsOpVector: - case EcsOpOpaque: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, 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 EcsOpArray: - case EcsOpVector: - case EcsOpOpaque: - case EcsOpPush: - case EcsOpPop: - 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_type_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 EcsOpArray: - case EcsOpVector: - case EcsOpOpaque: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpString: return *(const char**)ptr; - case EcsOpOpaque: { - /* 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 EcsOpArray: - case EcsOpVector: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpEntity: return *(ecs_entity_t*)ptr; - case EcsOpArray: - case EcsOpVector: - case EcsOpOpaque: - case EcsOpPush: - case EcsOpPop: - 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_meta_cursor_get_scope(cursor); - ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); - void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); - switch(op->kind) { - case EcsOpEntity: return *(ecs_id_t*)ptr; /* Entities are valid ids */ - case EcsOpId: return *(ecs_id_t*)ptr; - case EcsOpArray: - case EcsOpVector: - case EcsOpOpaque: - case EcsOpPush: - case EcsOpPop: - 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_type_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. - */ - - -#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(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*) - } - }), - .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.build_info_t", - .root_sep = "" - }), - .members = { - { .name = "compiler", .type = const_string }, - { .name = "addons", .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 doc components */ -static -void flecs_meta_import_doc_definitions( - ecs_world_t *world) -{ - (void)world; -#ifdef FLECS_DOC - ecs_struct(world, { - .entity = ecs_id(EcsDocDescription), - .members = { - { .name = "value", .type = ecs_id(ecs_string_t) } - } - }); -#endif -} - -/* 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 } - } - }); - - /* Meta doc definitions */ -#ifdef FLECS_DOC - ecs_entity_t meta = ecs_lookup(world, "flecs.meta"); - ecs_doc_set_brief(world, meta, "Flecs module with reflection components"); - - ecs_doc_set_brief(world, ecs_id(EcsType), "Component added to types"); - ecs_doc_set_brief(world, ecs_id(EcsTypeSerializer), "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"); -#endif -} - -void flecs_meta_import_definitions( - ecs_world_t *world) -{ - flecs_meta_import_core_definitions(world); - flecs_meta_import_doc_definitions(world); - flecs_meta_import_meta_definitions(world); -} - -#endif - -/** - * @file addons/meta/meta.c - * @brief Meta addon. - */ - - -#ifdef FLECS_META - -/* ecs_string_t lifecycle */ - -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); -}) - -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; -}) - -static ECS_DTOR(ecs_string_t, ptr, { - ecs_os_free(*(ecs_string_t*)ptr); - *(ecs_string_t*)ptr = NULL; -}) - - -/* EcsTypeSerializer lifecycle */ - -void ecs_meta_dtor_serialized( - EcsTypeSerializer *ptr) -{ - int32_t i, count = ecs_vec_count(&ptr->ops); - ecs_meta_type_op_t *ops = ecs_vec_first(&ptr->ops); - - for (i = 0; i < count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; - if (op->members) { - flecs_name_index_free(op->members); - } - } - - ecs_vec_fini_t(NULL, &ptr->ops, ecs_meta_type_op_t); -} - -static ECS_COPY(EcsTypeSerializer, dst, src, { - ecs_meta_dtor_serialized(dst); - - dst->ops = ecs_vec_copy_t(NULL, &src->ops, ecs_meta_type_op_t); - - int32_t o, count = ecs_vec_count(&dst->ops); - ecs_meta_type_op_t *ops = ecs_vec_first_t(&dst->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 = flecs_name_index_copy(op->members); - } - } -}) - -static ECS_MOVE(EcsTypeSerializer, dst, src, { - ecs_meta_dtor_serialized(dst); - dst->ops = src->ops; - src->ops = (ecs_vec_t){0}; -}) - -static ECS_DTOR(EcsTypeSerializer, ptr, { - ecs_meta_dtor_serialized(ptr); -}) - - -/* EcsStruct lifecycle */ - -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); -} - -static ECS_COPY(EcsStruct, dst, src, { - flecs_struct_dtor(dst); - - dst->members = ecs_vec_copy_t(NULL, &src->members, ecs_member_t); - - ecs_member_t *members = ecs_vec_first_t(&dst->members, ecs_member_t); - int32_t m, count = ecs_vec_count(&dst->members); - - 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); }) - - -/* EcsEnum lifecycle */ - -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 void flecs_constants_copy( - ecs_map_t *dst, - const ecs_map_t *src) -{ - ecs_map_copy(dst, src); - - ecs_map_iter_t it = ecs_map_iter(dst); - while (ecs_map_next(&it)) { - ecs_enum_constant_t **r = ecs_map_ref(&it, ecs_enum_constant_t); - ecs_enum_constant_t *src_c = r[0]; - ecs_enum_constant_t *dst_c = ecs_os_calloc_t(ecs_enum_constant_t); - *dst_c = *src_c; - dst_c->name = ecs_os_strdup(dst_c->name); - r[0] = dst_c; - } -} - -static ECS_COPY(EcsEnum, dst, src, { - flecs_constants_dtor(&dst->constants); - flecs_constants_copy(&dst->constants, &src->constants); - dst->underlying_type = src->underlying_type; -}) - -static ECS_MOVE(EcsEnum, dst, src, { - flecs_constants_dtor(&dst->constants); - dst->constants = src->constants; - dst->underlying_type = src->underlying_type; - ecs_os_zeromem(&src->constants); -}) - -static ECS_DTOR(EcsEnum, ptr, { flecs_constants_dtor(&ptr->constants); }) - - -/* EcsBitmask lifecycle */ - -static ECS_COPY(EcsBitmask, dst, src, { - /* bitmask constant & enum constant have the same layout */ - flecs_constants_dtor(&dst->constants); - flecs_constants_copy(&dst->constants, &src->constants); -}) - -static ECS_MOVE(EcsBitmask, dst, src, { - flecs_constants_dtor(&dst->constants); - dst->constants = src->constants; - ecs_os_zeromem(&src->constants); -}) - -static ECS_DTOR(EcsBitmask, ptr, { flecs_constants_dtor(&ptr->constants); }) - - -/* EcsUnit lifecycle */ - -static void dtor_unit( - EcsUnit *ptr) -{ - ecs_os_free(ptr->symbol); -} - -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; -}) - -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; - - src->symbol = NULL; - src->base = 0; - src->over = 0; - src->prefix = 0; - src->translation = (ecs_unit_translation_t){0}; -}) - -static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) - - -/* EcsUnitPrefix lifecycle */ - -static void dtor_unit_prefix( - EcsUnitPrefix *ptr) -{ - ecs_os_free(ptr->symbol); -} - -static ECS_COPY(EcsUnitPrefix, dst, src, { - dtor_unit_prefix(dst); - dst->symbol = ecs_os_strdup(src->symbol); - dst->translation = src->translation; -}) - -static ECS_MOVE(EcsUnitPrefix, dst, src, { - dtor_unit_prefix(dst); - dst->symbol = src->symbol; - dst->translation = src->translation; - - src->symbol = NULL; - src->translation = (ecs_unit_translation_t){0}; -}) - -static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) - -/* Type initialization */ - -static -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"; - } -} - -static -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) { - /* Determine if this is an existing type or a reflection-defined type (runtime type) */ - meta_type->existing = ecs_has(world, type, EcsComponent); - - /* 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 && !ti->hooks.ctor) { - ti->hooks.ctor = flecs_default_ctor; - } - } 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 < 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; - } - - meta_type->kind = kind; - ecs_modified(world, type, EcsType); - - return 0; -} - -#define init_type_t(world, type, kind, T) \ - flecs_init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) - -static -void flecs_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, - EcsMemberRanges *ranges) -{ - member->member = entity; - member->type = type; - member->count = count; - member->unit = unit; - member->offset = offset; - - if (!count) { - member->count = 1; - } - - ecs_os_strset(ECS_CONST_CAST(char**, &member->name), name); - - if (ranges) { - member->range = ranges->value; - member->error_range = ranges->error; - member->warning_range = ranges->warning; - } else { - ecs_os_zeromem(&member->range); - ecs_os_zeromem(&member->error_range); - ecs_os_zeromem(&member->warning_range); - } -} - -static -int flecs_add_member_to_struct( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t member, - EcsMember *m, - EcsMemberRanges *ranges) -{ - 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); - - const char *name = ecs_get_name(world, member); - if (!name) { - char *path = ecs_get_path(world, 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, member); - ecs_err("member '%s' does not have a type", path); - ecs_os_free(path); - return -1; - } - - if (ecs_get_typeid(world, m->type) == 0) { - char *path = ecs_get_path(world, member); - char *ent_path = ecs_get_path(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; - } - - 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 (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; - } - } - } - - EcsStruct *s = ecs_ensure(world, type, EcsStruct); - ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); - - /* 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); - for (i = 0; i < count; i ++) { - if (members[i].member == member) { - flecs_set_struct_member(&members[i], member, name, m->type, - m->count, m->offset, unit, ranges); - break; - } - } - - /* If member wasn't added yet, add a new element to vector */ - if (i == count) { - 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, name, m->type, - m->count, m->offset, unit, ranges); - - /* Reobtain members array in case it was reallocated */ - members = ecs_vec_first_t(&s->members, ecs_member_t); - count ++; - } - - 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; - size = ECS_ALIGN(size, member_alignment); - elem->size = member_size; - elem->offset = size; - - /* Synchronize offset with Member component */ - if (elem->member == member) { - m->offset = elem->offset; - } else { - EcsMember *other = ecs_ensure(world, elem->member, EcsMember); - other->offset = elem->offset; - } - - 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]; - - 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->size = member_size; - size = elem->offset + member_size; - - const EcsComponent* comp = ecs_get(world, type, EcsComponent); - if (comp) { - alignment = comp->alignment; - } else { - alignment = member_alignment; - } - } - - if (size == 0) { - ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); - return -1; - } - - if (alignment == 0) { - ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); - return -1; - } - - /* Align struct size to struct alignment */ - size = ECS_ALIGN(size, alignment); - - ecs_modified(world, type, EcsStruct); - - /* Do this last as it triggers the update of EcsTypeSerializer */ - if (flecs_init_type(world, type, EcsStructType, size, alignment)) { - return -1; - } - - /* If current struct is also a member, assign to itself */ - if (ecs_has(world, type, EcsMember)) { - EcsMember *type_mbr = ecs_ensure(world, type, EcsMember); - ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); - - type_mbr->type = type; - type_mbr->count = 1; - - ecs_modified(world, type, EcsMember); - } - - return 0; -} - -static -int flecs_add_constant_to_enum( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t e, - ecs_id_t constant_id) -{ - EcsEnum *ptr = ecs_ensure(world, type, EcsEnum); - ecs_entity_t ut = ptr->underlying_type; - - /* 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 = ptr->underlying_type = ecs_pair_second(world, constant_id); - } else { - /* Default to i32 */ - ut = ecs_id(ecs_i32_t); - } - } - - ecs_assert(ut != 0, ECS_INVALID_OPERATION, - "missing underlying type for enum"); - - 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; - } - - 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; - } - - /* Remove constant from map 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)); - } - } - - /* 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; - } - - ecs_meta_cursor_t c; - if (ut_is_unsigned) { - /* It doesn't matter that the underlying value is an i64*/ - c = ecs_meta_cursor(world, ecs_id(ecs_u64_t), &value_unsigned); - } else { - c = ecs_meta_cursor(world, ecs_id(ecs_i64_t), &value); - } - - 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; - } - - value_set = true; - } - - /* 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 { - 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); - flecs_dump_backtrace(stdout); - return -1; - } - } else { - if (c->value >= value) { - value = c->value + 1; - } - } - } - } - - 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; - - } - c->name = ecs_os_strdup(ecs_get_name(world, e)); - c->constant = e; - - if (!value_set) { - void *cptr = ecs_ensure_id(world, e, ecs_pair(EcsConstant, ut)); - 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; - } - } - } - - ecs_modified(world, type, EcsEnum); - return 0; -} - -static -int flecs_add_constant_to_bitmask( - ecs_world_t *world, - ecs_entity_t type, - ecs_entity_t e, - ecs_id_t constant_id) -{ - EcsBitmask *ptr = ecs_ensure(world, type, EcsBitmask); - - /* Remove constant from map 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)); - } - } - - /* 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); - } - - /* 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; - } - } - - 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_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); - cptr[0] = value; - - return 0; -} - -static -void flecs_set_primitive(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsPrimitive *type = ecs_field(it, EcsPrimitive, 0); - - 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; - case EcsId: - init_type_t(world, e, EcsPrimitiveType, ecs_id_t); - break; - } - } -} - -static -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); - - 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 member '%s'", ecs_get_name(world, e)); - continue; - } - - flecs_add_member_to_struct(world, parent, e, &member[i], - ranges ? &ranges[i] : NULL); - } -} - -static -void flecs_set_member_ranges(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsMemberRanges *ranges = ecs_field(it, EcsMemberRanges, 0); - EcsMember *member = ecs_table_get_id(world, it->table, - ecs_id(EcsMember), it->offset); - if (!member) { - return; - } - - 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 member '%s'", ecs_get_name(world, e)); - continue; - } - - flecs_add_member_to_struct(world, parent, e, &member[i], - &ranges[i]); - } -} - -static -void flecs_add_enum(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - EcsEnum *data = ecs_field(it, EcsEnum, 0); - - 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 (!underlying_type) { - underlying_type = data[i].underlying_type = ecs_id(ecs_i32_t); - } - - const EcsComponent *uc = ecs_get(world, underlying_type, EcsComponent); - if (!uc) { - char *str = ecs_get_path(world, underlying_type); - ecs_err("uderlying_type entity for enum '%s' is not a type", str); - ecs_os_free(str); - continue; - } - - if (flecs_init_type(world, e, EcsEnumType, uc->size, uc->alignment)) { - continue; - } - - ecs_add_id(world, e, EcsExclusive); - ecs_add_id(world, e, EcsOneOf); - ecs_add_id(world, e, EcsPairIsTag); - } -} - -static -void flecs_add_bitmask(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, EcsBitmaskType, ecs_u32_t)) { - continue; - } - } -} - -static -void flecs_add_constant(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]; - 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 (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 -void flecs_set_array(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsArray *array = ecs_field(it, EcsArray, 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; - int32_t elem_count = array[i].count; - - 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; - } - - const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); - if (flecs_init_type(world, e, EcsArrayType, - elem_ptr->size * elem_count, elem_ptr->alignment)) - { - continue; - } - } -} - -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; - - 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_vec_t)) { - continue; - } - } -} - -static -void flecs_set_custom_type(ecs_iter_t *it) { - ecs_world_t *world = it->world; - EcsOpaque *serialize = ecs_field(it, EcsOpaque, 0); - - int i, count = it->count; - for (i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_entity_t elem_type = serialize[i].as_type; - - if (!elem_type) { - ecs_err("custom type '%s' has no mapping type", ecs_get_name(world, e)); - continue; - } - - const EcsComponent *comp = ecs_get(world, e, EcsComponent); - if (!comp || !comp->size || !comp->alignment) { - ecs_err("custom type '%s' has no size/alignment, register as component first", - ecs_get_name(world, e)); - flecs_dump_backtrace(stdout); - continue; - } - - if (flecs_init_type(world, e, EcsOpaqueType, comp->size, comp->alignment)) { - continue; - } - } -} - -bool flecs_unit_validate( - ecs_world_t *world, - ecs_entity_t t, - EcsUnit *data) -{ - 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 (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; - } - } - - 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; - } - - 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 (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; - } - } - - 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_appendch(&sbuf, '/'); - ecs_strbuf_appendstr(&sbuf, uptr->symbol); - must_match = true; - } - } - - 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); - } - } - - data->base = base; - data->over = over; - data->prefix = prefix; - data->translation = translation; - - return true; -error: - ecs_os_free(derived_symbol); - return false; -} - -static -void flecs_set_unit(ecs_iter_t *it) { - EcsUnit *u = ecs_field(it, EcsUnit, 0); - - 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]); - } -} - -static -void flecs_unit_quantity_monitor(ecs_iter_t *it) { - ecs_world_t *world = it->world; - - 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); - } - } -} - -static -void flecs_member_on_set(ecs_iter_t *it) { - EcsMember *mbr = ecs_field(it, EcsMember, 0); - if (!mbr->count) { - mbr->count = 1; - } -} - -void FlecsMetaImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsMeta); -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); -#endif - - 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_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) - }); - - ecs_component(world, { - .entity = ecs_entity(world, { .id = EcsConstant, - .name = "constant", .symbol = "EcsConstant", - .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) - }) - }); - - 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) - }); - - 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) - }); - - 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) - }); - - 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) - }); - - 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) - }); - - 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) - }); - - 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) - }); - - 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) - }); - - 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) - }); - - ecs_component(world, { - .entity = ecs_entity(world, { .id = EcsQuantity, - .name = "quantity", .symbol = "EcsQuantity", - .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsInherit)) - }) - }); - - 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_set_hooks(world, EcsStruct, { - .ctor = flecs_default_ctor, - .move = ecs_move(EcsStruct), - .copy = ecs_copy(EcsStruct), - .dtor = ecs_dtor(EcsStruct) - }); - - ecs_set_hooks(world, EcsMember, { - .ctor = flecs_default_ctor, - .on_set = flecs_member_on_set - }); - - ecs_set_hooks(world, EcsMemberRanges, { - .ctor = flecs_default_ctor - }); - - ecs_set_hooks(world, EcsEnum, { - .ctor = flecs_default_ctor, - .move = ecs_move(EcsEnum), - .copy = ecs_copy(EcsEnum), - .dtor = ecs_dtor(EcsEnum) - }); - - ecs_set_hooks(world, EcsBitmask, { - .ctor = flecs_default_ctor, - .move = ecs_move(EcsBitmask), - .copy = ecs_copy(EcsBitmask), - .dtor = ecs_dtor(EcsBitmask) - }); - - ecs_set_hooks(world, EcsUnit, { - .ctor = flecs_default_ctor, - .move = ecs_move(EcsUnit), - .copy = ecs_copy(EcsUnit), - .dtor = ecs_dtor(EcsUnit) - }); - - ecs_set_hooks(world, EcsUnitPrefix, { - .ctor = flecs_default_ctor, - .move = ecs_move(EcsUnitPrefix), - .copy = ecs_copy(EcsUnitPrefix), - .dtor = ecs_dtor(EcsUnitPrefix) - }); - - /* Register triggers to finalize type information from component data */ - ecs_entity_t old_scope = ecs_set_scope( /* Keep meta scope clean */ - world, EcsFlecsInternals); - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsPrimitive) }, - .events = {EcsOnSet}, - .callback = flecs_set_primitive - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsMember) }, - .events = {EcsOnSet}, - .callback = flecs_set_member - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsMemberRanges) }, - .events = {EcsOnSet}, - .callback = flecs_set_member_ranges - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsEnum) }, - .events = {EcsOnSet}, - .callback = flecs_add_enum - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsBitmask) }, - .events = {EcsOnAdd}, - .callback = flecs_add_bitmask - }); - - ecs_observer(world, { - .query.terms[0] = { .id = EcsConstant }, - .events = {EcsOnAdd}, - .callback = flecs_add_constant - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard) }, - .events = {EcsOnSet}, - .callback = flecs_add_constant - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsArray) }, - .events = {EcsOnSet}, - .callback = flecs_set_array - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsVector) }, - .events = {EcsOnSet}, - .callback = flecs_set_vector - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsOpaque) }, - .events = {EcsOnSet}, - .callback = flecs_set_custom_type - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsUnit) }, - .events = {EcsOnSet}, - .callback = flecs_set_unit - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsType) }, - .events = {EcsOnSet}, - .callback = ecs_meta_type_serialized_init - }); - - ecs_observer(world, { - .query.terms[0] = { .id = ecs_id(EcsType) }, - .events = {EcsOnSet}, - .callback = flecs_rtt_init_default_hooks - }); - - ecs_observer(world, { - .query.terms = { - { .id = ecs_id(EcsUnit) }, - { .id = EcsQuantity } - }, - .events = { EcsMonitor }, - .callback = flecs_unit_quantity_monitor - }); - ecs_set_scope(world, old_scope); - - /* 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_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); - - #undef ECS_PRIMITIVE - - ecs_set_hooks(world, ecs_string_t, { - .ctor = flecs_default_ctor, - .copy = ecs_copy(ecs_string_t), - .move = ecs_move(ecs_string_t), - .dtor = ecs_dtor(ecs_string_t) - }); - - /* Set default child components. Can be used as hint by deserializers */ - ecs_set(world, ecs_id(EcsStruct), EcsDefaultChildComponent, {ecs_id(EcsMember)}); - ecs_set(world, ecs_id(EcsMember), EcsDefaultChildComponent, {ecs_id(EcsMember)}); - ecs_set(world, ecs_id(EcsEnum), EcsDefaultChildComponent, {EcsConstant}); - ecs_set(world, ecs_id(EcsBitmask), EcsDefaultChildComponent, {EcsConstant}); - - /* Relationship properties */ - ecs_add_id(world, EcsQuantity, EcsExclusive); - ecs_add_id(world, EcsQuantity, EcsPairIsTag); - - /* 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; - } 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_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); - } - } -} - -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_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) -{ - ecs_type_hooks_t hooks = ti->hooks; - if (hooks.lifecycle_ctx_free) { - hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); - } - - ecs_rtt_struct_ctx_t *rtt_ctx = NULL; - if (ctor || dtor || move || copy) { - 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); - hooks.lifecycle_ctx = rtt_ctx; - hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_struct_ctx; - - if (ctor) { - hooks.ctor = flecs_rtt_struct_ctor; - } - if (dtor) { - hooks.dtor = flecs_rtt_struct_dtor; - } - if (move) { - hooks.move = flecs_rtt_struct_move; - } - if (copy) { - hooks.copy = flecs_rtt_struct_copy; - } - } else { - hooks.lifecycle_ctx = NULL; - hooks.lifecycle_ctx_free = NULL; - } - 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; - - /* 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; - 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); - - if (!rtt_ctx) { - return; /* no hooks 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; - 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; - 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; - 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; - if (member_ti->hooks.copy) { - copy_data->hook.copy = member_ti->hooks.copy; - } else { - copy_data->hook.copy = flecs_rtt_default_copy; - } - } - } -} - -/* - * - * 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); - } -} - -/* 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 *array_ti = - ecs_get_type_info(world, array_info->type); - bool ctor_hook_required = - array_ti->hooks.ctor && array_ti->hooks.ctor != flecs_default_ctor; - bool dtor_hook_required = array_ti->hooks.dtor != NULL; - bool move_hook_required = array_ti->hooks.move != NULL; - bool copy_hook_required = array_ti->hooks.copy != NULL; - ecs_flags32_t flags = array_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_free = NULL; - } - - if (ctor_hook_required || dtor_hook_required || move_hook_required || - copy_hook_required) { - ecs_rtt_array_ctx_t *rtt_ctx = ecs_os_malloc_t(ecs_rtt_array_ctx_t); - rtt_ctx->type_info = array_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; - } - - if (ctor_hook_required) { - hooks.ctor = flecs_rtt_array_ctor; - } - - if (dtor_hook_required) { - hooks.dtor = flecs_rtt_array_dtor; - } - - if (move_hook_required) { - hooks.move = flecs_rtt_array_move; - } - - if (copy_hook_required) { - hooks.copy = flecs_rtt_array_copy; - } - - 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); - 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); - } -} - -/* 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); - const ecs_type_info_t *vector_ti = - ecs_get_type_info(world, vector_info->type); - ecs_rtt_vector_ctx_t *rtt_ctx = ecs_os_malloc_t(ecs_rtt_vector_ctx_t); - rtt_ctx->type_info = vector_ti; - 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; - 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]; - const ecs_type_info_t *ti = ecs_get_type_info(world, component); - - if (ti) { - 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); - } - } - - /* 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 || !ti->hooks.ctor) { - ecs_set_hooks_id(world, component, &(ecs_type_hooks_t){ - .ctor = flecs_default_ctor - }); - } - } -} - -#endif - -/** - * @file addons/meta/serialized.c - * @brief Serialize type into flat operations array to speed up deserialization. - */ - - -#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_meta_type_op_kind_t flecs_meta_primitive_to_op_kind(ecs_primitive_kind_t kind) { - return EcsOpPrimitive + kind; -} - -static -ecs_size_t flecs_meta_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; -} - -static -ecs_meta_type_op_t* flecs_meta_ops_add(ecs_vec_t *ops, ecs_meta_type_op_kind_t kind) { - ecs_meta_type_op_t *op = ecs_vec_append_t(NULL, 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->member_index = 0; - return op; -} - -static -ecs_meta_type_op_t* flecs_meta_ops_get(ecs_vec_t *ops, int32_t index) { - ecs_meta_type_op_t* op = ecs_vec_get_t(ops, ecs_meta_type_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_type_op_t *op = flecs_meta_ops_add(ops, flecs_meta_primitive_to_op_kind(ptr->kind)); - op->offset = offset; - op->type = type; - op->size = flecs_meta_type_size(world, type); - 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_type_op_t *op = flecs_meta_ops_add(ops, EcsOpEnum); - op->offset = offset; - op->type = type; - op->size = ECS_SIZEOF(ecs_i32_t); - 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_type_op_t *op = flecs_meta_ops_add(ops, EcsOpBitmask); - op->offset = offset; - op->type = type; - op->size = ECS_SIZEOF(ecs_u32_t); - return 0; -} - -static -int flecs_meta_serialize_array( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) -{ - (void)world; - - ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpArray); - op->offset = offset; - op->type = type; - op->size = flecs_meta_type_size(world, type); - return 0; -} - -static -int flecs_meta_serialize_array_component( - ecs_world_t *world, - ecs_entity_t type, - ecs_vec_t *ops) -{ - const EcsArray *ptr = ecs_get(world, type, EcsArray); - if (!ptr) { - return -1; /* Should never happen, will trigger internal error */ - } - - if (flecs_meta_serialize_type(world, ptr->type, 0, ops) != 0) { - return -1; - } - - ecs_meta_type_op_t *first = ecs_vec_first(ops); - first->count = ptr->count; - return 0; -} - -static -int flecs_meta_serialize_vector( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) -{ - (void)world; - ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpVector); - op->offset = offset; - op->type = type; - op->size = flecs_meta_type_size(world, type); - return 0; -} - -static -int flecs_meta_serialize_custom_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) -{ - (void)world; - ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpOpaque); - op->offset = offset; - op->type = type; - op->size = flecs_meta_type_size(world, type); - return 0; -} - -static -int flecs_meta_serialize_struct( - ecs_world_t *world, - ecs_entity_t type, - ecs_size_t offset, - ecs_vec_t *ops) -{ - 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_type_op_t *op = flecs_meta_ops_add(ops, EcsOpPush); - op->offset = offset; - op->type = type; - op->size = flecs_meta_type_size(world, type); - - 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->members = member_index = flecs_name_index_new( - world, &world->allocator); - } - - for (i = 0; i < count; i ++) { - ecs_member_t *member = &members[i]; - - cur = ecs_vec_count(ops); - if (flecs_meta_serialize_type(world, member->type, offset + member->offset, ops) != 0) { - continue; - } - - op = flecs_meta_ops_get(ops, cur); - if (!op->type) { - op->type = member->type; - } - - if (op->count <= 1) { - op->count = member->count; - } - - const char *member_name = member->name; - op->name = member_name; - op->op_count = ecs_vec_count(ops) - cur; - op->member_index = i; - - flecs_name_index_ensure( - member_index, flecs_ito(uint64_t, cur - first - 1), - member_name, 0, 0); - } - - ecs_meta_type_op_t *pop = flecs_meta_ops_add(ops, EcsOpPop); - pop->type = type; - flecs_meta_ops_get(ops, first)->op_count = 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) -{ - const EcsType *ptr = ecs_get(world, type, EcsType); - if (!ptr) { - char *path = ecs_get_path(world, type); - ecs_err("missing EcsType for type %s'", path); - ecs_os_free(path); - return -1; - } - - switch(ptr->kind) { - case EcsPrimitiveType: return flecs_meta_serialize_primitive(world, type, offset, ops); - case EcsEnumType: return flecs_meta_serialize_enum(world, type, offset, ops); - case EcsBitmaskType: return flecs_meta_serialize_bitmask(world, type, offset, ops); - case EcsStructType: return flecs_meta_serialize_struct(world, type, offset, ops); - case EcsArrayType: return flecs_meta_serialize_array(world, type, offset, ops); - case EcsVectorType: return flecs_meta_serialize_vector(world, type, offset, ops); - case EcsOpaqueType: return flecs_meta_serialize_custom_type(world, type, offset, ops); - } - - return 0; -} - -static -int flecs_meta_serialize_component( - ecs_world_t *world, - ecs_entity_t type, - ecs_vec_t *ops) -{ - const EcsType *ptr = ecs_get(world, type, EcsType); - if (!ptr) { - char *path = ecs_get_path(world, type); - ecs_err("missing EcsType for type %s'", path); - ecs_os_free(path); - return -1; - } - - if (ptr->kind == EcsArrayType) { - return flecs_meta_serialize_array_component(world, type, ops); - } else { - return flecs_meta_serialize_type(world, type, 0, ops); - } -} - -void ecs_meta_type_serialized_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 e = it->entities[i]; - ecs_vec_t ops; - ecs_vec_init_t(NULL, &ops, ecs_meta_type_op_t, 0); - flecs_meta_serialize_component(world, e, &ops); - - EcsTypeSerializer *ptr = ecs_ensure( - world, e, EcsTypeSerializer); - if (ptr->ops.array) { - ecs_meta_dtor_serialized(ptr); - } - - ptr->ops = ops; - } -} - -#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) -{ - (void)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/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_entity_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 == EcsInOutNone || 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_entity_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_entity_t)[0] = - it.entities[i]; - 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_entity_t *systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); - for (i = 0; i < count; i ++) { - ecs_entity_t system = systems[i]; - const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); - flecs_poly_assert(poly->poly, ecs_system_t); - ecs_system_t *sys = (ecs_system_t*)poly->poly; - -#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; - - 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_entity_t* systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); - int32_t ran_since_merge = i - op->offset; - - for (; i < count; i++) { - ecs_entity_t system = systems[i]; - const EcsPoly* poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); - flecs_poly_assert(poly->poly, ecs_system_t); - ecs_system_t* sys = (ecs_system_t*)poly->poly; - - /* 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_intern(world, s, system, sys, stage_index, - stage_count, delta_time, NULL); - - ecs_os_linc(&world->info.systems_ran_frame); - 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); - } - - 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); - } - } - - /* 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_id_record_t *idr = flecs_id_record_get(world, - ecs_dependson(EcsOnStart)); - if (!idr || !flecs_table_cache_count(&idr->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; - pq->idr_inactive = flecs_id_record_ensure(world, EcsEmpty); - 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); -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsPipeline), - "Module that schedules and runs systems"); -#endif - 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/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_script_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_script_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_script_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; -} - -ecs_script_entity_t* flecs_script_insert_entity( - ecs_script_parser_t *parser, - const char *name, - bool name_is_expr) -{ - 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 (name_is_expr) { - parser->significant_newline = false; - result->name_expr = (ecs_expr_node_t*) - flecs_expr_interpolated_string(parser, name); - if (!result->name_expr) { - goto error; - } - parser->significant_newline = true; - } - - 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 -void flecs_script_set_id( - 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; -} - -ecs_script_pair_scope_t* flecs_script_insert_pair_scope( - ecs_script_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); - flecs_script_set_id(&result->id, first, second); - 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_script_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); - flecs_script_set_id(&result->id, first, second); - - flecs_ast_append(parser, scope->stmts, ecs_script_tag_t, result); - - return result; -} - -ecs_script_tag_t* flecs_script_insert_tag( - ecs_script_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_script_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); - flecs_script_set_id(&result->id, first, second); - - flecs_ast_append(parser, scope->stmts, ecs_script_component_t, result); - - return result; -} - -ecs_script_component_t* flecs_script_insert_component( - ecs_script_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_script_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_script_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_script_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_script_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_script_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_script_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_script_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_script_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_script_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_script_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) { - ecs_script_parameter_t *array = ecs_vec_first(params); - int32_t i, count = ecs_vec_count(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); -} - -static -ECS_MOVE(EcsScriptConstVar, dst, src, { - if (dst->type_info->hooks.dtor) { - dst->type_info->hooks.dtor(dst->value.ptr, 1, dst->type_info); - } - - ecs_os_free(dst->value.ptr); - - *dst = *src; - - src->value.ptr = NULL; - src->value.type = 0; - src->type_info = NULL; -}) - -static -ECS_DTOR(EcsScriptConstVar, ptr, { - if (ptr->type_info->hooks.dtor) { - ptr->type_info->hooks.dtor(ptr->value.ptr, 1, ptr->type_info); - } - ecs_os_free(ptr->value.ptr); -}) - -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_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); - - 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_entity_t ecs_function_init( - ecs_world_t *world, - const ecs_function_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->callback != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->return_type != 0, 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; - f->ctx = desc->ctx; - - 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, &f->params, ecs_script_parameter_t, 0); - } - - ecs_script_parameter_t *p = ecs_vec_append_t( - NULL, &f->params, ecs_script_parameter_t); - p->type = desc->params[i].type; - p->name = ecs_os_strdup(desc->params[i].name); - } - - 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_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->return_type != 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; - f->ctx = desc->ctx; - - 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, &f->params, ecs_script_parameter_t, 0); - } - - ecs_script_parameter_t *p = ecs_vec_append_t( - NULL, &f->params, ecs_script_parameter_t); - p->type = desc->params[i].type; - p->name = ecs_os_strdup(desc->params[i].name); - } - - 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), - .move = ecs_move(EcsScriptConstVar), - .flags = ECS_TYPE_HOOK_COPY_ILLEGAL - }); - - ecs_set_hooks(world, EcsScriptFunction, { - .ctor = flecs_default_ctor, - .dtor = ecs_dtor(EcsScriptFunction), - .move = ecs_move(EcsScriptFunction), - .flags = ECS_TYPE_HOOK_COPY_ILLEGAL - }); - - ecs_set_hooks(world, EcsScriptMethod, { - .ctor = flecs_default_ctor, - .dtor = ecs_dtor(EcsScriptMethod), - .move = ecs_move(EcsScriptMethod), - .flags = ECS_TYPE_HOOK_COPY_ILLEGAL - }); - - 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_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_entity_t m = 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_doc_set_brief(world, m, "Returns entity doc name"); - } - - { - ecs_entity_t m = 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_doc_set_brief(world, m, "Returns entity doc uuid"); - } - - { - ecs_entity_t m = 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_doc_set_brief(world, m, "Returns entity doc brief description"); - } - - { - ecs_entity_t m = 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_doc_set_brief(world, m, "Returns entity doc detailed description"); - } - - { - ecs_entity_t m = 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_doc_set_brief(world, m, "Returns entity doc link"); - } - - { - ecs_entity_t m = 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 - }); - - ecs_doc_set_brief(world, m, "Returns 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_entity_t m = ecs_method(world, { - .name = "name", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_name - }); - - ecs_doc_set_brief(world, m, "Returns entity name"); - } - - { - ecs_entity_t m = ecs_method(world, { - .name = "path", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_path - }); - - ecs_doc_set_brief(world, m, "Returns entity path"); - } - - { - ecs_entity_t m = ecs_method(world, { - .name = "parent", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_entity_t), - .callback = flecs_meta_entity_parent - }); - - ecs_doc_set_brief(world, m, "Returns entity parent"); - } - - { - ecs_entity_t m = 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_doc_set_brief(world, m, "Returns whether entity has component"); - } - - { - ecs_entity_t m = 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 - }); - - ecs_doc_set_brief(world, m, "Returns a pair identifier"); - } - - 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; - } -} - -#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)) - -FLECS_API -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 - }); -} - -#endif - -/** - * @file addons/script/parser.c - * @brief Script grammar parser. - */ - - -#ifdef FLECS_SCRIPT -/** - * @file addons/script/parser.h - * @brief Script grammar parser. - * - * Macro utilities that facilitate a simple recursive descent parser. - */ - -#ifndef FLECS_SCRIPT_PARSER_H -#define FLECS_SCRIPT_PARSER_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_script_parser_t parser = {\ - .script = flecs_script_impl(&script),\ - .pos = expr,\ - .token_cur = tokens\ - } - -/* Definitions for parser functions */ -#define ParserBegin\ - ecs_script_tokenizer_t _tokenizer;\ - ecs_os_zeromem(&_tokenizer);\ - _tokenizer.tokens = _tokenizer.stack.tokens;\ - ecs_script_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->script->pub.name, parser->script->pub.code,\ - (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ - goto error - -/* Warning */ -#define Warning(...)\ - ecs_parser_warning(parser->script->pub.name, parser->script->pub.code,\ - (pos - parser->script->pub.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) {\ - Error("expected '%c'", until);\ - }\ - 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_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_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_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_token(parser, pos, t, false))) {\ - goto error;\ - }\ - switch(t->kind) {\ - __VA_ARGS__\ - default:\ - if (t->value) {\ - Error("unexpected %s'%s'", \ - flecs_script_token_kind_str(t->kind), t->value);\ - } else {\ - Error("unexpected %s", \ - flecs_script_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_script_token_t lookahead_token;\ - const char *old_lh_token_cur = parser->token_cur;\ - if ((lookahead = flecs_script_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;\ - }\ - } - -/* 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 - - -#define EcsTokEndOfStatement\ - case ';':\ - case '\n':\ - case '\0' - -static -const char* flecs_script_stmt( - ecs_script_parser_t *parser, - const char *pos); - -/* Parse scope (statements inside {}) */ -static -const char* flecs_script_scope( - ecs_script_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); - - 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; - - ecs_assert(pos[-1] == '}', ECS_INTERNAL_ERROR, NULL); - return pos; - - ParserEnd; -} - -/* Parse comma expression (expressions separated by ',') */ -static -const char* flecs_script_comma_expr( - ecs_script_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), false); - } - - LookAhead_1(',', - pos = lookahead; - continue; - ) - ) - - break; - ) - - return pos; -} - -/* Parse with expression (expression after 'with' keyword) */ -static -const char* flecs_script_with_expr( - ecs_script_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_script_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_script_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)\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_script_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_1(EcsTokKeywordIf, - Scope(stmt->if_false, - return flecs_script_if_stmt(parser, pos); - ) - ) - } - - // if expr { } else { - case '{': { - return flecs_script_scope(parser, stmt->if_false, pos); - } - ) - ) - - EndOfRule; - }); - ) - - ParserEnd; -} - -static -const char* flecs_script_parse_var( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - bool is_prop) -{ - Parse_1(EcsTokIdentifier, - ecs_script_var_node_t *var = flecs_script_insert_var( - parser, Token(1)); - var->node.kind = is_prop ? EcsAstProp : EcsAstConst; - - Parse( - // const color = - case '=': { - // const color = Color : - LookAhead_2(EcsTokIdentifier, ':', - pos = lookahead; - - var->type = Token(3); - - // const color = Color: { - LookAhead_1('{', - // const color = Color: {expr} - pos = lookahead; - Initializer('}', - var->expr = INITIALIZER; - 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; -} - -/* Parse a single statement */ -static -const char* flecs_script_stmt( - ecs_script_parser_t *parser, - const char *pos) -{ - ParserBegin; - - bool name_is_expr_0 = false; - bool name_is_expr_1 = 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 EcsTokKeywordIf: goto if_stmt; - case EcsTokKeywordFor: goto for_stmt; - EcsTokEndOfStatement: EndOfRule; - ); - -anonymous_entity: { - return flecs_script_scope(parser, - flecs_script_insert_entity(parser, "_", false)->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), name_is_expr_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), false); - 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), name_is_expr_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_var(parser, pos, tokenizer, true); -} - -// const -const_var: { - // const color - return flecs_script_parse_var(parser, pos, tokenizer, false); -} - -// 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: { - // (Likes, Apples) - Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', - 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), name_is_expr_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\n - EcsTokEndOfStatement: - EndOfRule; - - // 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), name_is_expr_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: { - if (flecs_string_is_interpolated(Token(1))) { - name_is_expr_1 = true; - } -} - -identifier_identifier: { - ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, Token(1), name_is_expr_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)\n - EcsTokEndOfStatement: { - ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, NULL, false); - - 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, false); - - 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) -{ - (void)desc; /* Will be used in future to expand type checking features */ - - if (!code) { - code = ""; - } - - 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_script_parser_t parser = { - .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; - - return script; -error: - ecs_script_free(script); - return NULL; -} - -#endif - -/** - * @file addons/script/query_parser.c - * @brief Script grammar parser. - */ - - -#ifdef FLECS_SCRIPT - -#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_script_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_script_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->script->pub.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_script_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 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->script->pub.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_script_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_script_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_script_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_script_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_script_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_script_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_script_t *script, - ecs_term_t *terms, - int32_t *term_count_out) -{ - if (!ecs_os_strcmp(script->code, "0")) { - *term_count_out = 0; - return 0; - } - - ecs_script_parser_t parser = { - .script = flecs_script_impl(script), - .pos = script->code, - .merge_variable_members = true - }; - - parser.token_cur = flecs_script_impl(script)->token_buffer; - - int32_t term_count = 0; - const char *ptr = script->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, - ecs_term_t *term, - char *token_buffer) -{ - ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, name); - ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); - - EcsParserFixedBuffer(world, name, expr, token_buffer, 256); - 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_term_t term = {0}; - EcsParserFixedBuffer(world, name, expr, token_buffer, 256); - 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; - } - - if (ecs_term_finalize(world, &term)) { - 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_script_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_script_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]; - EcsParserFixedBuffer(q->world, q_name, expr, token_buffer, 256); - return flecs_query_args_parse(&parser, q, it, expr); -error: - 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); - -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->script = src->script; - dst->template_ = src->template_; - 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); - } -}) - -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_script_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_defer_begin(world); - ecs_iter_t it = ecs_children(world, instance); - while (ecs_children_next(&it)) { - 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_delete(world, it.entities[i]); - } - } - } - ecs_defer_end(world); - } -} - -int ecs_script_run( - ecs_world_t *world, - const char *name, - const char *code) -{ - ecs_script_t *script = ecs_script_parse(world, name, code, NULL); - if (!script) { - goto error; - } - - ecs_entity_t prev_scope = ecs_set_scope(world, 0); - - if (ecs_script_eval(script, NULL)) { - 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); - ecs_os_free(script); - return result; -} - -void ecs_script_free( - ecs_script_t *script) -{ - 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->script) { - ecs_script_free(s->script); - } - - s->script = ecs_script_parse(world, name, code, NULL); - if (!s->script) { - 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); - - ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); - - if (ecs_script_eval(s->script, NULL)) { - 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; - } - } - - if (ecs_script_update(world, e, 0, script)) { - goto error; - } - - if (script != desc->code) { - /* Safe cast, only happens when script is loaded from file */ - ecs_os_free(ECS_CONST_CAST(char*, script)); - } - - 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); -} - -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; - if (data->script) { - ser->member(ser, "name"); - ser->value(ser, ecs_id(ecs_string_t), &data->script->name); - ser->member(ser, "code"); - ser->value(ser, ecs_id(ecs_string_t), &data->script->code); - - 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, "name"); - ser->value(ser, ecs_id(ecs_string_t), &nullString); - ser->member(ser, "code"); - ser->value(ser, ecs_id(ecs_string_t), &nullString); - ser->member(ser, "ast"); - ser->value(ser, ecs_id(ecs_string_t), &nullString); - } - return 0; -} - -void FlecsScriptImport( - ecs_world_t *world) -{ - ECS_MODULE(world, FlecsScript); - ECS_IMPORT(world, FlecsMeta); -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsScript), - "Module with components for managing Flecs scripts"); -#endif - - ecs_set_name_prefix(world, "Ecs"); - ECS_COMPONENT_DEFINE(world, EcsScript); - - 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_struct(world, { - .entity = ecs_id(ecs_script_t), - .members = { - { .name = "name", .type = ecs_id(ecs_string_t) }, - { .name = "code", .type = ecs_id(ecs_string_t) }, - { .name = "ast", .type = ecs_id(ecs_string_t) } - } - }); - - ecs_opaque(world, { - .entity = ecs_id(EcsScript), - .type.as_type = ecs_id(ecs_script_t), - .type.serialize = EcsScript_serialize - }); - - ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); - ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); - 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. - */ - - -#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_type_op_t *ops, - int32_t op_count, - const void *base, - ecs_strbuf_t *str, - int32_t in_array, - bool is_expr); - -static -int flecs_expr_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *base, - ecs_strbuf_t *str, - bool is_expr); - -static -ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { - return kind - EcsOpPrimitive; -} - -/* Serialize enumeration */ -static -int flecs_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 val = *(const int32_t*)base; - - /* Enumeration constants are stored in a map that is keyed on the - * enumeration value. */ - ecs_enum_constant_t *c = ecs_map_get_deref(&enum_type->constants, - ecs_enum_constant_t, (ecs_map_key_t)val); - if (!c) { - char *path = ecs_get_path(world, op->type); - ecs_err("value %d is not valid for enum type '%s'", val, 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_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); - 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(&bitmask_type->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; -} - -/* 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, - bool is_array) -{ - ecs_strbuf_list_push(str, "[", ", "); - - const void *ptr = base; - - int i; - for (i = 0; i < elem_count; i ++) { - ecs_strbuf_list_next(str); - if (flecs_expr_ser_type_ops( - world, ops, op_count, ptr, str, is_array, true)) - { - return -1; - } - ptr = ECS_OFFSET(ptr, elem_size); - } - - ecs_strbuf_list_pop(str, "]"); - - return 0; -} - -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, - bool is_array) -{ - const EcsTypeSerializer *ser = ecs_get( - world, type, EcsTypeSerializer); - 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_vec_first_t(&ser->ops, ecs_meta_type_op_t); - int32_t op_count = ecs_vec_count(&ser->ops); - return expr_ser_elements( - world, ops, op_count, base, elem_count, comp->size, str, is_array); -} - -/* 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) -{ - const EcsArray *a = ecs_get(world, op->type, EcsArray); - ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); - - return expr_ser_type_elements( - world, a->type, ptr, a->count, str, true); -} - -/* 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) -{ - const ecs_vec_t *value = base; - const EcsVector *v = ecs_get(world, op->type, EcsVector); - ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t count = ecs_vec_count(value); - void *array = ecs_vec_first(value); - - /* Serialize contiguous buffer of vector */ - return expr_ser_type_elements(world, v->type, array, count, str, false); -} - -/* Forward serialization to the different type kinds */ -static -int flecs_expr_ser_type_op( - const ecs_world_t *world, - ecs_meta_type_op_t *op, - const void *ptr, - ecs_strbuf_t *str, - bool is_expr) -{ - ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); - - switch(op->kind) { - case EcsOpPush: - case EcsOpPop: - /* Should not be parsed as single op */ - ecs_throw(ECS_INVALID_PARAMETER, NULL); - break; - case EcsOpEnum: - if (flecs_expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { - goto error; - } - break; - case EcsOpBitmask: - if (flecs_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)) { - goto error; - } - break; - case EcsOpScope: - 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: - case EcsOpString: - case EcsOpOpaque: - if (flecs_expr_ser_primitive(world, flecs_expr_op_to_primitive_kind(op->kind), - ECS_OFFSET(ptr, op->offset), str, is_expr)) - { - /* Unknown operation */ - ecs_err("unknown serializer operation kind (%d)", op->kind); - goto error; - } - break; - default: - ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); - } - - return 0; -error: - return -1; -} - -/* Iterate over a slice of the type ops array */ -static -int flecs_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 in_array, - bool is_expr) -{ - for (int i = 0; i < op_count; i ++) { - ecs_meta_type_op_t *op = &ops[i]; - - if (in_array <= 0) { - if (op->name) { - ecs_strbuf_list_next(str); - ecs_strbuf_append(str, "%s: ", op->name); - } - - int32_t elem_count = op->count; - if (elem_count > 1) { - /* Serialize inline array */ - if (expr_ser_elements(world, op, op->op_count, base, - elem_count, op->size, str, true)) - { - return -1; - } - - i += op->op_count - 1; - continue; - } - } - - switch(op->kind) { - case EcsOpPush: - ecs_strbuf_list_push(str, "{", ", "); - in_array --; - break; - case EcsOpPop: - ecs_strbuf_list_pop(str, "}"); - in_array ++; - break; - case EcsOpArray: - case EcsOpVector: - case EcsOpEnum: - case EcsOpBitmask: - case EcsOpScope: - 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: - case EcsOpString: - case EcsOpOpaque: - if (flecs_expr_ser_type_op(world, op, base, str, is_expr)) { - goto error; - } - break; - default: - ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); - } - } - - 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_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); - int32_t count = ecs_vec_count(v_ops); - return flecs_expr_ser_type_ops(world, ops, count, base, str, 0, 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; - - const EcsStruct *st = ecs_get(world, template_entity, EcsStruct); - if (!st) { - ecs_os_memset(ptr, 0, count * ti->size); - 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; - 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]; - - /* 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_eval_visitor_t *v, - ecs_script_var_node_t *node) -{ - 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, &type) || !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_eval_visitor_t *v, - ecs_script_node_t *node) -{ - switch(node->kind) { - case EcsAstTag: - case EcsAstComponent: - case EcsAstVarComponent: - case EcsAstEntity: - case EcsAstScope: - case EcsAstDefaultComponent: - case EcsAstWithVar: - case EcsAstWithTag: - case EcsAstWithComponent: - case EcsAstUsing: - case EcsAstModule: - case EcsAstAnnotation: - case EcsAstConst: - case EcsAstPairScope: - case EcsAstWith: - case EcsAstIf: - case EcsAstFor: - break; - case EcsAstTemplate: - flecs_script_eval_error(v, node, "nested templates are not allowed"); - return -1; - case 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 = (ecs_visit_action_t)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); - 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_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}); - } - - 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/tokenizer.c - * @brief Script tokenizer. - */ - - -#ifdef FLECS_SCRIPT - -#define Keyword(keyword, _kind)\ - } else if (!ecs_os_strncmp(pos, keyword " ", ecs_os_strlen(keyword) + 1)) {\ - out->value = keyword;\ - out->kind = _kind;\ - return pos + ecs_os_strlen(keyword);\ - } else if (!ecs_os_strncmp(pos, keyword "\n", ecs_os_strlen(keyword) + 1)) {\ - 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_script_token_kind_str( - ecs_script_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: - return "keyword "; - case EcsTokIdentifier: - return "identifier "; - case EcsTokString: - return "string "; - case EcsTokNumber: - return "number "; - case EcsTokNewline: - return "newline"; - case EcsTokMember: - return "member"; - case EcsTokEnd: - return "end of script"; - default: - return ""; - } -} - -const char* flecs_script_token_str( - ecs_script_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 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 EcsTokString: return "string"; - 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_script_parser_t *parser, - const char *pos) -{ - (void)parser; - - 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_whitespace_and_comment( - ecs_script_parser_t *parser, - const char *pos) -{ -repeat_skip_whitespace_comment: - pos = flecs_scan_whitespace(parser, pos); - if (pos[0] == '/') { - if (pos[1] == '/') { - for (pos = pos + 2; pos[0] && pos[0] != '\n'; pos ++) { } - if (pos[0] == '\n') { - pos ++; - goto repeat_skip_whitespace_comment; - } - } else if (pos[1] == '*') { - for (pos = &pos[2]; pos[0] != 0; pos ++) { - if (pos[0] == '*' && pos[1] == '/') { - pos += 2; - goto repeat_skip_whitespace_comment; - } - } - - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "missing */ for multiline comment"); - } - } - - return pos; -} - -// Identifier token -static -bool flecs_script_is_identifier( - char c) -{ - return isalpha(c) || (c == '_') || (c == '$') || (c == '#'); -} - -const char* flecs_script_identifier( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out) -{ - if (out) { - out->kind = EcsTokIdentifier; - out->value = parser->token_cur; - } - - ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); - 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; - } - } - - do { - char c = pos[0]; - 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(parser->script->pub.name, - parser->script->pub.code, - pos - parser->script->pub.code, - "< without > in identifier"); - return NULL; - } - - if (outpos) { - *outpos = c; - outpos ++; - } - pos ++; - - if (!indent) { - break; - } - } while (true); - - if (outpos && parser) { - *outpos = '\0'; - parser->token_cur = outpos + 1; - } - return pos; - } else if (c == '>') { - ecs_parser_error(parser->script->pub.name, - parser->script->pub.code, - pos - parser->script->pub.code, - "> without < in identifier"); - return NULL; - } else { - if (outpos && parser) { - *outpos = '\0'; - parser->token_cur = outpos + 1; - } - return pos; - } - } - - if (outpos) { - *outpos = *pos; - outpos ++; - } - - pos ++; - } while (true); -} - -// Number token static -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_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out) -{ - out->kind = EcsTokNumber; - out->value = parser->token_cur; - - bool dot_parsed = false; - bool e_parsed = false; - - 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 ++; - } - - do { - char c = pos[0]; - bool valid_number = false; - - if (c == '.') { - if (!dot_parsed && !e_parsed) { - if (isdigit(pos[1])) { - dot_parsed = true; - valid_number = true; - } - } - } else if (c == 'e') { - if (!e_parsed) { - if (isdigit(pos[1])) { - e_parsed = true; - valid_number = true; - } - } - } else if (isdigit(c)) { - valid_number = true; - } - - if (!valid_number) { - *outpos = '\0'; - parser->token_cur = outpos + 1; - break; - } - - outpos[0] = pos[0]; - outpos ++; - pos ++; - } while (true); - - return pos; -} - -static -const char* flecs_script_skip_string( - ecs_script_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->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "unterminated string"); - return NULL; - } - - return pos; -} - -static -const char* flecs_script_string( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_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_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out) -{ - char ch; - const char *end = pos + 1; - while ((ch = end[0]) && (ch != '`')) { - if (ch == '\\' && end[1] == '`') { - end ++; - } - end ++; - } - - if (ch != '`') { - 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_script_until( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_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->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected end of script"); - return NULL; - } else - if (until == '\n') { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected newline"); - return NULL; - } else { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected '%c'", until); - return NULL; - } - } - - int32_t len = flecs_ito(int32_t, pos - start); - ecs_os_memcpy(parser->token_cur, start, len); - out->value = parser->token_cur; - parser->token_cur += len; - - while (isspace(parser->token_cur[-1])) { - parser->token_cur --; - } - - parser->token_cur[0] = '\0'; - parser->token_cur ++; - - return pos; -} - -const char* flecs_script_token( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out, - bool is_lookahead) -{ - parser->pos = pos; - - // Skip whitespace and comments - pos = flecs_scan_whitespace_and_comment(parser, pos); - - out->kind = EcsTokUnknown; - out->value = NULL; - - if (pos[0] == '\0') { - out->kind = EcsTokEnd; - return pos; - } else if (pos[0] == '\n') { - out->kind = EcsTokNewline; - - // Parse multiple newlines/whitespaces as a single token - pos = flecs_scan_whitespace_and_comment(parser, pos + 1); - 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 ("module", EcsTokKeywordModule) - - } 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_script_identifier(parser, pos, out); - } - - if (!is_lookahead) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "unknown token '%c'", pos[0]); - } - - return NULL; -} - -#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 */ - { - int32_t i, var_count = it->variable_count; - for (i = 1 /* skip this variable */ ; i < var_count; i ++) { - const ecs_entity_t *e_ptr = NULL; - ecs_var_t *query_var = &it->variables[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, it->variable_names[i]); - if (!var) { - var = ecs_script_vars_declare(vars, it->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 - -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; -} - -int ecs_script_visit_scope_( - ecs_script_visit_t *v, - ecs_script_scope_t *scope) -{ - ecs_script_node_t **nodes = ecs_vec_first_t( - &scope->stmts, ecs_script_node_t*); - - v->nodes[v->depth ++] = (ecs_script_node_t*)scope; - - 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; - } - - v->nodes[v->depth ++] = nodes[i]; - - if (v->visit(v, nodes[i])) { - return -1; - } - - v->depth --; - } - - v->depth --; - - return 0; -} - -int ecs_script_visit_node_( - ecs_script_visit_t *v, - ecs_script_node_t *node) -{ - v->nodes[v->depth ++] = node; - - if (v->visit(v, node)) { - return -1; - } - - v->depth --; - - return 0; -} - -int ecs_script_visit_( - ecs_script_visit_t *visitor, - ecs_visit_action_t visit, - ecs_script_impl_t *script) -{ - visitor->script = script; - visitor->visit = visit; - visitor->depth = 0; - int result = ecs_script_visit_node(visitor, script->root); - if (result) { - return -1; - } - - if (visitor->depth) { - ecs_parser_error(script->pub.name, NULL, 0, "unexpected end of script"); - return -1; - } - - return 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 existst */ - 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); -} - -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, &node->id.first_sp, &dummy)) - { - return -1; - } - - if (flecs_script_find_entity( - v, 0, node->id.second, &node->id.second_sp, &dummy)) - { - 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_eval_visitor_t *v, - ecs_script_node_t *node) -{ - 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 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 - -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 && ti->hooks.dtor) { - ti->hooks.dtor(val->ptr, 1, ti); - } - } - - 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_id_record_t *idr = flecs_id_record_ensure(v->world, id); - if (!idr) { - goto error; - } - - if (!idr->type_info) { - goto error; - } - - return idr->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); - flecs_dump_backtrace(stdout); - 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; - } -} - -int flecs_script_find_entity( - ecs_script_eval_visitor_t *v, - ecs_entity_t from, - const char *path, - int32_t *sp, - ecs_entity_t *out) -{ - if (!path) { - goto error; - } - - if (path[0] == '$') { - if (!sp) { - flecs_script_eval_error(v, NULL, - "variable identifier '%s' not allowed here", path); - goto error; - } - - const ecs_script_var_t *var = flecs_script_find_var( - v->vars, &path[1], v->dynamic_variable_binding ? NULL : sp); - if (!var) { - goto error; - } - - 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; - } - - ecs_entity_t 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; - } - - *out = result; - - return 0; - } - - if (from) { - *out = 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) { - 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) { - *out = e; - return 0; - } - } - } - - *out = ecs_lookup_path_w_sep( - v->world, v->parent, path, NULL, NULL, true); - } - - return 0; -error: - return -1; -} - -ecs_entity_t flecs_script_create_entity( - ecs_script_eval_visitor_t *v, - const char *name) -{ - ecs_value_t *with = NULL; - if (flecs_script_with_count(v)) { - with = ecs_vec_first_t(&v->r->with, ecs_value_t); - } - - ecs_entity_desc_t desc = {0}; - desc.name = name; - desc.parent = v->parent; - desc.set = with; - return ecs_entity_init(v->world, &desc); -} - -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, &result)) { - return result; - } - return 0; -} - -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); - - /* 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); - - 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; - } - } - } - - 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 (!id->first) { - flecs_script_eval_error(v, node, - "invalid component/tag identifier"); - return -1; - } - - 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; - } - } - } - - ecs_entity_t first = 0; - if (flecs_script_find_entity( - v, 0, id->first, &id->first_sp, &first) || !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); - - 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_sp, &second) || - !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; - } - - id->eval = id->flag | ecs_pair(first, second); - } else { - if (first == EcsAny) { - flecs_script_eval_error(v, node, - "cannot use anonymous entity as component or tag"); - return -1; - } - - id->eval = id->flag | first; - } - - 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 - }; - - 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; - } - } - - if (flecs_expr_visit_eval(script, *expr_ptr, &desc, value)) { - goto error; - } - - return 0; -error: - return -1; -} - -int flecs_script_eval_scope( - ecs_script_eval_visitor_t *v, - ecs_script_scope_t *node) -{ - 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 != EcsSingleton && 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; -} - -static -int flecs_script_apply_annot( - ecs_script_eval_visitor_t *v, - ecs_entity_t entity, - ecs_script_annot_t *node) -{ - if (!ecs_os_strcmp(node->name, "name")) { - ecs_doc_set_name(v->world, entity, node->expr); - } else - if (!ecs_os_strcmp(node->name, "brief")) { - ecs_doc_set_brief(v->world, entity, node->expr); - } else - if (!ecs_os_strcmp(node->name, "detail")) { - ecs_doc_set_detail(v->world, entity, node->expr); - } else - if (!ecs_os_strcmp(node->name, "link")) { - ecs_doc_set_link(v->world, entity, node->expr); - } else - if (!ecs_os_strcmp(node->name, "color")) { - ecs_doc_set_color(v->world, entity, node->expr); - } else { - flecs_script_eval_error(v, node, "unknown annotation '%s'", - node->name); - return -1; - } - - return 0; -} - -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 - }; - - 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 existst */ - ecs_script_scope_t *scope = ecs_script_current_scope(v); - if (scope && scope->default_component_eval) { - node->eval_kind = scope->default_component_eval; - } - } - - ecs_expr_node_t *name_expr = node->name_expr; - if (name_expr) { - 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 - }; - - if (!name_expr->type_info) { - if (flecs_expr_visit_type(script, name_expr, &desc)) { - return -1; - } - - if (flecs_expr_visit_fold(script, &node->name_expr, &desc)) { - return -1; - } - - name_expr = node->name_expr; - } - - ecs_value_t value = { .type = ecs_id(ecs_string_t) }; - if (flecs_expr_visit_eval(script, name_expr, &desc, &value)) { - return -1; - } - - char *name = *(char**)value.ptr; - if (!name) { - flecs_script_eval_error(v, node, "failed to evaluate entity name"); - return -1; - } - - node->eval = flecs_script_create_entity(v, name); - - ecs_value_free(script->world, value.type, value.ptr); - } else { - node->eval = flecs_script_create_entity(v, node->name); - } - - node->parent = v->entity; - - 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->eval, 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) -{ - if (entity == EcsVariable) { // Singleton ($) - if (ECS_IS_PAIR(id)) { - return ecs_pair_first(v->world, id); - } else { - return id & ECS_COMPONENT_MASK; - } - } - return entity; -} - -static -int flecs_script_eval_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; - } - - 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) -{ - 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; - } - - 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; - } - - 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_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), - .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); - if (!var) { - flecs_script_eval_error(v, node, - "unresolved variable '%s'", node->name); - return -1; - } - - if (v->is_with_scope) { - flecs_script_eval_error(v, node, "invalid component in with scope"); - 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), - .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; - } - - if (ecs_get_type_info(v->world, default_type) == 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_value_t value = { - .ptr = ecs_ensure_id(v->world, v->entity->eval, default_type), - .type = default_type - }; - - if (flecs_script_eval_expr(v, &node->expr, &value)) { - return -1; - } - - ecs_modified_id(v->world, v->entity->eval, default_type); - - 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; - } - - 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; - - return 0; -} - -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; - } - - 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; - - return 0; -} - -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; - } - - ecs_allocator_t *a = &v->r->allocator; - const ecs_type_info_t *ti = flecs_script_get_type_info( - v, node, node->id.eval); - - ecs_value_t *value = flecs_script_with_append(a, v, ti); - value->type = node->id.eval; - value->ptr = NULL; - - if (node->expr) { - if (!ti) { - return -1; - } - - value->ptr = flecs_stack_alloc(&v->r->stack, ti->size, ti->alignment); - value->type = ti->component; // Expression parser needs actual type - - if (ti->hooks.ctor) { - ti->hooks.ctor(value->ptr, 1, ti); - } - - if (flecs_script_eval_expr(v, &node->expr, value)) { - return -1; - } - - value->type = node->id.eval; // Restore so we're adding actual id - } - - return 0; -} - -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; - - if (ecs_script_visit_scope(v, node->expressions)) { - result = -1; - goto error; - } - - 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; - } - } - - bool old_is_with_scope = v->is_with_scope; - v->is_with_scope = true; - - if (ecs_script_visit_scope(v, node->scope)) { - result = -1; - goto error; - } - - v->is_with_scope = old_is_with_scope; - -error: - flecs_script_with_set_count(a, v, prev_with_count); - flecs_stack_restore_cursor(&v->r->stack, prev_stack_cursor); - return result; -} - -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); - - if (len > 2 && !ecs_os_strcmp(&node->name[len - 2], ".*")) { - char *path = flecs_strdup(a, node->name); - path[len - 2] = '\0'; - - 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; - } - - /* 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]; - } - } - - 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 = "" - }); - - if (!from) { - return -1; - } - } - - ecs_vec_append_t(a, &v->r->using, ecs_entity_t)[0] = from; - } - - return 0; -} - -static -int flecs_script_eval_module( - ecs_script_eval_visitor_t *v, - ecs_script_module_t *node) -{ - ecs_entity_t m = flecs_script_create_entity(v, node->name); - if (!m) { - return -1; - } - - ecs_add_id(v->world, m, EcsModule); - - v->module = m; - v->parent = m; - - return 0; -} - -int flecs_script_eval_const( - ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node) -{ - /* 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 = 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; - } - - ecs_entity_t type = 0; - const ecs_type_info_t *ti = NULL; - if (node->expr) { - type = node->expr->type; - ti = node->expr->type_info; - } - - if (!type && node->type) { - if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { - flecs_script_eval_error(v, node, - "unresolved type '%s' for const variable '%s'", - node->type, node->name); - return -1; - } - - 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; - } - } - - if (type && ti) { - ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - - var->value.ptr = flecs_stack_calloc( - &v->r->stack, ti->size, ti->alignment); - var->value.type = type; - var->type_info = ti; - - if (ti->hooks.ctor) { - ti->hooks.ctor(var->value.ptr, 1, ti); - } - - if (flecs_script_eval_expr(v, &node->expr, &var->value)) { - 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; - } - - 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); - 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); - } - - /* If variable resolves to a constant expression, mark it as const so that - * its value can be folded. */ - if (node->expr->kind == EcsExprValue) { - var->is_const = true; - } - - return 0; -} - -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_sp, &first) || !first) - { - first = flecs_script_create_entity(v, node->id.first); - if (!first) { - return -1; - } - } - - 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_sp, &second)) - { - return -1; - } - } else { - second = flecs_script_create_entity(v, node->id.second); - } - - } - - if (!second) { - return -1; - } - - 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; - - v->with_relationship = first; - - 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; - } - - if (ecs_script_visit_scope(v, node->scope)) { - return -1; - } - - 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); - } - - v->with_relationship = prev_first; - v->with_relationship_sp = prev_with_relationship_sp; - - 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)) { - return -1; - } - } - - v->vars = ecs_script_vars_pop(v->vars); - - return 0; -} - -static -int flecs_script_eval_annot( - ecs_script_eval_visitor_t *v, - ecs_script_annot_t *node) -{ - ecs_assert(v->base.next != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_allocator_t *a = &v->r->allocator; - ecs_vec_append_t(a, &v->r->annot, ecs_script_annot_t*)[0] = node; - - return 0; -} - -int flecs_script_eval_node( - ecs_script_eval_visitor_t *v, - ecs_script_node_t *node) -{ - 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); - 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 - }; - - if (!v->r) { - v->r = ecs_script_runtime_new(); - } - - 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); - - /* 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; - } - - /* Always include flecs.meta */ - ecs_vec_append_t(&v->r->allocator, &v->r->using, ecs_entity_t)[0] = - ecs_lookup(v->world, "flecs.meta"); -} - -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); - } - - if (!desc || (v->r != desc->runtime)) { - ecs_script_runtime_free(v->r); - } -} - -int ecs_script_eval( - 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_eval_node); - flecs_script_eval_visit_fini(&v, &priv_desc); - - return result; -} - -#endif - -/** - * @file addons/script/visit_free.c - * @brief Script free visitor (frees AST resources). - */ - - -#ifdef FLECS_SCRIPT - -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 -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); -} - -static -void flecs_script_template_free( - ecs_script_visit_t *v, - ecs_script_template_node_t *node) -{ - flecs_script_scope_free(v, node->scope); -} - -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); - } -} - -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); -} - -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); -} - -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 flecs_script_component_free( - ecs_script_visit_t *v, - ecs_script_component_t *node) -{ - flecs_expr_visit_free(&v->script->pub, node->expr); -} - -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); -} - -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); -} - -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_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: - flecs_script_var_node_free(v, (ecs_script_var_node_t*)node); - flecs_free_t(a, ecs_script_var_node_t, node); - break; - } - - return 0; -} - -int flecs_script_visit_free( - ecs_script_t *script) -{ - ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_script_visit_t v = { - .script = flecs_script_impl(script) - }; - - if (ecs_script_visit( - flecs_script_impl(script), &v, flecs_script_stmt_free)) - { - goto error; - } - - return 0; -error: - return - 1; -} - -#endif - -/** - * @file addons/script/visit_to_str.c - * @brief Script AST to string visitor. - */ - - -#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); -} - -static -void flecs_scriptbuf_append( - ecs_script_str_visitor_t *v, - const char *fmt, - ...) -{ - if (v->newline) { - ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); - v->newline = false; - } - - va_list args; - va_start(args, fmt); - ecs_strbuf_vappend(v->buf, fmt, args); - va_end(args); - - if (fmt[strlen(fmt) - 1] == '\n') { - v->newline = true; - } -} - -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_strbuf_appendstr(v->buf, str); - - if (str[strlen(str) - 1] == '\n') { - v->newline = true; - } -} - -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 (id->second) { - flecs_scriptbuf_append(v, "(%s, %s)", - id->first, id->second); - } else { - flecs_scriptbuf_appendstr(v, id->first); - } -} - -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.world, expr, v->buf, v->colors); - } else { - flecs_scriptbuf_appendstr(v, "{}"); - } -} - -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 EcsAstEntity: return "entity"; - case EcsAstPairScope: return "pair_scope"; - case EcsAstIf: return "if"; - case EcsAstFor: return "for"; - } - return "???"; -} - -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); -} - -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"); -} - -static -void flecs_script_component_to_str( - ecs_script_str_visitor_t *v, - ecs_script_component_t *node) -{ - 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"); -} - -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"); -} - -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"); -} - -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"); -} - -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); -} - -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); -} - -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"); -} - -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); -} - -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"); -} - -static -void flecs_script_entity_to_str( - ecs_script_str_visitor_t *v, - ecs_script_entity_t *node) -{ - 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 { - flecs_scriptbuf_appendstr(v, " "); - } - - if (!flecs_scope_is_empty(node->scope)) { - flecs_script_scope_to_str(v, node->scope); - } else { - flecs_scriptbuf_appendstr(v, "\n"); - } -} - -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); -} - -static -void flecs_script_if_to_str( - ecs_script_str_visitor_t *v, - ecs_script_if_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_expr_to_str(v, node->expr); - - 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"); -} - -static -void flecs_script_for_range_to_str( - ecs_script_str_visitor_t *v, - ecs_script_for_range_t *node) -{ - flecs_scriptbuf_node(v, &node->node); - flecs_scriptbuf_appendstr(v, node->loop_var); - flecs_scriptbuf_appendstr(v, " "); - flecs_expr_to_str(v, node->from); - flecs_scriptbuf_appendstr(v, " .. "); - flecs_expr_to_str(v, node->to); - - flecs_scriptbuf_appendstr(v, " {\n"); - v->depth ++; - flecs_script_scope_to_str(v, node->scope); - v->depth --; - flecs_scriptbuf_appendstr(v, "}\n"); -} - -static -int flecs_script_scope_to_str( - ecs_script_str_visitor_t *v, - ecs_script_scope_t *scope) -{ - if (!ecs_vec_count(&scope->stmts)) { - flecs_scriptbuf_appendstr(v, "{}\n"); - return 0; - } - - flecs_scriptbuf_appendstr(v, "{\n"); - - v->depth ++; - - if (ecs_script_visit_scope(v, scope)) { - return -1; - } - - v->depth --; - - flecs_scriptbuf_appendstr(v, "}\n"); - - return 0; -} - -static -int flecs_script_stmt_to_str( - ecs_script_str_visitor_t *v, - ecs_script_node_t *node) -{ - 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 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; - } - - return 0; -} - -int ecs_script_ast_to_buf( - ecs_script_t *script, - ecs_strbuf_t *buf, - bool colors) -{ - 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; -} - -char* ecs_script_ast_to_str( - ecs_script_t *script, - bool colors) -{ - ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_strbuf_t buf = ECS_STRBUF_INIT; - - if (flecs_script_impl(script)->expr) { - flecs_expr_to_str_buf( - script->world, flecs_script_impl(script)->expr, &buf, colors); - } else { - if (ecs_script_ast_to_buf(script, &buf, colors)) { - goto error; - } - } - - return ecs_strbuf_get(&buf); -error: - return NULL; -} - -#endif - -/** - * @file addons/monitor.c - * @brief Stats addon module. - */ - -/** - * @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; -} ecs_stats_api_t; - -void flecs_stats_api_import( - ecs_world_t *world, - 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); - -#endif - - -#ifdef FLECS_STATS - -ECS_COMPONENT_DECLARE(FlecsStats); - -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; - -#define FlecsDayIntervalCount (24) -#define FlecsWeekIntervalCount (168) - -typedef struct { - ecs_stats_api_t api; - ecs_query_t *query; -} ecs_monitor_stats_ctx_t; - -typedef struct { - ecs_stats_api_t api; -} ecs_reduce_stats_ctx_t; - -typedef struct { - ecs_stats_api_t api; - int32_t interval; -} ecs_aggregate_stats_ctx_t; - -static -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, 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 ++; - } - - ecs_iter_t qit; - int32_t cur = -1, count = 0; - void *stats = NULL; - ecs_map_t *stats_map = 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); - } - - 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; - } - - cur = 0; - count = qit.count; - if (!count) { - cur = -1; - continue; - } - } else { - cur ++; - } - - 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); - } - - 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); - } - - 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); - } - } - - if (last && ctx->api.fini) { - ctx->api.fini(last); - } - - if (!ctx->query) { - break; - } - } while (true); - - if (dif > 1) { - hdr->reduce_count = 0; - } -} - -static -void ReduceStats(ecs_iter_t *it) { - ecs_reduce_stats_ctx_t *ctx = it->ctx; - - void *dst = ecs_field_w_size(it, 0, 0); - void *src = ecs_field_w_size(it, 0, 1); - - dst = ECS_OFFSET_T(dst, EcsStatsHeader); - src = ECS_OFFSET_T(src, EcsStatsHeader); - - if (!ctx->api.query_component_id) { - 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); - } - } -} - -static -void AggregateStats(ecs_iter_t *it) { - ecs_aggregate_stats_ctx_t *ctx = it->ctx; - int32_t interval = ctx->interval; - - EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 0); - EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 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; - } - - 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)); - } - - 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; - } -} - -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); -} - -static -void flecs_reduce_ctx_free( - void *ptr) -{ - ecs_os_free(ptr); -} - -static -void flecs_aggregate_ctx_free( - void *ptr) -{ - ecs_os_free(ptr); -} - -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); - - 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 - }); - } - - // 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 - }); - } - - // 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 - }); - } - - // 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); -} - -void FlecsStatsImport( - ecs_world_t *world) -{ - ECS_MODULE_DEFINE(world, FlecsStats); - ECS_IMPORT(world, FlecsPipeline); - ECS_IMPORT(world, FlecsTimer); -#ifdef FLECS_META - ECS_IMPORT(world, FlecsMeta); -#endif -#ifdef FLECS_UNITS - ECS_IMPORT(world, FlecsUnits); -#endif -#ifdef FLECS_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsStats), - "Module that automatically monitors statistics for the world & systems"); -#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); - - if (ecs_os_has_time()) { - ecs_measure_frame_time(world, true); - ecs_measure_system_time(world, true); - } -} - -#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); -}) - -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; -} - - -static -void flecs_pipeline_stats_copy_last( - void *stats, - void *src) -{ - ecs_pipeline_stats_copy_last(stats, src); -} - -static -void flecs_pipeline_stats_get( - ecs_world_t *world, - ecs_entity_t res, - void *stats) -{ - ecs_pipeline_stats_get(world, res, stats); -} - -static -void flecs_pipeline_stats_reduce( - void *stats, - void *src) -{ - ecs_pipeline_stats_reduce(stats, src); -} - -static -void flecs_pipeline_stats_reduce_last( - void *stats, - void *last, - int32_t reduce_count) -{ - ecs_pipeline_stats_reduce_last(stats, last, reduce_count); -} - -static -void flecs_pipeline_stats_repeat_last( - void* stats) -{ - ecs_pipeline_stats_repeat_last(stats); -} - -static -void flecs_pipeline_stats_fini( - void *stats) -{ - ecs_pipeline_stats_fini(stats); -} - -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); -} - -#endif - -/** - * @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; -} - -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); -} - -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 -void flecs_counter_print( - const char *name, - int32_t t, - const ecs_metric_t *m) -{ - flecs_metric_print(name, m->counter.rate.avg[t]); -} - -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); - - bool min_set = false; - dst->gauge.avg[t_dst] = 0; - dst->gauge.min[t_dst] = 0; - dst->gauge.max[t_dst] = 0; - - ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; - - 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; - - 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]; - } - } - - dst->counter.value[t_dst] = src->counter.value[t_src]; - -error: - return; -} - -void ecs_metric_reduce_last( - ecs_metric_t *m, - int32_t prev, - int32_t count) -{ - 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]; - } - - if (m->gauge.max[t] > m->gauge.max[prev]) { - m->gauge.max[prev] = m->gauge.max[t]; - } - - 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]; - - cur *= ((fcount - 1) / fcount); - next *= 1 / fcount; - - m->gauge.avg[prev] = cur + next; - m->counter.value[prev] = m->counter.value[t]; - -error: - return; -} - -void ecs_metric_copy( - ecs_metric_t *m, - int32_t dst, - int32_t src) -{ - ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); - - 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]; - -error: - return; -} - -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); - } -} - -static -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) -{ - 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); - - /* 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]; - } -} - -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); - } -} - -static -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) -{ - 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]; - } -} - -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); - - world = ecs_get_world(world); - - int32_t t = s->t = t_next(s->t); - - 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_frame); - ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame); - 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); - -#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 - -error: - return; -} - -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); -} - -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); -} - -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))); -} - -void ecs_world_stats_copy_last( - ecs_world_stats_t *dst, - const ecs_world_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)); -} - -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; - - 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); - -error: - return; -} - -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); -} - -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); -} - -void ecs_query_cache_stats_repeat_last( - ecs_query_stats_t *stats) -{ - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->t = t_next(stats->t))); -} - -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)); -} - -#ifdef FLECS_SYSTEM - -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); - - world = ecs_get_world(world); - - const ecs_system_t *ptr = flecs_poly_get(world, system, ecs_system_t); - if (!ptr) { - return false; - } - - ecs_query_stats_get(world, ptr->query, &s->query); - int32_t t = s->query.t; - - ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); - - s->task = !(ptr->query->flags & EcsQueryMatchThis); - - return true; -error: - return false; -} - -void ecs_system_stats_reduce( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src) -{ - 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); -} - -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); -} - -void ecs_system_stats_repeat_last( - ecs_system_stats_t *stats) -{ - ecs_query_cache_stats_repeat_last(&stats->query); - flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), - (stats->query.t)); -} - -void ecs_system_stats_copy_last( - ecs_system_stats_t *dst, - const ecs_system_stats_t *src) -{ - 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)); -} - -#endif - -#ifdef FLECS_PIPELINE - -bool ecs_pipeline_stats_get( - ecs_world_t *stage, - ecs_entity_t pipeline, - ecs_pipeline_stats_t *s) -{ - 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); - - int32_t sys_count = 0, active_sys_count = 0; - - /* Count number of active systems */ - ecs_iter_t it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { - continue; - } - active_sys_count += it.count; - } - - /* Count total number of systems in pipeline */ - it = ecs_query_iter(stage, pq->query); - while (ecs_query_next(&it)) { - sys_count += it.count; - } - - /* 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; - } - - 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); - - /* 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 (flecs_id_record_get_table(pq->idr_inactive, 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 { - ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); - } - - /* 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); - - 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); - - ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); - ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, - cur->commands_enqueued); - - el->system_count = cur->count; - el->multi_threaded = cur->multi_threaded; - el->immediate = cur->immediate; - } - } - } - - s->t = t_next(s->t); - - return true; -error: - return false; -} - -void ecs_pipeline_stats_fini( - ecs_pipeline_stats_t *stats) -{ - ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); - ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); -} - -void ecs_pipeline_stats_reduce( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src) -{ - 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); - - 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; - } - - dst->t = t_next(dst->t); -} - -void ecs_pipeline_stats_reduce_last( - ecs_pipeline_stats_t *dst, - const ecs_pipeline_stats_t *src, - int32_t count) -{ - 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 (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; - } - - dst->t = t_prev(dst->t); -} - -void ecs_pipeline_stats_repeat_last( - ecs_pipeline_stats_t *stats) -{ - 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 (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)); - } - - stats->t = t_next(stats->t); -} - -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); - - 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; - } -} - -#endif - -void ecs_world_stats_log( - const ecs_world_t *world, - const ecs_world_stats_t *s) -{ - int32_t t = s->t; - - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); - - 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; -} - -#endif - -/** - * @file addons/stats/system_monitor.c - * @brief Stats addon system monitor - */ - - -#ifdef FLECS_STATS - -ECS_COMPONENT_DECLARE(EcsSystemStats); - -static -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); -} - -static ECS_CTOR(EcsSystemStats, ptr, { - ecs_os_zeromem(ptr); - ecs_map_init(&ptr->stats, NULL); -}) - -static ECS_COPY(EcsSystemStats, dst, src, { - (void)dst; - (void)src; - ecs_abort(ECS_INVALID_OPERATION, "cannot copy system stats component"); -}) - -static ECS_MOVE(EcsSystemStats, dst, src, { - flecs_system_monitor_dtor(dst); - ecs_os_memcpy_t(dst, src, EcsSystemStats); - ecs_os_zeromem(src); -}) - -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 flecs_system_stats_copy_last( - void *stats, - void *src) -{ - ecs_system_stats_copy_last(stats, src); -} - -static -void flecs_system_stats_get( - ecs_world_t *world, - ecs_entity_t res, - void *stats) -{ - ecs_system_stats_get(world, res, stats); -} - -static -void flecs_system_stats_reduce( - void *stats, - void *src) -{ - ecs_system_stats_reduce(stats, src); -} - -static -void flecs_system_stats_reduce_last( - void *stats, - void *last, - int32_t reduce_count) -{ - ecs_system_stats_reduce_last(stats, last, reduce_count); -} - -static -void flecs_system_stats_repeat_last( - void* stats) -{ - ecs_system_stats_repeat_last(stats); -} - -void FlecsSystemMonitorImport( - ecs_world_t *world) -{ - ECS_COMPONENT_DEFINE(world, EcsSystemStats); - - ecs_set_hooks(world, EcsSystemStats, { - .ctor = ecs_ctor(EcsSystemStats), - .copy = ecs_copy(EcsSystemStats), - .move = ecs_move(EcsSystemStats), - .dtor = ecs_dtor(EcsSystemStats) - }); - - 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 - }; - - flecs_stats_api_import(world, &api); -} - -#endif - -/** - * @file addons/stats/world_monitor.c - * @brief Stats addon world monitor. - */ - - -#ifdef FLECS_STATS - -ECS_COMPONENT_DECLARE(EcsWorldStats); - -static -void flecs_world_stats_get( - ecs_world_t *world, ecs_entity_t res, void *stats) -{ - (void)res; - ecs_world_stats_get(world, stats); -} - -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; -} - -static -void flecs_world_stats_copy_last( - void *stats, - void *src) -{ - ecs_world_stats_copy_last(stats, src); -} - -static -void flecs_world_stats_reduce( - void *stats, - void *src) -{ - ecs_world_stats_reduce(stats, src); -} - -static -void flecs_world_stats_reduce_last( - void *stats, - void *last, - int32_t reduce_count) -{ - ecs_world_stats_reduce_last(stats, last, reduce_count); -} - -static -void flecs_world_stats_repeat_last( - void* stats) -{ - ecs_world_stats_repeat_last(stats); -} - -void FlecsWorldMonitorImport( - ecs_world_t *world) -{ - ECS_COMPONENT_DEFINE(world, EcsWorldStats); - - ecs_set_hooks(world, EcsWorldStats, { - .ctor = flecs_default_ctor - }); - - 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) - }; - - flecs_stats_api_import(world, &api); -} - -#endif - -/** - * @file addons/world_summary.c - * @brief Monitor addon. - */ - - -#ifdef FLECS_STATS - -ECS_COMPONENT_DECLARE(EcsWorldSummary); - -static -void flecs_copy_world_summary( - ecs_world_t *world, - EcsWorldSummary *dst) -{ - const ecs_world_info_t *info = ecs_get_world_info(world); - - dst->target_fps = (double)info->target_fps; - dst->time_scale = (double)info->time_scale; - - dst->frame_time_last = (double)info->frame_time_total - dst->frame_time_total; - dst->system_time_last = (double)info->system_time_total - dst->system_time_total; - dst->merge_time_last = (double)info->merge_time_total - dst->merge_time_total; - - 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; - - dst->frame_count ++; - dst->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; - - dst->build_info = *ecs_get_build_info(); -} - -static -void UpdateWorldSummary(ecs_iter_t *it) { - EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); - - int32_t i, count = it->count; - for (i = 0; i < count; i ++) { - flecs_copy_world_summary(it->world, &summary[i]); - } -} - -static -void OnSetWorldSummary(ecs_iter_t *it) { - EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); - - 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); - } -} - -void FlecsWorldSummaryImport( - ecs_world_t *world) -{ - ECS_COMPONENT_DEFINE(world, EcsWorldSummary); - -#if defined(FLECS_META) && defined(FLECS_UNITS) - ecs_entity_t build_info = ecs_lookup(world, "flecs.core.build_info_t"); - ecs_struct(world, { - .entity = ecs_id(EcsWorldSummary), - .members = { - { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, - { .name = "time_scale", .type = ecs_id(ecs_f64_t) }, - { .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 = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, - { .name = "frame_count", .type = ecs_id(ecs_u64_t) }, - { .name = "command_count", .type = ecs_id(ecs_u64_t) }, - { .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_observer(world, { - .entity = ecs_entity(world, { - .name = "OnSetWorldSummary" - }), - .events = { EcsOnSet }, - .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, - .callback = OnSetWorldSummary - }); - - ecs_set(world, EcsWorld, EcsWorldSummary, { - .target_fps = (double)info->target_fps, - .time_scale = (double)info->time_scale - }); - - EcsWorldSummary *summary = ecs_ensure(world, EcsWorld, EcsWorldSummary); - flecs_copy_world_summary(world, summary); - ecs_modified(world, EcsWorld, EcsWorldSummary); -} - -#endif - -/** - * @file addons/system/system.c - * @brief System addon. - */ - - -#ifdef FLECS_SYSTEM - - -ecs_mixins_t ecs_system_t_mixins = { - .type_name = "ecs_system_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_system_t, world), - [EcsMixinEntity] = offsetof(ecs_system_t, entity), - [EcsMixinDtor] = offsetof(ecs_system_t, dtor) - } -}; - -/* -- Public API -- */ - -ecs_entity_t flecs_run_intern( - 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_poly_assert(world, ecs_world_t); - ecs_ftime_t time_elapsed = delta_time; - ecs_entity_t tick_source = system_data->tick_source; - - /* Support legacy behavior */ - if (!param) { - param = system_data->ctx; - } - - if (tick_source) { - const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); - - if (tick) { - time_elapsed = tick->time_elapsed; - - /* 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_os_perf_trace_push(system_data->name); - - 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); - } - - ecs_time_t time_start; - bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); - if (measure_time) { - ecs_os_get_time(&time_start); - } - - ecs_world_t *thread_ctx = world; - if (stage) { - thread_ctx = stage->thread_ctx; - } else { - stage = world->stages[0]; - } - - flecs_poly_assert(stage, ecs_stage_t); - - /* Prepare the query iterator */ - ecs_iter_t wit, qit = ecs_query_iter(thread_ctx, system_data->query); - ecs_iter_t *it = &qit; - - 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; - - flecs_defer_begin(world, stage); - - 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 { - run(it); - } - } else { - if (system_data->query->term_count) { - if (it == &qit) { - while (ecs_query_next(&qit)) { - action(&qit); - } - } else { - while (ecs_iter_next(it)) { - action(it); - } - } - } else { - action(&qit); - ecs_iter_fini(&qit); - } - } - - flecs_stage_set_system(stage, old_system); - - if (measure_time) { - system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); - } - - flecs_defer_end(world, stage); - - ecs_os_perf_trace_pop(system_data->name); - - return it->interrupted_by; -} - -/* -- 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) -{ - 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); - - return flecs_run_intern( - world, stage, system, system_data, stage_index, stage_count, - delta_time, param); -} - -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); - return flecs_run_intern( - world, stage, system, system_data, 0, 0, delta_time, param); -} - -/* System deinitialization */ -static -void flecs_system_fini(ecs_system_t *sys) { - if (sys->ctx_free) { - sys->ctx_free(sys->ctx); - } - - if (sys->callback_ctx_free) { - sys->callback_ctx_free(sys->callback_ctx); - } - - if (sys->run_ctx_free) { - sys->run_ctx_free(sys->run_ctx); - } - - /* Safe cast, type owns name */ - ecs_os_free(ECS_CONST_CAST(char*, sys->name)); - - flecs_poly_free(sys, ecs_system_t); -} - -/* ecs_poly_dtor_t-compatible wrapper */ -static -void flecs_system_poly_fini(void *sys) -{ - flecs_system_fini(sys); -} - -static -int flecs_system_init_timer( - ecs_world_t *world, - ecs_entity_t entity, - const ecs_system_desc_t *desc) -{ - 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_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 0; -} - -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_system_t *system = flecs_poly_new(ecs_system_t); - ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); - - poly->poly = system; - system->world = world; - system->dtor = flecs_system_poly_fini; - system->entity = entity; - - ecs_query_desc_t query_desc = desc->query; - query_desc.entity = entity; - - ecs_query_t *query = ecs_query_init(world, &query_desc); - if (!query) { - ecs_delete(world, entity); - return 0; - } - - /* Prevent the system from moving while we're initializing */ - flecs_defer_begin(world, world->stages[0]); - - system->query = query; - system->query_entity = query->entity; - - system->run = desc->run; - system->action = desc->callback; - - system->ctx = desc->ctx; - system->callback_ctx = desc->callback_ctx; - system->run_ctx = desc->run_ctx; - - system->ctx_free = desc->ctx_free; - system->callback_ctx_free = desc->callback_ctx_free; - system->run_ctx_free = desc->run_ctx_free; - - system->tick_source = desc->tick_source; - - system->multi_threaded = desc->multi_threaded; - system->immediate = desc->immediate; - - system->name = ecs_get_path(world, entity); - - if (flecs_system_init_timer(world, entity, desc)) { - ecs_delete(world, entity); - ecs_defer_end(world); - 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 (system->ctx_free) { - if (system->ctx && system->ctx != desc->ctx) { - system->ctx_free(system->ctx); - } - } - - 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 (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; - } - } - - if (desc->run) { - system->run = desc->run; - if (!desc->callback) { - system->action = NULL; - } - } - - if (desc->callback) { - system->action = desc->callback; - if (!desc->run) { - system->run = NULL; - } - } - - if (desc->ctx) { - system->ctx = desc->ctx; - } - - if (desc->callback_ctx) { - system->callback_ctx = desc->callback_ctx; - } - - if (desc->run_ctx) { - system->run_ctx = desc->run_ctx; - } - - if (desc->ctx_free) { - system->ctx_free = desc->ctx_free; - } - - 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; - } - } - - flecs_poly_modified(world, entity, ecs_system_t); - - return entity; -error: - return 0; -} - -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_DOC - ECS_IMPORT(world, FlecsDoc); - ecs_doc_set_brief(world, ecs_id(FlecsSystem), - "Module that implements Flecs systems"); -#endif - - ecs_set_name_prefix(world, "Ecs"); - - flecs_bootstrap_tag(world, EcsSystem); - flecs_bootstrap_component(world, EcsTickSource); - - /* 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); -} - -#endif - -/** - * @file query/compiler/compile.c - * @brief Compile query program from query. - */ - - -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; -} - -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 */ - } - } - - 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; - } - - var_id = flecs_query_find_var_id(query, name, EcsVarTable); - if (var_id != EcsVarNone) { - return var_id; - } - - kind = EcsVarTable; - } else { - var_id = flecs_query_find_var_id(query, name, kind); - if (var_id != EcsVarNone) { - return var_id; - } - } - - if (kind == EcsVarTable) { - var_index = &query->tvar_index; - } else { - var_index = &query->evar_index; - } - - /* 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 { - 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 ++; - } - - 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); - - 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] == '_'; - - /* 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 -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) -{ - const char *name = flecs_term_ref_var_name(term_id); - if (!name) { - return EcsVarNone; - } - - return flecs_query_add_var(query, name, vars, kind); -} - -/* 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_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ - ecs_vec_reset_t(NULL, vars, ecs_query_var_t); - - 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; - - /* 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; - } - - 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 */ - } - - /* If first is a wildcard, insert anonymous variable */ - if (flecs_term_ref_is_wildcard(first)) { - anonymous_count ++; - } - } - - 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 (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 ++; - } - - 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; - } - } - - /* 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); - } - - 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 (flecs_query_add_var_for_term_id( - query, second, vars, EcsVarEntity) == EcsVarNone) - { - /* If second is a wildcard, insert anonymous variable */ - if (flecs_term_ref_is_wildcard(second)) { - anonymous_count ++; - } - } - - 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 ++; - } - } - - /* 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); - } - - /* 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; - } - - 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); - - if (first_is_this || second_is_this) { - if (!table_this) { - entity_before_table_this = true; - } - } - } - - int32_t var_count = ecs_vec_count(vars); - ecs_var_id_t placeholder = EcsVarNone - 1; - bool replace_placeholders = false; - - /* 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; - } - } - - 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; - } - - 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); - } - } - - 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; - - 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 ++; - } - } - - var_count = ecs_vec_count(vars); - } - - /* 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'; - - var->table_id = flecs_query_find_var_id( - query, var_name, EcsVarTable); - ecs_assert(var->table_id != EcsVarNone, - ECS_INTERNAL_ERROR, NULL); - - ecs_os_free(var_name); - } - } - } - - /* Always include spot for This variable, even if query doesn't use it */ - var_count ++; - - 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)); - } - - 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; - - 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; - - 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; - } - - query->pub.vars = (char**)var_names; - - query_vars ++; - var_names ++; - var_count --; - - 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); - } - } - - /* Hide anonymous table variables from application */ - query->pub.var_count = - flecs_ito(int8_t, query->pub.var_count - anonymous_table_count); - - /* 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 0; -error: - return -1; -} - -static -bool flecs_query_var_is_unknown( - ecs_query_impl_t *query, - ecs_var_id_t var_id, - ecs_query_compile_ctx_t *ctx) -{ - 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); - } - } - return true; -} - -/* Returns whether term is unknown. A term is unknown when it has variable - * elements (first, second, src) that are all unknown. */ -static -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; - } - - if (dummy.flags & (EcsQueryIsVar << EcsQueryFirst)) { - if (!flecs_query_var_is_unknown(query, dummy.first.var, ctx)) { - return false; - } - } - if (dummy.flags & (EcsQueryIsVar << EcsQuerySecond)) { - if (!flecs_query_var_is_unknown(query, dummy.second.var, ctx)) { - return false; - } - } - if (dummy.flags & (EcsQueryIsVar << EcsQuerySrc)) { - if (!flecs_query_var_is_unknown(query, dummy.src.var, ctx)) { - return false; - } - } - - 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 -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) -{ - ecs_query_t *q = &query->pub; - ecs_term_t *terms = q->terms; - int32_t i, count = q->term_count; - - for (i = offset; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (compiled & (1ull << i)) { - continue; - } - - /* Only evaluate And terms */ - if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ - continue; - } - - /* Don't reorder terms in scopes */ - if (term->flags_ & EcsTermIsScope) { - continue; - } - - if (flecs_query_term_is_unknown(query, term, ctx)) { - continue; - } - - return i; - } - - 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 -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; - - /* Trivial search always ignores prefabs and disabled entities */ - if (query->pub.flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)) { - return; - } - - /* Find trivial terms, which can be handled in single instruction */ - int32_t trivial_wildcard_terms = 0; - int32_t trivial_terms = 0; - - for (i = 0; i < term_count; i ++) { - /* Term is already compiled */ - if (*compiled & (1ull << i)) { - continue; - } - - ecs_term_t *term = &terms[i]; - if (!(term->flags_ & EcsTermIsTrivial)) { - continue; - } - - /* We can only add trivial terms to plan if they no up traversal */ - if ((term->src.id & EcsTraverseFlags) != EcsSelf) { - continue; - } - - /* Wildcards are not supported for trivial queries */ - if (ecs_id_is_wildcard(term->id)) { - continue; - } - - trivial_set |= (1llu << i); - - trivial_terms ++; - } - - 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 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; - } - - /* Store the bitset with trivial terms on the instruction */ - trivial.src.entity = trivial_set; - flecs_query_op_insert(&trivial, ctx); - - /* Mark $this as written */ - ctx->written |= (1llu << 0); - } -} - -static -void flecs_query_insert_cache_search( - ecs_query_impl_t *query, - ecs_flags64_t *compiled, - ecs_query_compile_ctx_t *ctx) -{ - if (!query->cache) { - return; - } - - ecs_query_t *q = &query->pub; - - 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; - - for (i = 0; i < count; i ++) { - if ((*compiled) & (1ull << i)) { - continue; - } - - ecs_term_t *term = &terms[i]; - if (!(term->flags_ & EcsTermIsCacheable)) { - continue; - } - - *compiled |= (1ull << i); - } - } - - /* Insert the operation for cache traversal */ - ecs_query_op_t op = {0}; - - if (q->flags & EcsQueryIsCacheable) { - op.kind = EcsQueryIsCache; - } else { - op.kind = EcsQueryCache; - } - - flecs_query_write(0, &op.written); - flecs_query_write_ctx(0, ctx, false); - flecs_query_op_insert(&op, ctx); -} - -static -bool flecs_term_ref_match_multiple( - ecs_term_ref_t *ref) -{ - return (ref->id & EcsIsVariable) && (ECS_TERM_REF_ID(ref) != EcsAny); -} - -static -bool flecs_term_match_multiple( - ecs_term_t *term) -{ - return flecs_term_ref_match_multiple(&term->first) || - flecs_term_ref_match_multiple(&term->second); -} - -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; - - for (i = 0; i < term_count; i ++) { - if (fields_done & (1llu << i)) { - continue; - } - - 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); - - ecs_flags64_t and_toggles = 0; - ecs_flags64_t not_toggles = 0; - ecs_flags64_t optional_toggles = 0; - - for (j = i; j < term_count; j ++) { - if (fields_done & (1llu << j)) { - continue; - } - - /* Also includes term[i], so flags get set correctly */ - term = &terms[j]; - - /* 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; - } - - /* 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); - } - - fields_done |= (1llu << j); - } - - if (and_toggles || not_toggles) { - ecs_query_op_t op = {0}; - op.kind = EcsQueryToggle; - op.src = cur.src; - op.flags = cur.flags; - - 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 0; -} - -static -int flecs_query_insert_fixed_src_terms( - ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_flags64_t *compiled, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_t *q = &impl->pub; - int32_t i, term_count = q->term_count; - ecs_term_t *terms = q->terms; - - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - - 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; - } - } - - /* Don't reorder terms in scopes */ - if (term->flags_ & EcsTermIsScope) { - continue; - } - - if (term->src.id & EcsIsEntity && ECS_TERM_REF_ID(&term->src)) { - if (flecs_query_compile_term(world, impl, term, ctx)) { - return -1; - } - - *compiled |= (1llu << i); - } - } - - return 0; -} - -int flecs_query_compile( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_query_impl_t *query) -{ - /* 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; - ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; - if ((flags & trivial_flags) == trivial_flags) { - if (query->cache) { - if (flags & EcsQueryIsCacheable) { - needs_plan = false; - } - } else { - if (!(flags & EcsQueryMatchWildcards)) { - needs_plan = false; - } - } - } - - 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; - } - - 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); - - /* Find all variables defined in query */ - if (flecs_query_discover_vars(stage, query)) { - return -1; - } - - /* 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]; - - if (term->src.id & EcsIsEntity) { - ecs_query_op_t set_fixed = {0}; - set_fixed.kind = EcsQuerySetFixed; - flecs_query_op_insert(&set_fixed, &ctx); - break; - } - } - - /* 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; - } - } - } - - ecs_flags64_t compiled = 0; - - /* Always evaluate terms with fixed source before other terms */ - flecs_query_insert_fixed_src_terms( - world, query, &compiled, &ctx); - - /* Compile cacheable terms */ - flecs_query_insert_cache_search(query, &compiled, &ctx); - - /* Insert trivial term search if query allows for it */ - flecs_query_insert_trivial_search(query, &compiled, &ctx); - - /* 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; - } - } - - 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 (compiled & (1ull << i)) { - continue; /* Already compiled */ - } - - 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; - } - - bool can_reorder = true; - if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ - can_reorder = false; - } - - /* 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 */ - } - } - - if (flecs_query_compile_term(world, query, term, &ctx)) { - return -1; - } - - compiled |= (1ull << compile); - } - - if (start_term) { - start_term = 0; /* Repeat, now also insert optional terms */ - } else { - break; - } - } while (true); - - 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); - } - } - } - } - } - - /* 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; - - for (i = 0; i < q->field_count; i ++) { - ecs_var_id_t var_id = query->src_vars[i]; - if (!var_id) { - continue; - } - - 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; - } - } - } - } - - /* 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); - } - - for (i = 0; i < q->field_count; i ++) { - ecs_var_id_t var_id = query->src_vars[i]; - if (!var_id) { - continue; - } - - if (query->vars[var_id].kind == EcsVarTable) { - var_id = flecs_query_find_var_id(query, query->vars[var_id].name, - EcsVarEntity); - - /* Variables used as source that aren't This must be entities */ - ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); - } - - query->src_vars[i] = var_id; - } - } - - ecs_assert((term_count - ctx.skipped) >= 0, ECS_INTERNAL_ERROR, NULL); - - /* 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); - } - - /* 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); - } - - 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; -} - -/** - * @file query/compiler/compiler_term.c - * @brief Compile query term. - */ - - -#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) -{ - 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 { - printf("VARNONE\n"); - flecs_dump_backtrace(stdout); - return EcsVarNone; - } - } - - if (!flecs_name_index_is_init(&query->tvar_index)) { - return EcsVarNone; - } - - uint64_t index = flecs_name_index_find( - &query->tvar_index, name, 0, 0); - if (index == 0) { - return EcsVarNone; - } - return flecs_utovar(index); - } - - if (kind == EcsVarEntity) { - if (!flecs_name_index_is_init(&query->evar_index)) { - return EcsVarNone; - } - - uint64_t index = flecs_name_index_find( - &query->evar_index, name, 0, 0); - if (index == 0) { - return EcsVarNone; - } - return flecs_utovar(index); - } - - ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); - - /* 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; - } - - return flecs_query_find_var_id(query, name, EcsVarTable); -} - -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) -{ - if (kind == EcsVarTable || kind == EcsVarEntity) { - return flecs_query_find_var_id(query, name, kind); - } - - 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; - } - - 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; - } - - /* 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; - } -} - -ecs_query_lbl_t flecs_query_op_insert( - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx) -{ - 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; - } - } - - elem->next = flecs_itolbl(count); - elem->prev = flecs_itolbl(count - 2); - return flecs_itolbl(count - 1); -} - -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); -} - -void flecs_query_end_block( - ecs_query_compile_ctx_t *ctx, - bool reset) -{ - 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; - } - - ctx->cur->lbl_begin = -1; -} - -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; - - 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); - } - - /* 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; - } - - /* 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)); - - ecs_query_op_t jmp_op = {0}; - jmp_op.kind = EcsQueryIfVar; - - 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; - } - - flecs_query_op_insert(&jmp_op, ctx); - } else { - ctx->cur->lbl_cond_eval = -1; - } -} - -static -void flecs_query_end_block_cond_eval( - ecs_query_compile_ctx_t *ctx) -{ - if (ctx->cur->lbl_cond_eval == -1) { - return; - } - - 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); - - 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; -} - -static -void flecs_query_begin_block_or( - ecs_query_op_t *op, - ecs_term_t *term, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t *or_op = flecs_query_begin_block(EcsQueryNot, ctx); - or_op->kind = EcsQueryOr; - or_op->field_index = term->field_index; - - /* 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 (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - if (add_src) { - or_op->flags = (EcsQueryIsVar << EcsQuerySrc); - or_op->src = op->src; - ctx->cur->src_or = op->src; - } - - ctx->cur->src_written_or = flecs_query_is_written( - op->src.var, ctx->written); - } -} - -static -void flecs_query_end_block_or( - ecs_query_impl_t *impl, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t op = {0}; - op.kind = EcsQueryEnd; - ecs_query_lbl_t end = flecs_query_op_insert(&op, ctx); - - 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); - } - - ops[i].next = flecs_itolbl(end); - - prev_or = i + 1; - } - } - - 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; - - 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); - - /* 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"; - } - - 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; - - /* 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); - - /* Skip variable if it's the source for the OR chain */ - if (src_is_var && (i == first->src.var)) { - continue; - } - - 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_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) -{ - (void)world; - - if (!ecs_term_ref_is_set(term_ref)) { - return; - } - - 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); - } -} - -static -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_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); - bool written = false; - - if (flags & EcsQueryIsVar) { - ecs_var_id_t var_id = ref->var; - ecs_query_var_t *var = &query->vars[var_id]; - - 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; - } - - 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; - } - } - } - - written |= flecs_query_is_written(var_id, ctx->written); - } else { - /* If it's not a variable, it's always written */ - written = true; - } - - if (written_out) { - *written_out = written; - } - - return 0; -} - -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_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; - } -} - -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_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); - } -} - -static -void flecs_query_insert_pair_eq( - int32_t field_index, - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t contains = {0}; - contains.kind = EcsQueryPairEq; - contains.field_index = flecs_ito(int8_t, field_index); - flecs_query_op_insert(&contains, ctx); -} - -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); - - 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); - - 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]); - } - - 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; - } - } - - ecs_assert(flags_src & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); - (void)flags_src; - - 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 -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_var_id_t var = ref->var; - - 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; - } - } - } - - return 0; -error: - return -1; -} - -static -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) -{ - /* 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; - } - } - - if (op.flags & (EcsQueryIsVar << EcsQuerySecond)) { - if (flecs_query_ensure_scope_var( - query, &op, &op.second, EcsQuerySecond, ctx)) - { - goto error; - } - } - - cur ++; - } - - return 0; -error: - return -1; -} - -static -void flecs_query_compile_push( - ecs_query_compile_ctx_t *ctx) -{ - ctx->cur = &ctx->ctrlflow[++ ctx->scope]; - ctx->cur->lbl_begin = -1; - ctx->cur->lbl_begin = -1; -} - -static -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]; -} - -static -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 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 */ - } - - return 0; -error: - return -1; -} - -static -ecs_flags32_t flecs_query_to_table_flags( - const ecs_query_t *q) -{ - 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; - } - - return table_flags; - } - return 0; -} - -static -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; - - if (term->oper == EcsNot || term->oper == EcsOptional || - term->oper == EcsNotFrom || pred_match) - { - ecs_query_op_t match_any = {0}; - match_any.kind = EcsAnd; - 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; -} - -#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); - - first_id = ECS_TERM_REF_ID(&term->first); - - /* First compile as if it's a regular term, to match the component */ - term->flags_ &= (uint16_t)~EcsTermIsMember; - - /* 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_has(world, component, EcsComponent)) { - ecs_err("parent of member is not a component"); - return -1; - } - - 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; - } - - return 0; -} - -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); - - /* 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; - - if (term->oper == EcsOptional) { - second_wildcard = true; - } - - 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); - - /* 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; - } - - /* 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]; - } - - ecs_query_op_t *if_op = flecs_query_begin_block(EcsQueryIfSet, ctx); - if_op->other = term->field_index; - - if (ctx->oper == EcsNot) { - mbr_op.kind = EcsQueryMemberNeq; - } - } - - 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); - - /* Mark entity variable as written */ - flecs_query_write_ctx(evar, ctx, cond_write); - flecs_query_write(evar, &mbr_op.written); - } - - flecs_query_compile_term_ref(world, impl, &mbr_op, &term->src, - &mbr_op.src, EcsQuerySrc, EcsVarEntity, ctx, true); - - 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; - } - - flecs_query_write_ctx(mbr_op.second.var, ctx, cond_write); - flecs_query_write(mbr_op.second.var, &mbr_op.written); - } - } - - flecs_query_op_insert(&mbr_op, ctx); - - if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { - flecs_query_end_block(ctx, false); - } - - 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; -} - -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 - -static -void flecs_query_mark_last_or_op( - ecs_query_compile_ctx_t *ctx) -{ - ecs_query_op_t *op_ptr = ecs_vec_last_t(ctx->ops, ecs_query_op_t); - op_ptr->next = FlecsRuleOrMarker; -} - -static -void flecs_query_set_op_kind( - ecs_query_op_t *op, - ecs_term_t *term, - bool src_is_var) -{ - /* 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; - - /* If term queries for union pair, use union instruction */ - } else if (term->flags_ & EcsTermIsUnion) { - if (op->kind == EcsQueryAnd) { - op->kind = EcsQueryUnionEq; - if (term->oper == EcsNot) { - if (!ecs_id_is_wildcard(ECS_TERM_REF_ID(&term->second))) { - term->oper = EcsAnd; - op->kind = EcsQueryUnionNeq; - } - } - } else { - op->kind = EcsQueryUnionEqWith; - } - - if ((term->src.id & trav_flags) == EcsUp) { - if (op->kind == EcsQueryUnionEq) { - op->kind = EcsQueryUnionEqUp; - } - } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { - if (op->kind == EcsQueryUnionEq) { - op->kind = EcsQueryUnionEqSelfUp; - } - } - } else { - if ((term->src.id & trav_flags) == EcsUp) { - op->kind = EcsQueryUp; - } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { - op->kind = EcsQuerySelfUp; - } else if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { - op->kind = EcsQueryAndAny; - } - } -} - -int flecs_query_compile_term( - ecs_world_t *world, - 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; - - for (i = 0; i < count; i ++) { - ecs_id_t ti_id = ti_ids[i]; - ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); - if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { - break; - } - } - - if (i == count) { - /* Type did not contain any ids to perform operation on */ - ctx->skipped ++; - return 0; - } - } - - /* !_ (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; - } - - 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; - } - - if (builtin_pred) { - ecs_entity_t id_noflags = ECS_TERM_REF_ID(&term->second); - if (id_noflags == EcsWildcard || id_noflags == EcsAny) { - /* Noop */ - return 0; - } - } - - op.field_index = flecs_ito(int8_t, term->field_index); - op.term_index = flecs_ito(int8_t, term - q->terms); - - flecs_query_set_op_kind(&op, term, src_is_var); - - bool is_not = (term->oper == EcsNot) && !builtin_pred; - - /* 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; - - /* 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); - - 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); - } - - /* 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; - } - - if (flecs_query_compile_ensure_vars( - query, &op, &op.second, EcsQuerySecond, ctx, cond_write, &second_written)) - { - goto error; - } - - /* 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; - } - - /* 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); - } - - /* 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 (flecs_query_compile_ensure_vars( - query, &op, &op.src, EcsQuerySrc, ctx, cond_write, NULL)) - { - goto error; - } - - /* 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 = EcsQueryOnlyAny; /* Uses Any (_) id 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); - } - - 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 (term->src.id & EcsSelf) { - op.flags |= EcsQueryIsSelf; - } - - /* 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 (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 - } - } - 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 (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); - } - } - - /* 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); - } - } - - /* 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); - } - - /* 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 (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 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); - } - } - - return 0; -error: - return -1; -} - -/** - * @file query/engine/cache.c - * @brief Cached query implementation. - */ - - -int32_t flecs_query_cache_table_count( - ecs_query_cache_t *cache) -{ - return cache->cache.tables.count; -} - -int32_t flecs_query_cache_entity_count( - const ecs_query_cache_t *cache) -{ - int32_t result = 0; - ecs_table_cache_hdr_t *cur, *last = cache->cache.tables.last; - if (!last) { - return 0; - } - - for (cur = cache->cache.tables.first; cur != NULL; cur = cur->next) { - result += ecs_table_count(cur->table); - } - - return result; -} - -static -uint64_t flecs_query_cache_get_group_id( - ecs_query_cache_t *cache, - ecs_table_t *table) -{ - if (cache->group_by_callback) { - return cache->group_by_callback(cache->query->world, table, - cache->group_by, cache->group_by_ctx); - } else { - return 0; - } -} - -static -void flecs_query_cache_compute_group_id( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - - if (cache->group_by_callback) { - ecs_table_t *table = match->table; - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - match->group_id = flecs_query_cache_get_group_id(cache, table); - } else { - match->group_id = 0; - } -} - -static -ecs_query_cache_table_list_t* flecs_query_cache_get_group( - const ecs_query_cache_t *cache, - uint64_t group_id) -{ - return ecs_map_get_deref( - &cache->groups, ecs_query_cache_table_list_t, group_id); -} - -static -ecs_query_cache_table_list_t* flecs_query_cache_ensure_group( - ecs_query_cache_t *cache, - uint64_t id) -{ - ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, - ecs_query_cache_table_list_t, id); - - if (!group) { - group = ecs_map_insert_alloc_t(&cache->groups, - ecs_query_cache_table_list_t, id); - ecs_os_zeromem(group); - if (cache->on_group_create) { - group->info.ctx = cache->on_group_create( - cache->query->world, id, cache->group_by_ctx); - } - } - - return group; -} - -static -void flecs_query_cache_remove_group( - ecs_query_cache_t *cache, - uint64_t id) -{ - if (cache->on_group_delete) { - ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, - ecs_query_cache_table_list_t, id); - if (group) { - cache->on_group_delete(cache->query->world, id, - group->info.ctx, cache->group_by_ctx); - } - } - - ecs_map_remove_free(&cache->groups, id); -} - -static -uint64_t flecs_query_cache_default_group_by( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - void *ctx) -{ - (void)ctx; - - ecs_id_t match; - if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { - return ecs_pair_second(world, match); - } - return 0; -} - -/* Find the last node of the group after which this group should be inserted */ -static -ecs_query_cache_table_match_t* flecs_query_cache_find_group_insertion_node( - ecs_query_cache_t *cache, - uint64_t group_id) -{ - /* Grouping must be enabled */ - ecs_assert(cache->group_by_callback != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_map_iter_t it = ecs_map_iter(&cache->groups); - ecs_query_cache_table_list_t *list, *closest_list = NULL; - uint64_t id, closest_id = 0; - - bool desc = false; - - if (cache->cascade_by) { - desc = (cache->query->terms[ - cache->cascade_by - 1].src.id & EcsDesc) != 0; - } - - /* Find closest smaller group id */ - while (ecs_map_next(&it)) { - id = ecs_map_key(&it); - - if (!desc) { - if (id >= group_id) { - continue; - } - } else { - if (id <= group_id) { - continue; - } - } - - list = ecs_map_ptr(&it); - if (!list->last) { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - continue; - } - - bool comp; - if (!desc) { - comp = ((group_id - id) < (group_id - closest_id)); - } else { - comp = ((group_id - id) > (group_id - closest_id)); - } - - if (!closest_list || comp) { - closest_id = id; - closest_list = list; - } - } - - if (closest_list) { - return closest_list->last; - } else { - return NULL; /* Group should be first in query */ - } -} - -/* Initialize group with first node */ -static -void flecs_query_cache_create_group( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - uint64_t group_id = match->group_id; - - /* If query has grouping enabled & this is a new/empty group, find - * the insertion point for the group */ - ecs_query_cache_table_match_t *insert_after = - flecs_query_cache_find_group_insertion_node(cache, group_id); - - if (!insert_after) { - /* This group should appear first in the query list */ - ecs_query_cache_table_match_t *query_first = cache->list.first; - if (query_first) { - /* If this is not the first match for the query, insert before it */ - match->next = query_first; - query_first->prev = match; - cache->list.first = match; - } else { - /* If this is the first match of the query, initialize its list */ - ecs_assert(cache->list.last == NULL, ECS_INTERNAL_ERROR, NULL); - cache->list.first = match; - cache->list.last = match; - } - } else { - ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - - /* This group should appear after another group */ - ecs_query_cache_table_match_t *insert_before = insert_after->next; - if (match != insert_after) { - match->prev = insert_after; - } - insert_after->next = match; - match->next = insert_before; - if (insert_before) { - insert_before->prev = match; - } else { - ecs_assert(cache->list.last == insert_after, - ECS_INTERNAL_ERROR, NULL); - - /* This group should appear last in the query list */ - cache->list.last = match; - } - } -} - -/* Find the list the node should be part of */ -static -ecs_query_cache_table_list_t* flecs_query_cache_get_node_list( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - if (cache->group_by_callback) { - return flecs_query_cache_get_group(cache, match->group_id); - } else { - return &cache->list; - } -} - -/* Find or create the list the node should be part of */ -static -ecs_query_cache_table_list_t* flecs_query_cache_ensure_node_list( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - if (cache->group_by_callback) { - return flecs_query_cache_ensure_group(cache, match->group_id); - } else { - return &cache->list; - } -} - -/* Remove node from list */ -static -void flecs_query_cache_remove_table_node( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - ecs_query_cache_table_match_t *prev = match->prev; - ecs_query_cache_table_match_t *next = match->next; - - ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); - - ecs_query_cache_table_list_t *list = - flecs_query_cache_get_node_list(cache, match); - - if (!list || !list->first) { - /* If list contains no matches, the match 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; - } - - ecs_assert(prev != NULL || cache->list.first == match, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(next != NULL || cache->list.last == match, - ECS_INTERNAL_ERROR, NULL); - - if (prev) { - prev->next = next; - } - if (next) { - next->prev = prev; - } - - ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); - list->info.table_count --; - - if (cache->group_by_callback) { - uint64_t group_id = match->group_id; - - /* Make sure query.list is updated if this is the first or last group */ - if (cache->list.first == match) { - ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); - cache->list.first = next; - prev = next; - } - if (cache->list.last == match) { - ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); - cache->list.last = prev; - next = prev; - } - - ecs_assert(cache->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL); - cache->list.info.table_count --; - list->info.match_count ++; - - /* Make sure group list only contains nodes that belong to the group */ - if (prev && prev->group_id != group_id) { - /* The previous node belonged to another group */ - prev = next; - } - if (next && next->group_id != group_id) { - /* The next node belonged to another group */ - next = prev; - } - - /* Do check again, in case both prev & next belonged to another group */ - if ((!prev && !next) || (prev && prev->group_id != group_id)) { - /* There are no more matches left in this group */ - flecs_query_cache_remove_group(cache, group_id); - list = NULL; - } - } - - if (list) { - if (list->first == match) { - list->first = next; - } - if (list->last == match) { - list->last = prev; - } - } - - match->prev = NULL; - match->next = NULL; - - cache->match_count ++; -} - -/* Add node to list */ -static -void flecs_query_cache_insert_table_node( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *match) -{ - /* Node should not be part of an existing list */ - ecs_assert(match->prev == NULL && match->next == NULL, - ECS_INTERNAL_ERROR, NULL); - - /* If this is the first match, activate system */ - if (!cache->list.first && cache->entity) { - ecs_remove_id(cache->query->world, cache->entity, EcsEmpty); - } - - flecs_query_cache_compute_group_id(cache, match); - - ecs_query_cache_table_list_t *list = - flecs_query_cache_ensure_node_list(cache, match); - - if (list->last) { - ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_query_cache_table_match_t *last = list->last; - ecs_query_cache_table_match_t *last_next = last->next; - - match->prev = last; - match->next = last_next; - last->next = match; - - if (last_next) { - last_next->prev = match; - } - - list->last = match; - - if (cache->group_by_callback) { - /* Make sure to update query list if this is the last group */ - if (cache->list.last == last) { - cache->list.last = match; - } - } - } else { - ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); - - list->first = match; - list->last = match; - - if (cache->group_by_callback) { - /* Initialize group with its first node */ - flecs_query_cache_create_group(cache, match); - } - } - - if (cache->group_by_callback) { - list->info.table_count ++; - list->info.match_count ++; - } - - cache->list.info.table_count ++; - cache->match_count ++; - - ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL); - - ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); -} - -static -ecs_query_cache_table_match_t* flecs_query_cache_cache_add( - ecs_world_t *world, - ecs_query_cache_table_t *elem) -{ - ecs_query_cache_table_match_t *result = - flecs_bcalloc(&world->allocators.query_table_match); - - 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; - } - - return result; -} - -/* 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 flecs_query_cache_group_by_cascade( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - void *ctx) -{ - (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); -} - -static -ecs_query_cache_table_match_t* flecs_query_cache_add_table_match( - ecs_query_cache_t *cache, - ecs_query_cache_table_t *qt, - ecs_table_t *table) -{ - /* Add match for table. One table can have more than one match, if - * the query contains wildcards. */ - ecs_query_cache_table_match_t *qm = flecs_query_cache_cache_add( - cache->query->world, qt); - - qm->table = table; - qm->trs = flecs_balloc(&cache->allocators.trs); - - /* Insert match to iteration list if table is not empty */ - flecs_query_cache_insert_table_node(cache, qm); - - return qm; -} - -static -void flecs_query_cache_set_table_match( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *qm, - ecs_iter_t *it) -{ - ecs_query_t *query = cache->query; - int8_t i, field_count = query->field_count; - - ecs_assert(field_count > 0, ECS_INTERNAL_ERROR, NULL); - - /* Reset resources in case this is an existing record */ - ecs_os_memcpy_n(ECS_CONST_CAST(ecs_table_record_t**, qm->trs), - it->trs, ecs_table_record_t*, field_count); - - /* 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; - } - } - - 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; - } - } - - /* 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 (i != field_count) { - if (qm->sources == cache->sources || !qm->sources) { - qm->sources = flecs_balloc(&cache->allocators.sources); - } - ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); - } else { - if (qm->sources != cache->sources) { - flecs_bfree(&cache->allocators.sources, qm->sources); - qm->sources = cache->sources; - } - } - - qm->set_fields = it->set_fields; - qm->up_fields = it->up_fields; -} - -static -ecs_query_cache_table_t* flecs_query_cache_table_insert( - ecs_world_t *world, - ecs_query_cache_t *cache, - ecs_table_t *table) -{ - ecs_query_cache_table_t *qt = flecs_bcalloc(&world->allocators.query_table); - if (table) { - qt->table_id = table->id; - } else { - qt->table_id = 0; - } - - ecs_table_cache_insert(&cache->cache, table, &qt->hdr); - - return qt; -} - -/** Populate query cache with tables */ -static -void flecs_query_cache_match_tables( - ecs_world_t *world, - ecs_query_cache_t *cache) -{ - ecs_table_t *table = NULL; - ecs_query_cache_table_t *qt = NULL; - - ecs_iter_t it = ecs_query_iter(world, cache->query); - ECS_BIT_SET(it.flags, EcsIterNoData); - ECS_BIT_SET(it.flags, EcsIterTableOnly); - - while (ecs_query_next(&it)) { - if ((table != it.table) || (!it.table && !qt)) { - /* New table matched, add record to cache */ - table = it.table; - qt = flecs_query_cache_table_insert(world, cache, table); - ecs_dbg_3("query cache matched existing table [%s]", NULL); - } - - ecs_query_cache_table_match_t *qm = - flecs_query_cache_add_table_match(cache, qt, table); - flecs_query_cache_set_table_match(cache, qm, &it); - } -} - -static -bool flecs_query_cache_match_table( - ecs_world_t *world, - ecs_query_cache_t *cache, - ecs_table_t *table) -{ - if (!ecs_map_is_init(&cache->cache.index)) { - return false; - } - - ecs_query_cache_table_t *qt = NULL; - ecs_query_t *q = cache->query; - - /* 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); - - while (ecs_query_next(&it)) { - ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); - if (qt == NULL) { - table = it.table; - qt = flecs_query_cache_table_insert(world, cache, table); - } - - ecs_query_cache_table_match_t *qm = flecs_query_cache_add_table_match( - cache, qt, table); - flecs_query_cache_set_table_match(cache, qm, &it); - } - - return qt != NULL; -} - -static -bool flecs_query_cache_has_refs( - ecs_query_cache_t *cache) -{ - ecs_term_t *terms = cache->query->terms; - int32_t i, count = cache->query->term_count; - for (i = 0; i < count; i ++) { - if (terms[i].src.id & (EcsUp | EcsIsEntity)) { - return true; - } - } - - return false; -} - -static -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)) -{ - 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)) { - callback(world, term->id, q); - } - } -} - -static -bool flecs_query_cache_is_term_ref_supported( - ecs_term_ref_t *ref) -{ - if (!(ref->id & EcsIsVariable)) { - return true; - } - if (ecs_id_is_wildcard(ref->id)) { - return true; - } - return false; -} - -static -int flecs_query_cache_process_signature( - ecs_world_t *world, - ecs_query_impl_t *impl, - ecs_query_cache_t *cache) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - 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 *first = &term->first; - ecs_term_ref_t *src = &term->src; - ecs_term_ref_t *second = &term->second; - - bool is_src_ok = flecs_query_cache_is_term_ref_supported(src); - bool is_first_ok = flecs_query_cache_is_term_ref_supported(first); - bool is_second_ok = flecs_query_cache_is_term_ref_supported(second); - - (void)first; - (void)second; - (void)is_src_ok; - (void)is_first_ok; - (void)is_second_ok; - - /* Queries do not support named variables */ - ecs_check(is_src_ok || ecs_term_match_this(term), - ECS_UNSUPPORTED, NULL); - ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL); - ecs_check(is_second_ok, ECS_UNSUPPORTED, NULL); - ecs_check(term->inout != EcsInOutFilter, ECS_INVALID_PARAMETER, - "invalid usage of InOutFilter for query"); - - 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; - } - } - - impl->pub.flags |= - (ecs_flags32_t)(flecs_query_cache_has_refs(cache) * EcsQueryHasRefs); - - flecs_query_cache_for_each_component_monitor( - world, impl, cache, flecs_monitor_register); - - return 0; -error: - return -1; -} - -/* Remove table */ -static -void flecs_query_cache_table_match_free( - ecs_query_cache_t *cache, - ecs_query_cache_table_match_t *first) -{ - ecs_query_cache_table_match_t *cur, *next; - ecs_world_t *world = cache->query->world; - - for (cur = first; cur != NULL; cur = next) { - flecs_bfree(&cache->allocators.trs, ECS_CONST_CAST(void*, cur->trs)); - - if (cur->ids != cache->query->ids) { - flecs_bfree(&cache->allocators.ids, cur->ids); - } - - if (cur->sources != cache->sources) { - flecs_bfree(&cache->allocators.sources, cur->sources); - } - - if (cur->monitor) { - flecs_bfree(&cache->allocators.monitors, cur->monitor); - } - - flecs_query_cache_remove_table_node(cache, cur); - - next = cur->next_match; - - flecs_bfree(&world->allocators.query_table_match, cur); - } -} - -static -void flecs_query_cache_table_free( - ecs_query_cache_t *cache, - ecs_query_cache_table_t *elem) -{ - flecs_query_cache_table_match_free(cache, elem->first); - flecs_bfree(&cache->query->world->allocators.query_table, elem); -} - -static -void flecs_query_cache_unmatch_table( - ecs_query_cache_t *cache, - ecs_table_t *table, - ecs_query_cache_table_t *elem) -{ - if (!elem) { - elem = ecs_table_cache_get(&cache->cache, table); - } - if (elem) { - ecs_table_cache_remove(&cache->cache, elem->table_id, &elem->hdr); - flecs_query_cache_table_free(cache, elem); - } -} - -/* Rematch system with tables after a change happened to a watched entity */ -static -void flecs_query_cache_rematch_tables( - ecs_world_t *world, - ecs_query_impl_t *impl) -{ - flecs_poly_assert(world, ecs_world_t); - - ecs_iter_t it; - ecs_table_t *table = NULL; - ecs_query_cache_table_t *qt = NULL; - ecs_query_cache_table_match_t *qm = NULL; - ecs_query_cache_t *cache = impl->cache; - - if (cache->monitor_generation == world->monitor_generation) { - return; - } - - ecs_os_perf_trace_push("flecs.query.rematch"); - - cache->monitor_generation = world->monitor_generation; - - it = ecs_query_iter(world, cache->query); - ECS_BIT_SET(it.flags, EcsIterNoData); - - world->info.rematch_count_total ++; - int32_t rematch_count = ++ cache->rematch_count; - - ecs_time_t t = {0}; - if (world->flags & EcsWorldMeasureFrameTime) { - ecs_time_measure(&t); - } - - while (ecs_query_next(&it)) { - if ((table != it.table) || (!it.table && !qt)) { - if (qm && qm->next_match) { - flecs_query_cache_table_match_free(cache, qm->next_match); - qm->next_match = NULL; - } - - table = it.table; - - qt = ecs_table_cache_get(&cache->cache, table); - if (!qt) { - qt = flecs_query_cache_table_insert(world, cache, table); - } - - 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 = flecs_query_cache_add_table_match(cache, qt, table); - } - - flecs_query_cache_set_table_match(cache, qm, &it); - - if (table && cache->group_by_callback) { - if (flecs_query_cache_get_group_id(cache, table) != qm->group_id) { - /* Update table group */ - flecs_query_cache_remove_table_node(cache, qm); - flecs_query_cache_insert_table_node(cache, qm); - } - } - } - - if (qm && qm->next_match) { - flecs_query_cache_table_match_free(cache, qm->next_match); - qm->next_match = NULL; - } - - /* Iterate all tables in cache, remove ones that weren't just matched */ - ecs_table_cache_iter_t cache_it; - if (flecs_table_cache_all_iter(&cache->cache, &cache_it)) { - while ((qt = flecs_table_cache_next(&cache_it, ecs_query_cache_table_t))) { - if (qt->rematch_count != rematch_count) { - flecs_query_cache_unmatch_table(cache, qt->hdr.table, qt); - } - } - } - - if (world->flags & EcsWorldMeasureFrameTime) { - world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); - } - - ecs_os_perf_trace_pop("flecs.query.rematch"); -} - -/* -- Private API -- */ - -void flecs_query_cache_notify( - ecs_world_t *world, - ecs_query_t *q, - ecs_query_cache_event_t *event) -{ - flecs_poly_assert(q, ecs_query_t); - ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_query_cache_t *cache = impl->cache; - - switch(event->kind) { - case EcsQueryTableMatch: - /* Creation of new table */ - flecs_query_cache_match_table(world, cache, event->table); - break; - case EcsQueryTableUnmatch: - /* Deletion of table */ - flecs_query_cache_unmatch_table(cache, event->table, NULL); - break; - case EcsQueryTableRematch: - /* Rematch tables of query */ - flecs_query_cache_rematch_tables(world, impl); - break; - } -} - -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, NULL); - - /* 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; - } - } - - 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; - } - } - - 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; - - ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); - flecs_query_cache_sort_tables(world, impl); - - if (!cache->table_slices.array) { - flecs_query_cache_build_sorted_tables(cache); - } - - return 0; -error: - return -1; -} - -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"); - - if (!group_by) { - /* Builtin function that groups by relationship */ - group_by = flecs_query_cache_default_group_by; - } - - cache->group_by = sort_component; - cache->group_by_callback = group_by; - - ecs_map_init_w_params(&cache->groups, - &cache->query->world->allocators.query_table_list); -error: - return; -} - -static -void flecs_query_cache_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. */ - 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_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 (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); - - /* 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(&cache->cache, table) == NULL) { - return; - } - - 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); - } - - if (event == EcsOnTableDelete) { - /* Deletion of table */ - flecs_query_cache_unmatch_table(cache, table, NULL); - return; - } -} - -static -void flecs_query_cache_table_cache_free( - ecs_query_cache_t *cache) -{ - ecs_table_cache_iter_t it; - ecs_query_cache_table_t *qt; - - if (flecs_table_cache_all_iter(&cache->cache, &it)) { - while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { - flecs_query_cache_table_free(cache, qt); - } - } - - ecs_table_cache_fini(&cache->cache); -} - -static -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.trs, - 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.sources, - field_count * ECS_SIZEOF(ecs_entity_t)); - flecs_ballocator_init(&cache->allocators.monitors, - (1 + field_count) * ECS_SIZEOF(int32_t)); - } -} - -static -void flecs_query_cache_allocators_fini( - ecs_query_cache_t *cache) -{ - int32_t field_count = cache->query->field_count; - if (field_count) { - flecs_ballocator_fini(&cache->allocators.trs); - flecs_ballocator_fini(&cache->allocators.ids); - flecs_ballocator_fini(&cache->allocators.sources); - flecs_ballocator_fini(&cache->allocators.monitors); - } -} - -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); - - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - - if (cache->observer) { - flecs_observer_fini(cache->observer); - } - - 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_table_list_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; - } - - 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_table_cache_free(cache); - - ecs_map_fini(&cache->groups); - - ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); - - if (cache->query->term_count) { - flecs_bfree(&cache->allocators.sources, cache->sources); - } - - flecs_query_cache_allocators_fini(cache); - ecs_query_fini(cache->query); - - flecs_bfree(&stage->allocators.query_cache, cache); -} - -/* -- Public API -- */ - -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_stage_t *stage = impl->stage; - flecs_poly_assert(stage, ecs_stage_t); - - 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; - } - - /* 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)); - - flecs_query_cache_allocators_init(result); - - /* 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.sources); - } - - if (q->term_count) { - observer_desc.run = flecs_query_cache_on_event; - observer_desc.ctx = impl; - - int32_t event_index = 0; - observer_desc.events[event_index ++] = EcsOnTableCreate; - observer_desc.events[event_index ++] = EcsOnTableDelete; - observer_desc.flags_ = EcsObserverBypassQuery; - - /* 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, FLECS_TERM_COUNT_MAX); - observer_desc.query.expr = NULL; /* Already parsed */ - - result->observer = flecs_observer_init(world, entity, &observer_desc); - if (!result->observer) { - goto error; - } - } - - result->prev_match_count = -1; - - 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); - } - - ecs_log_push_1(); - - if (flecs_query_cache_process_signature(world, impl, result)) { - goto error; - } - - /* 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]; - } - - 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; - } - - ecs_table_cache_init(world, &result->cache); - flecs_query_cache_match_tables(world, result); - - 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; - } - } - - if (entity) { - if (!flecs_query_cache_table_count(result) && result->query->term_count){ - ecs_add_id(world, entity, EcsEmpty); - } - } - - ecs_log_pop_1(); - - return result; -error: - return NULL; -} - -ecs_query_cache_table_t* flecs_query_cache_get_table( - ecs_query_cache_t *cache, - ecs_table_t *table) -{ - return ecs_table_cache_get(&cache->cache, table); -} - -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(qit->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); - - ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( - cache, group_id); - if (!node) { - qit->node = NULL; - qit->last = NULL; - return; - } - - ecs_query_cache_table_match_t *first = node->first; - if (first) { - qit->node = node->first; - qit->last = node->last; - } else { - qit->node = NULL; - qit->last = NULL; - } - -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_table_list_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; - } -} - -/** - * @file query/engine/cache_iter.c - * @brief Compile query term. - */ - - -static -void flecs_query_update_node_up_trs( - const ecs_query_run_ctx_t *ctx, - ecs_query_cache_table_match_t *node) -{ - ecs_termset_t fields = node->up_fields & node->set_fields; - if (fields) { - const ecs_query_impl_t *impl = ctx->query; - const ecs_query_t *q = &impl->pub; - ecs_query_cache_t *cache = impl->cache; - int8_t *field_map = cache->field_map; - int32_t i, field_count = q->field_count; - for (i = 0; i < field_count; i ++) { - if (!(fields & (1llu << i))) { - continue; - } - - ecs_entity_t src = node->sources[i]; - if (src) { - const ecs_table_record_t *tr = node->trs[i]; - 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 != tr->hdr.table) { - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert(idr->id == q->ids[field_map ? field_map[i] : i], - ECS_INTERNAL_ERROR, NULL); - tr = node->trs[i] = flecs_id_record_get_table(idr, r->table); - if (field_map) { - ctx->it->trs[field_map[i]] = tr; - } - } - } - } - } -} - -static -ecs_query_cache_table_match_t* flecs_query_cache_next( - const ecs_query_run_ctx_t *ctx, - bool match_empty) -{ - ecs_iter_t *it = ctx->it; - ecs_query_iter_t *qit = &it->priv_.iter.query; - - repeat: { - ecs_query_cache_table_match_t *node = qit->node; - ecs_query_cache_table_match_t *prev = qit->prev; - - if (prev != qit->last) { - ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); - ctx->vars[0].range.table = node->table; - it->group_id = node->group_id; - qit->node = node->next; - qit->prev = node; - if (node) { - if (!ecs_table_count(node->table)) { - if (!match_empty) { - if (ctx->query->pub.flags & EcsQueryHasMonitor) { - flecs_query_sync_match_monitor( - flecs_query_impl(qit->query), node); - } - goto repeat; - } - } - } - return node; - } - } - - return NULL; -} - -static -ecs_query_cache_table_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; - } - - ecs_query_iter_t *qit = &it->priv_.iter.query; - qit->prev = NULL; - qit->node = qt->first; - qit->last = qt->last; - } - - return flecs_query_cache_next(ctx, true); -} - -static -void flecs_query_cache_init_mapped_fields( - const ecs_query_run_ctx_t *ctx, - ecs_query_cache_table_match_t *node) -{ - ecs_iter_t *it = ctx->it; - const ecs_query_impl_t *impl = ctx->query; - ecs_query_cache_t *cache = impl->cache; - 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->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->set_fields & bit); - ECS_TERMSET_COND(it->up_fields, field_bit, node->up_fields & bit); - } -} - -/* Iterate cache for query that's partially cached */ -bool flecs_query_cache_search( - const ecs_query_run_ctx_t *ctx) -{ - ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx, - ctx->query->pub.flags & EcsQueryMatchEmptyTables); - if (!node) { - return false; - } - - 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; -} - -/* Iterate cache for query that's entirely cached */ -bool flecs_query_is_cache_search( - const ecs_query_run_ctx_t *ctx) -{ - ecs_query_cache_table_match_t *node = flecs_query_cache_next(ctx, - ctx->query->pub.flags & EcsQueryMatchEmptyTables); - if (!node) { - return false; - } - - ecs_iter_t *it = ctx->it; - it->trs = node->trs; - it->ids = node->ids; - it->sources = node->sources; - it->set_fields = node->set_fields; - it->up_fields = node->up_fields; - - flecs_query_update_node_up_trs(ctx, node); - - return true; -} - -/* Test if query that is entirely cached matches constrained $this */ -bool flecs_query_cache_test( - const ecs_query_run_ctx_t *ctx, - bool redo) -{ - ecs_query_cache_table_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; -} - -/* 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_query_cache_table_match_t *node = flecs_query_test(ctx, redo); - if (!node) { - return false; - } - - ecs_iter_t *it = ctx->it; - it->trs = node->trs; - it->ids = node->ids; - it->sources = node->sources; - - flecs_query_update_node_up_trs(ctx, node); - - return true; -} - -/** - * @file query/engine/cache_order_by.c - * @brief Order by implementation - */ - - -ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_cache_sort_table_generic, order_by, static) - -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; - } - - 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_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; - -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); - } -} - -static -ecs_entity_t e_from_helper( - sort_helper_t *helper) -{ - if (helper->row < helper->count) { - return helper->entities[helper->row]; - } else { - return 0; - } -} - -static -void flecs_query_cache_build_sorted_table_range( - ecs_query_cache_t *cache, - ecs_query_cache_table_list_t *list) -{ - 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"); - - ecs_entity_t id = cache->order_by; - ecs_order_by_action_t compare = cache->order_by_callback; - int32_t table_count = list->info.table_count; - if (!table_count) { - return; - } - - ecs_vec_init_if_t(&cache->table_slices, ecs_query_cache_table_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); - ecs_query_cache_table_match_t *cur, *end = list->last->next; - for (cur = list->first; cur != end; cur = cur->next) { - ecs_table_t *table = cur->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 = cur->sources[field]; - if (src == 0) { - int32_t column_index = cur->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; - } - - helper[to_sort].match = cur; - helper[to_sort].entities = table->data.entities; - helper[to_sort].row = 0; - helper[to_sort].count = ecs_table_count(table); - to_sort ++; - } - - if (!to_sort) { - goto done; - } - - 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; - } - } - - 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]); - } - } - - sort_helper_t *cur_helper = &helper[min]; - if (!cur || cur->trs != cur_helper->match->trs) { - cur = ecs_vec_append_t(NULL, &cache->table_slices, - ecs_query_cache_table_match_t); - *cur = *(cur_helper->match); - cur->offset = cur_helper->row; - cur->count = 1; - } else { - cur->count ++; - } - - cur_helper->row ++; - } while (proceed); - - /* 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_vec_count(&cache->table_slices); - ecs_query_cache_table_match_t *nodes = ecs_vec_first(&cache->table_slices); - for (i = 0; i < count; i ++) { - nodes[i].prev = &nodes[i - 1]; - nodes[i].next = &nodes[i + 1]; - } - - if (nodes) { - nodes[0].prev = NULL; - nodes[i - 1].next = NULL; - } - -done: - flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); -} - -void flecs_query_cache_build_sorted_tables( - ecs_query_cache_t *cache) -{ - ecs_vec_clear(&cache->table_slices); - - if (cache->group_by_callback) { - /* Populate sorted node list in grouping order */ - ecs_query_cache_table_match_t *cur = cache->list.first; - if (cur) { - do { - /* Find list for current group */ - uint64_t group_id = cur->group_id; - ecs_query_cache_table_list_t *list = ecs_map_get_deref( - &cache->groups, ecs_query_cache_table_list_t, group_id); - ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Sort tables in current group */ - flecs_query_cache_build_sorted_table_range(cache, list); - - /* Find next group to sort */ - cur = list->last->next; - } while (cur); - } - } else { - flecs_query_cache_build_sorted_table_range(cache, &cache->list); - } -} - -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; - } - - 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; - - /* Iterate over non-empty tables. Don't bother with empty tables as they - * have nothing to sort */ - - bool tables_sorted = false; - - ecs_id_record_t *idr = flecs_id_record_get(world, order_by); - ecs_table_cache_iter_t it; - ecs_query_cache_table_t *qt; - flecs_table_cache_all_iter(&cache->cache, &it); - - while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { - ecs_table_t *table = qt->hdr.table; - bool dirty = false; - - if (flecs_query_check_table_monitor(impl, qt, 0)) { - tables_sorted = true; - dirty = true; - - 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. */ - ecs_query_cache_table_match_t *cur, *next; - for (cur = qt->first; cur != NULL; cur = next) { - flecs_query_sync_match_monitor(impl, cur); - next = cur->next_match; - } - } - } - - int32_t column = -1; - if (order_by) { - if (flecs_query_check_table_monitor(impl, qt, order_by_term + 1)) { - dirty = true; - } - - if (dirty) { - column = -1; - - const ecs_table_record_t *tr = flecs_id_record_get_table( - idr, table); - if (tr) { - column = tr->column; - } - - 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; - } - - 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 */ - } -} - -/** - * @file query/engine/change_detection.c - * @brief Compile query term. - */ - - -typedef struct { - ecs_table_t *table; - int32_t column; -} flecs_table_column_t; - -static -void flecs_query_get_column_for_field( - const ecs_query_t *q, - ecs_query_cache_table_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; - - const ecs_table_record_t *tr = match->trs[field]; - ecs_table_t *table = tr->hdr.table; - int32_t column = tr->column; - - 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 -bool flecs_query_get_match_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - if (match->monitor) { - return false; - } - - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *monitor = flecs_balloc(&cache->allocators.monitors); - monitor[0] = 0; - - /* 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; - - for (i = 0; i < term_count; i ++) { - if (field == q->terms[i].field_index) { - if (monitor[field + 1] != -1) { - continue; - } - } - - field = q->terms[i].field_index; - monitor[field + 1] = -1; - - /* 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; - } - - /* Don't track fields that aren't set */ - if (!(match->set_fields & (1llu << field))) { - continue; - } - - flecs_query_get_column_for_field(q, match, field, &tc); - if (tc.column == -1) { - continue; /* Don't track terms that aren't stored */ - } - - monitor[field + 1] = 0; - } - - match->monitor = monitor; - - impl->pub.flags |= EcsQueryHasMonitor; - - 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 -bool flecs_query_get_fixed_monitor( - ecs_query_impl_t *impl, - bool check) -{ - 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 (!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 */ - } - - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - int16_t field_index = term->field_index; - - if (!(q->read_fields & flecs_ito(uint32_t, 1 << field_index))) { - continue; /* If term doesn't read data there's nothing to track */ - } - - if (!(term->src.id & EcsIsEntity)) { - continue; /* Not a term with a fixed source */ - } - - 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_id_record_t *idr = flecs_id_record_get(world, term->id); - if (!idr) { - continue; /* If id doesn't exist, entity can't have it */ - } - - ecs_table_record_t *tr = flecs_id_record_get_table(idr, r->table); - if (!tr) { - continue; /* Entity doesn't have the component */ - } - - /* 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 (!check) { - impl->monitor[field_index] = dirty_state[tr->column + 1]; - } else { - if (impl->monitor[field_index] != dirty_state[tr->column + 1]) { - return true; - } - } - } - - return !check; -} - -bool flecs_query_update_fixed_monitor( - ecs_query_impl_t *impl) -{ - return flecs_query_get_fixed_monitor(impl, false); -} - -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 -bool flecs_query_check_match_monitor_term( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match, - int32_t field) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - - if (flecs_query_get_match_monitor(impl, match)) { - return true; - } - - int32_t *monitor = match->monitor; - int32_t state = monitor[field]; - if (state == -1) { - return false; - } - - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = match->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]; -} - -static -bool flecs_query_check_cache_monitor( - ecs_query_impl_t *impl) -{ - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - - /* 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; - } - - ecs_table_cache_iter_t it; - if (flecs_table_cache_all_iter(&cache->cache, &it)) { - ecs_query_cache_table_t *qt; - while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { - if (flecs_query_check_table_monitor(impl, qt, -1)) { - return true; - } - } - } - - return false; -} - -static -void flecs_query_init_query_monitors( - ecs_query_impl_t *impl) -{ - /* Change monitor for cache */ - ecs_query_cache_t *cache = impl->cache; - if (cache) { - ecs_query_cache_table_match_t *cur = cache->list.first; - - /* Ensure each match has a monitor */ - for (; cur != NULL; cur = cur->next) { - ecs_query_cache_table_match_t *match = - (ecs_query_cache_table_match_t*)cur; - flecs_query_get_match_monitor(impl, match); - } - } -} - -static -bool flecs_query_check_match_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match, - const ecs_iter_t *it) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - - if (flecs_query_get_match_monitor(impl, match)) { - return true; - } - - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *monitor = match->monitor; - ecs_table_t *table = match->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; - } - } - - const ecs_query_t *query = cache->query; - ecs_world_t *world = query->world; - int32_t i, field_count = query->field_count; - ecs_entity_t *sources = match->sources; - const ecs_table_record_t **trs = it ? it->trs : match->trs; - ecs_flags64_t set_fields = it ? it->set_fields : match->set_fields; - - 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[i]; - 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 */ - } - } - - /* Component from non-this source */ - 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 false; -} - -/* Check if any term for matched table has changed */ -bool flecs_query_check_table_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_t *table, - int32_t field) -{ - ecs_query_cache_table_match_t *cur, *end = table->last->next; - - for (cur = table->first; cur != end; cur = cur->next) { - ecs_query_cache_table_match_t *match = - (ecs_query_cache_table_match_t*)cur; - if (field == -1) { - if (flecs_query_check_match_monitor(impl, match, NULL)) { - return true; - } - } else { - if (flecs_query_check_match_monitor_term(impl, match, field)) { - return true; - } - } - } - - return false; -} - -void flecs_query_mark_fields_dirty( - ecs_query_impl_t *impl, - ecs_iter_t *it) -{ - ecs_query_t *q = &impl->pub; - - /* 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; - } - - 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 */ - } - - 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; - } - - 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; - } - } - - 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; - } - - ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); - int32_t column = table->column_map[type_index]; - dirty_state[column + 1] ++; - } -} - -void flecs_query_mark_fixed_fields_dirty( - ecs_query_impl_t *impl, - ecs_iter_t *it) -{ - /* 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_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 */ - } - - 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; - } - - int32_t *dirty_state = table->dirty_state; - if (!dirty_state) { - continue; - } - - 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] ++; - } -} - -/* Synchronize match monitor with table dirty state */ -void flecs_query_sync_match_monitor( - ecs_query_impl_t *impl, - ecs_query_cache_table_match_t *match) -{ - ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); - - if (!match->monitor) { - if (impl->pub.flags & EcsQueryHasMonitor) { - flecs_query_get_match_monitor(impl, match); - } else { - return; - } - } - - ecs_query_cache_t *cache = impl->cache; - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t *monitor = match->monitor; - ecs_table_t *table = match->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_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; - } - - flecs_query_get_column_for_field(q, match, field, &tc); - - /* Query for cache should never point to stage */ - ecs_assert(q->world == q->real_world, ECS_INTERNAL_ERROR, NULL); - - monitor[field + 1] = flecs_table_get_dirty_state( - q->world, tc.table)[tc.column + 1]; - } - } - - cache->prev_match_count = cache->match_count; -} - -bool ecs_query_changed( - ecs_query_t *q) -{ - flecs_poly_assert(q, ecs_query_t); - ecs_query_impl_t *impl = flecs_query_impl(q); - - ecs_assert(q->cache_kind != EcsQueryCacheNone, ECS_INVALID_OPERATION, - "change detection is only supported on cached queries"); - - /* 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; - } - } - - /* 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 & EcsQueryHasMonitor)) { - flecs_query_init_query_monitors(impl); - } - - /* Check cache entries for changes */ - return flecs_query_check_cache_monitor(impl); - } - - return false; -} - -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); - - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_impl_t *impl = flecs_query_impl(qit->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)); - } - - if (it->flags & EcsIterFixedInChanged) { - return true; - } - } - - /* If query has a cache, check for changes in current matched result */ - if (impl->cache) { - ecs_query_cache_table_match_t *qm = - (ecs_query_cache_table_match_t*)it->priv_.iter.query.prev; - ecs_check(qm != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs_query_check_match_monitor(impl, qm, it); - } - -error: - return false; -} - -void ecs_iter_skip( - ecs_iter_t *it) -{ - 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; -} - -/** - * @file query/engine/eval.c - * @brief Query engine implementation. - */ - - -// #define FLECS_QUERY_TRACE - -#ifdef FLECS_QUERY_TRACE -static int flecs_query_trace_indent = 0; -#endif - -static -bool flecs_query_dispatch( - const ecs_query_op_t *op, - bool redo, - 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) -{ - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - ecs_id_record_t *idr = op_ctx->idr; - ecs_table_record_t *tr; - ecs_table_t *table; - - if (!redo) { - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; - } - } - - if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { - return false; - } - } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { - return false; - } - } - } - -repeat: - if (!redo || !op_ctx->remaining) { - 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 = (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, idr->id, op_ctx->column); - op_ctx->remaining --; - } - - if (flecs_query_table_filter(table, op->other, filter_mask)) { - goto repeat; - } - - flecs_query_set_match(op, table, op_ctx->column, ctx); - return true; -} - -bool flecs_query_select( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - 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)); -} - -bool flecs_query_with( - 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); - ecs_id_record_t *idr = op_ctx->idr; - ecs_table_record_t *tr; - - ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); - if (!table) { - return false; - } - - if (!redo) { - ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; - } - } - - tr = flecs_id_record_get_table(idr, 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 { - 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; - } - - op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); - ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); - } - - flecs_query_set_match(op, table, op_ctx->column, ctx); - return true; -} - -static -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); - } -} - -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 (!redo) { - ecs_id_t id = it->ids[field]; - ecs_id_record_t *idr = op_ctx->idr; - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; - } - } - - if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { - return false; - } - } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { - return false; - } - } - } - -repeat: {} - const ecs_table_record_t *tr = flecs_table_cache_next( - &op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } - - 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; -} - -bool flecs_query_with_id( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - if (redo) { - return false; - } - - 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); - - ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); - if (!table) { - return false; - } - - ecs_id_t id = it->ids[field]; - ecs_id_record_t *idr = op_ctx->idr; - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { - return false; - } - } - - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return false; - } - - flecs_query_it_set_tr(it, field, tr); - return true; -} - -static -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); - } -} - -static -bool flecs_query_self_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_self_up_with(op, redo, ctx, false); - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectDefault); - } -} - -static -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; - } - } - - 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; - } - - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - - if (match_flags & EcsTermMatchAny && op_ctx->remaining) { - op_ctx->remaining = flecs_ito(int16_t, remaining); - } - - int32_t field = op->field_index; - if (field != -1) { - ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); - } - - ctx->it->trs[field] = (ecs_table_record_t*)op_ctx->it.cur; - - return result; -} - -static -bool flecs_query_only_any( - 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_and_any(op, redo, ctx); - } else { - return flecs_query_select_w_id(op, redo, ctx, EcsAny, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); - } -} - -static -bool flecs_query_triv( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - 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); - } -} - -static -bool flecs_query_cache( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - (void)op; - (void)redo; - - 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); - } -} - -static -bool flecs_query_is_cache( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - (void)op; - - 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); - } -} - -static -int32_t flecs_query_next_inheritable_id( - ecs_world_t *world, - ecs_type_t *type, - int32_t index) -{ - int32_t i; - for (i = index; i < type->count; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, type->array[i]); - if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { - return i; - } - } - return -1; -} - -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_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; - - 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_table_t *table; - if (!r || !(table = r->table)) { - /* Nothing to match */ - return false; - } - - /* 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; - - 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; - } - - ecs_id_t *ids = type->array; - - /* 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); - } - - do { - int32_t id_index = op_ctx->cur_id_index; - - /* If source is not yet written, find tables with first id */ - if (!src_written) { - ecs_entity_t first_id = ids[id_index]; - - 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; - } - } - - return false; - } - - id_index ++; /* First id got matched */ - } else if (redo && src_written) { - return false; - } - - ecs_table_t *src_table = flecs_query_get_table( - op, &op->src, EcsQuerySrc, ctx); - if (!src_table) { - continue; - } - - redo = true; - - 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_id_record_t *idr = flecs_id_record_get(world, ids[i]); - if (!idr) { - continue; - } - - if (idr->flags & EcsIdOnInstantiateDontInherit) { - continue; - } - - if (flecs_id_record_get_table(idr, src_table) != NULL) { - /* Already matched */ - break; - } - } - if (i != op_ctx->cur_id_index) { - continue; - } - } - goto match; - } - - if (oper == EcsAndFrom || oper == EcsNotFrom || src_written) { - for (i = id_index; i < type->count; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); - if (!idr) { - if (oper == EcsAndFrom) { - return false; - } else { - continue; - } - } - - if (idr->flags & EcsIdOnInstantiateDontInherit) { - continue; - } - - if (flecs_id_record_get_table(idr, 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 */ - } - } - } - - if (i == type->count) { - if (oper == EcsAndFrom || oper == EcsNotFrom) { - break; /* All ids matched */ - } - } - } - } while (true); - -match: - ctx->it->ids[op->field_index] = type_id; - return true; -} - -static -bool flecs_query_and_from( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - return flecs_query_x_from(op, redo, ctx, EcsAndFrom); -} - -static -bool flecs_query_not_from( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - return flecs_query_x_from(op, redo, ctx, EcsNotFrom); -} - -static -bool flecs_query_or_from( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - return flecs_query_x_from(op, redo, ctx, EcsOrFrom); -} - -static -bool flecs_query_ids( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - if (redo) { - return false; - } - - ecs_id_record_t *cur; - ecs_id_t id = flecs_query_op_get_id(op, ctx); - - { - cur = flecs_id_record_get(ctx->world, id); - if (!cur || !cur->cache.tables.count) { - return false; - } - } - - flecs_query_set_vars(op, cur->id, ctx); - - 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 true; -} - -static -bool flecs_query_idsright( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); - ecs_id_record_t *cur; - - 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; - } - - cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); - if (!cur) { - return false; - } - } else { - if (!op_ctx->cur) { - return false; - } - } - - do { - cur = op_ctx->cur = op_ctx->cur->first.next; - } while (cur && !cur->cache.tables.count); /* Skip empty ids */ - - if (!cur) { - return false; - } - - flecs_query_set_vars(op, cur->id, ctx); - - 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; -} - -static -bool flecs_query_idsleft( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); - ecs_id_record_t *cur; - - 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; - } - - cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); - if (!cur) { - return false; - } - } else { - if (!op_ctx->cur) { - return false; - } - } - - do { - cur = op_ctx->cur = op_ctx->cur->second.next; - } while (cur && !cur->cache.tables.count); /* Skip empty ids */ - - if (!cur) { - return false; - } - - flecs_query_set_vars(op, cur->id, ctx); - - 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; -} - -static -bool flecs_query_each( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - ecs_query_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); - int32_t row; - - ecs_table_range_t range = flecs_query_var_get_range(op->first.var, ctx); - ecs_table_t *table = range.table; - if (!table) { - return false; - } - - if (!redo) { - if (!ecs_table_count(table)) { - return false; - } - 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; -} - -static -bool flecs_query_store( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - if (!redo) { - flecs_query_var_set_entity(op, op->src.var, op->first.entity, ctx); - return true; - } else { - return false; - } -} - -static -bool flecs_query_reset( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - if (!redo) { - return true; - } else { - flecs_query_var_reset(op->src.var, ctx); - return false; - } -} - -static -bool flecs_query_lookup( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - if (redo) { - return false; - } - - 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; - } - - flecs_query_var_set_entity(op, op->src.var, result, ctx); - - return true; -} - -static -bool flecs_query_setvars( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - (void)op; - - 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; - - if (redo) { - return false; - } - - 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; - } - - if (up_fields & (1u << i)) { - continue; - } - - it->sources[i] = flecs_query_var_get_entity(var_id, ctx); - } - - return true; -} - -static -bool flecs_query_setthis( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - 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]; - - if (!redo) { - /* Save values so we can restore them later */ - op_ctx->range = vars[0].range; - - /* Constrain This table variable to a single entity from the table */ - vars[0].range = flecs_range_from_entity(this_var->entity, ctx); - 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; - } -} - -static -bool flecs_query_setfixed( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - (void)op; - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; - ecs_iter_t *it = ctx->it; - - if (redo) { - return false; - } - - 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); - } - } - - return true; -} - -bool flecs_query_setids( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - (void)op; - const ecs_query_impl_t *query = ctx->query; - const ecs_query_t *q = &query->pub; - ecs_iter_t *it = ctx->it; - - if (redo) { - return false; - } - - 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 true; -} - -static -bool flecs_query_setid( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - if (redo) { - return false; - } - - ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); - ctx->it->ids[op->field_index] = op->first.entity; - return true; -} - -/* 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) -{ - if (redo) { - return false; - } - - 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); -} - -/* 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) -{ - 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); -} - -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_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; - } - - /* 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); - - /* 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); - - /* Overwrite id with cleared out variables */ - ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (id) { - it->ids[field] = id; - } - - 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; - } - -done: - op_ctx->op_index = op_index; -} - -static -bool flecs_query_run_block( - bool redo, - ecs_query_run_ctx_t *ctx, - ecs_query_ctrl_ctx_t *op_ctx) -{ - 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; - } - - ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; - - 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; -} - -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) -{ - const ecs_query_op_t *cur_op, *last_op = &ops[last]; - - do { - cur_op = &ops[cur]; - cur ++; - } while (cur_op->next != last && cur_op != last_op); - - return cur; -} - -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_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; -} - -static -bool flecs_query_select_or( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - 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_query_lbl_t first = flecs_itolbl(ctx->op_index + 1); - if (!redo) { - op_ctx->op_index = first; - } - - 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; - - 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]; - } - } - - 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]; - - flecs_query_run_until(false, ctx, ops, flecs_itolbl(first - 1), - prev, cur); - - 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 (dup_found) { - continue; - } - - /* Restore id in case op set it */ - it->ids[field_index] = prev_id; - it->trs[field_index] = prev_tr; - break; - } - - /* 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); - - return result; -} - -static -bool flecs_query_with_or( - 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); - - 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; - } - - return result; -} - -static -bool flecs_query_or( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - 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); - } - } - - return flecs_query_with_or(op, redo, ctx); -} - -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); - - bool result = flecs_query_run_block(redo, ctx, op_ctx); - flecs_query_reset_after_block(op, ctx, op_ctx, result); - return result; -} - -static -bool flecs_query_not( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - if (redo) { - return false; - } - - return !flecs_query_run_block_w_reset(op, redo, ctx); -} - -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); - if (!redo) { - return true; /* Return at least once */ - } else { - return result; - } -} - -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) -{ - 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; -} - -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_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); - return flecs_query_run_block(redo, ctx, op_ctx); -} - -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_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 (!op_ctx->is_set) { - return !redo; - } - - return flecs_query_run_block(redo, ctx, op_ctx); -} - -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; -} - -static -bool flecs_query_dispatch( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - switch(op->kind) { - case EcsQueryAnd: return flecs_query_and(op, redo, ctx); - case EcsQueryAndAny: return flecs_query_and_any(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 EcsQueryOnlyAny: return flecs_query_only_any(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 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 EcsQueryUnionEq: return flecs_query_union(op, redo, ctx); - case EcsQueryUnionEqWith: return flecs_query_union_with(op, redo, ctx, false); - case EcsQueryUnionNeq: return flecs_query_union_neq(op, redo, ctx); - case EcsQueryUnionEqUp: return flecs_query_union_up(op, redo, ctx); - case EcsQueryUnionEqSelfUp: return flecs_query_union_self_up(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; - } - return false; -} - -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_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); - -#ifdef FLECS_QUERY_TRACE - printf("%*sblock:\n", flecs_query_trace_indent*2, ""); - flecs_query_trace_indent ++; -#endif - - do { - #ifdef FLECS_DEBUG - ctx->qit->profile[ctx->op_index].count[redo] ++; - #endif - -#ifdef FLECS_QUERY_TRACE - printf("%*s%d: %s\n", flecs_query_trace_indent*2, "", - ctx->op_index, flecs_query_op_str(op->kind)); -#endif - - 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 -void flecs_query_iter_run_ctx_init( - ecs_iter_t *it, - ecs_query_run_ctx_t *ctx) -{ - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->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; -} - -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; - } - - /* Initialize table variable with constrained entity variable */ - ecs_var_t *tvar = &vars[var->table_id]; - tvar->range = flecs_range_from_entity(vars[var_id].entity, &ctx); - ctx.written[0] |= (1ull << var->table_id); /* Mark as written */ - } - } - - /* 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_query_cache_t *cache = query->cache; - if (flags & EcsQueryIsTrivial) { - if ((flags & EcsQueryMatchOnlySelf)) { - 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 & EcsQueryMatchWildcards)) { - it->flags |= EcsIterTrivialTest; - } - } else if (flags & EcsQueryIsCacheable) { - it->flags |= EcsIterTrivialTest|EcsIterTrivialCached; - } - } else { - if (!cache) { - if (!(flags & EcsQueryMatchWildcards)) { - it->flags |= EcsIterTrivialSearch; - } - } else if (flags & EcsQueryIsCacheable) { - if (!cache->order_by_callback) { - it->flags |= EcsIterTrivialSearch|EcsIterTrivialCached; - } - } - } - - /* If we're using a specialized iterator mode, make sure to - * initialize static component ids. Usually this is the first - * instruction of a query plan, but because we're not running the - * query plan when using a specialized iterator mode, manually call - * the operation on iterator init. */ - flecs_query_setids(NULL, false, &ctx); - } - } -} - -bool ecs_query_next( - ecs_iter_t *it) -{ - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(it->next == ecs_query_next, 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*, qit->query); - ecs_query_run_ctx_t ctx; - flecs_query_iter_run_ctx_init(it, &ctx); - const ecs_query_op_t *ops = qit->ops; - - bool redo = it->flags & EcsIterIsValid; - if (redo) { - /* Change detection */ - if (!(it->flags & EcsIterSkip)) { - /* Mark table columns that are written to dirty */ - flecs_query_mark_fields_dirty(impl, it); - if (qit->prev) { - if (ctx.query->pub.flags & EcsQueryHasMonitor) { - /* If this query uses change detection, synchronize the - * monitor for the iterated table with the query */ - flecs_query_sync_match_monitor(impl, qit->prev); - } - } - } - } - - 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) { - /* Cached iterator modes */ - if (it->flags & EcsIterTrivialSearch) { - if (flecs_query_is_cache_search(&ctx)) { - goto trivial_search_yield; - } - } else if (it->flags & EcsIterTrivialTest) { - if (flecs_query_is_cache_test(&ctx, redo)) { - goto yield; - } - } - } else { - /* Uncached iterator modes */ - if (it->flags & EcsIterTrivialSearch) { - 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) { - 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 { - /* 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; - } - } - } - - /* 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)); - } - - ecs_iter_fini(it); - return false; - -trivial_search_yield: - it->table = ctx.vars[0].range.table; - it->count = ecs_table_count(it->table); - it->entities = ecs_table_entities(it->table); - -yield: - return true; -} - -static -void flecs_query_iter_fini_ctx( - ecs_iter_t *it, - ecs_query_iter_t *qit) -{ - const ecs_query_impl_t *query = flecs_query_impl(qit->query); - int32_t i, count = query->op_count; - ecs_query_op_t *ops = query->ops; - ecs_query_op_ctx_t *ctx = qit->op_ctx; - ecs_allocator_t *a = flecs_query_get_allocator(it); - - for (i = 0; i < count; i ++) { - ecs_query_op_t *op = &ops[i]; - switch(op->kind) { - case EcsQueryTrav: - flecs_query_trav_cache_fini(a, &ctx[i].is.trav.cache); - break; - case EcsQueryUp: - case EcsQuerySelfUp: - case EcsQueryUnionEqUp: - case EcsQueryUnionEqSelfUp: { - ecs_trav_up_cache_t *cache = &ctx[i].is.up.cache; - if (cache->dir == EcsTravDown) { - flecs_query_down_cache_fini(a, cache); - } else { - flecs_query_up_cache_fini(cache); - } - break; - } - default: - break; - } - } -} - -static -void flecs_query_iter_fini( - ecs_iter_t *it) -{ - ecs_query_iter_t *qit = &it->priv_.iter.query; - ecs_assert(qit->query != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_poly_assert(qit->query, ecs_query_t); - int32_t op_count = flecs_query_impl(qit->query)->op_count; - int32_t var_count = flecs_query_impl(qit->query)->var_count; - -#ifdef FLECS_DEBUG - if (it->flags & EcsIterProfile) { - char *str = ecs_query_plan_w_profile(qit->query, it); - printf("%s\n", str); - ecs_os_free(str); - } - - flecs_iter_free_n(qit->profile, ecs_query_op_profile_t, op_count); -#endif - - 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); - - qit->vars = NULL; - qit->written = NULL; - qit->op_ctx = NULL; - qit->query = NULL; -} - -ecs_iter_t flecs_query_iter( - const ecs_world_t *world, - const ecs_query_t *q) -{ - ecs_iter_t it = {0}; - ecs_query_iter_t *qit = &it.priv_.iter.query; - ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); - - flecs_poly_assert(q, ecs_query_t); - ecs_query_impl_t *impl = flecs_query_impl(q); - - 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 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); - } - - 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"); - - 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); - - flecs_iter_init(it.world, &it, - flecs_iter_cache_ids | - flecs_iter_cache_trs | - flecs_iter_cache_sources | - flecs_iter_cache_ptrs); - - qit->query = q; - qit->query_vars = impl->vars; - qit->ops = impl->ops; - - ecs_query_cache_t *cache = impl->cache; - if (cache) { - qit->node = cache->list.first; - qit->last = cache->list.last; - - if (cache->order_by_callback && cache->list.info.table_count) { - flecs_query_cache_sort_tables(it.real_world, impl); - if (ecs_vec_count(&cache->table_slices)) { - qit->node = ecs_vec_first(&cache->table_slices); - qit->last = ecs_vec_last_t( - &cache->table_slices, ecs_query_cache_table_match_t); - } - } - - cache->prev_match_count = cache->match_count; - } - - if (var_count) { - qit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); - } - - 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); - } - -#ifdef FLECS_DEBUG - qit->profile = flecs_iter_calloc_n(&it, ecs_query_op_profile_t, op_count); -#endif - - for (i = 1; i < var_count; i ++) { - qit->vars[i].entity = EcsWildcard; - } - - it.variables = qit->vars; - it.variable_count = impl->pub.var_count; - it.variable_names = impl->pub.vars; - - /* Set flags for unconstrained query iteration. Can be reinitialized when - * variables are constrained on iterator. */ - flecs_query_iter_constrain(&it); -error: - return it; -} - -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 flecs_query_iter(world, q); -} - -/** - * @file query/engine/eval_member.c - * @brief Component member evaluation. - */ - - -static -bool flecs_query_member_cmp( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx, - bool neq) -{ - 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); - } - - ecs_table_t *table = range.table; - if (!table) { - return false; - } - - 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 (!range.count) { - range.count = ecs_table_count(range.table); - } - - int32_t row, end = range.count; - if (end) { - end += range.offset; - } else { - end = ecs_table_count(range.table); - } - - 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; - } - - 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; - - 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); - - 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 (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); - } - -match: - if (op->other) { - ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); - flecs_query_var_set_entity(op, op->src.var, e, ctx); - } - - 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 flecs_query_member_eq( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - return flecs_query_member_cmp(op, redo, ctx, false); -} - -bool flecs_query_member_neq( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - return flecs_query_member_cmp(op, redo, ctx, true); -} - -/** - * @file query/engine/eval_pred.c - * @brief Equality predicate evaluation. - */ - - -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; -} - -static -bool flecs_query_compare_range( - const ecs_table_range_t *l, - const ecs_table_range_t *r) -{ - if (l->table != r->table) { - return false; - } - - 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 */ - } - - return true; -} - -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; - } - - 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 { - ecs_table_range_t l = flecs_query_get_range( - op, &op->src, EcsQuerySrc, ctx); - - if (!flecs_query_compare_range(&l, &r)) { - return false; - } - - ctx->vars[src_var].range.offset = r.offset; - ctx->vars[src_var].range.count = r.count; - return true; - } -} - -bool flecs_query_pred_eq( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - 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 flecs_query_pred_eq_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 false; - } - - ecs_table_range_t r = flecs_range_from_entity(e, ctx); - return flecs_query_pred_eq_w_range(op, redo, ctx, r); -} - -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) -{ - 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; - } - - 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; - } -} - -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 (!l.count) { - l.count = ecs_table_count(l.table); - } - - 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 flecs_query_pred_eq_match( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - return flecs_query_pred_match(op, redo, ctx, false); -} - -bool flecs_query_pred_neq_match( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - return flecs_query_pred_match(op, redo, ctx, true); -} - -bool flecs_query_pred_neq( - const ecs_query_op_t *op, - bool redo, - ecs_query_run_ctx_t *ctx) -{ - 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"); - - 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; - } - - ecs_table_range_t r = flecs_range_from_entity(e, ctx); - return flecs_query_pred_neq_w_range(op, redo, ctx, r); -} - -/** - * @file query/engine/eval_toggle.c - * @brief Bitset toggle evaluation. - */ - - -typedef struct { - ecs_flags64_t mask; - bool has_bitset; -} flecs_query_row_mask_t; - -static -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 (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); - } - - 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 (flecs_query_row_mask_t){ mask, has_bitset }; -} - -static -bool flecs_query_toggle_for_up( - ecs_iter_t *it, - ecs_flags64_t and_fields, - ecs_flags64_t not_fields) -{ - int32_t i, field_count = it->field_count; - ecs_flags64_t fields = (and_fields | not_fields) & it->up_fields; - - for (i = 0; i < field_count; i ++) { - uint64_t field_bit = 1llu << i; - if (!(fields & field_bit)) { - continue; - } - - 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]); - } - - if (field_bit & not_fields) { - match = !match; - } - - if (!match) { - return false; - } - } - - return true; -} - -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) -{ - 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); - - 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; - } - - 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; - } - - 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 &= ~up_fields; - - 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; - } - } - } - - if (table && !range.count) { - range.count = ecs_table_count(table); - if (!range.count) { - return false; - } - } - - int32_t i, j; - int32_t first, 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; - first = range.offset; - last = range.offset + range.count; - } else { - if (!op_ctx->has_bitset) { - goto done; - } - - 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; - } - - first = cur; - block_index = op_ctx->block_index; - block = op_ctx->block; - } - - /* If end of last iteration is start of new block, compute new block */ - int32_t new_block_index = cur / 64, row = first; - if (new_block_index != block_index) { -compute_block: - block_index = op_ctx->block_index = new_block_index; - - flecs_query_row_mask_t row_mask = flecs_query_get_row_mask( - it, table, block_index, and_fields, not_fields, op_ctx); - - /* 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; - } - } - - /* 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; - } - - op_ctx->cur = cur; - goto compute_block; - } - - op_ctx->block = block; - } - - /* Find first enabled bit (TODO: use faster bitmagic) */ - 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); - - for (i = first_bit; i < last_bit; i ++) { - uint64_t bit = (1ull << i); - bool cond = 0 != (block & bit); - if (cond) { - /* Find last enabled bit */ - for (j = i; j < last_bit; j ++) { - bit = (1ull << j); - cond = !(block & bit); - if (cond) { - break; - } - } - - row = i + (block_index * 64); - cur = j + (block_index * 64); - break; - } - } - - if (i == last_bit) { - goto next_block; - } - - ecs_assert(row >= first, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); - ecs_assert(cur >= first, ECS_INTERNAL_ERROR, NULL); - - if (!(cur - row)) { - goto done; - } - - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, row, cur - row, ctx); - } - op_ctx->cur = cur; - - return true; - -done: - /* Restore range & set fields */ - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, - table, op_ctx->range.offset, op_ctx->range.count, ctx); - } - - it->set_fields = op_ctx->prev_set_fields; - return false; -} - -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; - } - - ecs_flags64_t and_fields = op->first.entity; - ecs_flags64_t not_fields = op->second.entity & op_ctx->prev_set_fields; - - return flecs_query_toggle_cmp( - op, redo, ctx, and_fields, not_fields); -} - -bool flecs_query_toggle_option( - 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; - op_ctx->optional_not = false; - op_ctx->has_bitset = false; - } - -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; - } - - 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; - } - } - - return result; -} - - -/** - * @file query/engine/eval_trav.c - * @brief Transitive/reflexive relationship traversal. - */ - - -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); - } - - 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; - } - } - 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]; - } - - flecs_query_set_trav_match(op, NULL, trav, second, ctx); - return true; -} - -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_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); - - 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_query_set_trav_match(op, NULL, trav, second, ctx); - return true; -} - -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_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; - - /* 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); - - if (!tr) { - if (op->match_flags & EcsTermReflexive) { - return flecs_query_trav_fixed_src_reflexive(op, ctx, - &range, trav, second); - } else { - return false; - } - } - - flecs_query_set_trav_match(op, tr, trav, second, ctx); - return true; -} - -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_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 (!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; - - /* 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); - } - - /* 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; - - 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); - } - } - - if (trav_ctx->index == -1) { - redo = false; /* First result after handling reflexive relationship */ - trav_ctx->index = 0; - } - - /* 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.idr = el->idr; /* 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 false; -} - -static -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_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); - - 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_entity_t entity = ecs_table_entities(range->table)[trav_ctx->index]; - flecs_query_set_trav_match(op, NULL, trav, entity, ctx); - - /* 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(entity, ctx); - } - - return true; -} - -static -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_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); - - 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 (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; - } - - if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { - return false; - } - - 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; -} - -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]; - - 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); - } - } 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); - } - } -} - -/** - * @file query/engine/eval_union.c - * @brief Union relationship evaluation. - */ - - -static -bool flecs_query_union_with_wildcard( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel, - bool neq) -{ - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; - - ecs_table_range_t range; - ecs_table_t *table; - if (!redo) { - range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); - table = range.table; - if (!range.count) { - range.count = ecs_table_count(table); - } - - op_ctx->range = range; - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return neq; - } - - if (neq) { - if (flecs_id_record_get_table(op_ctx->idr, table) != NULL) { - /* If table has (R, Union) none match !(R, _) */ - return false; - } else { - /* If table doesn't have (R, Union) all match !(R, _) */ - return true; - } - } - - op_ctx->row = 0; - } else { - if (neq) { - /* !(R, _) terms only can have a single result */ - return false; - } - - range = op_ctx->range; - table = range.table; - op_ctx->row ++; - } - -next_row: - if (op_ctx->row >= range.count) { - /* Restore range */ - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - op_ctx->range.offset, op_ctx->range.count, ctx); - } - return false; - } - - ecs_entity_t e = ecs_table_entities(range.table) - [range.offset + op_ctx->row]; - ecs_entity_t tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); - if (!tgt) { - op_ctx->row ++; - goto next_row; - } - - it->ids[field_index] = ecs_pair(rel, tgt); - - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - range.offset + op_ctx->row, 1, ctx); - } - flecs_query_set_vars(op, it->ids[field_index], ctx); - - return true; -} - -static -bool flecs_query_union_with_tgt( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel, - ecs_entity_t tgt, - bool neq) -{ - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; - - ecs_table_range_t range; - ecs_table_t *table; - if (!redo) { - range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); - table = range.table; - if (!range.count) { - range.count = ecs_table_count(table); - } - - op_ctx->range = range; - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return false; - } - - op_ctx->row = 0; - } else { - range = op_ctx->range; - table = range.table; - op_ctx->row ++; - } - -next_row: - if (op_ctx->row >= range.count) { - /* Restore range */ - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - op_ctx->range.offset, op_ctx->range.count, ctx); - } - return false; - } - - ecs_entity_t e = ecs_table_entities(range.table) - [range.offset + op_ctx->row]; - ecs_entity_t e_tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); - bool match = e_tgt == tgt; - if (neq) { - match = !match; - } - - if (!match) { - op_ctx->row ++; - goto next_row; - } - - it->ids[field_index] = ecs_pair(rel, tgt); - - if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { - flecs_query_var_narrow_range(op->src.var, table, - range.offset + op_ctx->row, 1, ctx); - } - - flecs_query_set_vars(op, it->ids[field_index], ctx); - - return true; -} - -bool flecs_query_union_with( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - bool neq) -{ - ecs_id_t id = flecs_query_op_get_id(op, ctx); - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t tgt = ecs_pair_second(ctx->world, id); - - if (tgt == EcsWildcard) { - return flecs_query_union_with_wildcard(op, redo, ctx, rel, neq); - } else { - return flecs_query_union_with_tgt(op, redo, ctx, rel, tgt, neq); - } -} - -static -bool flecs_query_union_select_tgt( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel, - ecs_entity_t tgt) -{ - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; - - if (!redo) { - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return false; - } - - op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, tgt); - } else { - op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); - } - - if (!op_ctx->cur) { - return false; - } - - ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); - flecs_query_var_set_range(op, op->src.var, - range.table, range.offset, range.count, ctx); - flecs_query_set_vars(op, it->ids[field_index], ctx); - - it->ids[field_index] = ecs_pair(rel, tgt); - - return true; -} - -static -bool flecs_query_union_select_wildcard( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx, - ecs_entity_t rel) -{ - ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); - ecs_iter_t *it = ctx->it; - int8_t field_index = op->field_index; - - if (!redo) { - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { - return false; - } - - op_ctx->tgt_iter = flecs_switch_targets(op_ctx->idr->sparse); - op_ctx->tgt = 0; - } - -next_tgt: - if (!op_ctx->tgt) { - if (!ecs_map_next(&op_ctx->tgt_iter)) { - return false; - } - - op_ctx->tgt = ecs_map_key(&op_ctx->tgt_iter); - op_ctx->cur = 0; - it->ids[field_index] = ecs_pair(rel, op_ctx->tgt); - } - - if (!op_ctx->cur) { - op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, op_ctx->tgt); - } else { - op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); - } - - if (!op_ctx->cur) { - op_ctx->tgt = 0; - goto next_tgt; - } - - ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); - flecs_query_var_set_range(op, op->src.var, - range.table, range.offset, range.count, ctx); - flecs_query_set_vars(op, it->ids[field_index], ctx); - - return true; -} - -bool flecs_query_union_select( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - ecs_id_t id = flecs_query_op_get_id(op, ctx); - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t tgt = ecs_pair_second(ctx->world, id); - - if (tgt == EcsWildcard) { - return flecs_query_union_select_wildcard(op, redo, ctx, rel); - } else { - return flecs_query_union_select_tgt(op, redo, ctx, rel, tgt); - } -} - -bool flecs_query_union( - 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_union_with(op, redo, ctx, false); - } else { - return flecs_query_union_select(op, redo, ctx); - } -} - -bool flecs_query_union_neq( - 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_union_with(op, redo, ctx, true); - } else { - return false; - } -} - -static -void flecs_query_union_set_shared( - const ecs_query_op_t *op, - const ecs_query_run_ctx_t *ctx) -{ - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_id_record_t *idr = op_ctx->idr_with; - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_entity_t rel = ECS_PAIR_FIRST(idr->id); - idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); - - int8_t field_index = op->field_index; - ecs_iter_t *it = ctx->it; - ecs_entity_t src = it->sources[field_index]; - ecs_entity_t tgt = flecs_switch_get(idr->sparse, (uint32_t)src); - - it->ids[field_index] = ecs_pair(rel, tgt); -} - -bool flecs_query_union_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) { - if (!flecs_query_up_with(op, redo, ctx)) { - return false; - } - - flecs_query_union_set_shared(op, ctx); - return true; - } else { - return false; - } - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectUp, FlecsQueryUpSelectUnion); - } -} - -bool flecs_query_union_self_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) { - goto next_for_union; - } - -next_for_self_up_with: - if (!flecs_query_self_up_with(op, redo, ctx, false)) { - return false; - } - - int8_t field_index = op->field_index; - ecs_iter_t *it = ctx->it; - if (it->sources[field_index]) { - flecs_query_union_set_shared(op, ctx); - return true; - } - -next_for_union: - if (!flecs_query_union_with(op, redo, ctx, false)) { - goto next_for_self_up_with; - } - - return true; - } else { - return flecs_query_up_select(op, redo, ctx, - FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectUnion); - } -} - -/** - * @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_iter_t *it = ctx->it; - bool self = trav_kind == FlecsQueryUpSelectSelfUp; - ecs_table_range_t range; - - do { - bool result; - 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, - op_ctx->with, 0); - } else if (kind == FlecsQueryUpSelectUnion) { - result = flecs_query_union_select(op, redo, ctx); - } else { - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } - - if (!result) { - /* No remaining tables with component found. */ - return false; - } - - redo = true; - - 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); - } - - op_ctx->table = range.table; - op_ctx->row = range.offset; - op_ctx->end = range.offset + range.count; - op_ctx->matched = it->ids[op->field_index]; - - return true; -} - -/* Find next traversable entity in table. */ -static -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_world_t *world = ctx->world; - ecs_iter_t *it = ctx->it; - const ecs_query_t *q = &ctx->query->pub; - ecs_table_t *table = op_ctx->table; - bool self = trav_kind == FlecsQueryUpSelectSelfUp; - - if (table->_->traversable_count == 0) { - /* No traversable entities in table */ - op_ctx->table = NULL; - return NULL; - } else { - int32_t row; - ecs_entity_t entity = 0; - const ecs_entity_t *entities = ecs_table_entities(table); - - for (row = op_ctx->row; row < op_ctx->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 (row == op_ctx->end) { - /* No traversable entities remaining in table */ - op_ctx->table = NULL; - return NULL; - } - - op_ctx->row = row; - - /* Get down cache entry for traversable entity */ - bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; - op_ctx->down = flecs_query_get_down_cache(ctx, &op_ctx->cache, - op_ctx->trav, entity, op_ctx->idr_with, self, match_empty); - op_ctx->cache_elem = -1; - } - - return op_ctx->down; -} - -/* 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_iter_t *it = ctx->it; - bool redo_select = redo; - const ecs_query_t *q = &ctx->query->pub; - bool self = trav_kind == FlecsQueryUpSelectSelfUp; - - op_ctx->trav = q->terms[op->term_index].trav; - - /* Reuse id record from previous iteration if possible*/ - if (!op_ctx->idr_trav) { - op_ctx->idr_trav = flecs_id_record_get(ctx->world, - ecs_pair(op_ctx->trav, EcsWildcard)); - } - - /* If id record is not found, or if it doesn't have any tables, revert to - * iterating owned components (no traversal) */ - if (!op_ctx->idr_trav || - !flecs_table_cache_count(&op_ctx->idr_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 == FlecsQueryUpSelectUnion) { - return flecs_query_union_select(op, redo, ctx); - } else { - /* Invalid select kind */ - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } - } - - if (!redo) { - /* Get component id to match */ - op_ctx->with = flecs_query_op_get_id(op, ctx); - - /* Get id record for component to match */ - op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); - if (!op_ctx->idr_with) { - /* If id record does not exist, there can't be any results */ - return false; - } - - op_ctx->down = NULL; - op_ctx->cache_elem = 0; - } - - /* 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 = op_ctx->down; - -next_down_entry: - /* Get (next) entry in down traversal cache */ - while (!down) { - ecs_table_t *table = op_ctx->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)) - { - return false; - } - - table = op_ctx->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); - op_ctx->row --; - return true; - } - } - - redo_select = true; - } else { - /* Evaluate next entity in table */ - op_ctx->row ++; - } - - /* 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; - } - } - -next_down_elem: - /* Get next element (table) in cache entry */ - if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { - /* No more elements in cache entry, find next.*/ - down = NULL; - goto next_down_entry; - } - - ecs_trav_down_elem_t *elem = ecs_vec_get_t( - &down->elems, ecs_trav_down_elem_t, op_ctx->cache_elem); - flecs_query_var_set_range(op, op->src.var, elem->table, 0, 0, ctx); - flecs_query_set_vars(op, op_ctx->matched, ctx); - - if (flecs_query_table_filter(elem->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; - } - - flecs_set_source_set_flag(it, op->field_index); - - return true; -} - -/* 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) -{ - const ecs_query_t *q = &ctx->query->pub; - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_iter_t *it = ctx->it; - - op_ctx->trav = q->terms[op->term_index].trav; - if (!op_ctx->idr_trav) { - op_ctx->idr_trav = flecs_id_record_get(ctx->world, - ecs_pair(op_ctx->trav, EcsWildcard)); - } - - if (!op_ctx->idr_trav || - !flecs_table_cache_count(&op_ctx->idr_trav->cache)) - { - /* If there are no tables with traversable relationship, there are no - * matches. */ - return false; - } - - if (!redo) { - op_ctx->trav = q->terms[op->term_index].trav; - op_ctx->with = flecs_query_op_get_id(op, ctx); - op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); - - /* If id record for component doesn't exist, there are no matches */ - if (!op_ctx->idr_with) { - return false; - } - - /* 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; - } - - /* 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, &op_ctx->cache, - range.table, op_ctx->with, op_ctx->trav, op_ctx->idr_with, - op_ctx->idr_trav); - - if (!up) { - /* Component is not reachable from table */ - return false; - } - - 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; - flecs_query_set_vars(op, up->id, ctx); - flecs_set_source_set_flag(it, op->field_index); - return true; - } else { - /* The table either can or can't reach the component, nothing to do for - * a second evaluation of this operation.*/ - return false; - } -} - -/* 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, - bool id_only) -{ - if (!redo) { - bool result; - - if (id_only) { - /* Simple id, no wildcards */ - result = flecs_query_with_id(op, redo, ctx); - ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - op_ctx->remaining = 1; - } else { - result = flecs_query_with(op, redo, ctx); - } - - flecs_reset_source_set_flag(ctx->it, op->field_index); - - if (result) { - /* Table has component, no need to traverse*/ - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - op_ctx->trav = 0; - if (flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar) { - /* Matching self, so set sources to 0 */ - ecs_iter_t *it = ctx->it; - it->sources[op->field_index] = 0; - } - return true; - } - - /* Table doesn't have component, traverse relationship */ - return flecs_query_up_with(op, redo, ctx); - } else { - ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - if (op_ctx->trav == 0) { - /* If matching components without traversing, make sure to still - * match remaining components that match the id (wildcard). */ - return flecs_query_with(op, redo, ctx); - } - } - - return false; -} - -/** - * @file query/engine/eval_utils.c - * @brief Query engine evaluation utilities. - */ - - -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; - } -} - -ecs_query_op_ctx_t* flecs_op_ctx_( - const ecs_query_run_ctx_t *ctx) -{ - return &ctx->op_ctx[ctx->op_index]; -} - -#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) -{ - ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); - ECS_TERMSET_CLEAR(it->up_fields, 1u << field_index); -} - -void flecs_set_source_set_flag( - ecs_iter_t *it, - int32_t field_index) -{ - ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); - ECS_TERMSET_SET(it->up_fields, 1u << field_index); -} - -ecs_table_range_t flecs_range_from_entity( - ecs_entity_t e, - const ecs_query_run_ctx_t *ctx) -{ - ecs_record_t *r = flecs_entities_get(ctx->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 - }; -} - -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; - } - - ecs_entity_t entity = var->entity; - if (entity && entity != EcsWildcard) { - var->range = flecs_range_from_entity(entity, ctx); - return var->range; - } - - return (ecs_table_range_t){ 0 }; -} - -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; - } - - ecs_entity_t entity = var->entity; - if (entity && entity != EcsWildcard) { - var->range = flecs_range_from_entity(entity, ctx); - return var->range.table; - } - - 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); - } -} - -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_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(ref->entity, ctx); - } else { - 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(var->entity, ctx); - } - } - return (ecs_table_range_t){0}; -} - -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 flecs_query_var_reset( - ecs_var_id_t var_id, - const ecs_query_run_ctx_t *ctx) -{ - ctx->vars[var_id].entity = EcsWildcard; - ctx->vars[var_id].range.table = NULL; -} - -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) -{ - (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 - }; -} - -void flecs_query_var_narrow_range( - ecs_var_id_t var_id, - ecs_table_t *table, - int32_t offset, - int32_t count, - const ecs_query_run_ctx_t *ctx) -{ - ecs_var_t *var = &ctx->vars[var_id]; - - var->entity = 0; - var->range = (ecs_table_range_t){ - .table = table, - .offset = offset, - .count = count - }; - - ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); - if (ctx->query_vars[var_id].kind != EcsVarTable) { - ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); - var->entity = ecs_table_entities(table)[offset]; - } -} - -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(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 flecs_query_set_vars( - const ecs_query_op_t *op, - ecs_id_t id, - 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); - - 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); - } - } -} - -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) -{ - if (flag & EcsQueryIsEntity) { - return flecs_range_from_entity(ref->entity, ctx); - } else if (flag & EcsQueryIsVar) { - return flecs_query_var_get_range(ref->var, ctx); - } - return (ecs_table_range_t){0}; -} - -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; -} - -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; - } - } - - if (flags_2nd & (EcsQueryIsVar | EcsQueryIsEntity)) { - return ecs_pair(first, second); - } else { - return flecs_entities_get_alive(ctx->world, first); - } -} - -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); -} - -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); -} - -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; -} - -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(column >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); - return it->ids[field_index] = table->type.array[column]; -} - -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_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - int32_t field_index = op->field_index; - if (field_index == -1) { - return; - } - - 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; - } - - 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); -} - -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; -} - -/** - * @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_allocator_t *a, - const ecs_query_run_ctx_t *ctx, - ecs_trav_cache_t *cache, - ecs_entity_t trav, - ecs_entity_t entity) -{ - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); - if (!idr) { - return; - } - - ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, - ecs_trav_elem_t); - elem->entity = entity; - elem->idr = idr; - - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr->cache, &it)) { - 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; - } - - 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; - - for (; i < end; i ++) { - ecs_entity_t second = ecs_pair_second(world, ids[i]); - if (is_root) { - root_column = i; - } - - 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->idr = NULL; - - ecs_record_t *r = flecs_entities_get_any(world, second); - if (r->table) { - ecs_table_record_t *r_tr = flecs_id_record_get_table( - cache->idr, r->table); - if (!r_tr) { - return; - } - flecs_query_build_up_cache(world, a, ctx, cache, trav, r->table, - r_tr, root_column); - } - } -} - -void flecs_query_trav_cache_fini( - ecs_allocator_t *a, - ecs_trav_cache_t *cache) -{ - ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); -} - -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) -{ - 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; - } -} - -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_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_id_record_t *idr = cache->idr; - if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) { - idr = cache->idr = flecs_id_record_get(world, - ecs_pair(trav, EcsWildcard)); - if (!idr) { - ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); - return; - } - } - - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); - return; - } - - ecs_id_t id = table->type.array[tr->index]; - - 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; - } -} - -/** - * @file query/engine/trav_down_cache.c - * @brief Compile query term. - */ - - -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_id_record_t *idr_with, - bool self, - bool empty); - -static -ecs_trav_down_t* 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_id_record_t *idr_trav, - ecs_id_record_t *idr_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) -{ - 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]; -} - -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_t *table, - ecs_id_record_t *idr_with, - bool self, - bool empty) -{ - ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); - - if (!table->_->traversable_count) { - return dst; - } - - ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); - - 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 entity = entities[i]; - ecs_record_t *record = flecs_entities_get(world, entity); - if (!record) { - continue; - } - - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); - if (flags & EcsEntityIsTraversable) { - ecs_id_record_t *idr_trav = flecs_id_record_get(world, - ecs_pair(trav, entity)); - if (!idr_trav) { - continue; - } - - flecs_trav_entity_down(world, a, cache, dst, - trav, idr_trav, idr_with, self, empty); - } - } - - return dst; -} - -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_id_record_t *idr_with, - bool self, - bool empty) -{ - if (trav == EcsIsA || !world->idr_isa_wildcard) { - return; - } - - ecs_id_record_t *idr_isa = flecs_id_record_get( - world, ecs_pair(EcsIsA, entity)); - if (!idr_isa) { - return; - } - - ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr_isa->cache, &it)) { - 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; - } - - if (ecs_table_has_id(world, table, idr_with->id)) { - /* Table owns component */ - 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 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_id_record_t *idr_trav = flecs_id_record_get(world, - ecs_pair(trav, e)); - if (idr_trav) { - flecs_trav_entity_down(world, a, cache, dst, trav, - idr_trav, idr_with, self, empty); - } - - flecs_trav_entity_down_isa(world, a, cache, dst, trav, e, - idr_with, self, empty); - } - } - } - } -} - -static -ecs_trav_down_t* 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_id_record_t *idr_trav, - ecs_id_record_t *idr_with, - bool self, - bool empty) -{ - ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t first = ecs_vec_count(&dst->elems); - - ecs_table_cache_iter_t it; - bool result; - if (empty) { - result = flecs_table_cache_all_iter(&idr_trav->cache, &it); - } else { - result = flecs_table_cache_iter(&idr_trav->cache, &it); - } - - 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 (flecs_id_record_get_table(idr_with, table) != NULL) { - if (self) { - continue; - } - leaf = true; - } - - /* 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, - idr_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; - } - } - } - - ecs_trav_down_elem_t *elem = ecs_vec_append_t( - a, &dst->elems, ecs_trav_down_elem_t); - elem->table = table; - elem->leaf = leaf; - } - } - - /* 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->table, idr_with, self, empty); - } - } - - return dst; -} - -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_id_record_t *idr_with, - bool self, - bool empty) -{ - 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; - } - - ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); - if (!idr_trav) { - if (trav != EcsIsA) { - flecs_trav_entity_down_isa( - world, a, cache, result, trav, e, idr_with, self, empty); - } - result->ready = true; - return result; - } - - 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 (idr_with->flags & EcsIdOnInstantiateInherit) { - flecs_trav_entity_down_isa( - world, a, cache, result, trav, e, idr_with, self, empty); - } - - flecs_trav_entity_down( - world, a, cache, result, trav, idr_trav, idr_with, self, empty); - result->ready = true; - - return result; -} - -void flecs_query_down_cache_fini( - ecs_allocator_t *a, - ecs_trav_up_cache_t *cache) -{ - 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); -} - -/** - * @file query/engine/trav_up_cache.c - * @brief Compile query term. - */ - - -static -ecs_trav_up_t* flecs_trav_up_ensure( - const ecs_query_run_ctx_t *ctx, - ecs_trav_up_cache_t *cache, - uint64_t table_id) -{ - ecs_trav_up_t **trav = ecs_map_ensure_ref( - &cache->src, ecs_trav_up_t, table_id); - if (!trav[0]) { - trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); - } - - return trav[0]; -} - -static -int32_t flecs_trav_type_search( - ecs_trav_up_t *up, - const ecs_table_t *table, - ecs_id_record_t *idr_with, - ecs_type_t *type) -{ - ecs_table_record_t *tr = ecs_table_cache_get(&idr_with->cache, table); - if (tr) { - up->id = type->array[tr->index]; - up->tr = tr; - return tr->index; - } - - return -1; -} - -static -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); - - 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 -1; -} - -static -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_id_record_t *idr_with, - ecs_id_record_t *idr_trav) -{ - ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); - if (up->ready) { - return up; - } - - ecs_record_t *src_record = flecs_entities_get_any(world, src); - ecs_table_t *table = src_record->table; - if (!table) { - goto not_found; - } - - ecs_type_t type = table->type; - if (flecs_trav_type_search(up, table, idr_with, &type) >= 0) { - up->src = src; - goto found; - } - - ecs_flags32_t flags = table->flags; - if ((flags & EcsTableHasPairs) && rel) { - bool is_a = idr_trav == world->idr_isa_wildcard; - if (is_a) { - if (!(flags & EcsTableHasIsA)) { - goto not_found; - } - - if (!flecs_type_can_inherit_id(world, table, idr_with, with)) { - goto not_found; - } - } - - ecs_trav_up_t up_pair = {0}; - int32_t r_column = flecs_trav_type_search( - &up_pair, table, idr_trav, &type); - - 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, idr_with, idr_trav); - if (up_parent->tr) { - up->src = up_parent->src; - up->tr = up_parent->tr; - up->id = up_parent->id; - goto found; - } - - r_column = flecs_trav_type_offset_search( - &up_pair, table, r_column + 1, rel, &type); - } - - if (!is_a && (idr_with->flags & EcsIdOnInstantiateInherit)) { - idr_trav = world->idr_isa_wildcard; - r_column = flecs_trav_type_search( - &up_pair, table, idr_trav, &type); - - 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, idr_with, idr_trav); - if (up_parent->tr) { - up->src = up_parent->src; - up->tr = up_parent->tr; - up->id = up_parent->id; - goto found; - } - - 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; -} - -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, - ecs_id_t with, - ecs_entity_t trav, - ecs_id_record_t *idr_with, - ecs_id_record_t *idr_trav) -{ - if (cache->with && cache->with != with) { - flecs_query_up_cache_fini(cache); - } - - 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_assert(cache->dir != EcsTravDown, ECS_INTERNAL_ERROR, NULL); - cache->dir = EcsTravUp; - cache->with = with; - - ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_record_t *tr = ecs_table_cache_get(&idr_trav->cache, table); - if (!tr) { - return NULL; /* Table doesn't have the relationship */ - } - - 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), idr_with, idr_trav); - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - if (result->src != 0) { - return result; - } - } - - return NULL; -} - -void flecs_query_up_cache_fini( - ecs_trav_up_cache_t *cache) -{ - ecs_map_fini(&cache->src); -} - -/** - * @file query/engine/trivial_iter.c - * @brief Iterator for trivial queries. - */ - - -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; - } - } - } - - ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); - op_ctx->start_from = t; - - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, query->ids[t]); - if (!idr) { - return false; - } - - if (query->flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)){ - return false; - } - } else { - if (!flecs_table_cache_iter(&idr->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; -} - -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; - - if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, term_set)) { - return false; - } - - do { - const ecs_table_record_t *tr = flecs_table_cache_next( - &op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } - - ecs_table_t *table = tr->hdr.table; - if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { - continue; - } - - for (t = op_ctx->first_to_eval; t < term_count; t ++) { - if (!(term_set & (1llu << t))) { - continue; - } - - const ecs_term_t *term = &terms[t]; - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); - if (!idr) { - break; - } - - const ecs_table_record_t *tr_with = flecs_id_record_get_table( - idr, 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; -} - -bool flecs_query_is_trivial_search( - const ecs_query_run_ctx_t *ctx, - ecs_query_trivial_ctx_t *op_ctx, - bool redo) -{ - 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; - - if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, 0)) { - return false; - } - -next: - { - const ecs_table_record_t *tr = flecs_table_cache_next( - &op_ctx->it, ecs_table_record_t); - if (!tr) { - return false; - } - - ecs_table_t *table = tr->hdr.table; - if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { - goto next; - } - - for (t = 1; t < term_count; t ++) { - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, ids[t]); - if (!idr) { - return false; - } - - const ecs_table_record_t *tr_with = flecs_id_record_get_table( - idr, table); - if (!tr_with) { - goto next; - } - - it->trs[t] = tr_with; - } - - it->table = table; - it->count = ecs_table_count(table); - it->entities = ecs_table_entities(table); - it->trs[0] = tr; - } - - return true; -} - -bool flecs_query_trivial_test( - const ecs_query_run_ctx_t *ctx, - bool redo, - ecs_flags64_t term_set) -{ - if (redo) { - return false; - } else { - 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; - - ecs_table_t *table = it->table; - ecs_assert(table != NULL, ECS_INVALID_OPERATION, - "the variable set on the iterator is missing a table"); - - for (t = 0; t < term_count; t ++) { - if (!(term_set & (1llu << t))) { - continue; - } - - const ecs_term_t *term = &terms[t]; - ecs_id_record_t *idr = flecs_id_record_get(q->world, term->id); - if (!idr) { - return false; - } - - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, 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; - } -} - -/** - * @file addons/script/expr_ast.c - * @brief Script expression AST implementation. - */ - - -#ifdef FLECS_SCRIPT - -#define flecs_expr_ast_new(parser, T, kind)\ - (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) - -static -void* flecs_expr_ast_new_( - ecs_script_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; -} - -ecs_expr_value_node_t* flecs_expr_value_from( - ecs_script_t *script, - ecs_expr_node_t *node, - ecs_entity_t type) -{ - 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; -} - -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_expr_value_node_t* flecs_expr_bool( - ecs_script_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; -} - -ecs_expr_value_node_t* flecs_expr_int( - ecs_script_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; -} - -ecs_expr_value_node_t* flecs_expr_uint( - ecs_script_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; -} - -ecs_expr_value_node_t* flecs_expr_float( - ecs_script_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; -} - -ecs_expr_value_node_t* flecs_expr_string( - ecs_script_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)) { - return NULL; - } - - return result; -} - -ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( - ecs_script_parser_t *parser, - const char *value) -{ - 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); - - return result; -} - -ecs_expr_value_node_t* flecs_expr_entity( - ecs_script_parser_t *parser, - ecs_entity_t value) -{ - 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; -} - -ecs_expr_initializer_t* flecs_expr_initializer( - ecs_script_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; -} - -ecs_expr_identifier_t* flecs_expr_identifier( - ecs_script_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; -} - -ecs_expr_variable_t* flecs_expr_variable( - ecs_script_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; -} - -ecs_expr_unary_t* flecs_expr_unary( - ecs_script_parser_t *parser) -{ - ecs_expr_unary_t *result = flecs_expr_ast_new( - parser, ecs_expr_unary_t, EcsExprUnary); - return result; -} - -ecs_expr_binary_t* flecs_expr_binary( - ecs_script_parser_t *parser) -{ - ecs_expr_binary_t *result = flecs_expr_ast_new( - parser, ecs_expr_binary_t, EcsExprBinary); - return result; -} - -ecs_expr_member_t* flecs_expr_member( - ecs_script_parser_t *parser) -{ - ecs_expr_member_t *result = flecs_expr_ast_new( - parser, ecs_expr_member_t, EcsExprMember); - return result; -} - -ecs_expr_function_t* flecs_expr_function( - ecs_script_parser_t *parser) -{ - ecs_expr_function_t *result = flecs_expr_ast_new( - parser, ecs_expr_function_t, EcsExprFunction); - return result; -} - -ecs_expr_element_t* flecs_expr_element( - ecs_script_parser_t *parser) -{ - ecs_expr_element_t *result = flecs_expr_ast_new( - parser, ecs_expr_element_t, EcsExprElement); - return result; -} - -ecs_expr_match_t* flecs_expr_match( - ecs_script_parser_t *parser) -{ - ecs_expr_match_t *result = flecs_expr_ast_new( - parser, ecs_expr_match_t, EcsExprMatch); - return result; -} - -static -bool flecs_expr_explicit_cast_allowed( - ecs_world_t *world, - ecs_entity_t from, - ecs_entity_t to) -{ - if (from == to) { - return true; - } - - 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); - - /* 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 (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); - } - - 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; - } - } - - /* Cannot cast complex types that are not the same */ - return false; - } - - /* Anything can be casted to a number */ - if (flecs_expr_is_type_number(to)) { - return true; - } - - /* Anything can be casted to a number */ - if (to == ecs_id(ecs_string_t)) { - return true; - } - - return true; -} - -ecs_expr_cast_t* flecs_expr_cast( - ecs_script_t *script, - ecs_expr_node_t *expr, - ecs_entity_t type) -{ - 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; - } - - 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; - } - - 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 -const char* flecs_script_parse_lhs( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out); - -static -void flecs_script_parser_expr_free( - ecs_script_parser_t *parser, - ecs_expr_node_t *node) -{ - flecs_expr_visit_free(&parser->script->pub, node); -} - -static -bool flecs_has_precedence( - ecs_script_token_kind_t first, - ecs_script_token_kind_t second) -{ - if (!flecs_expr_precedence[first]) { - return false; - } - return flecs_expr_precedence[first] <= flecs_expr_precedence[second]; -} - -static -ecs_entity_t flecs_script_default_lookup( - const ecs_world_t *world, - const char *name, - void *ctx) -{ - (void)ctx; - return ecs_lookup(world, name); -} - -static -const char* flecs_script_parse_match_elems( - ecs_script_parser_t *parser, - const char *pos, - ecs_expr_match_t *node) -{ - ecs_allocator_t *a = &parser->script->allocator; - bool old_significant_newline = parser->significant_newline; - parser->significant_newline = true; - - ecs_vec_init_t(NULL, &node->elements, ecs_expr_match_element_t, 0); - - do { - ParserBegin; - - 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; -} - -const char* flecs_script_parse_initializer( - ecs_script_parser_t *parser, - const char *pos, - char until, - ecs_expr_initializer_t **node_out) -{ - 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; - }) - } - { - 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; - }) - } - - 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 ')': - case '}': - /* Return last character of initializer */ - pos = lookahead - 1; - - case '\n': { - if ((char)lookahead_token.kind != until) { - Error("expected '%c'", until); - } - EndOfRule; - } - ) - } - } while (true); - - ParserEnd; -} - -static -const char* flecs_script_parse_collection_initializer( - ecs_script_parser_t *parser, - const char *pos, - ecs_expr_initializer_t **node_out) -{ - 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_1(']', { - 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); - - 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 -const char* flecs_script_parse_rhs( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out) -{ - const char *last_pos = pos; - - do { - TokenFramePush(); - - last_pos = pos; - - 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: - case EcsTokParenOpen: - { - ecs_script_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_1(EcsTokIdentifier, { - ecs_expr_member_t *result = flecs_expr_member(parser); - result->left = *out; - result->member_name = Token(1); - *out = (ecs_expr_node_t*)result; - break; - }); - - break; - } - - case EcsTokParenOpen: { - 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; - } - - *out = (ecs_expr_node_t*)result; - - if (pos[0] != ')') { - Error("expected end of argument list"); - } - - pos ++; - 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; - } - ) - - TokenFramePop(); - } while (pos != last_pos); - - return pos; -error: - return NULL; -} - -static -const char* flecs_script_parse_lhs( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out) -{ - TokenFramePush(); - - bool can_have_rhs = true; - - Parse( - case EcsTokNumber: { - const char *expr = Token(0); - if (strchr(expr, '.') || strchr(expr, 'e')) { - *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); - } else if (expr[0] == '-') { - char *end; - *out = (ecs_expr_node_t*)flecs_expr_int(parser, - strtoll(expr, &end, 10)); - } else { - char *end; - *out = (ecs_expr_node_t*)flecs_expr_uint(parser, - strtoull(expr, &end, 10)); - } - break; - } - - 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; - } - - case EcsTokIdentifier: { - 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); - } - } - break; - } - - 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; - } - - node->operator = EcsTokNot; - *out = (ecs_expr_node_t*)node; - break; - } - - 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; - } - - node->left = (ecs_expr_node_t*)flecs_expr_int(parser, -1); - node->operator = EcsTokMul; - *out = (ecs_expr_node_t*)node; - break; - } - - 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; - } - - 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; - } - - Parse_1('}', { - *out = (ecs_expr_node_t*)node; - break; - }) - - break; - }) - - can_have_rhs = false; - - break; - } - - case '(': { - pos = flecs_script_parse_expr(parser, pos, 0, out); - if (!pos) { - goto error; - } - - Parse_1(')', { - break; - }) - - break; - } - - 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; - } - - Parse_1('}', { - break; - }) - - can_have_rhs = false; - - *out = (ecs_expr_node_t*)node; - break; - } - - 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; - } - - node->is_collection = true; - - Parse_1(']', { - break; - }) - - can_have_rhs = false; - - *out = (ecs_expr_node_t*)node; - break; - } - ) - - TokenFramePop(); - - /* 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; - } - - /* Parse right-hand side of expression if there is one */ - return flecs_script_parse_rhs(parser, pos, tokenizer, left_oper, out); -error: - return NULL; -} - -const char* flecs_script_parse_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out) -{ - ParserBegin; - - pos = flecs_script_parse_lhs(parser, pos, tokenizer, left_oper, out); - - EndOfRule; - - ParserEnd; -} - -ecs_script_t* ecs_expr_parse( - ecs_world_t *world, - const char *expr, - const ecs_expr_eval_desc_t *desc) -{ - ecs_expr_eval_desc_t priv_desc = {0}; - if (desc) { - priv_desc = *desc; - } - - if (!priv_desc.lookup_action) { - priv_desc.lookup_action = flecs_script_default_lookup; - } - - ecs_script_t *script = flecs_script_new(world); - ecs_script_impl_t *impl = flecs_script_impl(script); - - ecs_script_parser_t parser = { - .script = impl, - .scope = impl->root, - .significant_newline = false - }; - - 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; - - const char *ptr = flecs_script_parse_expr(&parser, expr, 0, &impl->expr); - if (!ptr) { - goto error; - } - - impl->next_token = ptr; - impl->token_remaining = parser.token_cur; - - if (flecs_expr_visit_type(script, impl->expr, &priv_desc)) { - goto error; - } - - //printf("%s\n", ecs_script_ast_to_str(script, true)); - - if (!desc || !desc->disable_folding) { - if (flecs_expr_visit_fold(script, &impl->expr, &priv_desc)) { - goto error; - } - } - - //printf("%s\n", ecs_script_ast_to_str(script, true)); - - return script; -error: - ecs_script_free(script); - return NULL; -} - -int ecs_expr_eval( - const ecs_script_t *script, - ecs_value_t *value, - const ecs_expr_eval_desc_t *desc) -{ - 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); - - ecs_expr_eval_desc_t priv_desc = {0}; - if (desc) { - priv_desc = *desc; - } - - if (!priv_desc.lookup_action) { - priv_desc.lookup_action = flecs_script_default_lookup; - } - - if (flecs_expr_visit_eval(script, impl->expr, &priv_desc, value)) { - goto error; - } - - return 0; -error: - return -1; -} - -FLECS_API -const char* ecs_expr_run( - ecs_world_t *world, - const char *expr, - ecs_value_t *value, - const ecs_expr_eval_desc_t *desc) -{ - 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_script_t *s = ecs_expr_parse(world, expr, &priv_desc); - if (!s) { - goto error; - } - - if (ecs_expr_eval(s, value, &priv_desc)) { - ecs_script_free(s); - goto error; - } - - const char *result = flecs_script_impl(s)->next_token; - - ecs_script_free(s); - - return result; -error: - return NULL; -} - -FLECS_API -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; - } - - char *expr = flecs_asprintf("\"%s\"", str); - - 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; - } - - ecs_os_free(expr); - - return r; -} - -#endif - -/** - * @file addons/script/expr/stack.c - * @brief Script expression stack implementation. - */ - - -#ifdef FLECS_SCRIPT - -static -void flecs_expr_value_alloc( - ecs_expr_stack_t *stack, - ecs_expr_value_t *v, - const ecs_type_info_t *ti) -{ - 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 (ti->hooks.ctor) { - ti->hooks.ctor(v->value.ptr, 1, ti); - } -} - -static -void flecs_expr_value_free( - ecs_expr_value_t *v) -{ - const ecs_type_info_t *ti = v->type_info; - v->type_info = NULL; - - if (!v->owned) { - return; /* Runtime doesn't own value, don't destruct */ - } - - 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); - } - - v->value.ptr = NULL; -} - -void flecs_expr_stack_init( - ecs_expr_stack_t *stack) -{ - ecs_os_zeromem(stack); - flecs_stack_init(&stack->stack); -} - -void flecs_expr_stack_fini( - ecs_expr_stack_t *stack) -{ - ecs_assert(stack->frame == 0, ECS_INTERNAL_ERROR, NULL); - flecs_stack_fini(&stack->stack); -} - -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); -} - -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); - - 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]; - - if (ti) { - flecs_expr_value_alloc(stack, v, ti); - } - - return v; -} - -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; - } -} - -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; - } - - for (sp = end - 1; sp >= start; sp --) { - flecs_expr_value_free(&stack->values[sp]); - } - - flecs_stack_restore_cursor(&stack->stack, stack->frames[frame].cur); -} - -#endif - -/** - * @file addons/script/expr/parser.c * brief Scriptexpoutsion parser. - */ - - -#ifdef FLECS_SCRIPT - -int flecs_value_copy_to( - ecs_world_t *world, - ecs_value_t *dst, - const ecs_expr_value_t *src) -{ - 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); - - 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; -} - -int flecs_value_move_to( - ecs_world_t *world, - ecs_value_t *dst, - ecs_value_t *src) -{ - 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); - - 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_value_free(world, src->type, tmp.ptr); - } - - return 0; -error: - return -1; -} - - -int flecs_value_unary( - const ecs_script_t *script, - const ecs_value_t *expr, - ecs_value_t *out, - ecs_script_token_kind_t operator) -{ - (void)script; - switch(operator) { - case 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; - 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 EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - case EcsTokMod: - case EcsTokBitwiseOr: - case EcsTokBitwiseAnd: - case EcsTokOptional: - case EcsTokAnnotation: - case EcsTokNewline: - 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: - case EcsTokIdentifier: - case EcsTokString: - case EcsTokNumber: - case EcsTokKeywordModule: - case EcsTokKeywordUsing: - case EcsTokKeywordWith: - case EcsTokKeywordIf: - case EcsTokKeywordElse: - case EcsTokKeywordFor: - case EcsTokKeywordIn: - case EcsTokKeywordMatch: - case EcsTokKeywordTemplate: - case EcsTokKeywordProp: - case EcsTokKeywordConst: - default: - ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); - } - - return 0; -} - -#define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) - -#define ECS_BOP(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_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");\ - } - -#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");\ - } - -#define ECS_BINARY_ASSIGN_OP(left, right, result, op)\ - ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_ASSIGN)\ - -#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");\ - } - -int flecs_value_binary( - const ecs_script_t *script, - const ecs_value_t *left, - const ecs_value_t *right, - ecs_value_t *out, - ecs_script_token_kind_t operator) -{ - (void)script; - - 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; - } - } - - 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 EcsTokString: - case EcsTokNumber: - case EcsTokKeywordModule: - case EcsTokKeywordUsing: - case EcsTokKeywordWith: - case EcsTokKeywordIf: - case EcsTokKeywordElse: - case EcsTokKeywordFor: - case EcsTokKeywordIn: - case EcsTokKeywordTemplate: - case EcsTokKeywordMatch: - case EcsTokKeywordProp: - case EcsTokKeywordConst: - default: - ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); - } - - return 0; -} - -bool flecs_string_is_interpolated( - const char *value) -{ - const char *ptr = value; - - for (ptr = strchr(ptr, '$'); ptr; ptr = strchr(ptr + 1, '$')) { - if (ptr != value) { - if (ptr[-1] == '\\') { - continue; /* Escaped */ - } - } - - if (isspace(ptr[1]) || !ptr[1]) { - continue; /* $ by itself */ - } - - return true; - } - - ptr = value; - - for (ptr = strchr(ptr, '{'); ptr; ptr = strchr(ptr + 1, '{')) { - if (ptr != value) { - if (ptr[-1] == '\\') { - continue; /* Escaped */ - } - } - - return true; - } - - return false; -} - -char* flecs_string_escape( - char *str) -{ - const char *ptr; - char *out = str, ch; - - 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 { - ptr = flecs_chrparse(ptr, &ch); - if (!ptr) { - ecs_err("invalid escape sequence in string '%s'", str); - return NULL; - } - } - } 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; - } -} - -#endif - -/** - * @file addons/script/expr_ast.c - * @brief Script expression AST implementation. - */ - - -#ifdef FLECS_SCRIPT - -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; - -static -int flecs_expr_visit_eval_priv( - ecs_script_eval_ctx_t *ctx, - ecs_expr_node_t *node, - ecs_expr_value_t *out); - -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; -} - -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_assert(node->node.type == ecs_id(ecs_string_t), - ECS_INTERNAL_ERROR, NULL); - - ecs_strbuf_t buf = ECS_STRBUF_INIT; - - flecs_expr_stack_push(ctx->stack); - - 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; - } - - ecs_assert(val->value.type == ecs_id(ecs_string_t), - ECS_INTERNAL_ERROR, NULL); - - ecs_strbuf_appendstr(&buf, *(char**)val->value.ptr); - } - } - - *(char**)out->value.ptr = ecs_strbuf_get(&buf); - out->owned = true; - - flecs_expr_stack_pop(ctx->stack); - return 0; -error: - flecs_expr_stack_pop(ctx->stack); - return -1; -} - -static -int flecs_expr_initializer_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_initializer_t *node, - void *value); - -static -int flecs_expr_initializer_eval_static( - ecs_script_eval_ctx_t *ctx, - ecs_expr_initializer_t *node, - void *value) -{ - flecs_expr_stack_push(ctx->stack); - - 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 (elem->value->kind == EcsExprInitializer) { - if (flecs_expr_initializer_eval(ctx, - (ecs_expr_initializer_t*)elem->value, value)) - { - goto error; - } - continue; - } - - 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; - } - - /* 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 (!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) - }; - - if (flecs_value_binary( - ctx->script, NULL, &expr->value, &dst, elem->operator)) - { - goto error; - } - } - } - - flecs_expr_stack_pop(ctx->stack); - return 0; -error: - flecs_expr_stack_pop(ctx->stack); - return -1; -} - -static -int flecs_expr_initializer_eval_dynamic( - ecs_script_eval_ctx_t *ctx, - ecs_expr_initializer_t *node, - void *value) -{ - flecs_expr_stack_push(ctx->stack); - - ecs_meta_cursor_t cur = ecs_meta_cursor( - ctx->world, node->node.type, value); - - if (ecs_meta_push(&cur)) { - goto error; - } - - 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]; - - if (i) { - ecs_meta_next(&cur); - } - - if (elem->value->kind == EcsExprInitializer) { - if (flecs_expr_initializer_eval(ctx, - (ecs_expr_initializer_t*)elem->value, value)) - { - goto error; - } - continue; - } - - 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; - } - - if (elem->member) { - ecs_meta_member(&cur, elem->member); - } - - ecs_value_t v_elem_value = { - .ptr = expr->value.ptr, - .type = expr->value.type - }; - - if (ecs_meta_set_value(&cur, &v_elem_value)) { - goto error; - } - } - - 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 -int flecs_expr_initializer_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_initializer_t *node, - void *value) -{ - if (node->is_dynamic) { - return flecs_expr_initializer_eval_dynamic(ctx, node, value); - } else { - return flecs_expr_initializer_eval_static(ctx, node, value); - } -} - -static -int flecs_expr_initializer_visit_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_initializer_t *node, - ecs_expr_value_t *out) -{ - out->owned = true; - return flecs_expr_initializer_eval(ctx, node, out->value.ptr); -} - -static -int flecs_expr_unary_visit_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_unary_t *node, - ecs_expr_value_t *out) -{ - 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; - } - - if (flecs_value_unary( - ctx->script, &expr->value, &out->value, node->operator)) - { - goto error; - } - - flecs_expr_stack_pop(ctx->stack); - return 0; -error: - flecs_expr_stack_pop(ctx->stack); - return -1; -} - -static -int flecs_expr_binary_visit_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_binary_t *node, - ecs_expr_value_t *out) -{ - flecs_expr_stack_push(ctx->stack); - - /* 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; - } - - 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; - } - - if (flecs_value_binary( - ctx->script, &left->value, &right->value, &out->value, node->operator)) - { - goto error; - } - - flecs_expr_stack_pop(ctx->stack); - return 0; -error: - flecs_expr_stack_pop(ctx->stack); - return -1; -} - -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; - } - - ecs_assert(out->value.type == ecs_id(ecs_entity_t), - ECS_INTERNAL_ERROR, NULL); - *(ecs_entity_t*)out->value.ptr = e; - } - - return 0; -error: - return -1; -} - -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; - } - - /* 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; -} - -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; - - ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, - "variables available at parse time are not provided"); - - ecs_assert(node->global_value.type == node->node.type, - ECS_INTERNAL_ERROR, NULL); - out->value = node->global_value; - out->owned = false; - - return 0; -} - -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); - - /* 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; - } - - /* 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; - } - - flecs_expr_stack_pop(ctx->stack); - return 0; -error: - flecs_expr_stack_pop(ctx->stack); - return -1; -} - -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; - } - - return false; -} - -static -bool flecs_expr_get_unsigned( - const ecs_value_t *value, - uint64_t *out) -{ - 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; - } - - return false; -} - -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; - } - - return false; -} - -#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;\ - -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 -} - -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 -} - -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 -} - -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); - - /* 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; - } - - 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_); - } - - flecs_expr_stack_pop(ctx->stack); - return 0; -error: - flecs_expr_stack_pop(ctx->stack); - return -1; -} - -static -int flecs_expr_function_args_visit_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_initializer_t *node, - ecs_value_t *args) -{ - 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); - - if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { - goto error; - } - - args[i] = expr->value; - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_function_visit_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_function_t *node, - ecs_expr_value_t *out) -{ - flecs_expr_stack_push(ctx->stack); - - ecs_function_ctx_t call_ctx = { - .world = ctx->world, - .function = node->calldata.function, - .ctx = node->calldata.ctx - }; - - ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); - - 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; - } - } - - node->calldata.callback(&call_ctx, argc, argv, &out->value); - out->owned = true; - - flecs_expr_stack_pop(ctx->stack); - return 0; -error: - flecs_expr_stack_pop(ctx->stack); - return -1; -} - -static -int flecs_expr_method_visit_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_function_t *node, - ecs_expr_value_t *out) -{ - flecs_expr_stack_push(ctx->stack); - - 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; - } - - ecs_function_ctx_t call_ctx = { - .world = ctx->world, - .function = node->calldata.function, - .ctx = node->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; - } - } - - node->calldata.callback(&call_ctx, argc, argv, &out->value); - out->owned = true; - } - - flecs_expr_stack_pop(ctx->stack); - return 0; -error: - flecs_expr_stack_pop(ctx->stack); - return -1; -} - -static -int flecs_expr_member_visit_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_member_t *node, - ecs_expr_value_t *out) -{ - flecs_expr_stack_push(ctx->stack); - - 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; - } - - out->value.ptr = ECS_OFFSET(expr->value.ptr, node->offset); - out->value.type = node->node.type; - out->owned = false; - - flecs_expr_stack_pop(ctx->stack); - return 0; -error: - flecs_expr_stack_pop(ctx->stack); - return -1; -} - -static -int flecs_expr_element_visit_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_element_t *node, - ecs_expr_value_t *out) -{ - 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; - } - - 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; - } - - 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; - - return 0; -error: - return -1; -} - -static -int flecs_expr_match_visit_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_match_t *node, - ecs_expr_value_t *out) -{ - 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; - } - - int32_t i, count = ecs_vec_count(&node->elements); - ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); - - for (i = 0; i < count; i ++) { - ecs_expr_match_element_t *elem = &elems[i]; - - 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; - } - - bool value = false; - ecs_value_t result = { .type = ecs_id(ecs_bool_t), .ptr = &value }; - - if (flecs_value_binary( - ctx->script, &expr->value, &compare->value, &result, EcsTokEq)) - { - goto error; - } - - flecs_expr_stack_pop(ctx->stack); - - if (value) { - if (flecs_expr_visit_eval_priv(ctx, elem->expr, out)) { - goto error; - } - break; - } - } - - 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; - } - } - - flecs_expr_stack_pop(ctx->stack); - return 0; -error: - flecs_expr_stack_pop(ctx->stack); - return -1; -} - -static -int flecs_expr_component_visit_eval( - ecs_script_eval_ctx_t *ctx, - ecs_expr_element_t *node, - ecs_expr_value_t *out) -{ - 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; - } - - /* 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; - } - - ecs_assert(index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); - - 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 -1; -} - -static -int flecs_expr_visit_eval_priv( - ecs_script_eval_ctx_t *ctx, - ecs_expr_node_t *node, - ecs_expr_value_t *out) -{ - ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - - switch(node->kind) { - case EcsExprValue: - if (flecs_expr_value_visit_eval( - ctx, (ecs_expr_value_node_t*)node, out)) - { - goto error; - } - break; - case EcsExprInterpolatedString: - if (flecs_expr_interpolated_string_visit_eval( - ctx, (ecs_expr_interpolated_string_t*)node, out)) - { - goto error; - } - break; - case EcsExprEmptyInitializer: - 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 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 0; -error: - return -1; -} - -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_expr_stack_t *stack = NULL, stack_local; - if (desc && desc->runtime) { - stack = &desc->runtime->expr_stack; - ecs_assert(stack->frame == 0, ECS_INTERNAL_ERROR, NULL); - } - if (!stack) { - stack = &stack_local; - flecs_expr_stack_init(stack); - } - - flecs_expr_stack_push(stack); - - 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 { - val = flecs_expr_stack_result(stack, node); - } - - ecs_script_eval_ctx_t ctx = { - .script = script, - .world = script->world, - .stack = stack, - .desc = desc - }; - - if (flecs_expr_visit_eval_priv(&ctx, node, val)) { - goto error; - } - - if (desc && !out->type) { - out->type = desc->type; - } - - if (!out->type) { - out->type = node->type; - } - - if (out->type && !out->ptr) { - out->ptr = ecs_value_new(ctx.world, out->type); - } - - 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: - flecs_expr_stack_pop(stack); - if (stack == &stack_local) { - flecs_expr_stack_fini(stack); - } - return -1; -} - -#endif - -/** - * @file addons/script/expr_fold.c - * @brief Script expression constant folding. - */ - - -#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 -int flecs_expr_unary_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr, - const ecs_expr_eval_desc_t *desc) -{ - ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; - - if (node->operator != EcsTokNot) { - flecs_expr_visit_error(script, node, - "operator invalid for unary expression"); - goto error; - } - - 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_; - - 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 -int flecs_expr_binary_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr, - const ecs_expr_eval_desc_t *desc) -{ - ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; - - if (flecs_expr_visit_fold(script, &node->left, desc)) { - goto error; - } - - if (flecs_expr_visit_fold(script, &node->right, desc)) { - goto error; - } - - if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { - /* Only folding literals */ - return 0; - } - - 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; - - ecs_value_t lop = { .type = left->node.type, .ptr = left->ptr }; - ecs_value_t rop = { .type = right->node.type, .ptr = right->ptr }; - - /* 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; - } - } - - 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 }; - - if (flecs_value_binary(script, &lop, &rop, &res, node->operator)) { - goto error; - } - - flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); - return 0; -error: - return -1; -} - -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_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; - - if (flecs_expr_visit_fold(script, &node->expr, desc)) { - goto error; - } - - if (node->expr->kind != EcsExprValue) { - /* Only folding literals for now */ - return 0; - } - - 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; - - 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); - } - - expr->node.type = dst_type; - expr->ptr = dst_ptr; - - node->expr = NULL; /* Prevent cleanup */ - flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)expr); - - return 0; -error: - return -1; -} - -static -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_expr_interpolated_string_t *node = - (ecs_expr_interpolated_string_t*)*node_ptr; - - bool can_fold = true; - - 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 ++); - - if (flecs_expr_visit_fold(script, expr_ptr, desc)) { - goto error; - } - - if (expr_ptr[0]->kind != EcsExprValue) { - can_fold = false; - } - } - } - - 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 { - 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); - } - } - - char **value = ecs_value_new(script->world, ecs_id(ecs_string_t)); - *value = ecs_strbuf_get(&buf); - - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, ecs_id(ecs_string_t)); - result->ptr = value; - - flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); - } - - return 0; -error: - return -1; -} - -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_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 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; - } - - if (elem->value->kind != EcsExprValue) { - *can_fold = false; - } - - if (elem->operator) { - *can_fold = false; - } - } - - if (node->is_dynamic) { - *can_fold = false; - return 0; - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_initializer_post_fold( - ecs_script_t *script, - ecs_expr_initializer_t *node, - void *value) -{ - 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 (elem->value->kind == EcsExprInitializer) { - if (flecs_expr_initializer_post_fold( - script, (ecs_expr_initializer_t*)elem->value, value)) - { - goto error; - } - continue; - } - - 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 (ecs_value_copy(script->world, type, - ECS_OFFSET(value, elem->offset), elem_value->ptr)) - { - goto error; - } - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_initializer_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr, - const ecs_expr_eval_desc_t *desc) -{ - bool can_fold = true; - - 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) { - void *value = ecs_value_new(script->world, node->node.type); - - if (flecs_expr_initializer_post_fold(script, node, value)) { - goto error; - } - - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, node->node.type); - result->ptr = value; - - flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_identifier_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr, - const ecs_expr_eval_desc_t *desc) -{ - (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 -int flecs_expr_variable_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr, - const ecs_expr_eval_desc_t *desc) -{ - (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 0; -} - -static -int flecs_expr_global_variable_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr, - const ecs_expr_eval_desc_t *desc) -{ - (void)desc; - - 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 -int flecs_expr_arguments_visit_fold( - ecs_script_t *script, - ecs_expr_initializer_t *node, - const ecs_expr_eval_desc_t *desc) -{ - 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 (flecs_expr_visit_fold(script, &elem->value, desc)) { - goto error; - } - } - - return 0; -error: - return -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; - - if (flecs_expr_visit_fold(script, &node->left, desc)) { - goto error; - } - - if (node->args) { - if (flecs_expr_arguments_visit_fold(script, node->args, desc)) { - goto error; - } - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_member_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr, - const ecs_expr_eval_desc_t *desc) -{ - ecs_expr_member_t *node = (ecs_expr_member_t*)*node_ptr; - - if (flecs_expr_visit_fold(script, &node->left, desc)) { - goto error; - } - - return 0; -error: - return -1; -} - -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 (flecs_expr_visit_fold(script, &node->index, desc)) { - goto error; - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_match_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node_ptr, - const ecs_expr_eval_desc_t *desc) -{ - ecs_expr_match_t *node = (ecs_expr_match_t*)*node_ptr; - - if (flecs_expr_visit_fold(script, &node->expr, desc)) { - goto error; - } - - int32_t i, count = ecs_vec_count(&node->elements); - ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); - - 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 (flecs_expr_visit_fold(script, &elem->expr, desc)) { - goto error; - } - } - - return 0; -error: - return -1; -} - -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; - - 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 EcsExprCast: - case EcsExprCastNumber: - if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { - goto error; - } - break; - } - - return 0; -error: - return -1; -} - -#endif - -/** - * @file addons/script/expr/visit_free.c - * @brief Visitor to free expression AST. - */ - - -#ifdef FLECS_SCRIPT - -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 -void flecs_expr_interpolated_string_visit_free( - ecs_script_t *script, - ecs_expr_interpolated_string_t *node) -{ - 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_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); -} - -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); - } - - ecs_allocator_t *a = &flecs_script_impl(script)->allocator; - ecs_vec_fini_t(a, &node->elements, ecs_expr_initializer_element_t); -} - -static -void flecs_expr_unary_visit_free( - ecs_script_t *script, - ecs_expr_unary_t *node) -{ - flecs_expr_visit_free(script, node->expr); -} - -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 flecs_expr_identifier_visit_free( - ecs_script_t *script, - ecs_expr_identifier_t *node) -{ - flecs_expr_visit_free(script, node->expr); -} - -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); -} - -static -void flecs_expr_member_visit_free( - ecs_script_t *script, - ecs_expr_member_t *node) -{ - flecs_expr_visit_free(script, node->left); -} - -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); -} - -static -void flecs_expr_match_visit_free( - ecs_script_t *script, - ecs_expr_match_t *node) -{ - flecs_expr_visit_free(script, node->expr); - - int32_t i, count = ecs_vec_count(&node->elements); - ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); - - 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); - } - - 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 -void flecs_expr_cast_visit_free( - ecs_script_t *script, - ecs_expr_cast_t *node) -{ - flecs_expr_visit_free(script, node->expr); -} - -void flecs_expr_visit_free( - ecs_script_t *script, - ecs_expr_node_t *node) -{ - if (!node) { - return; - } - - 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 EcsExprCast: - case EcsExprCastNumber: - flecs_expr_cast_visit_free( - script, (ecs_expr_cast_t*)node); - flecs_free_t(a, ecs_expr_cast_t, node); - break; - } -} - -#endif - -/** - * @file addons/script/expr_to_str.c - * @brief Script expression AST to string visitor. - */ - - -#ifdef FLECS_SCRIPT - -typedef struct ecs_expr_str_visitor_t { - 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); -} - -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 = ecs_ptr_to_str_buf( - v->world, node->node.type, node->ptr, v->buf); - flecs_expr_color_to_str(v, ECS_NORMAL); - return ret; -} - -static -int flecs_expr_interpolated_string_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_interpolated_string_t *node) -{ - 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); - - ecs_strbuf_appendlit(v->buf, "interpolated("); - - for (i = 0; i < count; i ++) { - char *fragment = fragments[i]; - - if (i) { - ecs_strbuf_appendlit(v->buf, ", "); - } - - 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_strbuf_appendlit(v->buf, ")"); - - return 0; -} - -static -int flecs_expr_unary_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_unary_t *node) -{ - ecs_strbuf_appendstr(v->buf, flecs_script_token_str(node->operator)); - - if (flecs_expr_node_to_str(v, node->expr)) { - goto error; - } - - return 0; -error: - return -1; -} - -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_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, ", "); - } - - ecs_expr_initializer_element_t *elem = &elems[i]; - if (elem->member) { - ecs_strbuf_appendstr(v->buf, elem->member); - ecs_strbuf_appendlit(v->buf, ":"); - } - - if (flecs_expr_node_to_str(v, elem->value)) { - goto error; - } - } - - ecs_strbuf_appendlit(v->buf, "}"); - - return 0; -error: - return -1; -} - -static -int flecs_expr_binary_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_binary_t *node) -{ - ecs_strbuf_appendlit(v->buf, "("); - - if (flecs_expr_node_to_str(v, node->left)) { - goto error; - } - - ecs_strbuf_appendlit(v->buf, " "); - - ecs_strbuf_appendstr(v->buf, flecs_script_token_str(node->operator)); - - ecs_strbuf_appendlit(v->buf, " "); - - if (flecs_expr_node_to_str(v, node->right)) { - goto error; - } - - ecs_strbuf_appendlit(v->buf, ")"); - - return 0; -error: - return -1; -} - -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; -} - -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 -int flecs_expr_member_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_member_t *node) -{ - if (flecs_expr_node_to_str(v, node->left)) { - return -1; - } - - ecs_strbuf_appendlit(v->buf, "."); - ecs_strbuf_appendstr(v->buf, node->member_name); - return 0; -} - -static -int flecs_expr_function_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_function_t *node) -{ - 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 -int flecs_expr_element_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_element_t *node) -{ - if (flecs_expr_node_to_str(v, node->left)) { - return -1; - } - - ecs_strbuf_appendlit(v->buf, "["); - if (flecs_expr_node_to_str(v, node->index)) { - return -1; - } - ecs_strbuf_appendlit(v->buf, "]"); - return 0; -} - -static -int flecs_expr_match_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_match_t *node) -{ - 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, "("); - } - - 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; - } - - ecs_strbuf_appendlit(v->buf, " {\n"); - - int32_t i, count = ecs_vec_count(&node->elements); - ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); - - for (i = 0; i < count; i ++) { - ecs_strbuf_appendlit(v->buf, " "); - - ecs_expr_match_element_t *elem = &elems[i]; - if (flecs_expr_node_to_str(v, elem->compare)) { - return -1; - } - - ecs_strbuf_appendlit(v->buf, ": "); - - if (flecs_expr_node_to_str(v, elem->expr)) { - return -1; - } - - ecs_strbuf_appendlit(v->buf, "\n"); - } - - ecs_strbuf_appendlit(v->buf, "}"); - - if (node->node.type) { - ecs_strbuf_appendlit(v->buf, ")"); - } - - ecs_strbuf_appendlit(v->buf, "\n"); - - return 0; -} - -static -int flecs_expr_cast_to_str( - ecs_expr_str_visitor_t *v, - const ecs_expr_cast_t *node) -{ - ecs_entity_t type = node->node.type; - - 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 (flecs_expr_node_to_str(v, node->expr)) { - return -1; - } - - ecs_strbuf_append(v->buf, ")"); - - return 0; -} - -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); - - switch(node->kind) { - case EcsExprValue: - if (flecs_expr_value_to_str(v, - (const ecs_expr_value_node_t*)node)) - { - goto error; - } - break; - case EcsExprInterpolatedString: - if (flecs_expr_interpolated_string_to_str(v, - (const ecs_expr_interpolated_string_t*)node)) - { - goto error; - } - break; - case EcsExprInitializer: - case EcsExprEmptyInitializer: - if (flecs_expr_initializer_to_str(v, - (const ecs_expr_initializer_t*)node)) - { - goto error; - } - break; - case EcsExprUnary: - if (flecs_expr_unary_to_str(v, - (const ecs_expr_unary_t*)node)) - { - goto error; - } - break; - case EcsExprBinary: - if (flecs_expr_binary_to_str(v, - (const ecs_expr_binary_t*)node)) - { - goto error; - } - break; - case EcsExprIdentifier: - if (flecs_expr_identifier_to_str(v, - (const ecs_expr_identifier_t*)node)) - { - goto error; - } - 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 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"); - } - - return 0; -error: - return -1; -} - -void flecs_expr_to_str_buf( - const ecs_world_t *world, - const ecs_expr_node_t *expr, - ecs_strbuf_t *buf, - bool colors) -{ - ecs_expr_str_visitor_t v = { .world = world, .buf = buf, .colors = colors }; - if (flecs_expr_node_to_str(&v, expr)) { - ecs_strbuf_reset(buf); - } -} - -#endif - -/** - * @file addons/script/expr_ast.c - * @brief Script expression AST implementation. - */ - - -#ifdef FLECS_SCRIPT - -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); - -bool flecs_expr_is_type_integer( - ecs_entity_t type) -{ - 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; -} - -bool flecs_expr_is_type_number( - ecs_entity_t type) -{ - 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; -} - -/* 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) -{ - if (type == ecs_id(ecs_bool_t)) return 1; - else if (type == ecs_id(ecs_char_t)) return 2; - - 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; - - 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; - - else if (type == ecs_id(ecs_f32_t)) return 12; - else if (type == ecs_id(ecs_f64_t)) return 13; - - else if (type == ecs_id(ecs_string_t)) return -1; - else if (type == ecs_id(ecs_entity_t)) return -1; - else return false; -} - -/* 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 (type == ecs_id(ecs_bool_t)) return 1; - else if (type == ecs_id(ecs_char_t)) return 1; - - /* 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; - - 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; -} - -/* 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; - } - - if (to_e >= from_e) { - return flecs_expr_storage_score(to) >= flecs_expr_storage_score(from); - } - - return false; -} - -static -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; - } - - return operand; -} - -static -ecs_entity_t flecs_expr_narrow_type( - ecs_entity_t lvalue, - ecs_expr_node_t *node) -{ - ecs_entity_t type = node->type; - - if (node->kind != EcsExprValue) { - return type; - } - - if (!flecs_expr_is_type_number(type)) { - return type; - } - - void *ptr = ((ecs_expr_value_node_t*)node)->ptr; - - 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 { - /* 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 (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); - } - } - - 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); - } -} - -static -bool flecs_expr_oper_valid_for_type( - ecs_world_t *world, - ecs_entity_t type, - ecs_script_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 EcsTokString: - case EcsTokNumber: - case EcsTokKeywordModule: - case EcsTokKeywordUsing: - case EcsTokKeywordWith: - case EcsTokKeywordIf: - case EcsTokKeywordElse: - case EcsTokKeywordFor: - case EcsTokKeywordIn: - case EcsTokKeywordMatch: - case EcsTokKeywordTemplate: - case EcsTokKeywordProp: - case EcsTokKeywordConst: - case EcsTokEnd: - default: - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } -} - -static -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_script_token_kind_t operator, - ecs_entity_t *operand_type, - ecs_entity_t *result_type) -{ - 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; - } - } - } - - 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 { - *operand_type = left->type; - *result_type = left->type; - } - - 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 EcsTokString: - case EcsTokNumber: - case EcsTokKeywordModule: - case EcsTokKeywordUsing: - case EcsTokKeywordWith: - case EcsTokKeywordIf: - case EcsTokKeywordElse: - case EcsTokKeywordFor: - case EcsTokKeywordIn: - case EcsTokKeywordMatch: - case EcsTokKeywordTemplate: - case EcsTokKeywordProp: - case EcsTokKeywordConst: - case EcsTokEnd: - default: - ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); - } - - /* 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; - } - - 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) { - /* Only primitives, bitmask constants and enums are allowed */ - if (left->type == right->type) { - if (ecs_get(world, left->type, EcsBitmask) != NULL) { - *operand_type = left->type; - goto done; - } - } - - { - const EcsEnum *ptr = ecs_get(script->world, left->type, EcsEnum); - if (ptr) { - *operand_type = ptr->underlying_type; - goto done; - } - } - - { - const EcsEnum *ptr = ecs_get(script->world, right->type, EcsEnum); - if (ptr) { - *operand_type = ptr->underlying_type; - goto done; - } - } - - char *lname = ecs_get_path(world, left->type); - char *rname = ecs_get_path(world, right->type); - flecs_expr_visit_error(script, node, - "invalid non-primitive type in expression (%s, %s)", - lname, rname); - ecs_os_free(lname); - ecs_os_free(rname); - 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 = flecs_expr_narrow_type(node_type, left); - 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. */ - - if (flecs_expr_is_type_number(ltype) && flecs_expr_is_type_number(rtype)) { - - /* 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; - } - - /* 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; - } - } - - /* If all of that didn't work, give up */ - - 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; - -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); - } - - if (!*result_type) { - *result_type = *operand_type; - } - - if (ecs_get(script->world, *result_type, EcsBitmask) != NULL) { - *operand_type = ecs_id(ecs_u64_t); - } - - return 0; -} - -static -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) -{ - 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); -} - -static -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; - - for (ptr = node->value; (ch = ptr[0]); ptr ++) { - if (ch == '\\') { - ptr ++; - /* Next character is escaped, ignore */ - continue; - } - - if ((ch == '$') && (isspace(ptr[1]) || !ptr[1])) { - /* $ by itself */ - continue; - } - - if (ch == '$' || ch == '{') { - if (!frag) { - frag = node->value; - } - - char *frag_end = ptr; - - ecs_expr_node_t *result = NULL; - - if (ch == '$') { - char *var_name = ++ ptr; - ptr = ECS_CONST_CAST(char*, flecs_script_identifier( - NULL, ptr, NULL)); - if (!ptr) { - goto error; - } - - /* 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_expr_variable_t *var = flecs_expr_variable_from( - script, (ecs_expr_node_t*)node, var_name); - if (!var) { - goto error; - } - - result = (ecs_expr_node_t*)var; - } else { - ecs_script_impl_t *impl = flecs_script_impl(script); - - ecs_script_parser_t parser = { - .script = impl, - .scope = impl->root, - .significant_newline = false, - .token_cur = impl->token_remaining - }; - - ptr = ECS_CONST_CAST(char*, flecs_script_parse_expr( - &parser, ptr + 1, 0, &result)); - if (!ptr) { - goto error; - } - - if (ptr[0] != '}') { - flecs_expr_visit_error(script, node, - "expected '}' at end of interpolated expression"); - goto error; - } - - ptr ++; - } - - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_expr_eval_desc_t priv_desc = *desc; - priv_desc.type = ecs_id(ecs_string_t); /* String output */ - - if (flecs_expr_visit_type_priv(script, result, cur, &priv_desc)) { - flecs_expr_visit_free(script, result); - goto error; - } - - 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; - } - } - - ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, - &node->expressions, ecs_expr_node_t*)[0] = result; - - frag_end[0] = '\0'; - - if (frag != frag_end) { - ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, - &node->fragments, char*)[0] = frag; - } - - ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, - &node->fragments, char*)[0] = NULL; - - frag = ptr; /* Point to next fragment */ - if (!ptr[0]) { - break; /* We already parsed the end of the string */ - } - } - } - - /* 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); - - /* Add remaining fragment */ - if (frag != ptr) { - ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, - &node->fragments, char*)[0] = frag; - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_initializer_collection_check( - ecs_script_t *script, - ecs_expr_initializer_t *node, - ecs_meta_cursor_t *cur) -{ - 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_os_free(type_str); - goto error; - } - } - - ecs_entity_t type = node->node.type; - if (type) { - const EcsOpaque *op = ecs_get(script->world, type, EcsOpaque); - if (op) { - type = op->as_type; - } - - 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; - } - } - } - } - - return 0; -error: - return -1; -} - -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) -{ - (void)desc; - - node->node.type = ecs_meta_get_type(cur); - if (!node->node.type) { - flecs_expr_visit_error(script, node, - "unknown type for initializer"); - goto error; - } - - if (ecs_meta_push(cur)) { - goto error; - } - - if (flecs_expr_initializer_collection_check(script, node, cur)) { - goto error; - } - - if (ecs_meta_pop(cur)) { - goto error; - } - - return 0; -error: - return -1; -} - -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; - } - - ecs_entity_t type = ecs_meta_get_type(cur); - ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); - - /* Opaque types do not have deterministic offsets */ - bool is_opaque = ecs_get(script->world, type, EcsOpaque) != NULL; - node->is_dynamic = is_opaque; - - if (ecs_meta_push(cur)) { - goto error; - } - - if (flecs_expr_initializer_collection_check(script, node, cur)) { - goto error; - } - - - 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; - } - } - - ecs_expr_initializer_element_t *elem = &elems[i]; - if (elem->member) { - if (ecs_meta_dotmember(cur, elem->member)) { /* x: */ - flecs_expr_visit_error(script, node, "cannot resolve member"); - goto error; - } - } - - /* 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; - } - } - } - - 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; - } - - 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; - } - - 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; - } - } - - if (!is_opaque) { - elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); - } - } - - node->node.type = type; - - if (ecs_meta_pop(cur)) { - goto error; - } - - return 0; -error: - return -1; -} - -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; - } - - /* The only supported unary expression is not (!) which returns a bool */ - node->node.type = ecs_id(ecs_bool_t); - - 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; - } - } - - return 0; -error: - return -1; -} - -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; - - /* Resulting type of binary expression */ - ecs_entity_t result_type = 0; - - 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); - - /* 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); - } - } - - if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { - goto error; - } - - if (flecs_expr_visit_type_priv(script, node->right, cur, desc)) { - goto error; - } - - if (flecs_expr_type_for_binary_expr( - script, node, &operand_type, &result_type)) - { - goto error; - } - - 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_script_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; - } - } - - if (operand_type != node->right->type) { - node->right = (ecs_expr_node_t*)flecs_expr_cast( - script, node->right, operand_type); - if (!node->right) { - goto error; - } - } - - node->node.type = result_type; - - return 0; -error: - return -1; -} - -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_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; - } - - result->ptr = &result->storage.u64; - node->expr = (ecs_expr_node_t*)result; - - return 0; -error: - return -1; -} - -static -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) -{ - (void)desc; - - ecs_entity_t type = node->node.type; - if (cur->valid) { - type = ecs_meta_get_type(cur); - } - - 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 (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) { - const EcsScriptConstVar *global = ecs_get( - script->world, e, EcsScriptConstVar); - if (!global) { - if (!type) { - type = ecs_id(ecs_entity_t); - } - - ecs_expr_value_node_t *result = flecs_expr_value_from( - script, (ecs_expr_node_t*)node, type); - result->storage.entity = e; - result->ptr = &result->storage.entity; - 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; - } - - /* 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 0; -error: - return -1; -} - -static -int flecs_expr_global_variable_resolve( - ecs_script_t *script, - ecs_expr_variable_t *node, - const ecs_expr_eval_desc_t *desc) -{ - 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 -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) -{ - 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 -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) -{ - (void)cur; - - if (flecs_expr_global_variable_resolve(script, node, desc)) { - goto error; - } - - return 0; -error: - return -1; -} - -static -int flecs_expr_arguments_visit_type( - ecs_script_t *script, - ecs_expr_initializer_t *node, - const ecs_expr_eval_desc_t *desc, - const ecs_vec_t *param_vec) -{ - ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); - int32_t i, count = ecs_vec_count(&node->elements); - - if (count != ecs_vec_count(param_vec)) { - flecs_expr_visit_error(script, node, "expected %d arguments, got %d", - ecs_vec_count(param_vec), count); - goto error; - } - - ecs_script_parameter_t *params = ecs_vec_first(param_vec); - - for (i = 0; i < count; i ++) { - ecs_expr_initializer_element_t *elem = &elems[i]; - - ecs_meta_cursor_t cur = ecs_meta_cursor( - script->world, params[i].type, NULL); - - if (flecs_expr_visit_type_priv(script, elem->value, &cur, desc)){ - goto error; - } - - if (elem->value->type != params[i].type) { - elem->value = (ecs_expr_node_t*)flecs_expr_cast( - script, elem->value, params[i].type); - if (!elem->value) { - goto error; - } - } - } - - return 0; -error: - return -1; -} - -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) -{ - bool is_method = false; - char *last_elem = NULL; - const char *func_identifier = NULL; - - 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; - - 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; - } - - } 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; - - 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; - } - - /* 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); - - if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { - goto error; - } - - ecs_world_t *world = script->world; - const ecs_vec_t *params = 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; - } - - const EcsScriptMethod *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.callback = func_data->callback; - node->calldata.ctx = func_data->ctx; - params = &func_data->params; - } - -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; - } - - const EcsScriptFunction *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.callback = func_data->callback; - node->calldata.ctx = func_data->ctx; - params = &func_data->params; - } - - if (flecs_expr_arguments_visit_type(script, node->args, desc, params)) { - goto error; - } - - return 0; -error: - return -1; -} - -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 (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { - goto error; - } - - ecs_world_t *world = script->world; - ecs_entity_t left_type = node->left->type; - - 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; - } - - 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; - } - - if (ecs_meta_push(cur)) { - goto error; - } - - 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); - - node->node.type = ecs_meta_get_type(cur); -#ifdef FLECS_DEBUG - const EcsMember *m = ecs_get(world, ecs_meta_get_member_id(cur), EcsMember); - ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); -#endif - node->offset = (uintptr_t)ecs_meta_get_ptr(cur); - - return 0; -error: - return -1; -} - -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) -{ - if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { - goto error; - } - - ecs_meta_cursor_t index_cur = {0}; - if (flecs_expr_visit_type_priv( - script, node->index, &index_cur, desc)) - { - goto error; - } - - ecs_world_t *world = script->world; - ecs_entity_t left_type = node->left->type; - - 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; - } - - 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 (is_entity_type) { - if (node->index->kind == EcsExprIdentifier) { - 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); - } else { - flecs_expr_visit_error(script, node, - "invalid component expression"); - goto error; - } - } else { - if (ecs_meta_push(cur)) { - goto not_a_collection; - } - - if (!ecs_meta_is_collection(cur)) { - goto not_a_collection; - } - - node->node.type = ecs_meta_get_type(cur); - - const ecs_type_info_t *elem_ti = ecs_get_type_info( - script->world, node->node.type); - node->elem_size = elem_ti->size; - } - - return 0; - -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); -} -error: - return -1; -} - -static -bool flecs_expr_identifier_is_any( - ecs_expr_node_t *node) -{ - 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; -} - -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_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); - - 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; - } - - int32_t i, count = ecs_vec_count(&node->elements); - ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); - - if (!count) { - flecs_expr_visit_error(script, node, - "match statement must have at least one case"); - goto error; - } - - /* Determine most expressive type of all elements */ - node->node.type = ecs_meta_get_type(cur); - - for (i = 0; i < count; i ++) { - ecs_expr_match_element_t *elem = &elems[i]; - - if (node->node.type) { - expr_cur = ecs_meta_cursor(script->world, node->node.type, NULL); - } else { - ecs_os_zeromem(&expr_cur); - } - - 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)) - { - goto error; - } - - /* "Accumulate" most expressive type in result node */ - node->node.type = result_type; - } else { - /* 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; - } - } - } - - /* 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; - } - } - } - - /* 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); - } - - /* Make sure that case values match the input type */ - for (i = 0; i < count; i ++) { - ecs_expr_match_element_t *elem = &elems[i]; - - 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; - } - - 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; - } - - 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; - } - } - } - } - - return 0; -error: - return -1; -} - -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 EcsExprCast: - case EcsExprCastNumber: - break; - case EcsExprMethod: - case EcsExprComponent: - /* Expressions are derived by type visitor */ - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } - - 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; -} - -int flecs_expr_visit_type( - ecs_script_t *script, - ecs_expr_node_t *node, - const ecs_expr_eval_desc_t *desc) -{ - if (node->kind == EcsExprEmptyInitializer) { - node->type = desc->type; - if (node->type) { - if (flecs_expr_initializer_collection_check( - script, (ecs_expr_initializer_t*)node, NULL)) - { - return -1; - } - - node->type_info = ecs_get_type_info(script->world, node->type); - return 0; - } - } - - 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 deleted file mode 100644 index 867de1c..0000000 --- a/deps/flecs.h +++ /dev/null @@ -1,33512 +0,0 @@ -// Comment out this line when using as DLL -#define flecs_STATIC -/** - * @file flecs.h - * @brief Flecs public API. - * - * This file contains the public API for Flecs. - */ - -#ifndef FLECS_H -#define FLECS_H - -/** - * @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. - * - * @{ - */ - -/* Flecs version macros */ -#define FLECS_VERSION_MAJOR 4 /**< Flecs major version. */ -#define FLECS_VERSION_MINOR 0 /**< 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 - -/** @def FLECS_LEGACY - * Define when building for C89 - */ -// #define FLECS_LEGACY - -/** @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" -#endif -#if defined(FLECS_DEBUG) && defined(NDEBUG) -#error "invalid configuration: cannot both define FLECS_DEBUG and NDEBUG" -#endif - -/** @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 -#define FLECS_DEBUG -#endif -#endif - -/** @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 - -/* 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! */ - -/** @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 - -/** @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 - -/** @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_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 - * blacklisted addon, an error will be thrown during the build. - * - * 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: - * - * @code - * ecs_log_set_level(0); - * @endcode - * - * which outputs the full list of addons Flecs was compiled with. - */ -// #define FLECS_CUSTOM_BUILD - -#ifndef FLECS_CUSTOM_BUILD -#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 (disabled by default) */ -#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 (disabled by default) */ -#define FLECS_PIPELINE /**< Pipeline support */ -#define FLECS_REST /**< REST API for querying application data */ -#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_SPARSE_PAGE_BITS (4) -#define FLECS_ENTITY_PAGE_BITS (6) -#define FLECS_USE_OS_ALLOC -#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. */ -#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 id 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 (12) -#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 - -/** @} */ - -/** - * @file api_defines.h - * @brief Supporting defines for the public API. - * - * 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. - */ - -#ifndef FLECS_API_DEFINES_H -#define FLECS_API_DEFINES_H - -/** - * @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) - - -//////////////////////////////////////////////////////////////////////////////// -//// Id flags (used by ecs_id_record_t::flags) -//////////////////////////////////////////////////////////////////////////////// - -#define EcsIdOnDeleteRemove (1u << 0) -#define EcsIdOnDeleteDelete (1u << 1) -#define EcsIdOnDeletePanic (1u << 2) -#define EcsIdOnDeleteMask\ - (EcsIdOnDeletePanic|EcsIdOnDeleteRemove|EcsIdOnDeleteDelete) - -#define EcsIdOnDeleteObjectRemove (1u << 3) -#define EcsIdOnDeleteObjectDelete (1u << 4) -#define EcsIdOnDeleteObjectPanic (1u << 5) -#define EcsIdOnDeleteObjectMask\ - (EcsIdOnDeleteObjectPanic|EcsIdOnDeleteObjectRemove|\ - EcsIdOnDeleteObjectDelete) - -#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 EcsIdTag (1u << 11) -#define EcsIdWith (1u << 12) -#define EcsIdCanToggle (1u << 13) -#define EcsIdIsTransitive (1u << 14) - -#define EcsIdHasOnAdd (1u << 16) /* Same values as table flags */ -#define EcsIdHasOnRemove (1u << 17) -#define EcsIdHasOnSet (1u << 18) -#define EcsIdHasOnTableFill (1u << 19) -#define EcsIdHasOnTableEmpty (1u << 20) -#define EcsIdHasOnTableCreate (1u << 21) -#define EcsIdHasOnTableDelete (1u << 22) -#define EcsIdIsSparse (1u << 23) -#define EcsIdIsUnion (1u << 24) -#define EcsIdEventMask\ - (EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet|\ - EcsIdHasOnTableFill|EcsIdHasOnTableEmpty|EcsIdHasOnTableCreate|\ - EcsIdHasOnTableDelete|EcsIdIsSparse|EcsIdIsUnion) - -#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))) - - -//////////////////////////////////////////////////////////////////////////////// -//// 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 << 3u) /* Iterator has no results */ -#define EcsIterIgnoreThis (1u << 4u) /* Only evaluate non-this terms */ -#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 EcsIterCacheSearch (1u << 15u) /* Cache search */ -#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 */ - -/* 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 EcsQueryHasMonitor (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 EcsQueryNested (1u << 28u) /* Query created by a query (for observer, cache) */ - -//////////////////////////////////////////////////////////////////////////////// -//// 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 << 7) -#define EcsTermIsScope (1u << 8) -#define EcsTermIsMember (1u << 9) -#define EcsTermIsToggle (1u << 10) -#define EcsTermKeepAlive (1u << 11) -#define EcsTermIsSparse (1u << 12) -#define EcsTermIsUnion (1u << 13) -#define EcsTermIsOr (1u << 14) - - -//////////////////////////////////////////////////////////////////////////////// -//// Observer flags (used by ecs_observer_t::flags) -//////////////////////////////////////////////////////////////////////////////// - -#define EcsObserverIsMulti (1u << 1u) /* Does observer have multiple terms */ -#define EcsObserverIsMonitor (1u << 2u) /* Is observer a monitor */ -#define EcsObserverIsDisabled (1u << 3u) /* Is observer entity disabled */ -#define EcsObserverIsParentDisabled (1u << 4u) /* Is module parent of observer disabled */ -#define EcsObserverBypassQuery (1u << 5u) /* Don't evaluate query for multi-component observer*/ -#define EcsObserverYieldOnCreate (1u << 6u) /* Yield matching entities when creating observer */ -#define EcsObserverYieldOnDelete (1u << 7u) /* Yield matching entities when deleting observer */ - - -//////////////////////////////////////////////////////////////////////////////// -//// Table flags (used by ecs_table_t::flags) -//////////////////////////////////////////////////////////////////////////////// - -#define EcsTableHasBuiltins (1u << 1u) /* Does table have builtin components */ -#define EcsTableIsPrefab (1u << 2u) /* Does the table store prefabs */ -#define EcsTableHasIsA (1u << 3u) /* Does the table have IsA relationship */ -#define EcsTableHasChildOf (1u << 4u) /* Does the table type ChildOf relationship */ -#define EcsTableHasName (1u << 5u) /* Does the table type have (Identifier, Name) */ -#define EcsTableHasPairs (1u << 6u) /* Does the table type have pairs */ -#define EcsTableHasModule (1u << 7u) /* Does the table have module data */ -#define EcsTableIsDisabled (1u << 8u) /* Does the table type has EcsDisabled */ -#define EcsTableNotQueryable (1u << 9u) /* Table should never be returned by queries */ -#define EcsTableHasCtors (1u << 10u) -#define EcsTableHasDtors (1u << 11u) -#define EcsTableHasCopy (1u << 12u) -#define EcsTableHasMove (1u << 13u) -#define EcsTableHasToggle (1u << 14u) -#define EcsTableHasOverrides (1u << 15u) - -#define EcsTableHasOnAdd (1u << 16u) /* Same values as id flags */ -#define EcsTableHasOnRemove (1u << 17u) -#define EcsTableHasOnSet (1u << 18u) -#define EcsTableHasOnTableFill (1u << 19u) -#define EcsTableHasOnTableEmpty (1u << 20u) -#define EcsTableHasOnTableCreate (1u << 21u) -#define EcsTableHasOnTableDelete (1u << 22u) -#define EcsTableHasSparse (1u << 23u) -#define EcsTableHasUnion (1u << 24u) - -#define EcsTableHasTraversable (1u << 26u) -#define EcsTableMarkedForDelete (1u << 30u) - -/* 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 | EcsTableHasUnion) -#define EcsTableAddEdgeFlags (EcsTableHasOnAdd | EcsTableHasSparse | EcsTableHasUnion) -#define EcsTableRemoveEdgeFlags (EcsTableHasOnRemove | EcsTableHasSparse | EcsTableHasUnion) - -//////////////////////////////////////////////////////////////////////////////// -//// 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(__ANDROID__) -#define ECS_TARGET_ANDROID -#define ECS_TARGET_POSIX -#elif defined(__linux__) -#define ECS_TARGET_LINUX -#define ECS_TARGET_POSIX -#elif defined(__FreeBSD__) -#define ECS_TARGET_FREEBSD -#define ECS_TARGET_POSIX -#elif defined(__APPLE__) && defined(__MACH__) -#define ECS_TARGET_DARWIN -#define ECS_TARGET_POSIX -#elif defined(__EMSCRIPTEN__) -#define ECS_TARGET_EM -#define ECS_TARGET_POSIX -#endif - -#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 - -/* Non-standard but required. If not provided by platform, add manually. */ -#include - -/* Contains macros for importing / exporting symbols */ -/* - ) - (.) - .|. - | | - _.--| |--._ - .-'; ;`-'& ; `&. - \ & ; & &_/ - |"""---...---"""| - \ | | | | | | | / - `---.|.|.|.---' - - * This file is generated by bake.lang.c for your convenience. Headers of - * dependencies will automatically show up in this file. Include bake_config.h - * in your main project file. Do not edit! */ - -#ifndef FLECS_BAKE_CONFIG_H -#define FLECS_BAKE_CONFIG_H - -/* Headers of public dependencies */ -/* No dependencies */ - -/* Convenience macro for exporting symbols */ -#ifndef flecs_STATIC -#if defined(flecs_EXPORTS) && (defined(_MSC_VER) || defined(__MINGW32__)) - #define FLECS_API __declspec(dllexport) -#elif defined(flecs_EXPORTS) - #define FLECS_API __attribute__((__visibility__("default"))) -#elif defined(_MSC_VER) - #define FLECS_API __declspec(dllimport) -#else - #define FLECS_API -#endif -#else - #define FLECS_API -#endif - -#endif - - - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __BAKE_LEGACY__ -#define FLECS_LEGACY -#endif - -/* Some symbols are only exported when building in debug build, to enable - * white-box testing of internal data structures */ -#ifndef FLECS_NDEBUG -#define FLECS_DBG_API FLECS_API -#else -#define FLECS_DBG_API -#endif - - -//////////////////////////////////////////////////////////////////////////////// -//// Language support defines -//////////////////////////////////////////////////////////////////////////////// - -#ifndef FLECS_LEGACY -#include -#endif - -#ifndef NULL -#define NULL ((void*)0) -#endif - -/* The API uses the native bool type in C++, or a custom one in C */ -#if !defined(__cplusplus) && !defined(__bool_true_false_are_defined) -#undef bool -#undef true -#undef false -typedef char bool; -#define false 0 -#define true !false -#endif - -/* Utility types to indicate usage as bitmask */ -typedef uint8_t ecs_flags8_t; -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. */ -#ifdef __cplusplus -#define ECS_ALIGNOF(T) static_cast(alignof(T)) -#elif defined(ECS_TARGET_MSVC) -#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 - -#ifndef FLECS_NO_DEPRECATED_WARNINGS -#if defined(ECS_TARGET_GNU) -#define ECS_DEPRECATED(msg) __attribute__((deprecated(msg))) -#elif defined(ECS_TARGET_MSVC) -#define ECS_DEPRECATED(msg) __declspec(deprecated(msg)) -#else -#define ECS_DEPRECATED(msg) -#endif -#else -#define ECS_DEPRECATED(msg) -#endif - -#define ECS_ALIGN(size, alignment) (ecs_size_t)((((((size_t)size) - 1) / ((size_t)alignment)) + 1) * ((size_t)alignment)) - -/* Simple utility for determining the max of two values */ -#define ECS_MAX(a, b) (((a) > (b)) ? a : b) -#define ECS_MIN(a, b) (((a) < (b)) ? a : b) - -/* Abstraction on top of C-style casts so that C functions can be used in C++ - * code without producing warnings */ -#ifndef __cplusplus -#define ECS_CAST(T, V) ((T)(V)) -#else -#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 macros -//////////////////////////////////////////////////////////////////////////////// - -#define ECS_ROW_MASK (0x0FFFFFFFu) -#define ECS_ROW_FLAGS_MASK (~ECS_ROW_MASK) -#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_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_ID_FLAGS_MASK) -#define ECS_HAS_ID_FLAG(e, flag) ((e) & ECS_##flag) -#define ECS_IS_PAIR(id) (((id) & ECS_ID_FLAGS_MASK) == ECS_PAIR) -#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_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_ID##T##ID_ - - -//////////////////////////////////////////////////////////////////////////////// -//// Utilities for working with pair identifiers -//////////////////////////////////////////////////////////////////////////////// - -#define ecs_entity_t_lo(value) ECS_CAST(uint32_t, value) -#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_pair_t(pred, obj) (ECS_PAIR | ecs_entity_t_comb(obj, ecs_id(pred))) -#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_target ecs_pair_second - -#define flecs_poly_id(tag) ecs_pair(ecs_id(EcsPoly), tag) - - -//////////////////////////////////////////////////////////////////////////////// -//// Debug macros -//////////////////////////////////////////////////////////////////////////////// - -#ifndef FLECS_NDEBUG -#define ECS_TABLE_LOCK(world, table) ecs_table_lock(world, table) -#define ECS_TABLE_UNLOCK(world, table) ecs_table_unlock(world, table) -#else -#define ECS_TABLE_LOCK(world, table) -#define ECS_TABLE_UNLOCK(world, table) -#endif - - -//////////////////////////////////////////////////////////////////////////////// -//// Actions that drive iteration -//////////////////////////////////////////////////////////////////////////////// - -#define EcsIterNextYield (0) /* Move to next table, yield current */ -#define EcsIterYield (-1) /* Stay on current table, yield */ -#define EcsIterNext (1) /* Move to next table, don't yield */ - -//////////////////////////////////////////////////////////////////////////////// -//// Convenience macros for ctor, dtor, move and copy -//////////////////////////////////////////////////////////////////////////////// - -#ifndef FLECS_LEGACY - -/* Constructor/Destructor convenience macro */ -#define ECS_XTOR_IMPL(type, postfix, var, ...)\ - void type##_##postfix(\ - void *_ptr,\ - int32_t _count,\ - const ecs_type_info_t *type_info)\ - {\ - (void)_ptr;\ - (void)_count;\ - (void)type_info;\ - for (int32_t i = 0; i < _count; i ++) {\ - type *var = &((type*)_ptr)[i];\ - (void)var;\ - __VA_ARGS__\ - }\ - } - -/* Copy convenience macro */ -#define ECS_COPY_IMPL(type, dst_var, src_var, ...)\ - void type##_##copy(\ - void *_dst_ptr,\ - const void *_src_ptr,\ - int32_t _count,\ - const ecs_type_info_t *type_info)\ - {\ - (void)_dst_ptr;\ - (void)_src_ptr;\ - (void)_count;\ - (void)type_info;\ - for (int32_t i = 0; i < _count; i ++) {\ - type *dst_var = &((type*)_dst_ptr)[i];\ - const type *src_var = &((const type*)_src_ptr)[i];\ - (void)dst_var;\ - (void)src_var;\ - __VA_ARGS__\ - }\ - } - -/* Move convenience macro */ -#define ECS_MOVE_IMPL(type, dst_var, src_var, ...)\ - void type##_##move(\ - void *_dst_ptr,\ - void *_src_ptr,\ - int32_t _count,\ - const ecs_type_info_t *type_info)\ - {\ - (void)_dst_ptr;\ - (void)_src_ptr;\ - (void)_count;\ - (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];\ - (void)dst_var;\ - (void)src_var;\ - __VA_ARGS__\ - }\ - } - -#define ECS_HOOK_IMPL(type, func, var, ...)\ - void func(ecs_iter_t *_it)\ - {\ - for (int32_t i = 0; i < _it->count; i ++) {\ - ecs_entity_t entity = _it->entities[i];\ - type *var = ecs_field(_it, type, 0);\ - (void)entity;\ - (void)var;\ - __VA_ARGS__\ - }\ - } - -#endif - -#ifdef __cplusplus -} -#endif - -#endif - -/** - * @file vec.h - * @brief Vector with allocator support. - */ - -#ifndef FLECS_VEC_H -#define FLECS_VEC_H - - -#ifdef __cplusplus -extern "C" { -#endif - -/** 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; - -FLECS_API -void ecs_vec_init( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size, - int32_t elem_count); - -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_vec_init_t(allocator, vec, T, elem_count) \ - ecs_vec_init_w_dbg_info(allocator, vec, ECS_SIZEOF(T), elem_count, "vec<"#T">") - -FLECS_API -void ecs_vec_init_if( - ecs_vec_t *vec, - ecs_size_t size); - -#define ecs_vec_init_if_t(vec, T) \ - ecs_vec_init_if(vec, ECS_SIZEOF(T)) - -FLECS_API -void ecs_vec_fini( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size); - -#define ecs_vec_fini_t(allocator, vec, T) \ - ecs_vec_fini(allocator, vec, ECS_SIZEOF(T)) - -FLECS_API -ecs_vec_t* ecs_vec_reset( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size); - -#define ecs_vec_reset_t(allocator, vec, T) \ - ecs_vec_reset(allocator, vec, ECS_SIZEOF(T)) - -FLECS_API -void ecs_vec_clear( - ecs_vec_t *vec); - -FLECS_API -void* ecs_vec_append( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size); - -#define ecs_vec_append_t(allocator, vec, T) \ - ECS_CAST(T*, ecs_vec_append(allocator, vec, ECS_SIZEOF(T))) - -FLECS_API -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) - -FLECS_API -void ecs_vec_remove_last( - ecs_vec_t *vec); - -FLECS_API -ecs_vec_t ecs_vec_copy( - struct ecs_allocator_t *allocator, - const ecs_vec_t *vec, - ecs_size_t size); - -#define ecs_vec_copy_t(allocator, vec, T) \ - ecs_vec_copy(allocator, vec, ECS_SIZEOF(T)) - -FLECS_API -ecs_vec_t ecs_vec_copy_shrink( - struct ecs_allocator_t *allocator, - const ecs_vec_t *vec, - ecs_size_t size); - -#define ecs_vec_copy_shrink_t(allocator, vec, T) \ - ecs_vec_copy_shrink(allocator, vec, ECS_SIZEOF(T)) - -FLECS_API -void ecs_vec_reclaim( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size); - -#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_vec_set_size_t(allocator, vec, T, elem_count) \ - ecs_vec_set_size(allocator, vec, ECS_SIZEOF(T), elem_count) - -FLECS_API -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_vec_set_min_size_t(allocator, vec, T, elem_count) \ - ecs_vec_set_min_size(allocator, vec, ECS_SIZEOF(T), elem_count) - -FLECS_API -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_vec_set_min_count_t(allocator, vec, T, elem_count) \ - ecs_vec_set_min_count(allocator, vec, ECS_SIZEOF(T), elem_count) - -FLECS_API -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_vec_set_min_count_zeromem_t(allocator, vec, T, elem_count) \ - ecs_vec_set_min_count_zeromem(allocator, vec, ECS_SIZEOF(T), elem_count) - -FLECS_API -void ecs_vec_set_count( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size, - int32_t elem_count); - -#define ecs_vec_set_count_t(allocator, vec, T, elem_count) \ - ecs_vec_set_count(allocator, vec, ECS_SIZEOF(T), elem_count) - -FLECS_API -void* ecs_vec_grow( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size, - int32_t elem_count); - -#define ecs_vec_grow_t(allocator, vec, T, elem_count) \ - ecs_vec_grow(allocator, vec, ECS_SIZEOF(T), elem_count) - -FLECS_API -int32_t ecs_vec_count( - const ecs_vec_t *vec); - -FLECS_API -int32_t ecs_vec_size( - const ecs_vec_t *vec); - -FLECS_API -void* ecs_vec_get( - const ecs_vec_t *vec, - ecs_size_t size, - int32_t index); - -#define ecs_vec_get_t(vec, T, index) \ - ECS_CAST(T*, ecs_vec_get(vec, ECS_SIZEOF(T), index)) - -FLECS_API -void* ecs_vec_first( - const ecs_vec_t *vec); - -#define ecs_vec_first_t(vec, T) \ - ECS_CAST(T*, ecs_vec_first(vec)) - -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))) - -#ifdef __cplusplus -} -#endif - -#endif - -/** - * @file sparse.h - * @brief Sparse set data structure. - */ - -#ifndef FLECS_SPARSE_H -#define FLECS_SPARSE_H - - -#ifdef __cplusplus -extern "C" { -#endif - -/** The number of elements in a single page */ -#define FLECS_SPARSE_PAGE_SIZE (1 << FLECS_SPARSE_PAGE_BITS) - -/** 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)) - -/** This computes the offset of an index inside a page */ -#define FLECS_SPARSE_OFFSET(index) ((int32_t)index & (FLECS_SPARSE_PAGE_SIZE - 1)) - -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. */ - - 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; - -/** 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); - -#define flecs_sparse_init_t(result, allocator, page_allocator, T)\ - flecs_sparse_init(result, allocator, page_allocator, ECS_SIZEOF(T)) - -FLECS_DBG_API -void flecs_sparse_fini( - ecs_sparse_t *sparse); - -/** Remove all elements from sparse set */ -FLECS_DBG_API -void flecs_sparse_clear( - ecs_sparse_t *sparse); - -/** 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); - -#define flecs_sparse_add_t(sparse, T)\ - ECS_CAST(T*, flecs_sparse_add(sparse, ECS_SIZEOF(T))) - -/** Get last issued id. */ -FLECS_DBG_API -uint64_t flecs_sparse_last_id( - const ecs_sparse_t *sparse); - -/** Generate or recycle a new id. */ -FLECS_DBG_API -uint64_t flecs_sparse_new_id( - ecs_sparse_t *sparse); - -/** Remove an element */ -FLECS_DBG_API -void flecs_sparse_remove( - ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); - -#define flecs_sparse_remove_t(sparse, T, id)\ - flecs_sparse_remove(sparse, ECS_SIZEOF(T), id) - -/** Remove an element without liveliness checking */ -FLECS_DBG_API -void* flecs_sparse_remove_fast( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index); - -/** 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); - -/** 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); - -#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); - -/** 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); - -#define flecs_sparse_get_t(sparse, T, index)\ - ECS_CAST(T*, flecs_sparse_get(sparse, ECS_SIZEOF(T), index)) - -/** Same as flecs_sparse_get, but doesn't assert if id is not alive. */ -FLECS_DBG_API -void* flecs_sparse_try( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); - -#define flecs_sparse_try_t(sparse, T, index)\ - ECS_CAST(T*, flecs_sparse_try(sparse, ECS_SIZEOF(T), index)) - -/** 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); - -#define flecs_sparse_get_any_t(sparse, T, index)\ - ECS_CAST(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); - -#define flecs_sparse_ensure_t(sparse, T, index)\ - ECS_CAST(T*, flecs_sparse_ensure(sparse, ECS_SIZEOF(T), index)) - -/** 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); - -/* 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 { - ecs_block_allocator_chunk_header_t *head; - ecs_block_allocator_block_t *block_head; - ecs_block_allocator_block_t *block_tail; - int32_t chunk_size; - int32_t data_size; - int32_t chunks_per_block; - int32_t block_size; -#ifdef FLECS_SANITIZE - int32_t alloc_count; - ecs_map_t *outstanding; -#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 */ -#define ECS_STACK_PAGE_SIZE (4096) - -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; - -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 - -/** - * @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_bucket_entry_t *first; -} ecs_bucket_t; - -struct ecs_map_t { - uint8_t bucket_shift; - bool shared_allocator; - ecs_bucket_t *buckets; - int32_t bucket_count; - int32_t count; - struct ecs_block_allocator_t *entry_allocator; - struct ecs_allocator_t *allocator; -}; - -typedef struct ecs_map_iter_t { - const ecs_map_t *map; - ecs_bucket_t *bucket; - ecs_bucket_entry_t *entry; - ecs_map_data_t *res; -} ecs_map_iter_t; - -typedef struct ecs_map_params_t { - struct ecs_allocator_t *allocator; - struct ecs_block_allocator_t entry_allocator; -} ecs_map_params_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 - */ - -FLECS_API -void ecs_map_params_init( - ecs_map_params_t *params, - struct ecs_allocator_t *allocator); - -FLECS_API -void ecs_map_params_fini( - ecs_map_params_t *params); - -/** Initialize new map. */ -FLECS_API -void ecs_map_init( - ecs_map_t *map, - struct ecs_allocator_t *allocator); - -/** Initialize new map. */ -FLECS_API -void ecs_map_init_w_params( - ecs_map_t *map, - ecs_map_params_t *params); - -/** 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); - -FLECS_API -void ecs_map_init_w_params_if( - ecs_map_t *result, - ecs_map_params_t *params); - -/** Deinitialize map. */ -FLECS_API -void ecs_map_fini( - ecs_map_t *map); - -/** Get element for key, returns NULL if they key doesn't exist. */ -FLECS_API -ecs_map_val_t* ecs_map_get( - const ecs_map_t *map, - ecs_map_key_t key); - -/* Get element as pointer (auto-dereferences _ptr) */ -FLECS_API -void* ecs_map_get_deref_( - const ecs_map_t *map, - ecs_map_key_t key); - -/** Get or insert element for key. */ -FLECS_API -ecs_map_val_t* ecs_map_ensure( - ecs_map_t *map, - ecs_map_key_t key); - -/** Get or insert pointer element for key, allocate if the pointer is NULL */ -FLECS_API -void* ecs_map_ensure_alloc( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key); - -/** Insert element for key. */ -FLECS_API -void ecs_map_insert( - ecs_map_t *map, - ecs_map_key_t key, - ecs_map_val_t value); - -/** 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); - -/** Remove key from map. */ -FLECS_API -ecs_map_val_t ecs_map_remove( - ecs_map_t *map, - ecs_map_key_t key); - -/* Remove pointer element, free if not NULL */ -FLECS_API -void ecs_map_remove_free( - ecs_map_t *map, - ecs_map_key_t key); - -/** Remove all elements from map. */ -FLECS_API -void ecs_map_clear( - ecs_map_t *map); - -/** Return number of elements in map. */ -#define ecs_map_count(map) ((map) ? (map)->count : 0) - -/** 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 -bool ecs_map_next( - ecs_map_iter_t *iter); - -/** Copy map. */ -FLECS_API -void ecs_map_copy( - ecs_map_t *dst, - const ecs_map_t *src); - -#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_ensure_ref(m, T, k) ECS_CAST(T**, ecs_map_ensure(m, k)) - -#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 -} -#endif - -#endif - -/** - * @file switch_list.h - * @brief Interleaved linked list for storing mutually exclusive values. - */ - -#ifndef FLECS_SWITCH_LIST_H -#define FLECS_SWITCH_LIST_H - - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct ecs_switch_node_t { - uint32_t next; /* Next node in list */ - uint32_t prev; /* Prev node in list */ -} ecs_switch_node_t; - -typedef struct ecs_switch_page_t { - ecs_vec_t nodes; /* vec */ - ecs_vec_t values; /* vec */ -} ecs_switch_page_t; - -typedef struct ecs_switch_t { - ecs_map_t hdrs; /* map */ - ecs_vec_t pages; /* vec */ -} ecs_switch_t; - -/** Init new switch. */ -FLECS_DBG_API -void flecs_switch_init( - ecs_switch_t* sw, - ecs_allocator_t *allocator); - -/** Fini switch. */ -FLECS_DBG_API -void flecs_switch_fini( - ecs_switch_t *sw); - -/** Set value of element. */ -FLECS_DBG_API -bool flecs_switch_set( - ecs_switch_t *sw, - uint32_t element, - uint64_t value); - -/** Reset value of element. */ -FLECS_DBG_API -bool flecs_switch_reset( - ecs_switch_t *sw, - uint32_t element); - -/** Get value for element. */ -FLECS_DBG_API -uint64_t flecs_switch_get( - const ecs_switch_t *sw, - uint32_t element); - -/** Get first element for value. */ -FLECS_DBG_API -uint32_t flecs_switch_first( - const ecs_switch_t *sw, - uint64_t value); - -/** Get next element. */ -FLECS_DBG_API -uint32_t flecs_switch_next( - const ecs_switch_t *sw, - uint32_t previous); - -/** Get target iterator. */ -FLECS_DBG_API -ecs_map_iter_t flecs_switch_targets( - const ecs_switch_t *sw); - -#ifdef __cplusplus -} -#endif - -#endif - -/** - * @file allocator.h - * @brief Allocator that returns memory objects of any size. - */ - -#ifndef FLECS_ALLOCATOR_H -#define FLECS_ALLOCATOR_H - - -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 { - ecs_block_allocator_t chunks; - struct ecs_sparse_t sizes; /* */ -}; - -FLECS_API -void flecs_allocator_init( - ecs_allocator_t *a); - -FLECS_API -void flecs_allocator_fini( - ecs_allocator_t *a); - -FLECS_API -ecs_block_allocator_t* flecs_allocator_get( - ecs_allocator_t *a, - ecs_size_t size); - -FLECS_API -char* flecs_strdup( - ecs_allocator_t *a, - const char* str); - -FLECS_API -void flecs_strfree( - ecs_allocator_t *a, - char* str); - -FLECS_API -void* flecs_dup( - ecs_allocator_t *a, - ecs_size_t size, - const void *src); - -#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) - -#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 -void ecs_strbuf_append( - ecs_strbuf_t *buffer, - const char *fmt, - ...); - -/* Append format string with argument list to a buffer. - * Returns false when max is reached, true when there is still space */ -FLECS_API -void ecs_strbuf_vappend( - ecs_strbuf_t *buffer, - const char *fmt, - va_list args); - -/* Append string to buffer. - * Returns false when max is reached, true when there is still space */ -FLECS_API -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 -void ecs_strbuf_appendch( - ecs_strbuf_t *buffer, - char ch); - -/* Append int to buffer. - * Returns false when max is reached, true when there is still space */ -FLECS_API -void ecs_strbuf_appendint( - ecs_strbuf_t *buffer, - int64_t v); - -/* Append float to buffer. - * Returns false when max is reached, true when there is still space */ -FLECS_API -void ecs_strbuf_appendflt( - ecs_strbuf_t *buffer, - double v, - char nan_delim); - -/* Append boolean to buffer. - * Returns false when max is reached, true when there is still space */ -FLECS_API -void ecs_strbuf_appendbool( - ecs_strbuf_t *buffer, - bool v); - -/* Append source buffer to destination buffer. - * Returns false when max is reached, true when there is still space */ -FLECS_API -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 -void ecs_strbuf_appendstrn( - ecs_strbuf_t *buffer, - const char *str, - int32_t n); - -/* Return result string */ -FLECS_API -char* ecs_strbuf_get( - ecs_strbuf_t *buffer); - -/* Return small string from first element (appends \0) */ -FLECS_API -char* ecs_strbuf_get_small( - ecs_strbuf_t *buffer); - -/* Reset buffer without returning a string */ -FLECS_API -void ecs_strbuf_reset( - ecs_strbuf_t *buffer); - -/* Push a list */ -FLECS_API -void ecs_strbuf_list_push( - ecs_strbuf_t *buffer, - const char *list_open, - const char *separator); - -/* Pop a new list */ -FLECS_API -void ecs_strbuf_list_pop( - ecs_strbuf_t *buffer, - const char *list_close); - -/* Insert a new element in list */ -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 -void ecs_strbuf_list_append( - ecs_strbuf_t *buffer, - const char *fmt, - ...); - -/* Append string as a new element in list */ -FLECS_API -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 - -#endif - -/** - * @file os_api.h - * @brief Operating system abstraction API. - * - * 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/os_api folder. - */ - -#ifndef FLECS_OS_API_H -#define FLECS_OS_API_H - -/** - * @defgroup c_os_api OS API - * @ingroup c - * Interface for providing OS specific functionality. - * - * @{ - */ - -#include -#include -#include - -#if defined(ECS_TARGET_WINDOWS) -#include -#elif defined(ECS_TARGET_FREEBSD) -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/** Time type. */ -typedef struct ecs_time_t { - uint32_t sec; /**< Second part. */ - uint32_t nanosec; /**< Nanosecond part. */ -} ecs_time_t; - -/* 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. */ - -/* Use handle types that _at least_ can store pointers */ -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. */ -typedef void (*ecs_os_proc_t)(void); - -/** OS API init. */ -typedef -void (*ecs_os_api_init_t)(void); - -/** OS API deinit. */ -typedef -void (*ecs_os_api_fini_t)(void); - -/** OS API malloc function type. */ -typedef -void* (*ecs_os_api_malloc_t)( - ecs_size_t size); - -/** 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, - 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); - -/** 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 -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); - -/** OS API sleep function type. */ -typedef -void (*ecs_os_api_sleep_t)( - int32_t sec, - int32_t nanosec); - -/** 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); - -/** OS API log function type. */ -typedef -void (*ecs_os_api_log_t)( - int32_t level, /* Logging level */ - const char *file, /* File where message was logged */ - int32_t line, /* Line it was logged */ - const char *msg); - -/** OS API abort function type. */ -typedef -void (*ecs_os_api_abort_t)( - void); - -/** 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); - -/* 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_; /**< init callback. */ - ecs_os_api_fini_t fini_; /**< fini callback. */ - - /* Memory management */ - 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_; /**< strdup callback. */ - - /* Threads */ - 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 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_; /**< 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_; /**< 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_; /**< 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_; /**< 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_; /**< abort callback. */ - - /* Dynamic library loading */ - 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_; /**< 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_; /**< module_to_etc callback. */ - - /* Performance tracing */ - ecs_os_api_perf_trace_t perf_trace_push_; - - /* Performance tracing */ - ecs_os_api_perf_trace_t perf_trace_pop_; - - 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. */ - - ecs_flags32_t flags_; /**< OS API flags */ - - FILE *log_out_; /**< File used for logging output - * (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) -#endif -#ifndef ecs_os_free -#define ecs_os_free(ptr) ecs_os_api.free_(ptr) -#endif -#ifndef ecs_os_realloc -#define ecs_os_realloc(ptr, size) ecs_os_api.realloc_(ptr, size) -#endif -#ifndef ecs_os_calloc -#define ecs_os_calloc(size) ecs_os_api.calloc_(size) -#endif -#if defined(ECS_TARGET_WINDOWS) -#define ecs_os_alloca(size) _alloca((size_t)(size)) -#else -#define ecs_os_alloca(size) alloca((size_t)(size)) -#endif - -#define ecs_os_malloc_t(T) ECS_CAST(T*, ecs_os_malloc(ECS_SIZEOF(T))) -#define ecs_os_malloc_n(T, count) ECS_CAST(T*, ecs_os_malloc(ECS_SIZEOF(T) * (count))) -#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_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))) - -/* Strings */ -#ifndef ecs_os_strdup -#define ecs_os_strdup(str) ecs_os_api.strdup_(str) -#endif - -#ifdef __cplusplus -#define ecs_os_strlen(str) static_cast(strlen(str)) -#define ecs_os_strncmp(str1, str2, num) strncmp(str1, str2, static_cast(num)) -#define ecs_os_memcmp(ptr1, ptr2, num) memcmp(ptr1, ptr2, static_cast(num)) -#define ecs_os_memcpy(ptr1, ptr2, num) memcpy(ptr1, ptr2, static_cast(num)) -#define ecs_os_memset(ptr, value, num) memset(ptr, value, static_cast(num)) -#define ecs_os_memmove(dst, src, size) memmove(dst, src, static_cast(size)) -#else -#define ecs_os_strlen(str) (ecs_size_t)strlen(str) -#define ecs_os_strncmp(str1, str2, num) strncmp(str1, str2, (size_t)(num)) -#define ecs_os_memcmp(ptr1, ptr2, num) memcmp(ptr1, ptr2, (size_t)(num)) -#define ecs_os_memcpy(ptr1, ptr2, num) memcpy(ptr1, ptr2, (size_t)(num)) -#define ecs_os_memset(ptr, value, num) memset(ptr, value, (size_t)(num)) -#define ecs_os_memmove(dst, src, size) memmove(dst, src, (size_t)(size)) -#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) * (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) * (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)) -#define ecs_os_memdup_n(ptr, T, count) ecs_os_memdup(ptr, ECS_SIZEOF(T) * count) - -#define ecs_offset(ptr, T, index)\ - ECS_CAST(T*, ECS_OFFSET(ptr, ECS_SIZEOF(T) * index)) - -#if !defined(ECS_TARGET_POSIX) && !defined(ECS_TARGET_MINGW) -#define ecs_os_strcat(str1, str2) strcat_s(str1, INT_MAX, str2) -#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) -#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_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) -#define ecs_os_strncpy(str1, str2, len) strncpy(str1, str2, ECS_CAST(size_t, len)) -#endif - -/* Files */ -#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) -#endif - -/* 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_() -#define ecs_os_mutex_free(mutex) ecs_os_api.mutex_free_(mutex) -#define ecs_os_mutex_lock(mutex) ecs_os_api.mutex_lock_(mutex) -#define ecs_os_mutex_unlock(mutex) ecs_os_api.mutex_unlock_(mutex) - -/* Condition variable */ -#define ecs_os_cond_new() ecs_os_api.cond_new_() -#define ecs_os_cond_free(cond) ecs_os_api.cond_free_(cond) -#define ecs_os_cond_signal(cond) ecs_os_api.cond_signal_(cond) -#define ecs_os_cond_broadcast(cond) ecs_os_api.cond_broadcast_(cond) -#define ecs_os_cond_wait(cond, mutex) ecs_os_api.cond_wait_(cond, mutex) - -/* Time */ -#define ecs_os_sleep(sec, nanosec) ecs_os_api.sleep_(sec, nanosec) -#define ecs_os_now() ecs_os_api.now_() -#define ecs_os_get_time(time_out) ecs_os_api.get_time_(time_out) - -#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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** Convert errno to string. - * - * @param err The error number. - * @return A string describing the error. - */ -FLECS_API -const char* ecs_os_strerror( - int err); - -/** 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); - -/* 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 - -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. - * - * @param t The time in seconds. - */ -FLECS_API -void ecs_sleepf( - double t); - -/** 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. - * - * @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. - * - * @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, - ecs_size_t size); - -/** Are heap functions available? */ -FLECS_API -bool ecs_os_has_heap(void); - -/** Are threading functions available? */ -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); - -/** Are logging functions available? */ -FLECS_API -bool ecs_os_has_logging(void); - -/** Are dynamic library functions available? */ -FLECS_API -bool ecs_os_has_dl(void); - -/** Are module path functions available? */ -FLECS_API -bool ecs_os_has_modules(void); - -#ifdef __cplusplus -} -#endif - -/** @} */ - -#endif - - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @defgroup api_types API types - * Public API types. - * - * @{ - */ - -/** - * @defgroup core_types Core API Types - * Types for core API objects. - * - * @{ - */ - -/** 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; - -/** 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; - -/** A stage enables modification while iterating and from multiple threads */ -typedef struct ecs_stage_t ecs_stage_t; - -/** A table stores entities and components for a specific type. */ -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 returns entities matching a list of constraints. */ -typedef struct ecs_query_t ecs_query_t; - -/** 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; - -/** 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; - -/** 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; - -/** 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; - -/** 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; - -/** 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; - -/** Information about an entity, like its table and row. */ -typedef struct ecs_record_t ecs_record_t; - -/** Information about a (component) id, such as type info and tables with the id */ -typedef struct ecs_id_record_t ecs_id_record_t; - -/** 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; - -/** Type that stores poly mixins */ -typedef struct ecs_mixins_t ecs_mixins_t; - -/** 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 */ - int32_t refcount; /**< Refcount, to enable RAII handles */ - ecs_mixins_t *mixins; /**< Table with offsets to (optional) mixins */ -} ecs_header_t; - -/** Record for entity index */ -struct ecs_record_t { - ecs_id_record_t *idr; /**< Id record to (*, entity) for target entities */ - 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 */ -}; - -/** Header for table cache elements. */ -typedef struct ecs_table_cache_hdr_t { - struct ecs_table_cache_t *cache; /**< Table cache of element. Of type ecs_id_record_t* for component index elements. */ - 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; - -/** Metadata describing where a component id is stored in a table. - * This type is used as element type for the component index table cache. One - * record exists per table/component in the table. Only records for wildcard ids - * can have a count > 1. */ -typedef 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 */ -} ecs_table_record_t; - -/** @} */ - -/** - * @defgroup function_types Function types. - * Function callback types. - * - * @{ - */ - -/** 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 iterating an iterator. - * 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); - -/** 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); - -/** 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 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 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); - -/** Action callback on world exit */ -typedef void (*ecs_fini_action_t)( - ecs_world_t *world, - void *ctx); - -/** Function to cleanup context data */ -typedef void (*ecs_ctx_free_t)( - void *ctx); - -/** Callback used for sorting values */ -typedef int (*ecs_compare_action_t)( - const void *ptr1, - const void *ptr2); - -/** Callback used for hashing values */ -typedef uint64_t (*ecs_hash_value_action_t)( - const void *ptr); - -/** Constructor/destructor callback */ -typedef void (*ecs_xtor_t)( - void *ptr, - int32_t count, - const ecs_type_info_t *type_info); - -/** Copy is invoked when a component is copied into another component. */ -typedef void (*ecs_copy_t)( - void *dst_ptr, - const void *src_ptr, - int32_t count, - const ecs_type_info_t *type_info); - -/** Move is invoked when a component is moved to another component. */ -typedef void (*ecs_move_t)( - void *dst_ptr, - void *src_ptr, - int32_t count, - const ecs_type_info_t *type_info); - -/** Destructor function for poly objects. */ -typedef void (*flecs_poly_dtor_t)( - ecs_poly_t *poly); - -/** @} */ - -/** - * @defgroup query_types Query descriptor types. - * Types used to describe queries. - * - * @{ - */ - -/** Specify read/write access for term */ -typedef enum ecs_inout_kind_t { - 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; - -/** 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 */ -} ecs_oper_kind_t; - -/** 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; /**< Component id to be matched by term. Can be - * set directly, or will be populated from the - * first/second members, which provide more - * flexibility. */ - - 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_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() */ -}; - -/** 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[FLECS_TERM_COUNT_MAX]; /**< Query terms */ - int32_t sizes[FLECS_TERM_COUNT_MAX]; /**< Component sizes. Indexed by field */ - ecs_id_t ids[FLECS_TERM_COUNT_MAX]; /**< Component ids. Indexed by field */ - - 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 */ - - void *ctx; /**< User context to pass to callback */ - void *binding_ctx; /**< Context to be used for language bindings */ - - 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. */ - - int32_t eval_count; /**< Number of times query is evaluated */ -}; - -/** An observer reacts to events matching a query. - * Created with ecs_observer_init(). - */ -struct ecs_observer_t { - ecs_header_t hdr; /**< Object header */ - - 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 */ - - void *ctx; /**< Observer context */ - void *callback_ctx; /**< Callback language binding context */ - void *run_ctx; /**< Run language binding context */ - - 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 */ - - ecs_observable_t *observable; /**< Observable for observer */ - - ecs_world_t *world; /**< The world */ - ecs_entity_t entity; /**< Entity associated with observer */ -}; - -/** @} */ - -/** 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 (1 << 0) -#define ECS_TYPE_HOOK_DTOR (1 << 1) -#define ECS_TYPE_HOOK_COPY (1 << 2) -#define ECS_TYPE_HOOK_MOVE (1 << 3) -#define ECS_TYPE_HOOK_COPY_CTOR (1 << 4) -#define ECS_TYPE_HOOK_MOVE_CTOR (1 << 5) -#define ECS_TYPE_HOOK_CTOR_MOVE_DTOR (1 << 6) -#define ECS_TYPE_HOOK_MOVE_DTOR (1 << 7) - -/* Flags that can be used to set/check which hooks of a type are invalid */ -#define ECS_TYPE_HOOK_CTOR_ILLEGAL (1 << 8) -#define ECS_TYPE_HOOK_DTOR_ILLEGAL (1 << 9) -#define ECS_TYPE_HOOK_COPY_ILLEGAL (1 << 10) -#define ECS_TYPE_HOOK_MOVE_ILLEGAL (1 << 11) -#define ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL (1 << 12) -#define ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL (1 << 13) -#define ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL (1 << 14) -#define ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL (1 << 15) - -/* 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) - -/* 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) - -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; - - /** Ctor + move */ - ecs_move_t move_ctor; - - /** 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; - - /** 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; - - /** 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; - - /** 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; - - 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 - * @brief Supporting types for the public API. - * - * This file contains types 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. - */ - -#ifndef FLECS_API_TYPES_H -#define FLECS_API_TYPES_H - - -#ifdef __cplusplus -extern "C" { -#endif - -//////////////////////////////////////////////////////////////////////////////// -//// Opaque types -//////////////////////////////////////////////////////////////////////////////// - -/** Table data */ -typedef struct ecs_data_t ecs_data_t; - -/* Cached query table data */ -typedef struct ecs_query_cache_table_match_t ecs_query_cache_table_match_t; - -//////////////////////////////////////////////////////////////////////////////// -//// Non-opaque types -//////////////////////////////////////////////////////////////////////////////// - -/** 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; - -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 */ - uint64_t last_observer_id; -}; - -/** Range in table */ -typedef struct ecs_table_range_t { - ecs_table_t *table; - int32_t offset; /* Leave both members to 0 to cover entire table */ - int32_t count; -} ecs_table_range_t; - -/** Value of query variable */ -typedef struct ecs_var_t { - ecs_table_range_t range; /* Set when variable stores a range of entities */ - ecs_entity_t entity; /* Set when variable stores single entity */ - - /* Most entities can be stored as a range by setting range.count to 1, - * however in order to also be able to store empty entities in variables, - * a separate entity member is needed. Both range and entity may be set at - * the same time, as long as they are consistent. */ -} ecs_var_t; - -/** Cached reference. */ -struct ecs_ref_t { - ecs_entity_t entity; /* Entity */ - ecs_entity_t id; /* Component id */ - uint64_t table_id; /* Table id for detecting ABA issues */ - struct ecs_table_record_t *tr; /* Table record for component */ - ecs_record_t *record; /* Entity index record */ -}; - - -/* Page-iterator specific data */ -typedef struct ecs_page_iter_t { - int32_t offset; - int32_t limit; - int32_t remaining; -} ecs_page_iter_t; - -/* Worker-iterator specific data */ -typedef struct ecs_worker_iter_t { - int32_t index; - int32_t count; -} 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; - bool iter_fill; - bool iter_empty; -} ecs_table_cache_iter_t; - -/** Each iterator */ -typedef struct ecs_each_iter_t { - ecs_table_cache_iter_t it; - - /* 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; - -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 { - const ecs_query_t *query; - struct ecs_var_t *vars; /* Variable storage */ - const struct ecs_query_var_t *query_vars; - const struct ecs_query_op_t *ops; - struct ecs_query_op_ctx_t *op_ctx; /* Operation-specific state */ - ecs_query_cache_table_match_t *node, *prev, *last; /* For cached iteration */ - uint64_t *written; - int32_t skip_count; - - ecs_query_op_profile_t *profile; - - int16_t op; - int16_t sp; -} ecs_query_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_trs (1u << 1u) -#define flecs_iter_cache_sources (1u << 2u) -#define flecs_iter_cache_ptrs (1u << 3u) -#define flecs_iter_cache_variables (1u << 4u) -#define flecs_iter_cache_all (255) - -/* Inline iterator arrays to prevent allocations for small array sizes */ -typedef struct ecs_iter_cache_t { - ecs_stack_cursor_t *stack_cursor; /* Stack cursor to restore to */ - 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_query_iter_t query; - ecs_page_iter_t page; - ecs_worker_iter_t worker; - ecs_each_iter_t each; - } iter; /* Iterator specific data */ - - void *entity_iter; /* Query applied after matching a table */ - ecs_iter_cache_t cache; /* Inline arrays to reduce allocations */ -} ecs_iter_private_t; - -#ifdef __cplusplus -} -#endif - -#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. - */ - -#ifndef FLECS_API_SUPPORT_H -#define FLECS_API_SUPPORT_H - - -#ifdef __cplusplus -extern "C" { -#endif - -/** 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 */ -#define ECS_MAX_RECURSION (512) - -/** Maximum length of a parser token (used by parser-related addons) */ -#define ECS_MAX_TOKEN_SIZE (256) - -FLECS_API -char* flecs_module_path_from_c( - const char *c_name); - -bool flecs_identifier_is_0( - const char *id); - -/* Constructor that zeromem's a component value */ -FLECS_API -void flecs_default_ctor( - void *ptr, - int32_t count, - const ecs_type_info_t *ctx); - -/* Create allocated string from format */ -FLECS_DBG_API -char* flecs_vasprintf( - const char *fmt, - va_list args); - -/* Create allocated string from format */ -FLECS_API -char* flecs_asprintf( - const char *fmt, - ...); - -/** 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 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. - * - * @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. - * - * @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. - * - * @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. - * - * @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); - -/** 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); - -FLECS_DBG_API -int32_t flecs_table_observed_count( - const ecs_table_t *table); - -FLECS_DBG_API -void flecs_dump_backtrace( - void *stream); - -FLECS_API -int32_t flecs_poly_claim_( - ecs_poly_t *poly); - -FLECS_API -int32_t flecs_poly_release_( - ecs_poly_t *poly); - -FLECS_API -int32_t flecs_poly_refcount( - ecs_poly_t *poly); - -FLECS_API -int32_t flecs_component_ids_index_get(void); - -FLECS_API -ecs_entity_t flecs_component_ids_get( - const ecs_world_t *world, - int32_t index); - -FLECS_API -ecs_entity_t flecs_component_ids_get_alive( - const ecs_world_t *stage_world, - int32_t index); - -FLECS_API -void flecs_component_ids_set( - ecs_world_t *world, - int32_t index, - ecs_entity_t id); - -#define flecs_poly_claim(poly) \ - flecs_poly_claim_(ECS_CONST_CAST(void*, reinterpret_cast(poly))) - -#define flecs_poly_release(poly) \ - flecs_poly_release_(ECS_CONST_CAST(void*, reinterpret_cast(poly))) - - -/** 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)) - -#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) - -/** 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_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))) - -#define ECS_BIT_IS_SET(flags, bit) ((flags) & (bit)) - -#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) - -#ifdef __cplusplus -} -#endif - -#endif - -/** - * @file hashmap.h - * @brief Hashmap data structure. - */ - -#ifndef FLECS_HASHMAP_H -#define FLECS_HASHMAP_H - - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - ecs_vec_t keys; - ecs_vec_t values; -} ecs_hm_bucket_t; - -typedef struct { - ecs_hash_value_action_t hash; - ecs_compare_action_t compare; - ecs_size_t key_size; - ecs_size_t value_size; - ecs_block_allocator_t *hashmap_allocator; - ecs_block_allocator_t bucket_allocator; - ecs_map_t impl; -} ecs_hashmap_t; - -typedef struct { - ecs_map_iter_t it; - ecs_hm_bucket_t *bucket; - int32_t index; -} flecs_hashmap_iter_t; - -typedef struct { - void *key; - void *value; - uint64_t hash; -} flecs_hashmap_result_t; - -FLECS_DBG_API -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_allocator_t *allocator); - -#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_( - 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)) - -FLECS_DBG_API -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_DBG_API -void flecs_hashmap_set_( - ecs_hashmap_t *map, - ecs_size_t key_size, - void *key, - ecs_size_t value_size, - const void *value); - -#define flecs_hashmap_set(map, key, value)\ - flecs_hashmap_set_(map, ECS_SIZEOF(*key), key, ECS_SIZEOF(*value), value) - -FLECS_DBG_API -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_DBG_API -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); - -#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_DBG_API -ecs_hm_bucket_t* flecs_hashmap_get_bucket( - const ecs_hashmap_t *map, - uint64_t hash); - -FLECS_DBG_API -void flecs_hm_bucket_remove( - ecs_hashmap_t *map, - ecs_hm_bucket_t *bucket, - uint64_t hash, - int32_t index); - -FLECS_DBG_API -void flecs_hashmap_copy( - 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_( - 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)) - -#define flecs_hashmap_next_w_key(map, K, key, V)\ - (V*)flecs_hashmap_next_(map, ECS_SIZEOF(K), key, ECS_SIZEOF(V)) - -#ifdef __cplusplus -} -#endif - -#endif - - -/** 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; - -/** Used with ecs_entity_init(). - * - * @ingroup entities - */ -typedef struct ecs_entity_desc_t { - int32_t _canary; /**< Used for validity testing. Must be 0. */ - - ecs_entity_t id; /**< Set to modify existing entity (optional) */ - - ecs_entity_t parent; /**< Parent entity. */ - - 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. */ - - 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 */ - const ecs_entity_t *entities; /**< Entity identifiers */ - 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_var_t *variables; /**< Values of variables (if any) */ - const ecs_table_record_t **trs; /**< Info on where to find field in table */ - 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 */ - uint64_t group_id; /**< Group id for table, if group_by is used */ - 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. */ - int8_t variable_count; /**< Number of variables for query */ - const ecs_query_t *query; /**< Query being evaluated */ - char **variable_names; /**< Names of variables (if any) */ - - /* 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 */ - int32_t offset; /**< Offset relative to current table */ - int32_t count; /**< Number of entities to iterate */ - - /* 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) - - -/** 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; - - /** 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; - - /** Callback used for ordering query results. Same as order_by_callback, - * but more efficient. */ - ecs_sort_table_action_t order_by_table_callback; - - /** Component to sort on, used together with order_by_callback or - * order_by_table_callback. */ - ecs_entity_t order_by; - - /** Component id to be used for grouping. Used together with the - * group_by_callback. */ - ecs_id_t group_by; - - /** 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; - - /** Function to free group_by_ctx */ - ecs_ctx_free_t group_by_ctx_free; - - /** User context to pass to callback */ - void *ctx; - - /** Context to be used for language bindings */ - void *binding_ctx; - - /** Callback to free ctx */ - ecs_ctx_free_t ctx_free; - - /** Callback to free binding_ctx */ - ecs_ctx_free_t binding_ctx_free; - - /** Entity associated with query (optional) */ - ecs_entity_t entity; -} ecs_query_desc_t; - -/** Used with ecs_observer_init(). - * - * @ingroup observers - */ -typedef struct ecs_observer_desc_t { - /** Used for validity testing. Must be 0. */ - int32_t _canary; - - /** Existing entity to associate with observer (optional) */ - ecs_entity_t entity; - - /** Query for observer */ - ecs_query_desc_t query; - - /** Events to observe (OnAdd, OnRemove, OnSet) */ - ecs_entity_t events[FLECS_EVENT_DESC_MAX]; - - /** 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. */ - 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 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; - - /** User context to pass to callback */ - void *ctx; - - /** Callback to free ctx */ - ecs_ctx_free_t ctx_free; - - /** 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; - - /** Callback to free run ctx. */ - ecs_ctx_free_t run_ctx_free; - - /** Observable with which to register the observer */ - ecs_poly_t *observable; - - /** Optional shared last event id for multiple observers. Ensures only one - * of the observers with the shared id gets triggered for an event */ - int32_t *last_event_id; - - /** Used for internal purposes */ - int8_t term_index_; - ecs_flags32_t flags_; -} ecs_observer_desc_t; - -/** 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 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; - - /** 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; - - /** Single-entity alternative to setting table / offset / count */ - ecs_entity_t entity; - - /** 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; - - /** 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; - - /** Observable (usually the world) */ - ecs_poly_t *observable; - - /** Event flags */ - ecs_flags32_t flags; -} ecs_event_desc_t; - - -/** - * @defgroup misc_types Miscellaneous types - * Types used to create entities, observers, queries and more. - * - * @{ - */ - -/** 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 *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; - -/** 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_frame; /**< Total number of systems ran in last frame */ - int64_t observers_ran_frame; /**< Total number of times observer was invoked */ - - 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 */ - - /* -- Command counts -- */ - struct { - int64_t add_count; /**< Add commands processed */ - int64_t remove_count; /**< Remove commands processed */ - int64_t delete_count; /**< Selete 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; - -/** Type that contains information about a query group. */ -typedef struct ecs_query_group_info_t { - 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 builtin_components Builtin component types. - * Types that represent builtin components. - * - * @{ - */ - -/** 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; - -/** @} */ -/** @} */ - -/* Only include deprecated definitions if deprecated addon is required */ -#ifdef FLECS_DEPRECATED -/** - * @file addons/deprecated.h - * @brief The deprecated addon contains deprecated operations. - */ - -#ifdef FLECS_DEPRECATED - -#ifndef FLECS_DEPRECATED_H -#define FLECS_DEPRECATED_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __cplusplus -} -#endif - -#endif - -#endif - -#endif - -/** - * @defgroup api_constants API Constants - * Public API constants. - * - * @{ - */ - -/** - * @defgroup id_flags Component id flags. - * Id flags are bits that can be set on an id (ecs_id_t). - * - * @{ - */ - -/** Indicates that the id is a pair. */ -FLECS_API extern const ecs_id_t ECS_PAIR; - -/** Automatically override component when it is inherited */ -FLECS_API extern const ecs_id_t ECS_AUTO_OVERRIDE; - -/** Adds bitset to storage which allows component to be enabled/disabled */ -FLECS_API extern const ecs_id_t ECS_TOGGLE; - -/** @} */ - -/** - * @defgroup builtin_tags Builtin component ids. - * @{ - */ - -/* Builtin component ids */ - -/** Component component id. */ -FLECS_API extern const ecs_entity_t ecs_id(EcsComponent); - -/** Identifier component id. */ -FLECS_API extern const ecs_entity_t ecs_id(EcsIdentifier); - -/** Poly component id. */ -FLECS_API extern const ecs_entity_t ecs_id(EcsPoly); - -/** DefaultChildComponent component id. */ -FLECS_API extern const ecs_entity_t ecs_id(EcsDefaultChildComponent); - -/** 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 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 */ -FLECS_API extern const ecs_entity_t EcsFlecsCore; - -/** 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. */ -FLECS_API extern const ecs_entity_t EcsWildcard; - -/** Any entity ("_"). Matches any id, returns only the first. */ -FLECS_API extern const ecs_entity_t EcsAny; - -/** This entity. Default source for queries. */ -FLECS_API extern const ecs_entity_t EcsThis; - -/** Variable entity ("$"). Used in expressions to prefix variable names */ -FLECS_API extern const ecs_entity_t EcsVariable; - -/** Shortcut as EcsVariable is typically used as source for singleton terms */ -#define EcsSingleton EcsVariable - -/** 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 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 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; - -/** 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. - * Behavior: - * - * @code - * if R(X, Y) then R(Y, X) - * @endcode - */ -FLECS_API extern const ecs_entity_t EcsSymmetric; - -/** 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 relationship as acyclic. Acyclic relationships may not form cycles. */ -FLECS_API extern const ecs_entity_t EcsAcyclic; - -/** 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. - * - * 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; - -/** Mark a component as toggleable with ecs_enable_id(). */ -FLECS_API extern const ecs_entity_t EcsCanToggle; - -/** 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; - -/** Ensure that an entity is always used in pair as relationship. - * - * Behavior: - * - * @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. - * - * Behavior: - * - * @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; - -/** 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 added to module entities */ -FLECS_API extern const ecs_entity_t EcsModule; - -/** Tag to indicate an entity/component/system is private to a module */ -FLECS_API extern const ecs_entity_t EcsPrivate; - -/** 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 queries, unless - * #EcsDisabled is explicitly queried for. */ -FLECS_API extern const ecs_entity_t EcsDisabled; - -/** 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 that triggers when an id is removed from an entity */ -FLECS_API extern const ecs_entity_t EcsOnRemove; - -/** Event that triggers when a component is set for an entity */ -FLECS_API extern const ecs_entity_t EcsOnSet; - -/** Event that triggers observer when an entity starts/stops matching a query */ -FLECS_API extern const ecs_entity_t EcsMonitor; - -/** 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; - -/** 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 sparse */ -FLECS_API extern const ecs_entity_t EcsSparse; - -/** Mark relationship as union */ -FLECS_API extern const ecs_entity_t EcsUnion; - -/** Marker used to indicate `$var == ...` matching in queries. */ -FLECS_API extern const ecs_entity_t EcsPredEq; - -/** Marker used to indicate `$var == "name"` matching in queries. */ -FLECS_API extern const ecs_entity_t EcsPredMatch; - -/** Marker used to indicate `$var ~= "pattern"` matching in queries. */ -FLECS_API extern const ecs_entity_t EcsPredLookup; - -/** Marker used to indicate the start of a scope (`{`) in queries. */ -FLECS_API extern const ecs_entity_t EcsScopeOpen; - -/** Marker used to indicate the end of a scope (`}`) in queries. */ -FLECS_API extern const ecs_entity_t EcsScopeClose; - -/** 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 EcsEmpty; - -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. */ - -/** 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(EcsPoly)) - -/** The first user-defined component starts from this id. Ids up to this number - * are reserved for builtin components */ -#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_creation_deletion Creation & Deletion - * @{ - */ - -/** Create a new world. - * This operation automatically imports modules from addons Flecs has been built - * with, except when the module specifies otherwise. - * - * @return A new world - */ -FLECS_API -ecs_world_t* ecs_init(void); - -/** 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 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. Command line - * arguments are used to: - * - automatically derive the name of the application from argv[0] - * - * @return A new world - */ -FLECS_API -ecs_world_t* ecs_init_w_args( - int argc, - char *argv[]); - -/** Delete a world. - * This operation deletes the world, and everything it contains. - * - * @param world The world to delete. - * @return Zero if successful, non-zero if failed. - */ -FLECS_API -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. - */ -FLECS_API -bool ecs_is_fini( - const ecs_world_t *world); - -/** 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 */ -FLECS_API -void ecs_atfini( - ecs_world_t *world, - ecs_fini_action_t action, - void *ctx); - -/** 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. - * @return Struct with entity id array. - */ -FLECS_API -ecs_entities_t ecs_get_entities( - const ecs_world_t *world); - -/** 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 Flags set on the world. - */ -FLECS_API -ecs_flags32_t ecs_world_get_flags( - const ecs_world_t *world); - -/** @} */ - -/** - * @defgroup world_frame Frame functions - * @{ - */ - -/** 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. - * - * @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. - */ -FLECS_API -ecs_ftime_t ecs_frame_begin( - ecs_world_t *world, - ecs_ftime_t delta_time); - -/** 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_frame_end( - ecs_world_t *world); - -/** 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_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 requested. - * - * @param world The world. - * @return Whether a quit has been requested. - * @see ecs_quit() - */ -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 - * how much of that time was spent on systems and on merging. - * - * Frame time measurements add a small constant-time overhead to an application. - * When an application sets a target FPS, frame time measurements are enabled by - * default. - * - * @param world The world. - * @param enable Whether to enable or disable frame time measuring. - */ -FLECS_API void ecs_measure_frame_time( - ecs_world_t *world, - bool enable); - -/** Measure system time. - * System time measurements measure the time spent in each system. - * - * 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. - * - * @param world The world. - * @param enable Whether to enable or disable system time measuring. - */ -FLECS_API void ecs_measure_system_time( - ecs_world_t *world, - 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 - * 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 - * time spent in flecs as time spent outside of flecs are taken into - * account. - * - * @param world The world. - * @param fps The target FPS. - */ -FLECS_API -void ecs_set_target_fps( - ecs_world_t *world, - ecs_ftime_t fps); - -/** 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 flags The query flags. - */ -FLECS_API -void ecs_set_default_query_flags( - ecs_world_t *world, - ecs_flags32_t flags); - -/** @} */ - -/** - * @defgroup commands Commands - * @{ - */ - -/** 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 - * @param multi_threaded Whether to enable readonly/multi threaded mode. - * @return Whether world is in readonly mode. - */ -FLECS_API -bool ecs_readonly_begin( - ecs_world_t *world, - bool multi_threaded); - -/** 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 - */ -FLECS_API -void ecs_readonly_end( - ecs_world_t *world); - -/** 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 ecs_progress() or after ecs_readonly_end()). - * - * This operation may be called on an already merged stage or world. - * - * @param world The world. - */ -FLECS_API -void ecs_merge( - ecs_world_t *world); - -/** 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. - * - * This operation is thread safe. - * - * @param world The world. - * @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() - */ -FLECS_API -bool ecs_defer_begin( - ecs_world_t *world); - -/** Test if deferring is enabled for current stage. - * - * @param world The world. - * @return True if deferred, false if not. - * - * @see ecs_defer_begin() - * @see ecs_defer_end() - * @see ecs_defer_resume() - * @see ecs_defer_suspend() - */ -FLECS_API -bool ecs_is_deferred( - const ecs_world_t *world); - -/** End block of operations to defer. - * See ecs_defer_begin(). - * - * This operation is thread safe. - * - * @param world The world. - * @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 -bool ecs_defer_end( - ecs_world_t *world); - -/** 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. - * - * 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. - * - * @see ecs_defer_begin() - * @see ecs_defer_end() - * @see ecs_defer_is_deferred() - * @see ecs_defer_resume() - */ -FLECS_API -void ecs_defer_suspend( - ecs_world_t *world); - -/** Resume deferring. - * See ecs_defer_suspend(). - * - * @param world The world. - * - * @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); - -/** 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 stages The number of stages. - */ -FLECS_API -void ecs_set_stage_count( - ecs_world_t *world, - int32_t stages); - -/** Get number of configured stages. - * Return number of stages set by ecs_set_stage_count(). - * - * @param world The world. - * @return The number of stages used for threading. - */ -FLECS_API -int32_t ecs_get_stage_count( - const ecs_world_t *world); - -/** Get stage-specific world pointer. - * Flecs threads can safely invoke the API as long as they have a private - * context to write to, also referred to as the stage. This function returns a - * pointer to a stage, disguised as a world pointer. - * - * Note that this function does not(!) create a new world. It simply wraps the - * existing world in a thread-specific context, which the API knows how to - * unwrap. The reason the stage is returned as an ecs_world_t is so that it - * can be passed transparently to the existing API functions, vs. having to - * create a dedicated API for threading. - * - * @param world The world. - * @param stage_id The index of the stage to retrieve. - * @return A thread-specific pointer to the world. - */ -FLECS_API -ecs_world_t* ecs_get_stage( - const ecs_world_t *world, - int32_t stage_id); - -/** 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); - -/** 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. - * @return The stage. - */ -FLECS_API -ecs_world_t* ecs_stage_new( - ecs_world_t *world); - -/** Free unmanaged stage. - * - * @param stage The stage to free. - */ -FLECS_API -void ecs_stage_free( - ecs_world_t *stage); - -/** 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 world The world. - * @return The stage id. - */ -FLECS_API -int32_t ecs_stage_get_id( - const ecs_world_t *world); - -/** @} */ - -/** - * @defgroup world_misc Misc - * @{ - */ - -/** 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 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_set_ctx( - ecs_world_t *world, - void *ctx, - ecs_ctx_free_t ctx_free); - -/** 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 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_set_binding_ctx( - ecs_world_t *world, - void *ctx, - ecs_ctx_free_t ctx_free); - -/** Get the world context. - * This operation retrieves a previously set world context. - * - * @param world The world. - * @return The context set with ecs_set_ctx(). If no context was set, the - * function returns NULL. - */ -FLECS_API -void* ecs_get_ctx( - const ecs_world_t *world); - -/** Get the world binding context. - * This operation retrieves a previously set world binding context. - * - * @param world The world. - * @return The context set with ecs_set_binding_ctx(). If no context was set, the - * function returns NULL. - */ -FLECS_API -void* ecs_get_binding_ctx( - const ecs_world_t *world); - -/** Get build info. - * Returns information about the current Flecs build. - * - * @return A struct with information about the current Flecs build. - */ -FLECS_API -const ecs_build_info_t* ecs_get_build_info(void); - -/** Get world info. - * - * @param world The world. - * @return Pointer to the world info. Valid for as long as the world exists. - */ -FLECS_API -const ecs_world_info_t* ecs_get_world_info( - 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 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 `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); - -/** 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 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); - -/** Get the largest issued entity id (not counting generation). - * - * @param world The world. - * @return The largest issued entity id. - */ -FLECS_API -ecs_entity_t ecs_get_max_id( - const 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. - * - * 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 flags The flags specifying which actions to run. - */ -FLECS_API -void ecs_run_aperiodic( - ecs_world_t *world, - ecs_flags32_t flags); - -/** Used with ecs_delete_empty_tables(). */ -typedef struct ecs_delete_empty_tables_desc_t { - /** Optional component filter for the tables to evaluate. */ - ecs_id_t id; - - /** Free table data when generation > clear_generation. */ - uint16_t clear_generation; - - /** Delete table when generation > delete_generation. */ - uint16_t delete_generation; - - /** Minimum number of component ids the table should have. */ - int32_t min_id_count; - - /** Amount of time operation is allowed to spend. */ - double time_budget_seconds; -} ecs_delete_empty_tables_desc_t; - -/** 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. - * - * @param world The world. - * @param desc Configuration parameters. - * @return Number of deleted tables. - */ -FLECS_API -int32_t ecs_delete_empty_tables( - ecs_world_t *world, - const ecs_delete_empty_tables_desc_t *desc); - -/** Get world from poly. - * - * @param poly A pointer to a poly object. - * @return The world. - */ -FLECS_API -const ecs_world_t* ecs_get_world( - const ecs_poly_t *poly); - -/** Get entity from poly. - * - * @param poly A pointer to a poly object. - * @return Entity associated with the poly object. - */ -FLECS_API -ecs_entity_t ecs_get_entity( - const ecs_poly_t *poly); - -/** Test if pointer is of specified type. - * Usage: - * - * @code - * flecs_poly_is(ptr, ecs_world_t) - * @endcode - * - * This operation only works for poly types. - * - * @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 -bool flecs_poly_is_( - const ecs_poly_t *object, - int32_t type); - -/** 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. - * - * @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); - -/** @} */ - -/** @} */ - -/** - * @defgroup entities Entities - * Functions for working with `ecs_entity_t`. - * - * @{ - */ - -/** - * @defgroup creating_entities Creating & Deleting - * Functions for creating and deleting entities. - * - * @{ - */ - -/** 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. - * @return The new entity id. - */ -FLECS_API -ecs_entity_t ecs_new( - ecs_world_t *world); - -/** 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. - * @return The new component id. - */ -FLECS_API -ecs_entity_t ecs_new_low_id( - ecs_world_t *world); - -/** 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 component id to initialize the new entity with. - * @return The new entity. - */ -FLECS_API -ecs_entity_t ecs_new_w_id( - ecs_world_t *world, - ecs_id_t id); - -/** Create new entity in table. - * This operation creates a new entity in the specified table. - * - * @param world The world. - * @param table The table to which to add the new entity. - * @return The new entity. - */ -FLECS_API -ecs_entity_t ecs_new_w_table( - ecs_world_t *world, - ecs_table_t *table); - -/** 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 desc Entity init parameters. - * @return A handle to the new or existing entity, or 0 if failed. - */ -FLECS_API -ecs_entity_t ecs_entity_init( - ecs_world_t *world, - const ecs_entity_desc_t *desc); - -/** 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 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 ecs_entity_t* ecs_bulk_init( - ecs_world_t *world, - const ecs_bulk_desc_t *desc); - -/** 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 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. - */ -FLECS_API -const ecs_entity_t* ecs_bulk_new_w_id( - ecs_world_t *world, - ecs_id_t id, - int32_t count); - -/** 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. - * - * 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 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_clone( - ecs_world_t *world, - ecs_entity_t dst, - ecs_entity_t src, - bool copy_value); - -/** 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. - */ -FLECS_API -void ecs_delete( - ecs_world_t *world, - ecs_entity_t entity); - -/** 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. - * - * @param world The world. - * @param id The id. - */ -FLECS_API -void ecs_delete_with( - ecs_world_t *world, - ecs_id_t id); - -/** @} */ - -/** - * @defgroup adding_removing Adding & Removing - * Functions for adding and removing components. - * - * @{ - */ - -/** 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 entity The entity. - * @param id The id to add. - */ -FLECS_API -void ecs_add_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** Remove a (component) id from an entity. - * This operation removes a single (component) id to an entity. If the entity - * does not have the id, this operation will have no side effects. - * - * @param world The world. - * @param entity The entity. - * @param id The id to remove. - */ -FLECS_API -void ecs_remove_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** Add auto override for (component) id. - * 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 entity The entity. - * @param id The (component) id to auto override. - */ -FLECS_API -void ecs_auto_override_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** Clear all components. - * This operation will remove all components from an entity. - * - * @param world The world. - * @param entity The entity. - */ -FLECS_API -void ecs_clear( - ecs_world_t *world, - ecs_entity_t entity); - -/** Remove all instances of the specified (component) id. - * 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 id The id. - */ -FLECS_API -void ecs_remove_all( - ecs_world_t *world, - ecs_id_t id); - -/** Set current with id. - * New entities are automatically created with the specified id. - * - * @param world The world. - * @param id The id. - * @return The previous id. - */ -FLECS_API -ecs_entity_t ecs_set_with( - ecs_world_t *world, - ecs_id_t id); - -/** Get current with id. - * Get the id set with ecs_set_with(). - * - * @param world The world. - * @return The last id provided to ecs_set_with(). - */ -FLECS_API -ecs_id_t ecs_get_with( - const ecs_world_t *world); - -/** @} */ - -/** - * @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. - * - * @param world The world. - * @param entity The entity to enable or disable. - * @param enabled true to enable the entity, false to disable. - */ -FLECS_API -void ecs_enable( - ecs_world_t *world, - ecs_entity_t entity, - bool enabled); - -/** 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 entity. - * @param id The component. - * @param enable True to enable the component, false to disable. - */ -FLECS_API -void ecs_enable_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - 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 id The component. - * @return True if the component is enabled, otherwise false. - */ -FLECS_API -bool ecs_is_enabled_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** @} */ - -/** - * @defgroup getting Getting & Setting - * Functions for getting/setting components. - * - * @{ - */ - -/** 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 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. - * - * @see ecs_get_mut_id() - */ -FLECS_API -const void* ecs_get_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** 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. - * - * @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. - */ -FLECS_API -void* ecs_get_mut_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** Get a mutable pointer to a component. - * This operation returns a mutable pointer to a component. If the component did - * not yet exist, it will be added. - * - * 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 entity The entity. - * @param id The entity id of the component to obtain. - * @return The component pointer. - * - * @see ecs_ensure_modified_id() - * @see ecs_emplace_id() - */ -FLECS_API -void* ecs_ensure_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** Combines ensure + modified in single operation. - * This operation is a more efficient alternative to calling ecs_ensure_id() and - * ecs_modified_id() separately. This operation is only valid when the world is in - * deferred mode, which ensures that the Modified event is not emitted before - * the modification takes place. - * - * @param world The world. - * @param entity The entity. - * @param id The id of the component to obtain. - * @return The component pointer. - */ -FLECS_API -void* ecs_ensure_modified_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** 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. - * - * @param world The world. - * @param entity The entity. - * @param id The id of the component. - * @return The reference. - */ -FLECS_API -ecs_ref_t ecs_ref_init_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** Get component from ref. - * Get component pointer from ref. The ref must be created with ecs_ref_init(). - * - * @param world The world. - * @param ref The ref. - * @param id The component id. - * @return The component pointer, NULL if the entity does not have the component. - */ -FLECS_API -void* ecs_ref_get_id( - const ecs_world_t *world, - ecs_ref_t *ref, - ecs_id_t id); - -/** 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 ref The ref. - */ -FLECS_API -void ecs_ref_update( - const ecs_world_t *world, - ecs_ref_t *ref); - -/** Find record for entity. - * An entity record contains the table and row for the entity. - * - * @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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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 entity The entity. - * @param id The component to obtain. - * @param is_new Whether this is an existing or new component. - * @return The (uninitialized) component pointer. - */ -FLECS_API -void* ecs_emplace_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - bool *is_new); - -/** 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 entity The entity. - * @param id The id of the component that was modified. - */ -FLECS_API -void ecs_modified_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** 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 the provided entity is 0, a new entity will be created. - * - * @param world The world. - * @param entity The entity. - * @param id The id of the component to set. - * @param size The size of the pointed-to value. - * @param ptr The pointer to the value. - */ -FLECS_API -void ecs_set_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - size_t size, - const void *ptr); - -/** @} */ - -/** - * @defgroup liveliness Entity Liveliness - * Functions for testing and modifying entity liveliness. - * - * @{ - */ - -/** Test whether an entity is valid. - * Entities that are valid can be used with API functions. Using invalid - * entities with API operations will cause the function to panic. - * - * An entity is valid if it is not 0 and if it is alive. - * - * ecs_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_w() or similar. In - * this the function differs from ecs_is_alive(), which will return false for - * entities that do not yet exist. - * - * 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. - * - * @param world The world. - * @param e The entity. - * @return True if the entity is valid, false if the entity is not valid. - */ -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 - * - * @param world The world. - * @param e The entity. - * @return True if the entity is alive, false if the entity is not alive. - */ -FLECS_API -bool ecs_is_alive( - const ecs_world_t *world, - ecs_entity_t e); - -/** 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. - * @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_alive( - const ecs_world_t *world, - ecs_entity_t e); - -/** 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 entity The entity id to make alive. - * - * @see ecs_make_alive_id() - */ -FLECS_API -void ecs_make_alive( - ecs_world_t *world, - ecs_entity_t entity); - -/** Same as ecs_make_alive(), but for (component) ids. - * 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. - * @param id The id to make alive. - */ -FLECS_API -void ecs_make_alive_id( - ecs_world_t *world, - ecs_id_t id); - -/** Test whether an entity exists. - * Similar as ecs_is_alive(), but ignores entity generation count. - * - * @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); - -/** 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 entity Entity for which to set the generation with the new generation. - */ -FLECS_API -void ecs_set_version( - ecs_world_t *world, - ecs_entity_t entity); - -/** @} */ - -/** - * @defgroup entity_info Entity Information. - * Get information from entity. - * - * @{ - */ - -/** Get the type of an entity. - * - * @param world The world. - * @param entity The entity. - * @return The type of the entity, NULL if the entity has no components. - */ -FLECS_API -const ecs_type_t* ecs_get_type( - const ecs_world_t *world, - ecs_entity_t entity); - -/** Get the table of an entity. - * - * @param world The world. - * @param entity The entity. - * @return The table of the entity, NULL if the entity has no components/tags. - */ -FLECS_API -ecs_table_t* ecs_get_table( - const ecs_world_t *world, - ecs_entity_t entity); - -/** Convert type to string. - * The result of this operation must be freed with ecs_os_free(). - * - * @param world The world. - * @param type The type. - * @return The stringified type. - */ -FLECS_API -char* ecs_type_str( - const ecs_world_t *world, - const ecs_type_t* type); - -/** 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(). - * - * @param world The world. - * @param table The table. - * @return The stringified table type. - * - * @see ecs_table_get_type() - * @see ecs_type_str() - */ -FLECS_API -char* ecs_table_str( - const ecs_world_t *world, - const ecs_table_t *table); - -/** Convert entity to string. - * Same as combining: - * - ecs_get_path(world, entity) - * - ecs_type_str(world, ecs_get_type(world, entity)) - * - * The result of this operation must be freed with ecs_os_free(). - * - * @param world The world. - * @param entity The entity. - * @return The entity path with stringified type. - * - * @see ecs_get_path() - * @see ecs_type_str() - */ -FLECS_API -char* ecs_entity_str( - const ecs_world_t *world, - ecs_entity_t entity); - -/** Test if an entity has an id. - * This operation returns true if the entity has or inherits the specified id. - * - * @param world The world. - * @param entity The entity. - * @param id The id to test for. - * @return True if the entity has the id, false if not. - * - * @see ecs_owns_id() - */ -FLECS_API -bool ecs_has_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** Test if an entity owns an id. - * This operation returns true if the entity has the specified id. 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 world The world. - * @param entity The entity. - * @param id The id to test for. - * @return True if the entity has the id, false if not. - */ -FLECS_API -bool ecs_owns_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id); - -/** 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. - * - * If the index is larger than the total number of instances the entity has for - * the relationship, the operation will return 0. - * - * @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 -ecs_entity_t ecs_get_target( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel, - int32_t index); - -/** Get parent (target of `ChildOf` relationship) for entity. - * This operation is the same as calling: - * - * @code - * ecs_get_target(world, entity, EcsChildOf, 0); - * @endcode - * - * @param world The world. - * @param entity The entity. - * @return The parent of the entity, 0 if the entity has no parent. - * - * @see ecs_get_target() - */ -FLECS_API -ecs_entity_t ecs_get_parent( - const ecs_world_t *world, - ecs_entity_t entity); - -/** Get the target of a relationship 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 - * relationship, the operation will return 0. - * - * This operation can be used to lookup, for example, which prefab is providing - * a component by specifying the `IsA` relationship: - * - * @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 entity The entity. - * @param rel The relationship to follow. - * @param id The id to lookup. - * @return The entity for which the target has been found. - */ -FLECS_API -ecs_entity_t ecs_get_target_for_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel, - ecs_id_t id); - -/** 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 world The world. - * @param entity The entity. - * @param rel The relationship. - * @return The depth of the entity in the tree. - */ -FLECS_API -int32_t ecs_get_depth( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t rel); - -/** Count entities that have the specified id. - * Returns the number of entities that have the specified id. - * - * @param world The world. - * @param entity The id to search for. - * @return The number of entities that have the id. - */ -FLECS_API -int32_t ecs_count_id( - const ecs_world_t *world, - ecs_id_t entity); - -/** @} */ - - -/** - * @defgroup paths Entity Names - * Functions for working with entity names and paths. - * - * @{ - */ - -/** Get the name of an entity. - * This will return the name stored in `(EcsIdentifier, EcsName)`. - * - * @param world The world. - * @param entity The entity. - * @return The type of the entity, NULL if the entity has no name. - * - * @see ecs_set_name() - */ -FLECS_API -const char* ecs_get_name( - const ecs_world_t *world, - ecs_entity_t entity); - -/** Get the symbol of an entity. - * This will return the symbol stored in `(EcsIdentifier, EcsSymbol)`. - * - * @param world The world. - * @param entity The entity. - * @return The type of the entity, NULL if the entity has no name. - * - * @see ecs_set_symbol() - */ -FLECS_API -const char* ecs_get_symbol( - const ecs_world_t *world, - ecs_entity_t entity); - -/** 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. - * - * 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 -ecs_entity_t ecs_set_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name); - -/** 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). - * - * @param world The world. - * @param entity The entity. - * @param symbol The symbol. - * @return The provided entity, or a new entity if 0 was provided. - * - * @see ecs_get_symbol() - */ -FLECS_API -ecs_entity_t ecs_set_symbol( - ecs_world_t *world, - ecs_entity_t entity, - const char *symbol); - -/** 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. - * - * The symbol is stored in `(EcsIdentifier, EcsAlias)`. - * - * @param world The world. - * @param entity The entity. - * @param alias The alias. - */ -FLECS_API -void ecs_set_alias( - ecs_world_t *world, - ecs_entity_t entity, - const char *alias); - -/** Lookup an entity by it's path. - * This operation is equivalent to calling: - * - * @code - * ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true); - * @endcode - * - * @param world The world. - * @param path The entity path. - * @return The entity with the specified path, or 0 if no entity was found. - * - * @see ecs_lookup_child() - * @see ecs_lookup_path_w_sep() - * @see ecs_lookup_symbol() - */ -FLECS_API -ecs_entity_t ecs_lookup( - const ecs_world_t *world, - const char *path); - -/** 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 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 -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 -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); - -/** 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 -ecs_entity_t ecs_lookup_symbol( - const ecs_world_t *world, - const char *symbol, - bool lookup_as_path, - bool recursive); - -/** 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 -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); - -/** Write path identifier to buffer. - * Same as ecs_get_path_w_sep(), but writes result to an ecs_strbuf_t. - * - * @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() - */ -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); - -/** 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); - -/** Add specified path to entity. - * This operation is similar to ecs_new_from_path(), but will instead add the path - * to an existing entity. - * - * If an entity already exists for the path, it will be returned instead. - * - * @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. - */ -FLECS_API -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); - -/** 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 scope The entity to use as scope. - * @return The previous scope. - * - * @see ecs_get_scope() - */ -FLECS_API -ecs_entity_t ecs_set_scope( - ecs_world_t *world, - ecs_entity_t scope); - -/** 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. - * @return The current scope. - */ -FLECS_API -ecs_entity_t ecs_get_scope( - const ecs_world_t *world); - -/** 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 prefix The name prefix to use. - * @return The previous prefix. - */ -FLECS_API -const char* ecs_set_name_prefix( - ecs_world_t *world, - const char *prefix); - -/** 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. - * - * @param world The world. - * @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_set_lookup_path( - ecs_world_t *world, - const ecs_entity_t *lookup_path); - -/** Get current lookup path. - * Returns value set by ecs_set_lookup_path(). - * - * @param world The world. - * @return The current lookup path. - */ -FLECS_API -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 for an id. - * This function returns the type information for an id. The specified id can be - * any valid id. For the rules on how type information is determined based on - * id, see ecs_get_typeid(). - * - * @param world The world. - * @param id The id. - * @return The type information of the id. - */ -FLECS_API -const ecs_type_info_t* ecs_get_type_info( - const ecs_world_t *world, - ecs_id_t id); - -/** 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 id The component id 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 id, - const ecs_type_hooks_t *hooks); - -/** Get hooks for component. - * - * @param world The world. - * @param id The component id for which to retrieve the hooks. - * @return The hooks for the component, or NULL if not registered. - */ -FLECS_API -const ecs_type_hooks_t* ecs_get_hooks_id( - const ecs_world_t *world, - ecs_entity_t id); - -/** @} */ - -/** - * @defgroup ids Ids - * Functions for working with `ecs_id_t`. - * - * @{ - */ - -/** 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 #EcsPairIsTag tag - * - * @param world The world. - * @param id The id. - * @return Whether the provided id is a tag. - */ -FLECS_API -bool ecs_id_is_tag( - const ecs_world_t *world, - ecs_id_t id); - -/** 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. - * - * @param world The world. - * @param id The id. - * @return Whether the id is in use. - */ -FLECS_API -bool ecs_id_in_use( - const ecs_world_t *world, - ecs_id_t id); - -/** 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 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 id The id. - * @return The type id of the id. - */ -FLECS_API -ecs_entity_t ecs_get_typeid( - const ecs_world_t *world, - ecs_id_t id); - -/** 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). - * - * @param id The id. - * @param pattern The pattern to compare with. - * @return Whether the id matches the pattern. - */ -FLECS_API -bool ecs_id_match( - ecs_id_t id, - ecs_id_t pattern); - -/** Utility to check if id is a pair. - * - * @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. - * - * @param id The id. - * @return True if id is a wildcard or a pair containing a wildcard. - */ -FLECS_API -bool ecs_id_is_wildcard( - ecs_id_t id); - -/** 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 id The id. - * @return True if the id is valid. - */ -FLECS_API -bool ecs_id_is_valid( - const ecs_world_t *world, - ecs_id_t id); - -/** 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 id The id. - * @return Flags associated with the id, or 0 if the id is not in use. - */ -FLECS_API -ecs_flags32_t ecs_id_get_flags( - const ecs_world_t *world, - ecs_id_t id); - -/** Convert id flag to string. - * This operation converts an id flag to a string. - * - * @param id_flags The id flag. - * @return The id flag string, or NULL if no valid id is provided. - */ -FLECS_API -const char* ecs_id_flag_str( - ecs_id_t id_flags); - -/** Convert (component) id to string. - * This operation interprets the structure of an id and converts it to a string. - * - * @param world The world. - * @param id The id to convert to a string. - * @return The id converted to a string. - */ -FLECS_API -char* ecs_id_str( - const ecs_world_t *world, - ecs_id_t id); - -/** Write (component) id string to buffer. - * Same as ecs_id_str() but writes result to ecs_strbuf_t. - * - * @param world The world. - * @param id The id to convert to a string. - * @param buf The buffer to write to. - */ -FLECS_API -void ecs_id_str_buf( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf); - -/** Convert string to a (component) id. - * 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_id_t ecs_id_from_str( - const ecs_world_t *world, - const char *expr); - -/** @} */ - -/** - * @defgroup queries Queries - * @brief Functions for working with `ecs_term_t` and `ecs_query_t`. - * @{ - */ - -/** Test whether term id is set. - * - * @param id The term id. - * @return True when set, false when not set. - */ -FLECS_API -bool ecs_term_ref_is_set( - const ecs_term_ref_t *id); - -/** 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_term_is_initialized( - const ecs_term_t *term); - -/** 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. - * - * A term has a $this source when: - * - ecs_term_t::src::id is EcsThis - * - ecs_term_t::src::flags is EcsIsVariable - * - * If ecs_term_t::src is not populated, it will be automatically initialized to - * the $this source for the created query. - * - * @param term The term. - * @return True if term matches $this, false if not. - */ -FLECS_API -bool ecs_term_match_this( - const ecs_term_t *term); - -/** 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. - * - * 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_match_0( - const ecs_term_t *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 world The world. - * @param term The term. - * @return The term converted to a string. - */ -FLECS_API -char* ecs_term_str( - const ecs_world_t *world, - const ecs_term_t *term); - -/** Convert query to string expression. - * Convert query to a string expression. The resulting expression can be - * parsed to create the same query. - * - * @param query The query. - * @return The query converted to a string. - */ -FLECS_API -char* ecs_query_str( - const ecs_query_t *query); - -/** @} */ - -/** - * @defgroup each_iter Each iterator - * @brief Find all entities that have a single (component) id. - * @{ - */ - -/** 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. - * - * 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 - * - * 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 id The (component) id to iterate. - * @return An iterator that iterates all entities with the (component) id. -*/ -FLECS_API -ecs_iter_t ecs_each_id( - const ecs_world_t *world, - ecs_id_t id); - -/** Progress an iterator created with ecs_each_id(). - * - * @param it The iterator. - * @return True if the iterator has more results, false if not. - */ -FLECS_API -bool ecs_each_next( - ecs_iter_t *it); - -/** Iterate children of parent. - * Equivalent to: - * @code - * ecs_iter_t it = ecs_each_id(world, ecs_pair(EcsChildOf, parent)); - * @endcode - * - * @param world The world. - * @param parent The parent. - * @return An iterator that iterates all children of the parent. - * - * @see ecs_each_id() -*/ -FLECS_API -ecs_iter_t ecs_children( - const ecs_world_t *world, - ecs_entity_t parent); - -/** Progress an iterator created with ecs_children(). - * - * @param it The iterator. - * @return True if the iterator has more results, false if not. - */ -FLECS_API -bool ecs_children_next( - ecs_iter_t *it); - -/** @} */ - -/** - * @defgroup queries Queries - * Functions for working with `ecs_query_t`. - * - * @{ - */ - -/** Create a query. - * - * @param world The world. - * @param desc The descriptor (see ecs_query_desc_t) - * @return The query. - */ -FLECS_API -ecs_query_t* ecs_query_init( - ecs_world_t *world, - const ecs_query_desc_t *desc); - -/** Delete a query. - * - * @param query The query. - */ -FLECS_API -void ecs_query_fini( - ecs_query_t *query); - -/** 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 query The query. - * @param name The variable name. - * @return The variable index. - */ -FLECS_API -int32_t ecs_query_find_var( - const ecs_query_t *query, - const char *name); - -/** Get variable name. - * This operation returns the variable name for an index. - * - * @param query The query. - * @param var_id The variable index. - * @return The variable name. - */ -FLECS_API -const char* ecs_query_var_name( - const ecs_query_t *query, - int32_t var_id); - -/** 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 query The query. - * @param var_id The variable id. - * @return Whether the variable is an entity variable. - */ -FLECS_API -bool ecs_query_var_is_entity( - const ecs_query_t *query, - int32_t var_id); - -/** 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. - * - * 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. - * - * 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. - * @param query The query. - * @return An iterator. - * - * @see ecs_query_next() - */ -FLECS_API -ecs_iter_t ecs_query_iter( - const ecs_world_t *world, - const ecs_query_t *query); - -/** Progress query iterator. - * - * @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( - 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( - 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( - 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. - * - * The returned string must be freed with ecs_os_free(). - * - * @param query The query. - * @return The query plan. - */ -FLECS_API -char* ecs_query_plan( - const ecs_query_t *query); - -/** Convert query to string with profile. - * To use this you must set the EcsIterProfile flag on an iterator before - * starting iteration: - * - * @code - * it.flags |= EcsIterProfile - * @endcode - * - * 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 -char* ecs_query_plan_w_profile( - const ecs_query_t *query, - const ecs_iter_t *it); - -/** 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: - * - * @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 -const char* ecs_query_args_parse( - ecs_query_t *query, - ecs_iter_t *it, - const char *expr); - -/** 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 - * (EcsInOutNone) 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 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 -bool ecs_query_changed( - ecs_query_t *query); - -/** Get query object. - * Returns the query object. Can be used to access various information about - * the query. - * - * @param world The world. - * @param query The query. - * @return The query object. - */ -FLECS_API -const ecs_query_t* ecs_query_get( - const ecs_world_t *world, - ecs_entity_t query); - -/** 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. - */ -FLECS_API -void ecs_iter_skip( - ecs_iter_t *it); - -/** 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. - * - * 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. - * - * 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. - * - * 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 it The query iterator. - * @param group_id The group to iterate. - */ -FLECS_API -void ecs_iter_set_group( - ecs_iter_t *it, - uint64_t group_id); - -/** Get context of query group. - * This operation returns the context of a query group as returned by the - * on_group_create callback. - * - * @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_query_get_group_ctx( - const ecs_query_t *query, - uint64_t group_id); - -/** 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 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 -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. */ - int32_t empty_tables; /**< Number of empty tables returned by query. */ -} 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 observers Observers - * Functions for working with events and observers. - * - * @{ - */ - -/** 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 desc Event parameters. - * - * @see ecs_enqueue() - */ -FLECS_API -void ecs_emit( - ecs_world_t *world, - ecs_event_desc_t *desc); - -/** 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 this operation is called when the provided world is not in deferred mode - * it behaves just like ecs_emit(). - * - * @param world The world. - * @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 -ecs_entity_t ecs_observer_init( - ecs_world_t *world, - const ecs_observer_desc_t *desc); - -/** 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 observer The observer. - * @return The observer object. - */ -FLECS_API -const ecs_observer_t* ecs_observer_get( - const ecs_world_t *world, - ecs_entity_t observer); - -/** @} */ - -/** - * @defgroup iterator Iterators - * Functions for working with `ecs_iter_t`. - * - * @{ - */ - -/** 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_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 -bool ecs_iter_next( - ecs_iter_t *it); - -/** Cleanup iterator resources. - * This operation cleans up any resources associated with the iterator. - * - * 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 -void ecs_iter_fini( - ecs_iter_t *it); - -/** 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. - * - * 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_iter_count( - ecs_iter_t *it); - -/** 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). - * - * 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 -bool ecs_iter_is_true( - ecs_iter_t *it); - -/** Get first matching entity from iterator. - * After this operation the application should treat the iterator as if it has - * been iterated until completion. - * - * @param it The iterator. - * @return The first matching entity, or 0 if no entities were matched. - */ -FLECS_API -ecs_entity_t ecs_iter_first( - ecs_iter_t *it); - -/** 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: - * - * @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 - * - * 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_iter_set_var( - ecs_iter_t *it, - int32_t var_id, - ecs_entity_t entity); - -/** Same as ecs_iter_set_var(), but for a table. - * This constrains the variable to all entities in a table. - * - * @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_iter_set_var_as_table( - ecs_iter_t *it, - int32_t var_id, - const ecs_table_t *table); - -/** 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. - * - * @see ecs_iter_set_var() - * @see ecs_iter_set_var_as_table() - */ -FLECS_API -void ecs_iter_set_var_as_range( - ecs_iter_t *it, - int32_t var_id, - const ecs_table_range_t *range); - -/** 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. - * @return The variable value. - */ -FLECS_API -ecs_entity_t ecs_iter_get_var( - ecs_iter_t *it, - int32_t var_id); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** @} */ - -/** - * @defgroup tables Tables - * Functions for working with `ecs_table_t`. - * - * @{ - */ - -/** 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); - -/** Get type index for id. - * This operation returns the index for an id in the table's type. - * - * @param world The world. - * @param table The table. - * @param id The id. - * @return The index of the id 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 id); - -/** Get column index for id. - * This operation returns the column index for an id in the table's type. If the - * id is not a component, the function will return -1. - * - * @param world The world. - * @param table The table. - * @param id The component id. - * @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 id); - -/** 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); - -/** 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); - -/** 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); - -/** Get column from table by component id. - * This operation returns the component array for the provided component id. - * - * @param world The world. - * @param table The table. - * @param id The component id 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 id, - int32_t offset); - -/** 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); - -/** 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); - -/** 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 id. - * Same as `ecs_table_get_type_index(world, table, id) != -1`. - * - * @param world The world. - * @param table The table. - * @param id The id. - * @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 id); - -/** 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); - -/** 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 id The id 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 id); - -/** 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); - -/** 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. - * - * @param world The world. - * @param table The table. - * @param id The id 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 id); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - - -/** Search for component id in table type. - * This operation returns the index of first occurrence 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. - * - * @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. - * - * @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 id, - ecs_id_t *id_out); - -/** 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: - * - * @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 id 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 id 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 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. - * - * @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 id, - ecs_id_t *id_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 id The id to search for. - * @param rel The relationship to traverse (optional). - * @param flags Whether to search EcsSelf and/or EcsUp. - * @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 tr_out Internal datatype. - * @return The index of the id 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 id, - ecs_entity_t rel, - ecs_flags64_t flags, /* EcsSelf and/or EcsUp */ - ecs_entity_t *subject_out, - ecs_id_t *id_out, - struct ecs_table_record_t **tr_out); - -/** 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); - -/** @} */ - -/** - * @defgroup values Values - * Construct, destruct, copy and move dynamically created values. - * - * @{ - */ - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** @} */ - -/** @} */ - -/** - * @defgroup c_addons Addons - * @ingroup c - * C APIs for addons. - * - * @{ - * @} - */ - -/** - * @file addons/flecs_c.h - * @brief Extends the core API with convenience macros for C applications. - */ - -#ifndef FLECS_C_ -#define FLECS_C_ - -/** - * @defgroup flecs_c Macro API - * @ingroup c - * Convenience macro's for C API - * - * @{ - */ - -/** - * @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) - -/** Forward declare an entity. */ -#define ECS_ENTITY_DECLARE ECS_DECLARE - -/** 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__) - -/** 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) - -/** Declare & define a tag. - * - * Example: - * - * @code - * ECS_TAG(world, MyTag); - * @endcode - */ -#define ECS_TAG(world, id) ECS_ENTITY(world, id, 0) - -/** 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__) - -/** 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__) - -/** Forward declare a component. */ -#define ECS_COMPONENT_DECLARE(id) ecs_entity_t ecs_id(id) - -/** 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_) - -/** 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) - -/* Forward declare an observer. */ -#define ECS_OBSERVER_DECLARE(id) ecs_entity_t ecs_id(id) - -/** 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_);\ - } - -/** 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 - -/* Forward declare a query. */ -#define ECS_QUERY_DECLARE(name) ecs_query_t* name - -/** Define a forward declared observer. - * - * Example: - * - * @code - * ECS_QUERY_DEFINE(world, AddPosition, Position); - * @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_);\ - } - -/** Declare & define an observer. - * - * Example: - * - * @code - * ECS_OBSERVER(world, AddPosition, EcsOnAdd, Position); - * @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__ ) - -/** Shorthand for creating a component from a type. - * - * Example: - * - * @code - * ecs_component_t(world, Position); - * @endcode - */ -#define ecs_component_t(world, T)\ - ecs_component_init(world, &(ecs_component_desc_t) { \ - .entity = ecs_entity(world, { \ - .name = #T, \ - .symbol = #T, \ - .use_low_id = true \ - }), \ - .type.size = ECS_SIZEOF(T), \ - .type.alignment = ECS_ALIGNOF(T) \ - }) - -/** Shorthand for creating a query with ecs_query_cache_init. - * - * Example: - * ecs_query(world, { - * .terms = {{ ecs_id(Position) }} - * }); - */ -#define ecs_query(world, ...)\ - ecs_query_init(world, &(ecs_query_desc_t) __VA_ARGS__ ) - -/** 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__ ) - -/** @} */ - -/** - * @defgroup flecs_c_type_safe Type Safe API - * Macro's that wrap around core functions to provide a "type safe" API in C - * - * @{ - */ - -/** - * @defgroup flecs_c_entities Entity API - * @{ - */ - -/** - * @defgroup flecs_c_creation_deletion Creation & Deletion - * @{ - */ - -#define ecs_new_w(world, T) ecs_new_w_id(world, ecs_id(T)) - -#define ecs_new_w_pair(world, first, second)\ - ecs_new_w_id(world, ecs_pair(first, second)) - -#define ecs_bulk_new(world, component, count)\ - ecs_bulk_new_w_id(world, ecs_id(component), count) - -/** @} */ - -/** - * @defgroup flecs_c_adding_removing Adding & Removing - * @{ - */ - -#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_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 flecs_c_getting_setting Getting & Setting - * @{ - */ - -/* insert */ -#define ecs_insert(world, ...)\ - ecs_entity(world, { .set = ecs_values(__VA_ARGS__)}) - -/* 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), 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), 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)))) - -#define ecs_ensure_pair(world, subject, First, second)\ - (ECS_CAST(First*, ecs_ensure_id(world, subject,\ - ecs_pair(ecs_id(First), second)))) - -#define ecs_ensure_pair_second(world, subject, first, Second)\ - (ECS_CAST(Second*, ecs_ensure_id(world, subject,\ - ecs_pair(first, ecs_id(Second))))) - -#define ecs_ensure(world, entity, T)\ - (ECS_CAST(T*, ecs_ensure_id(world, entity, ecs_id(T)))) - -#define ecs_ensure_pair(world, subject, First, second)\ - (ECS_CAST(First*, ecs_ensure_id(world, subject,\ - ecs_pair(ecs_id(First), second)))) - -#define ecs_ensure_pair_second(world, subject, first, Second)\ - (ECS_CAST(Second*, ecs_ensure_id(world, subject,\ - ecs_pair(first, ecs_id(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_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_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: - * - * @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: - * - * @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 component hooks. - * Example: - * - * @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_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 -#define ecs_dtor(type) type##_dtor -#define ecs_copy(type) type##_copy -#define ecs_move(type) type##_move -#define ecs_on_set(type) type##_on_set -#define ecs_on_add(type) type##_on_add -#define ecs_on_remove(type) type##_on_remove - -/** @} */ - -/** - * @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_value_pair(R, t, ...) ((ecs_value_t){ecs_pair_t(R, t), &(R)__VA_ARGS__}) - -/** Convenience macro for creating compound literal pair value */ -#define ecs_value_pair_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)) - -/** Convenience macro for creating compound literal value literal */ -#define ecs_value(T, ...) ((ecs_value_t){ecs_id(T), &(T)__VA_ARGS__}) - -/** @} */ - -/** @} */ - -/** - * @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. - * - * This file should only be included by the main flecs.h header. - */ - -#ifndef FLECS_ADDONS_H -#define FLECS_ADDONS_H - -/* 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_SCRIPT_MATH -#undef FLECS_SCRIPT_MATH -#endif -#ifdef FLECS_NO_STATS -#undef FLECS_STATS -#endif -#ifdef FLECS_NO_SYSTEM -#undef FLECS_SYSTEM -#endif -#ifdef FLECS_NO_ALERTS -#undef FLECS_ALERTS -#endif -#ifdef FLECS_NO_PIPELINE -#undef FLECS_PIPELINE -#endif -#ifdef FLECS_NO_TIMER -#undef FLECS_TIMER -#endif -#ifdef FLECS_NO_META -#undef FLECS_META -#endif -#ifdef FLECS_NO_UNITS -#undef FLECS_UNITS -#endif -#ifdef FLECS_NO_JSON -#undef FLECS_JSON -#endif -#ifdef FLECS_NO_DOC -#undef FLECS_DOC -#endif -#ifdef FLECS_NO_LOG -#undef FLECS_LOG -#endif -#ifdef FLECS_NO_APP -#undef FLECS_APP -#endif -#ifdef FLECS_NO_OS_API_IMPL -#undef FLECS_OS_API_IMPL -#endif -#ifdef FLECS_NO_HTTP -#undef FLECS_HTTP -#endif -#ifdef FLECS_NO_REST -#undef FLECS_REST -#endif -#ifdef FLECS_NO_JOURNAL -#undef FLECS_JOURNAL -#endif - -/* Always included, if disabled functions are replaced with dummy macros */ -/** - * @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. - */ - -#ifdef FLECS_JOURNAL - -#ifndef FLECS_LOG -#define FLECS_LOG -#endif - -#ifndef FLECS_JOURNAL_H -#define FLECS_JOURNAL_H - -/** - * @defgroup c_addons_journal Journal - * @ingroup c_addons - * Journaling addon (disabled by default). - * - * - * @{ - */ - -/* Trace when log level is at or higher than level */ -#define FLECS_JOURNAL_LOG_LEVEL (0) - -#ifdef __cplusplus -extern "C" { -#endif - -/* Journaling API, meant to be used by internals. */ - -typedef enum ecs_journal_kind_t { - EcsJournalNew, - EcsJournalMove, - EcsJournalClear, - EcsJournalDelete, - EcsJournalDeleteWith, - EcsJournalRemoveAll, - EcsJournalTableEvents -} ecs_journal_kind_t; - -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); - -FLECS_DBG_API -void flecs_journal_end(void); - -#define flecs_journal(...)\ - flecs_journal_begin(__VA_ARGS__);\ - flecs_journal_end(); - -#ifdef __cplusplus -} -#endif // __cplusplus -/** @} */ -#endif // FLECS_JOURNAL_H -#else -#define flecs_journal_begin(...) -#define flecs_journal_end(...) -#define flecs_journal(...) - -#endif // FLECS_JOURNAL - -/** - * @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 - * 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 - * 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. - */ - -#ifndef FLECS_LOG_H -#define FLECS_LOG_H - -#ifdef __cplusplus -extern "C" { -#endif - -#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, - 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); - -/** 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); - -/** Should current level 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. - * @return Whether logging is enabled for the current level. - */ -FLECS_API -bool ecs_should_log(int32_t level); - -//////////////////////////////////////////////////////////////////////////////// -//// Error reporting -//////////////////////////////////////////////////////////////////////////////// - -/** Get description for error code */ -FLECS_API -const char* ecs_strerror( - int32_t error_code); - -#else // FLECS_LOG - -//////////////////////////////////////////////////////////////////////////////// -//// Dummy macros for when logging is disabled -//////////////////////////////////////////////////////////////////////////////// - -#define ecs_deprecated_(file, line, msg)\ - (void)file;\ - (void)line;\ - (void)msg - -#define ecs_log_push_(level) -#define ecs_log_pop_(level) -#define ecs_should_log(level) false - -#define ecs_strerror(error_code)\ - (void)error_code - -#endif // FLECS_LOG - - -//////////////////////////////////////////////////////////////////////////////// -//// Logging functions (do nothing when logging is enabled) -//////////////////////////////////////////////////////////////////////////////// - -FLECS_API -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, - const char *fmt, - ...); - -FLECS_API -void ecs_logv_( - int level, - const char *file, - int32_t line, - const char *fmt, - va_list args); - -FLECS_API -void ecs_abort_( - int32_t error_code, - const char *file, - int32_t line, - const char *fmt, - ...); - -FLECS_API -void ecs_assert_log_( - int32_t error_code, - const char *condition_str, - const char *file, - int32_t line, - const char *fmt, - ...); - -FLECS_API -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, - int64_t column, - const char *fmt, - ...); - -FLECS_API -void ecs_parser_warningv_( - const char *name, - const char *expr, - int64_t column, - const char *fmt, - va_list args); - - -//////////////////////////////////////////////////////////////////////////////// -//// Logging macros -//////////////////////////////////////////////////////////////////////////////// - -#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__) - -#define ecs_logv(level, 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__) - -/* 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__) - -/* 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__) - -/* 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__) - -/* Optionally include warnings about using deprecated features */ -#ifndef FLECS_NO_DEPRECATED_WARNINGS -#define ecs_deprecated(...)\ - ecs_deprecated_(__FILE__, __LINE__, __VA_ARGS__) -#else -#define ecs_deprecated(...) -#endif // FLECS_NO_DEPRECATED_WARNINGS - -/* If no tracing verbosity is defined, pick default based on build config */ -#if !(defined(FLECS_LOG_0) || defined(FLECS_LOG_1) || defined(FLECS_LOG_2) || defined(FLECS_LOG_3)) -#if !defined(FLECS_NDEBUG) -#define FLECS_LOG_3 /* Enable all tracing in debug mode. May slow things down */ -#else -#define FLECS_LOG_0 /* Only enable infrequent tracing in release mode */ -#endif // !defined(FLECS_NDEBUG) -#endif // !(defined(FLECS_LOG_0) || defined(FLECS_LOG_1) || defined(FLECS_LOG_2) || defined(FLECS_LOG_3)) - - -/* 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 */ -#define ecs_dbg_1(...) ecs_log(1, __VA_ARGS__); -#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_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) -#define ecs_should_log_3() ecs_should_log(3) - -#define FLECS_LOG_2 -#define FLECS_LOG_1 -#define FLECS_LOG_0 - -#elif defined(FLECS_LOG_2) /* Level 2 and below debug tracing enabled */ -#define ecs_dbg_1(...) ecs_log(1, __VA_ARGS__); -#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_3() - -#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) -#define ecs_should_log_2() ecs_should_log(2) -#define ecs_should_log_3() false - -#define FLECS_LOG_1 -#define FLECS_LOG_0 - -#elif defined(FLECS_LOG_1) /* Level 1 debug tracing enabled */ -#define ecs_dbg_1(...) ecs_log(1, __VA_ARGS__); -#define ecs_dbg_2(...) -#define ecs_dbg_3(...) - -#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_2() -#define ecs_log_pop_3() - -#define ecs_should_log_1() ecs_should_log(1) -#define ecs_should_log_2() false -#define ecs_should_log_3() false - -#define FLECS_LOG_0 - -#elif defined(FLECS_LOG_0) /* No debug tracing enabled */ -#define ecs_dbg_1(...) -#define ecs_dbg_2(...) -#define ecs_dbg_3(...) - -#define ecs_log_push_1() -#define ecs_log_push_2() -#define ecs_log_push_3() - -#define ecs_log_pop_1() -#define ecs_log_pop_2() -#define ecs_log_pop_3() - -#define ecs_should_log_1() false -#define ecs_should_log_2() false -#define ecs_should_log_3() false - -#else /* No tracing enabled */ -#undef ecs_trace -#define ecs_trace(...) -#define ecs_dbg_1(...) -#define ecs_dbg_2(...) -#define ecs_dbg_3(...) - -#define ecs_log_push_1() -#define ecs_log_push_2() -#define ecs_log_push_3() - -#define ecs_log_pop_1() -#define ecs_log_pop_2() -#define ecs_log_pop_3() - -#endif // defined(FLECS_LOG_3) - -/* Default debug tracing is at level 1 */ -#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) - -/** Abort. - * Unconditionally aborts process. */ -#define ecs_abort(error_code, ...)\ - ecs_abort_(error_code, __FILE__, __LINE__, __VA_ARGS__);\ - ecs_os_abort(); abort(); /* satisfy compiler/static analyzers */ - -/** 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 (!(condition)) {\ - ecs_assert_log_(error_code, #condition, __FILE__, __LINE__, __VA_ARGS__);\ - ecs_os_abort();\ - }\ - assert(condition) /* satisfy compiler/static analyzers */ -#endif // FLECS_NDEBUG - -#define ecs_assert_var(var, error_code, ...)\ - ecs_assert(var, error_code, __VA_ARGS__);\ - (void)var - -/** 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__) -#else -#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. - * 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 (!(condition)) {\ - ecs_assert_log_(error_code, #condition, __FILE__, __LINE__, __VA_ARGS__);\ - goto error;\ - } -#else // FLECS_SOFT_ASSERT -#define ecs_check(condition, error_code, ...)\ - ecs_assert(condition, error_code, __VA_ARGS__);\ - ecs_dummy_check -#endif -#endif // FLECS_NDEBUG - -/** 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__);\ - goto error; -#else -#define ecs_throw(error_code, ...)\ - ecs_abort(error_code, __VA_ARGS__);\ - ecs_dummy_check -#endif -#endif // FLECS_NDEBUG - -/** Parser error */ -#define ecs_parser_error(name, expr, column, ...)\ - 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) - -#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 - - -//////////////////////////////////////////////////////////////////////////////// -//// Functions that are always available -//////////////////////////////////////////////////////////////////////////////// - -/** 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 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 log level is defined and this is a debug build, FLECS_LOG_3 will - * have been automatically defined. - * - * 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 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. - * - * @param enabled Whether to enable tracing with colors. - * @return Previous color setting. - */ -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. - * - * @return Last error, 0 if none was logged since last call to last_error. - */ -FLECS_API -int ecs_log_last_error(void); - - -//////////////////////////////////////////////////////////////////////////////// -//// Error codes -//////////////////////////////////////////////////////////////////////////////// - -#define ECS_INVALID_OPERATION (1) -#define ECS_INVALID_PARAMETER (2) -#define ECS_CONSTRAINT_VIOLATED (3) -#define ECS_OUT_OF_MEMORY (4) -#define ECS_OUT_OF_RANGE (5) -#define ECS_UNSUPPORTED (6) -#define ECS_INTERNAL_ERROR (7) -#define ECS_ALREADY_DEFINED (8) -#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) -#define ECS_INCONSISTENT_COMPONENT_ID (26) -#define ECS_INCONSISTENT_COMPONENT_ACTION (27) -#define ECS_MODULE_UNDEFINED (28) -#define ECS_MISSING_SYMBOL (29) -#define ECS_ALREADY_IN_USE (30) - -#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_INVALID_WHILE_READONLY (70) -#define ECS_LOCKED_STORAGE (71) -#define ECS_INVALID_FROM_WORKER (72) - - -//////////////////////////////////////////////////////////////////////////////// -//// Used when logging with colors is enabled -//////////////////////////////////////////////////////////////////////////////// - -#define ECS_BLACK "\033[1;30m" -#define ECS_RED "\033[0;31m" -#define ECS_GREEN "\033[0;32m" -#define ECS_YELLOW "\033[0;33m" -#define ECS_BLUE "\033[0;34m" -#define ECS_MAGENTA "\033[0;35m" -#define ECS_CYAN "\033[0;36m" -#define ECS_WHITE "\033[1;37m" -#define ECS_GREY "\033[0;37m" -#define ECS_NORMAL "\033[0;49m" -#define ECS_BOLD "\033[1;49m" - -#ifdef __cplusplus -} -#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 addons/app.h - * @brief App addon. - * - * The app addon is a wrapper around the application's main loop. Its main - * purpose is to provide a hook to modules that need to take control of the - * main loop, as is for example the case with native applications that use - * emscripten with webGL. - */ - -#ifdef FLECS_APP - -#ifndef FLECS_PIPELINE -#define FLECS_PIPELINE -#endif - -#ifndef FLECS_APP_H -#define FLECS_APP_H - -#ifdef __cplusplus -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(). */ -typedef struct ecs_app_desc_t { - 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 */ -} ecs_app_desc_t; - -/** Callback type for run action. */ -typedef int(*ecs_app_run_action_t)( - 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, - 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. - * - * 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. - */ -FLECS_API -int ecs_app_run( - ecs_world_t *world, - ecs_app_desc_t *desc); - -/** 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. - * - * @param world The world. - * @param desc The desc struct passed to ecs_app_run(). - * @return value returned by ecs_progress() - */ -FLECS_API -int ecs_app_run_frame( - ecs_world_t *world, - const ecs_app_desc_t *desc); - -/** Set custom run action. - * See ecs_app_run(). - * - * @param callback The run action. - */ -FLECS_API -int ecs_app_set_run_action( - ecs_app_run_action_t callback); - -/** Set custom frame action. - * 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 - -#endif - -#endif // FLECS_APP - -#endif - -#ifdef FLECS_HTTP -#ifdef FLECS_NO_HTTP -#error "FLECS_NO_HTTP failed: HTTP is required by other addons" -#endif -/** - * @file addons/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. - */ - -#ifdef FLECS_HTTP - -/** - * @defgroup c_addons_http Http - * @ingroup c_addons - * Simple HTTP server used for serving up REST API. - * - * @{ - */ - -#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; - -/** An HTTP 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; - -/** 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; - -#define ECS_HTTP_REPLY_INIT \ - (ecs_http_reply_t){200, ECS_STRBUF_INIT, "OK", "application/json", ECS_STRBUF_INIT} - -/* 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. */ - -/** 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; - -/** 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); - -/** 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); - -/** 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) */ - void *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 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. - * - * @param world The world. - * @param tick_source The timer for which to set the timeout (0 to create one). - * @param timeout The timeout value. - * @return The timer entity. - */ -FLECS_API -ecs_entity_t ecs_set_timeout( - ecs_world_t *world, - ecs_entity_t tick_source, - 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 - * 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 - * operation will return 0. - * - * The timer is synchronous, and is incremented each frame by delta_time. - * - * 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. - * - * @param world The world. - * @param tick_source The timer. - * @return The current timeout value, or 0 if no timer is active. - */ -FLECS_API -ecs_ftime_t ecs_get_timeout( - const ecs_world_t *world, - ecs_entity_t tick_source); - -/** Set timer interval. - * 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 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. - * - * @param world The world. - * @param tick_source The timer for which to set the interval (0 to create one). - * @param interval The interval value. - * @return The timer entity. - */ -FLECS_API -ecs_entity_t ecs_set_interval( - ecs_world_t *world, - ecs_entity_t tick_source, - 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 - * not a timer, the operation will return 0. - * - * @param world The world. - * @param tick_source The timer for which to set the interval. - * @return The current interval value, or 0 if no timer is active. - */ -FLECS_API -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. - * - * @param world The world. - * @param tick_source The timer to start. - */ -FLECS_API -void ecs_start_timer( - ecs_world_t *world, - ecs_entity_t tick_source); - -/** Stop timer - * This operation stops a timer from triggering. - * - * @param world The world. - * @param tick_source The timer to stop. - */ -FLECS_API -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, - * which means that rate filters can be chained. - * - * Rate filters enable deterministic system execution which cannot be achieved - * 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. - * - * 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. - * - * 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. - * - * @param world The world. - * @param tick_source The rate filter entity (0 to create one). - * @param rate The rate to apply. - * @param source The tick source (0 to use frames) - * @return The filter entity. - */ -FLECS_API -ecs_entity_t ecs_set_rate( - ecs_world_t *world, - ecs_entity_t tick_source, - int32_t rate, - ecs_entity_t source); - -/** Assign tick source to system. - * 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 - * must use the same tick source, which is what this operation enables. - * - * When two systems share the same tick source, it is guaranteed that they tick - * in the same frame. The provided tick source can be any entity that is a tick - * 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 - * 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, - ecs_entity_t system, - ecs_entity_t 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); - -#ifdef __cplusplus -} -#endif - -#endif - -/** @} */ - -#endif - -#endif - -#ifdef FLECS_PIPELINE -#ifdef FLECS_NO_PIPELINE -#error "FLECS_NO_PIPELINE failed: PIPELINE is required by other addons" -#endif -/** - * @file addons/pipeline.h - * @brief Pipeline module. - * - * The pipeline module provides support for running systems automatically and - * on multiple threads. A pipeline is a collection of tags that can be added to - * 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. - */ - -#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 - -#ifndef FLECS_SYSTEM -#define FLECS_SYSTEM -#endif - -#if !defined(FLECS_OS_API_IMPL) && !defined(FLECS_NO_OS_API_IMPL) -#define FLECS_OS_API_IMPL -#endif - -#ifndef FLECS_PIPELINE_H -#define FLECS_PIPELINE_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef FLECS_LEGACY - -/** 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 id = 0, ecs_id(id) = 0; ECS_PIPELINE_DEFINE(world, id, __VA_ARGS__);\ - (void)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. - * - * @param world The world. - * @param pipeline The pipeline to set. - */ -FLECS_API -void ecs_set_pipeline( - ecs_world_t *world, - ecs_entity_t pipeline); - -/** Get the current pipeline. - * This operation gets the current pipeline. - * - * @param world The world. - * @return The current pipeline. - */ -FLECS_API -ecs_entity_t ecs_get_pipeline( - const ecs_world_t *world); - -/** Progress a world. - * This operation progresses the world by running all systems that are both - * enabled and periodic on their matching entities. - * - * An application can pass a delta_time into the function, which is the time - * passed since the last frame. This value is passed to systems so they can - * update entity values proportional to the elapsed time since their last - * invocation. - * - * 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. - */ -FLECS_API -bool ecs_progress( - ecs_world_t *world, - ecs_ftime_t delta_time); - -/** Set time scale. - * Increase or decrease simulation speed by the provided multiplier. - * - * @param world The world. - * @param scale The scale to apply (default = 1). - */ -FLECS_API -void ecs_set_time_scale( - ecs_world_t *world, - ecs_ftime_t scale); - -/** Reset world clock. - * Reset the clock that keeps track of the total time passed in the simulation. - * - * @param world The world. - */ -FLECS_API -void ecs_reset_clock( - ecs_world_t *world); - -/** Run pipeline. - * This will run all systems in the provided pipeline. This operation may be - * invoked from multiple threads, and only when staging is disabled, as the - * 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()). - * - * When using progress() this operation will be invoked automatically for the - * default pipeline (either the builtin pipeline or the pipeline set with - * set_pipeline()). An application may run additional pipelines. - * - * @param world The world. - * @param pipeline The pipeline to run. - * @param delta_time The delta_time to pass to systems. - */ -FLECS_API -void ecs_run_pipeline( - ecs_world_t *world, - ecs_entity_t pipeline, - ecs_ftime_t delta_time); - - -//////////////////////////////////////////////////////////////////////////////// -//// Threading -//////////////////////////////////////////////////////////////////////////////// - -/** Set number of worker threads. - * 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. - * 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); - -#ifdef __cplusplus -} -#endif - -#endif - -/** @} */ - -#endif - -#endif - -#ifdef FLECS_SYSTEM -#ifdef FLECS_NO_SYSTEM -#error "FLECS_NO_SYSTEM failed: SYSTEM is required by other addons" -#endif -/** - * @file addons/system.h - * @brief System module. - * - * The system module allows for creating and running systems. A system is a - * query in combination with a callback function. In addition systems have - * support for time management and can be monitored by the stats addon. - */ - -#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 - -#ifndef FLECS_SYSTEM_H -#define FLECS_SYSTEM_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** Component used to provide a tick source to systems */ -typedef struct EcsTickSource { - bool tick; /**< True if providing tick */ - ecs_ftime_t time_elapsed; /**< Time elapsed since last tick */ -} EcsTickSource; - -/** Use with ecs_system_init() to create or update a system. */ -typedef struct ecs_system_desc_t { - int32_t _canary; - - /** Existing entity to associate with system (optional) */ - ecs_entity_t entity; - - /** System query parameters */ - ecs_query_desc_t query; - - /** 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; - - /** 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) */ - void *ctx; - - /** Callback to free ctx. */ - ecs_ctx_free_t ctx_free; - - /** 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; - - /** 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 */ - int32_t rate; - - /** External tick source that determines when system ticks */ - ecs_entity_t tick_source; - - /** If true, system will be ran on multiple threads */ - bool multi_threaded; - - /** If true, system will have access to the actual world. Cannot be true at the - * same time as multi_threaded. */ - bool immediate; -} ecs_system_desc_t; - -/** 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; - - /** Entity associated with query */ - ecs_entity_t query_entity; - - /** 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 */ - ecs_world_t *world; - ecs_entity_t entity; - 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 - -/** 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}; \ - ecs_entity_desc_t edesc = {0}; \ - ecs_id_t add_ids[3] = {\ - ((phase) ? ecs_pair(EcsDependsOn, (phase)) : 0), \ - (phase), \ - 0 \ - };\ - edesc.id = ecs_id(id_);\ - edesc.name = #id_;\ - edesc.add = add_ids;\ - desc.entity = ecs_entity_init(world, &edesc);\ - desc.query.expr = #__VA_ARGS__; \ - desc.callback = id_; \ - ecs_id(id_) = ecs_system_init(world, &desc); \ - } \ - ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, "failed to create system %s", #id_) - -/** 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 - -/** 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. - * This operation runs a single system manually. It is an efficient way to - * 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 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. - * - * An application may pass custom data to a system through the param parameter. - * This data can be accessed by the system through the param member in the - * ecs_iter_t value that is passed to the system callback. - * - * 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 - * 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. - * - * @param world The world. - * @param system The system to run. - * @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( - ecs_world_t *world, - ecs_entity_t system, - ecs_ftime_t delta_time, - void *param); - -/** 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. - * @param stage_count The total number of stages. - * @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_worker( - ecs_world_t *world, - ecs_entity_t system, - int32_t stage_current, - int32_t stage_count, - ecs_ftime_t delta_time, - void *param); - -/** System module import function. - * Usage: - * @code - * ECS_IMPORT(world, FlecsSystem) - * @endcode - * - * @param world The world. - */ -FLECS_API -void FlecsSystemImport( - ecs_world_t *world); - -#ifdef __cplusplus -} -#endif - -#endif - -/** @} */ - -#endif - -#endif - -#ifdef FLECS_STATS -#ifdef FLECS_NO_STATS -#error "FLECS_NO_STATS failed: STATS is required by other addons" -#endif -/** - * @file addons/stats.h - * @brief Statistics addon. - * - * 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_STATS - -/** - * @defgroup c_addons_stats Stats - * @ingroup c_addons - * Collection of statistics for world, queries, systems and pipelines. - * - * @{ - */ - -#ifndef FLECS_STATS_H -#define FLECS_STATS_H - -#ifndef FLECS_MODULE -#define FLECS_MODULE -#endif - -#ifndef FLECS_PIPELINE -#define FLECS_PIPELINE -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define ECS_STAT_WINDOW (60) - -/** 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; - -/** 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; - -/** 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; - -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; - - /* 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; - - /* 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; - - /* 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; - - /* 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; - -/** 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_; - - /** Current position in ringbuffer */ - int32_t t; -} ecs_query_stats_t; - -/** 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_; - - bool task; /**< Is system a task */ - - ecs_query_stats_t query; -} ecs_system_stats_t; - -/** 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 stats Out parameter for statistics. - */ -FLECS_API -void ecs_world_stats_get( - const ecs_world_t *world, - ecs_world_stats_t *stats); - -/** Reduce source measurement window into single destination measurement. */ -FLECS_API -void ecs_world_stats_reduce( - ecs_world_stats_t *dst, - const ecs_world_stats_t *src); - -/** Reduce last measurement into previous measurement, restore old value. */ -FLECS_API -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, - const ecs_world_stats_t *stats); - -/** Get query statistics. - * Obtain statistics for the provided query. - * - * @param world The world. - * @param query The query. - * @param stats Out parameter for statistics. - */ -FLECS_API -void ecs_query_stats_get( - const ecs_world_t *world, - const ecs_query_t *query, - ecs_query_stats_t *stats); - -/** 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 system The system. - * @param stats Out parameter for statistics. - * @return true if success, false if not a system. - */ -FLECS_API -bool ecs_system_stats_get( - const ecs_world_t *world, - ecs_entity_t system, - ecs_system_stats_t *stats); - -/** 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 pipeline The pipeline. - * @param stats Out parameter for statistics. - * @return true if success, false if not a pipeline. - */ -FLECS_API -bool ecs_pipeline_stats_get( - ecs_world_t *world, - ecs_entity_t pipeline, - ecs_pipeline_stats_t *stats); - -/** Free pipeline stats. - * - * @param stats The stats to free. - */ -FLECS_API -void ecs_pipeline_stats_fini( - ecs_pipeline_stats_t *stats); - -/** 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); - -/** 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); - -/** Repeat last measurement. */ -FLECS_API -void ecs_pipeline_stats_repeat_last( - ecs_pipeline_stats_t *stats); - -/** Copy last measurement to destination. - * This operation copies the last measurement into the destination. It does not - * modify the cursor. - * - * @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); - -/** 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); - -/** Reduce last measurement into previous measurement */ -FLECS_API -void ecs_metric_reduce_last( - ecs_metric_t *m, - int32_t t, - int32_t count); - -/** 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. */ - -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; - -/** 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; - -/** Component that stores pipeline statistics. */ -typedef struct { - EcsStatsHeader hdr; - ecs_map_t stats; -} EcsPipelineStats; - -/** Component that stores a summary of world statistics. */ -typedef struct { - /* Time */ - double target_fps; /**< Target FPS */ - double time_scale; /**< Simulation time scale */ - - /* Total time */ - 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 */ - - /* Last frame time */ - double frame_time_last; /**< Time spent processing a frame */ - double system_time_last; /**< Time spent in systems */ - double merge_time_last; /**< Time spent in merges */ - - int64_t frame_count; /**< Number of frames processed */ - int64_t command_count; /**< Number of commands processed */ - - /* Build info */ - ecs_build_info_t build_info; /**< Build info */ -} EcsWorldSummary; - -/** Stats module import function. - * Usage: - * @code - * ECS_IMPORT(world, FlecsStats) - * @endcode - * - * @param world The world. - */ -FLECS_API -void FlecsStatsImport( - ecs_world_t *world); - -#ifdef __cplusplus -} -#endif - -#endif - -/** @} */ - -#endif - -#endif - -#ifdef FLECS_METRICS -#ifdef FLECS_NO_METRICS -#error "FLECS_NO_METRICS failed: METRICS is required by other addons" -#endif -/** - * @file addons/metrics.h - * @brief Metrics module. - * - * The metrics module extracts metrics from components and makes them available - * through a unified component interface. - */ - -#ifdef FLECS_METRICS - -/** - * @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 -#define FLECS_UNITS -#endif - -#ifndef FLECS_PIPELINE -#define FLECS_PIPELINE -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/** Flecs metrics module. */ -FLECS_API extern ECS_COMPONENT_DECLARE(FlecsMetrics); - -/** 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); - -/** Counter metric that is auto-incremented by source value. */ -FLECS_API extern ECS_TAG_DECLARE(EcsCounterIncrement); - -/** Counter metric that counts the number of entities with an id. */ -FLECS_API extern ECS_TAG_DECLARE(EcsCounterId); - -/** 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 FlecsMetricsImport( - ecs_world_t *world); - -#ifdef __cplusplus -} -#endif - -#endif - -/** @} */ - -#endif - -#endif - -#ifdef FLECS_ALERTS -#ifdef FLECS_NO_ALERTS -#error "FLECS_NO_ALERTS failed: ALERTS is required by other addons" -#endif -/** - * @file addons/alerts.h - * @brief Alerts module. - * - * 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_ALERTS - -/** - * @defgroup c_addons_alerts Alerts - * @ingroup c_addons - * Create alerts from monitoring queries. - * - * @{ - */ - -#ifndef FLECS_ALERTS_H -#define FLECS_ALERTS_H - -#ifndef FLECS_PIPELINE -#define FLECS_PIPELINE -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#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; - - /** Entity associated with alert */ - ecs_entity_t entity; - - /** 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; - - /** 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; - - /** User friendly name. Will only be set if FLECS_DOC addon is enabled. */ - const char *doc_name; - - /** Description of alert. Will only be set if FLECS_DOC addon is enabled */ - const char *brief; - - /** Metric kind. Must be EcsAlertInfo, EcsAlertWarning, EcsAlertError or - * EcsAlertCritical. Defaults to EcsAlertError. */ - ecs_entity_t severity; - - /** 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]; - - /** 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; - - /** 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; - - /** (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; - - /** 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; - -/** 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); - -/** 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); - -/** 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); - -/** Alert module import function. - * Usage: - * @code - * ECS_IMPORT(world, FlecsAlerts) - * @endcode - * - * @param world The world. - */ -FLECS_API -void FlecsAlertsImport( - ecs_world_t *world); - -#ifdef __cplusplus -} -#endif - -#endif - -/** @} */ - -#endif - -#endif - -#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. - */ - -#ifdef FLECS_JSON - -#ifndef FLECS_META -#define FLECS_META -#endif - -#ifndef FLECS_SCRIPT -#define FLECS_SCRIPT -#endif - -#ifndef FLECS_JSON_H -#define FLECS_JSON_H - -/** - * @defgroup c_addons_json Json - * @ingroup c_addons - * Functions for serializing to/from JSON. - * - * @{ - */ - -#ifdef __cplusplus -extern "C" { -#endif - -/** 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) */ - - /** Callback that allows for specifying a custom lookup function. The - * default behavior uses ecs_lookup() */ - ecs_entity_t (*lookup_action)( - const ecs_world_t*, - const char *value, - void *ctx); - void *lookup_ctx; - - /** 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 -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); - -/** 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 -const char* ecs_entity_from_json( - ecs_world_t *world, - ecs_entity_t entity, - const char *json, - const ecs_from_json_desc_t *desc); - -/** 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 -const char* ecs_world_from_json( - ecs_world_t *world, - const char *json, - const ecs_from_json_desc_t *desc); - -/** 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 -const char* ecs_world_from_json_file( - ecs_world_t *world, - const char *filename, - const ecs_from_json_desc_t *desc); - -/** 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 -char* ecs_array_to_json( - const ecs_world_t *world, - ecs_entity_t type, - const void *data, - int32_t count); - -/** 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_array_to_json_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *data, - int32_t count, - ecs_strbuf_t *buf_out); - -/** 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 -char* ecs_ptr_to_json( - const ecs_world_t *world, - ecs_entity_t type, - const void *data); - -/** 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 -int ecs_ptr_to_json_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *data, - ecs_strbuf_t *buf_out); - -/** 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 -char* ecs_type_info_to_json( - const ecs_world_t *world, - ecs_entity_t type); - -/** 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 -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_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 */ -} ecs_entity_to_json_desc_t; - -/** 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, \ -} -#else -#define ECS_ENTITY_TO_JSON_INIT {\ - false, \ - false, \ - true, \ - false, \ - true, \ - false, \ - false, \ - false, \ - 0, \ - false, \ -} -#endif - -/** 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 -char* ecs_entity_to_json( - const ecs_world_t *world, - ecs_entity_t entity, - const ecs_entity_to_json_desc_t *desc); - -/** 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_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_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 */ - ecs_poly_t *query; /**< Query object (required for serialize_query_[plan|profile]). */ -} ecs_iter_to_json_desc_t; - -/** 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, \ - .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, \ - nullptr \ -} -#endif - -/** 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 -char* ecs_iter_to_json( - ecs_iter_t *iter, - const ecs_iter_to_json_desc_t *desc); - -/** 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_iter_to_json_buf( - ecs_iter_t *iter, - ecs_strbuf_t *buf_out, - const ecs_iter_to_json_desc_t *desc); - -/** 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; - -/** 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 -char* ecs_world_to_json( - ecs_world_t *world, - const ecs_world_to_json_desc_t *desc); - -/** 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_world_to_json_buf( - ecs_world_t *world, - ecs_strbuf_t *buf_out, - const ecs_world_to_json_desc_t *desc); - -#ifdef __cplusplus -} -#endif - -#endif - -/** @} */ - -#endif - -#endif - -#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. - */ - -#ifdef FLECS_UNITS - -/** - * @defgroup c_addons_units Units. - * @ingroup c_addons - * Common unit annotations for reflection framework. - * - * @{ - */ - -#ifndef FLECS_MODULE -#define FLECS_MODULE -#endif - -#ifndef FLECS_META -#define FLECS_META -#endif - -#ifndef FLECS_UNITS_H -#define FLECS_UNITS_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @defgroup c_addons_units_prefixes Prefixes - * @ingroup c_addons_units - * Prefixes to indicate unit count (e.g. Kilo, Mega) - * - * @{ - */ - -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. */ - -/** @} */ - -/** - * @defgroup c_addons_units_duration Duration - * @ingroup c_addons_units - * @{ - */ - -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. */ - -/** @} */ - -/** - * @defgroup c_addons_units_time Time - * @ingroup c_addons_units - * @{ - */ - -FLECS_API extern ecs_entity_t EcsTime; /**< Time quantity. */ -FLECS_API extern ecs_entity_t EcsDate; /**< Date unit. */ - -/** @} */ - -/** - * @defgroup c_addons_units_mass Mass - * @ingroup c_addons_units - * @{ - */ - -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. */ - -/** @} */ - -/** - * @defgroup c_addons_units_electric_Current Electric Current - * @ingroup c_addons_units - * @{ - */ - -FLECS_API extern ecs_entity_t EcsElectricCurrent; /**< ElectricCurrent quantity. */ -FLECS_API extern ecs_entity_t EcsAmpere; /**< Ampere unit. */ - -/** @} */ - -/** - * @defgroup c_addons_units_amount Amount - * @ingroup c_addons_units - * @{ - */ - -FLECS_API extern ecs_entity_t EcsAmount; /**< Amount quantity. */ -FLECS_API extern ecs_entity_t EcsMole; /**< Mole unit. */ - -/** @} */ - -/** - * @defgroup c_addons_units_luminous_intensity Luminous Intensity - * @ingroup c_addons_units - * @{ - */ - -FLECS_API extern ecs_entity_t EcsLuminousIntensity; /**< LuminousIntensity quantity. */ -FLECS_API extern ecs_entity_t EcsCandela; /**< Candela unit. */ - -/** @} */ - -/** - * @defgroup c_addons_units_force Force - * @ingroup c_addons_units - * @{ - */ - -FLECS_API extern ecs_entity_t EcsForce; /**< Force quantity. */ -FLECS_API extern ecs_entity_t EcsNewton; /**< Newton unit. */ - -/** @} */ - -/** - * @defgroup c_addons_units_length Length - * @ingroup c_addons_units - * @{ - */ - -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. */ - -/** @} */ - -/** - * @defgroup c_addons_units_pressure Pressure - * @ingroup c_addons_units - * @{ - */ - -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. */ - -/** @} */ - -/** - * @defgroup c_addons_units_speed Speed - * @ingroup c_addons_units - * @{ - */ - -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. */ - -/** @} */ - -/** - * @defgroup c_addons_units_temperature Temperature - * @ingroup c_addons_units - * @{ - */ - -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. */ - -/** @} */ - -/** - * @defgroup c_addons_units_data Data - * @ingroup c_addons_units - * @{ - */ - -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. */ - -/** @} */ - -/** - * @defgroup c_addons_units_datarate Data Rate - * @ingroup c_addons_units - * @{ - */ - -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 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. */ - -/** @} */ - -/** - * @defgroup c_addons_units_angle Angle - * @ingroup c_addons_units - * @{ - */ - -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 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. - */ -FLECS_API -void FlecsUnitsImport( - ecs_world_t *world); - -#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 -extern ECS_COMPONENT_DECLARE(EcsScriptRng); - -/* Randon number generator */ -typedef struct { - uint64_t seed; - void *impl; -} EcsScriptRng; - -/** Script math import function. - * Usage: - * @code - * ECS_IMPORT(world, FlecsScriptMath) - * @endcode - * - * @param world The world. - */ -FLECS_API -void FlecsScriptMathImport( - ecs_world_t *world); - -#ifdef __cplusplus -} -#endif - -#endif - -/** @} */ - -#endif - -#endif - -#ifdef FLECS_SCRIPT -#ifdef FLECS_NO_SCRIPT -#error "FLECS_NO_SCRIPT failed: SCRIPT is required by other addons" -#endif -/** - * @file addons/script.h - * @brief Flecs script module. - * - * For script, see examples/script. - */ - -#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_SCRIPT_H -#define FLECS_SCRIPT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#define FLECS_SCRIPT_FUNCTION_ARGS_MAX (16) - -FLECS_API -extern ECS_COMPONENT_DECLARE(EcsScript); - -FLECS_API -extern ECS_DECLARE(EcsScriptTemplate); - -FLECS_API -extern ECS_COMPONENT_DECLARE(EcsScriptConstVar); - -FLECS_API -extern ECS_COMPONENT_DECLARE(EcsScriptFunction); - -FLECS_API -extern ECS_COMPONENT_DECLARE(EcsScriptMethod); - -/* Script template. */ -typedef struct ecs_script_template_t ecs_script_template_t; - -/** 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; - -/** Script variable scope. */ -typedef struct ecs_script_vars_t { - struct ecs_script_vars_t *parent; - int32_t sp; - - ecs_hashmap_t var_index; - ecs_vec_t vars; - - 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 { - 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); - -/** Function argument type. */ -typedef struct ecs_script_parameter_t { - const char *name; - ecs_entity_t type; -} ecs_script_parameter_t; - -/** 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; - -/** Function component. - * This component describes a function that can be called from a script. - */ -typedef struct EcsScriptFunction { - ecs_entity_t return_type; - ecs_vec_t params; /* vec */ - ecs_function_callback_t callback; - void *ctx; -} EcsScriptFunction; - -/** 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 EcsScriptMethod { - ecs_entity_t return_type; - ecs_vec_t params; /* vec */ - ecs_function_callback_t callback; - void *ctx; -} EcsScriptMethod; - -/* Parsing & running scripts */ - -/** 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; - -/** 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. - * - * @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. - * @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); - -/** 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. - * - * @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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - -/** 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); - - -/* Managed scripts (script associated with entity that outlives the function) */ - -/** 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; - -/** 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. - * - * This feature is experimental. - * - * @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); - -#define ecs_script(world, ...)\ - ecs_script_init(world, &(ecs_script_desc_t) __VA_ARGS__) - -/** Update script with new code. - * - * @param world The world. - * @param script The script entity. - * @param instance An template instance (optional). - * @param code The script code. - */ -FLECS_API -int ecs_script_update( - ecs_world_t *world, - ecs_entity_t script, - ecs_entity_t instance, - const char *code); - -/** Clear all entities associated with script. - * - * @param world The world. - * @param script The script entity. - * @param instance The script instance. - */ -FLECS_API -void ecs_script_clear( - ecs_world_t *world, - ecs_entity_t script, - ecs_entity_t instance); - - -/* Script variables */ - -/** 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. - * - * Use the `ecs_script_vars_push()` and `ecs_script_vars_pop()` functions to - * push and pop variable scopes. - * - * 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. - */ -FLECS_API -ecs_script_vars_t* ecs_script_vars_init( - ecs_world_t *world); - -/** 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_script_vars_fini( - ecs_script_vars_t *vars); - -/** Push new variable scope. - * - * Scopes created with ecs_script_vars_push() must be cleaned up with - * ecs_script_vars_pop(). - * - * 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 -ecs_script_vars_t* ecs_script_vars_push( - ecs_script_vars_t *parent); - -/** 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 -ecs_script_vars_t* ecs_script_vars_pop( - ecs_script_vars_t *vars); - -/** 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. - * - * 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 new variable, or NULL if the operation failed. - */ -FLECS_API -ecs_script_var_t* ecs_script_vars_declare( - ecs_script_vars_t *vars, - const char *name); - -/** 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. - * - * 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 -ecs_script_var_t* ecs_script_vars_define_id( - ecs_script_vars_t *vars, - const char *name, - ecs_entity_t type); - -#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 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 -ecs_script_var_t* ecs_script_vars_lookup( - const ecs_script_vars_t *vars, - const char *name); - -/** 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. - * - * 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_script_var_t* ecs_script_vars_from_sp( - const ecs_script_vars_t *vars, - int32_t sp); - -/** Print variables. - * This operation prints all variables in the vars scope and parent scopes.asm - * - * @param vars The variable scope. - */ -FLECS_API -void ecs_script_vars_print( - const ecs_script_vars_t *vars); - -/** 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 vars The variable scope. - * @param count The number of variables to preallocate space for. - */ -FLECS_API -void ecs_script_vars_set_size( - ecs_script_vars_t *vars, - int32_t count); - -/** 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 -void ecs_script_vars_from_iter( - const ecs_iter_t *it, - ecs_script_vars_t *vars, - int offset); - - -/* Standalone expression evaluation */ - -/** 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 */ - - /** Disable constant folding (slower evaluation, faster parsing) */ - bool disable_folding; - - /** 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; - - /** Allow for unresolved identifiers when parsing. Useful when entities can - * be created in between parsing & evaluating. */ - bool allow_unresolved_identifiers; - - ecs_script_runtime_t *runtime; /**< Reusable runtime (optional) */ -} ecs_expr_eval_desc_t; - -/** 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. - * - * If the provided value for value.ptr is NULL, the value must be freed with - * ecs_value_free() afterwards. - * - * @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 -const char* ecs_expr_run( - ecs_world_t *world, - const char *ptr, - ecs_value_t *value, - const ecs_expr_eval_desc_t *desc); - -/** 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 -ecs_script_t* ecs_expr_parse( - ecs_world_t *world, - const char *expr, - const ecs_expr_eval_desc_t *desc); - -/** 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. - * - * 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 -int ecs_expr_eval( - const ecs_script_t *script, - ecs_value_t *value, - const ecs_expr_eval_desc_t *desc); - -/** 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} - * - * 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 -char* ecs_script_string_interpolate( - ecs_world_t *world, - const char *str, - const ecs_script_vars_t *vars); - - -/* Global const variables */ - -/** Used with ecs_const_var_init */ -typedef struct ecs_const_var_desc_t { - /* Variable name. */ - const char *name; - - /* Variable parent (namespace). */ - ecs_entity_t parent; - - /* Variable type. */ - ecs_entity_t type; - - /* 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; - -/** 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); - -#define ecs_const_var(world, ...)\ - ecs_const_var_init(world, &(ecs_const_var_desc_t)__VA_ARGS__) - -/* Functions */ - -/** 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; - - /** Function parameters. */ - ecs_script_parameter_t params[FLECS_SCRIPT_FUNCTION_ARGS_MAX]; - - /** Function return type. */ - ecs_entity_t return_type; - - /** Function implementation. */ - ecs_function_callback_t callback; - - /** Context passed to function implementation. */ - void *ctx; -} ecs_function_desc_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); - -#define ecs_function(world, ...)\ - ecs_function_init(world, &(ecs_function_desc_t)__VA_ARGS__) - -/** 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); - -#define ecs_method(world, ...)\ - ecs_method_init(world, &(ecs_function_desc_t)__VA_ARGS__) - - -/* 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 type The type of the value to serialize. - * @param data The value to serialize. - * @return String with expression, or NULL if failed. - */ -FLECS_API -char* ecs_ptr_to_expr( - const ecs_world_t *world, - ecs_entity_t type, - const void *data); - -/** Serialize value into expression buffer. - * Same as ecs_ptr_to_expr(), 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 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); - -/** 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 type The type of the value to serialize. - * @param data The value to serialize. - * @return String with result, or NULL if failed. - */ -FLECS_API -char* ecs_ptr_to_str( - const ecs_world_t *world, - ecs_entity_t type, - const void *data); - -/** Serialize value into string buffer. - * Same as ecs_ptr_to_str(), 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 The strbuf to append the string to. - * @return Zero if success, non-zero if failed. - */ -FLECS_API -int ecs_ptr_to_str_buf( - const ecs_world_t *world, - ecs_entity_t type, - const void *data, - ecs_strbuf_t *buf); - -typedef struct ecs_expr_node_t ecs_expr_node_t; - -/** Script module import function. - * Usage: - * @code - * ECS_IMPORT(world, FlecsScript) - * @endcode - * - * @param world The world. - */ -FLECS_API -void FlecsScriptImport( - ecs_world_t *world); - -#ifdef __cplusplus -} -#endif - -#endif - -/** @} */ - -#endif - -#endif - -#ifdef FLECS_DOC -#ifdef FLECS_NO_DOC -#error "FLECS_NO_DOC failed: DOC is required by other addons" -#endif -/** - * @file addons/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. - */ - -#ifdef FLECS_DOC - -#ifndef FLECS_DOC_H -#define FLECS_DOC_H - -#ifndef FLECS_MODULE -#define FLECS_MODULE -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @defgroup c_addons_doc Doc - * @ingroup c_addons - * Utilities for documenting entities, components and systems. - * - * @{ - */ - -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 extern const ecs_entity_t EcsDocUuid; - -/** Tag for adding brief descriptions to entities. - * Added to an entity as (EcsDocDescription, EcsBrief) by ecs_doc_set_brief(). - */ -FLECS_API extern const ecs_entity_t EcsDocBrief; - -/** Tag for adding detailed descriptions to entities. - * Added to an entity as (EcsDocDescription, EcsDocDetail) by ecs_doc_set_detail(). - */ -FLECS_API extern const ecs_entity_t EcsDocDetail; - -/** Tag for adding a link to entities. - * Added to an entity as (EcsDocDescription, EcsDocLink) by ecs_doc_set_link(). - */ -FLECS_API extern const ecs_entity_t EcsDocLink; - -/** 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. - * - * @param world The world. - * @param entity The entity to which to add the UUID. - * @param uuid The UUID to add. - * - * @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 '*'. - * - * @param world The world. - * @param entity The entity to which to add the name. - * @param name The name to add. - * - * @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 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 -void ecs_doc_set_brief( - ecs_world_t *world, - ecs_entity_t entity, - const char *description); - -/** 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); - -/** 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); - -/** 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); - -/** 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 -const char* ecs_doc_get_uuid( - const ecs_world_t *world, - ecs_entity_t entity); - -/** 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 -const char* ecs_doc_get_name( - const ecs_world_t *world, - ecs_entity_t entity); - -/** 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 -const char* ecs_doc_get_brief( - const ecs_world_t *world, - ecs_entity_t entity); - -/** 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 -const char* ecs_doc_get_detail( - const ecs_world_t *world, - ecs_entity_t entity); - -/** 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 -const char* ecs_doc_get_link( - const ecs_world_t *world, - ecs_entity_t entity); - -/** 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_doc_get_color( - const ecs_world_t *world, - ecs_entity_t entity); - -/** Doc module import function. - * Usage: - * @code - * ECS_IMPORT(world, FlecsDoc) - * @endcode - * - * @param world The world. - */ -FLECS_API -void FlecsDocImport( - ecs_world_t *world); - -/** @} */ - -#ifdef __cplusplus -} -#endif - -#endif - -#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. - * - * @{ - */ - -#include - -#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(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 EcsConstant; /**< Tag added to enum/bitmask constants. */ -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). */ - 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 */ - 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; - - /** Populated from child entities with Constant component */ - ecs_map_t constants; /**< map */ -} 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 { - /* Populated from child entities with Constant component */ - ecs_map_t constants; /**< map */ -} EcsBitmask; - -/** 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 */ - -/** 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 */ - - /* 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_type_op_kind_t { - EcsOpArray, - EcsOpVector, - EcsOpOpaque, - EcsOpPush, - EcsOpPop, - - EcsOpScope, /**< Marks last constant that can open/close a scope */ - - 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_type_op_kind_t; - -/** Meta type serializer instruction data. */ -typedef struct ecs_meta_type_op_t { - ecs_meta_type_op_kind_t kind; /**< Instruction opcode. */ - ecs_size_t offset; /**< Offset of current field */ - int32_t count; /**< Number of elements (for inline arrays). */ - 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; /**< Type entity */ - int32_t member_index; /**< Index of member in struct */ - ecs_hashmap_t *members; /**< string -> member index (structs only) */ -} ecs_meta_type_op_t; - -/** Component that stores the type serializer. - * Added to all types with reflection data. - */ -typedef struct EcsTypeSerializer { - 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_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) */ - int32_t prev_depth; /**< Depth to restore, in case dotmember was used */ - void *ptr; /**< Pointer to the value being iterated */ - const EcsComponent *comp; /**< Pointer to component, in case size/alignment is needed */ - const EcsOpaque *opaque; /**< Opaque type interface */ - ecs_vec_t *vector; /**< Current vector, in case a vector is iterated */ - ecs_hashmap_t *members; /**< string -> member index */ - bool is_collection; /**< Is the scope iterating elements? */ - bool is_inline_array; /**< Is the scope iterating an inline array? */ - bool is_empty_scope; /**< Was scope populated (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. */ - int32_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)(const ecs_world_t*, const char*, void*); - void *lookup_ctx; /**< Context for lookup_action */ -} ecs_meta_cursor_t; - -/** 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); - -/** 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. - */ -FLECS_API -int ecs_meta_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. - */ -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); - -/* 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. */ -} 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); - - -/** 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 __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 __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 __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_find( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - const char *symbol, - size_t size, - size_t alignment, - bool implicit_name, - bool *existing_out); - -FLECS_API -ecs_entity_t ecs_cpp_component_register( - 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, - 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); - -#ifdef FLECS_META -FLECS_API -const 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++11 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; - -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 type_info_t = ecs_type_info_t; -using type_hooks_t = ecs_type_hooks_t; -using flags32_t = ecs_flags32_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; - -/* Builtin tags */ -static const flecs::entity_t Query = EcsQuery; -static const flecs::entity_t Observer = EcsObserver; -static const flecs::entity_t Private = EcsPrivate; -static const flecs::entity_t Module = EcsModule; -static const flecs::entity_t Prefab = EcsPrefab; -static const flecs::entity_t Disabled = EcsDisabled; -static const flecs::entity_t 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; - -/* 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 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; - -/* 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 Union = EcsUnion; - -/* 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 -{ - -// C++11/C++14 convenience template replacements - -template -using conditional_t = typename std::conditional::type; - -template -using decay_t = typename std::decay::type; - -template -using enable_if_t = typename std::enable_if::type; - -template -using remove_pointer_t = typename std::remove_pointer::type; - -template -using remove_reference_t = typename std::remove_reference::type; - -template -using underlying_type_t = typename std::underlying_type::type; - -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; - -// Determine constness even if T is a pointer type -template -using is_const_p = is_const< remove_pointer_t >; - -// Apply cv modifiers from source type to destination type -// (from: https://stackoverflow.com/questions/52559336/add-const-to-type-if-template-arg-is-const) -template -using transcribe_const_t = conditional_t::value, Dst const, Dst>; - -template -using transcribe_volatile_t = conditional_t::value, Dst volatile, Dst>; - -template -using transcribe_cv_t = transcribe_const_t< Src, transcribe_volatile_t< Src, Dst> >; - -template -using transcribe_pointer_t = conditional_t::value, Dst*, Dst>; - -template -using transcribe_cvp_t = transcribe_cv_t< Src, transcribe_pointer_t< Src, Dst> >; - - -// More convenience templates. The if_*_t templates use int as default type -// instead of void. This enables writing code that's a bit less cluttered when -// the templates are used in a template declaration: -// -// enable_if_t* = nullptr -// vs: -// if_t = 0 - -template -using if_t = enable_if_t; - -template -using if_not_t = enable_if_t; - -namespace _ -{ - -// Utility to prevent static assert from immediately triggering -template -struct always_false { - static const bool value = false; -}; - -} // namespace _ - -} // namespace flecs - -#include -/** - * @file addons/cpp/utils/array.hpp - * @brief Array class. - * - * Array class. Simple std::array like utility that is mostly there to aid - * template code where template expansion would lead to an array with size 0. - */ - -namespace flecs { - -template -struct array_iterator -{ - explicit array_iterator(T* value, int index) { - value_ = value; - index_ = index; - } - - bool operator!=(array_iterator const& other) const - { - return index_ != other.index_; - } - - T & operator*() const - { - return value_[index_]; - } - - array_iterator& operator++() - { - ++index_; - return *this; - } - -private: - T* value_; - int index_; -}; - -template -struct array final { }; - -template -struct array > final { - array() {}; - - array(const T (&elems)[Size]) { - int i = 0; - for (auto it = this->begin(); it != this->end(); ++ it) { - *it = elems[i ++]; - } - } - - T& operator[](int index) { - return array_[index]; - } - - T& operator[](size_t index) { - return array_[index]; - } - - array_iterator begin() { - return array_iterator(array_, 0); - } - - array_iterator end() { - return array_iterator(array_, Size); - } - - size_t size() { - return Size; - } - - T* ptr() { - return array_; - } - - template - void each(const Func& func) { - for (auto& elem : *this) { - func(elem); - } - } - -private: - T array_[Size]; -}; - -template -array to_array(const T (&elems)[Size]) { - return array(elems); -} - -// Specialized class for zero-sized array -template -struct array> final { - array() {}; - array(const T* (&elems)) { (void)elems; } - T operator[](size_t index) { ecs_os_abort(); (void)index; return T(); } - array_iterator begin() { return array_iterator(nullptr, 0); } - array_iterator end() { return array_iterator(nullptr, 0); } - - size_t size() { - return 0; - } - - T* ptr() { - return NULL; - } -}; - -} - -/** - * @file addons/cpp/utils/string.hpp - * @brief String utility that doesn't implicitly allocate memory. - */ - -namespace flecs { - -struct string_view; - -// This removes dependencies on std::string (and therefore STL) and allows the -// API to return allocated strings without incurring additional allocations when -// wrapping in an std::string. -struct string { - explicit string() - : str_(nullptr) - , const_str_("") - , length_(0) { } - - explicit string(char *str) - : str_(str) - , const_str_(str ? str : "") - , length_(str ? ecs_os_strlen(str) : 0) { } - - ~string() { - // If flecs is included in a binary but is not used, it is possible that - // the OS API is not initialized. Calling ecs_os_free in that case could - // crash the application during exit. However, if a string has been set - // flecs has been used, and OS API should have been initialized. - if (str_) { - ecs_os_free(str_); - } - } - - string(string&& str) noexcept { - ecs_os_free(str_); - str_ = str.str_; - const_str_ = str.const_str_; - length_ = str.length_; - str.str_ = nullptr; - } - - operator const char*() const { - return const_str_; - } - - string& operator=(string&& str) noexcept { - ecs_os_free(str_); - str_ = str.str_; - const_str_ = str.const_str_; - length_ = str.length_; - str.str_ = nullptr; - return *this; - } - - // Ban implicit copies/allocations - string& operator=(const string& str) = delete; - string(const string& str) = delete; - - bool operator==(const flecs::string& str) const { - if (str.const_str_ == const_str_) { - return true; - } - - if (!const_str_ || !str.const_str_) { - return false; - } - - if (str.length_ != length_) { - return false; - } - - return ecs_os_strcmp(str, const_str_) == 0; - } - - bool operator!=(const flecs::string& str) const { - return !(*this == str); - } - - bool operator==(const char *str) const { - if (const_str_ == str) { - return true; - } - - if (!const_str_ || !str) { - return false; - } - - return ecs_os_strcmp(str, const_str_) == 0; - } - - bool operator!=(const char *str) const { - return !(*this == str); - } - - const char* c_str() const { - return const_str_; - } - - std::size_t length() const { - return static_cast(length_); - } - - template - static constexpr size_t length( char const (&)[N] ) { - return N - 1; - } - - std::size_t size() const { - return length(); - } - - void clear() { - ecs_os_free(str_); - str_ = nullptr; - const_str_ = nullptr; - } - - bool contains(const char *substr) { - if (const_str_) { - return strstr(const_str_, substr) != nullptr; - } else { - return false; - } - } - -protected: - // Must be constructed through string_view. This allows for using the string - // class for both owned and non-owned strings, which can reduce allocations - // when code conditionally should store a literal or an owned string. - // Making this constructor private forces the code to explicitly create a - // string_view which emphasizes that the string won't be freed by the class. - string(const char *str) - : str_(nullptr) - , const_str_(str ? str : "") - , length_(str ? ecs_os_strlen(str) : 0) { } - - char *str_ = nullptr; - const char *const_str_; - ecs_size_t length_; -}; - -// For consistency, the API returns a string_view where it could have returned -// a const char*, so an application won't have to think about whether to call -// c_str() or not. The string_view is a thin wrapper around a string that forces -// the API to indicate explicitly when a string is owned or not. -struct string_view : string { - explicit string_view(const char *str) - : string(str) { } -}; - -} - -/** - * @file addons/cpp/utils/enum.hpp - * @brief Compile time enum reflection utilities. - * - * Discover at compile time valid enumeration constants for an enumeration type - * and their names. This is used to automatically register enum constants. - */ - -#include -#include - -// 126, so that FLECS_ENUM_MAX_COUNT is 127 which is the largest value -// representable by an int8_t. -#define FLECS_ENUM_MAX(T) _::to_constant::value -#define FLECS_ENUM_MAX_COUNT (FLECS_ENUM_MAX(int) + 1) - -#ifndef FLECS_CPP_ENUM_REFLECTION_SUPPORT -#if !defined(__clang__) && defined(__GNUC__) -#if __GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 5) -#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 1 -#else -#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 0 -#endif -#else -#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 1 -#endif -#endif - -#if defined(__clang__) && __clang_major__ >= 16 -// https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 -#define flecs_enum_cast(T, v) __builtin_bit_cast(T, v) -#elif defined(__GNUC__) && __GNUC__ > 10 -#define flecs_enum_cast(T, v) __builtin_bit_cast(T, v) -#else -#define flecs_enum_cast(T, v) static_cast(v) -#endif - -namespace flecs { - -/** Int to enum */ -namespace _ { -template Value> -struct to_constant { - static constexpr E value = flecs_enum_cast(E, Value); -}; - -template Value> -constexpr E to_constant::value; -} - -/** Convenience type with enum reflection data */ -template -struct enum_data; - -template -static enum_data enum_type(flecs::world_t *world); - -template -struct enum_last { - static constexpr E value = FLECS_ENUM_MAX(E); -}; - -/* Utility macro to override enum_last trait */ -#define FLECS_ENUM_LAST(T, Last)\ - namespace flecs {\ - template<>\ - struct enum_last {\ - static constexpr T value = Last;\ - };\ - } - -namespace _ { - -#if INTPTR_MAX == INT64_MAX - #ifdef ECS_TARGET_MSVC - #if _MSC_VER >= 1929 - #define ECS_SIZE_T_STR "unsigned __int64" - #else - #define ECS_SIZE_T_STR "unsigned int" - #endif - #elif defined(__clang__) - #define ECS_SIZE_T_STR "size_t" - #else - #ifdef ECS_TARGET_WINDOWS - #define ECS_SIZE_T_STR "constexpr size_t; size_t = long long unsigned int" - #else - #define ECS_SIZE_T_STR "constexpr size_t; size_t = long unsigned int" - #endif - #endif -#else - #ifdef ECS_TARGET_MSVC - #if _MSC_VER >= 1929 - #define ECS_SIZE_T_STR "unsigned __int32" - #else - #define ECS_SIZE_T_STR "unsigned int" - #endif - #elif defined(__clang__) - #define ECS_SIZE_T_STR "size_t" - #else - #ifdef ECS_TARGET_WINDOWS - #define ECS_SIZE_T_STR "constexpr size_t; size_t = unsigned int" - #else - #define ECS_SIZE_T_STR "constexpr size_t; size_t = unsigned int" - #endif - #endif -#endif - -template -constexpr size_t enum_type_len() { - return ECS_FUNC_TYPE_LEN(, enum_type_len, ECS_FUNC_NAME) - - (sizeof(ECS_SIZE_T_STR) - 1u); -} - -/** Test if value is valid for enumeration. - * This function leverages that when a valid value is provided, - * __PRETTY_FUNCTION__ contains the enumeration name, whereas if a value is - * invalid, the string contains a number or a negative (-) symbol. */ -#if defined(ECS_TARGET_CLANG) -#if ECS_CLANG_VERSION < 13 -template -constexpr bool enum_constant_is_valid() { - return !(( - (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + - enum_type_len() + 6 /* ', C = ' */] >= '0') && - (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + - enum_type_len() + 6 /* ', C = ' */] <= '9')) || - (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + - enum_type_len() + 6 /* ', C = ' */] == '-')); -} -#else -template -constexpr bool enum_constant_is_valid() { - return (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + - enum_type_len() + 6 /* ', E C = ' */] != '('); -} -#endif -#elif defined(ECS_TARGET_GNU) -template -constexpr bool enum_constant_is_valid() { - return (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(constexpr bool, enum_constant_is_valid) + - enum_type_len() + 8 /* ', E C = ' */] != '('); -} -#else -/* Use different trick on MSVC, since it uses hexadecimal representation for - * invalid enum constants. We can leverage that msvc inserts a C-style cast - * into the name, and the location of its first character ('(') is known. */ -template -constexpr bool enum_constant_is_valid() { - return ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid) + - enum_type_len() + 1] != '('; -} -#endif - -/* Without this wrapper __builtin_bit_cast doesn't work */ -template C> -constexpr bool enum_constant_is_valid_wrap() { - return enum_constant_is_valid(); -} - -template -struct enum_is_valid { - static constexpr bool value = enum_constant_is_valid(); -}; - -/** Extract name of constant from string */ -template -static const char* enum_constant_to_name() { - static const size_t len = ECS_FUNC_TYPE_LEN(const char*, enum_constant_to_name, ECS_FUNC_NAME); - static char result[len + 1] = {}; - return ecs_cpp_get_constant_name( - result, ECS_FUNC_NAME, string::length(ECS_FUNC_NAME), - ECS_FUNC_NAME_BACK); -} - -/** Enumeration constant data */ -template -struct enum_constant_data { - int32_t index; // Global index used to obtain world local entity id - T offset; -}; - -/** - * @brief Provides utilities for enum reflection. - * - * This struct provides static functions for enum reflection, including conversion - * between enum values and their underlying integral types, and iteration over enum - * values. - * - * @tparam E The enum type. - * @tparam Handler The handler for enum reflection operations. - */ -template -struct enum_reflection { - using U = underlying_type_t; - - /** - * @brief Iterates over the range [Low, High] of enum values between Low and High. - * - * Recursively divide and conquers the search space to reduce the template-depth. Once - * recursive division is complete, calls Handle::handle_constant in ascending order, - * passing the values computed up the chain. - * - * @tparam Low The lower bound of the search range, inclusive. - * @tparam High The upper bound of the search range, inclusive. - * @tparam Args Additional arguments to be passed through to Handler::handle_constant - * @param last_value The last value processed in the iteration. - * @param args Additional arguments to be passed through to Handler::handle_constant - * @return constexpr U The result of the iteration. - */ - template - static constexpr U each_enum_range(U last_value, Args... args) { - return High - Low <= 1 - ? High == Low - ? Handler::template handle_constant(last_value, args...) - : Handler::template handle_constant(Handler::template handle_constant(last_value, args...), args...) - : each_enum_range<(Low + High) / 2 + 1, High>( - each_enum_range(last_value, args...), - args... - ); - } - - /** - * @brief Iterates over the mask range (Low, High] of enum values between Low and High. - * - * Recursively iterates the search space, looking for enums defined as multiple-of-2 - * bitmasks. Each iteration, shifts bit to the right until it hits Low, then calls - * Handler::handle_constant for each bitmask in ascending order. - * - * @tparam Low The lower bound of the search range, not inclusive - * @tparam High The upper bound of the search range, inclusive. - * @tparam Args Additional arguments to be passed through to Handler::handle_constant - * @param last_value The last value processed in the iteration. - * @param args Additional arguments to be passed through to Handler::handle_constant - * @return constexpr U The result of the iteration. - */ - template - static constexpr U each_mask_range(U last_value, Args... args) { - // If Low shares any bits with Current Flag, or if High is less than/equal to Low (and High isn't negative because max-flag signed) - return (Low & High) || (High <= Low && High != high_bit) - ? last_value - : Handler::template handle_constant( - each_mask_range> 1) & ~high_bit)>(last_value, args...), - args... - ); - } - - /** - * @brief Handles enum iteration for gathering reflection data. - * - * Iterates over all enum values up to a specified maximum value - * (each_enum_range<0, Value>), then iterates the rest of the possible bitmasks - * (each_mask_range). - * - * @tparam Value The maximum enum value to iterate up to. - * @tparam Args Additional arguments to be passed through to Handler::handle_constant - * @param args Additional arguments to be passed through to Handler::handle_constant - * @return constexpr U The result of the iteration. - */ - template (FLECS_ENUM_MAX(E)), typename... Args> - static constexpr U each_enum(Args... args) { - return each_mask_range(each_enum_range<0, Value>(0, args...), args...); - } - - static const U high_bit = static_cast(1) << (sizeof(U) * 8 - 1); -}; - -/** Enumeration type data */ -template -struct enum_data_impl { -private: - using U = underlying_type_t; - - /** - * @brief Handler struct for generating compile-time count of enum constants. - */ - struct reflection_count { - template () > = 0> - static constexpr U handle_constant(U last_value) { - return last_value; - } - - template () > = 0> - static constexpr U handle_constant(U last_value) { - return 1 + last_value; - } - }; - -public: - int min; - int max; - bool has_contiguous; - // If enum constants start not-sparse, contiguous_until will be the index of the first sparse value, or end of the constants array - U contiguous_until; - // Compile-time generated count of enum constants. - static constexpr unsigned int constants_size = enum_reflection::template each_enum< static_cast(enum_last::value) >(); - // Constants array is sized to the number of found-constants, or 1 (to avoid 0-sized array) - enum_constant_data constants[constants_size? constants_size: 1]; -}; - -/** Class that scans an enum for constants, extracts names & creates entities */ -template -struct enum_type { -private: - using U = underlying_type_t; - - /** - * @brief Helper struct for filling enum_type's static `enum_data_impl` member with reflection data. - * - * Because reflection occurs in-order, we can use current value/last value to determine continuity, and - * use that as a lookup heuristic later on. - */ - struct reflection_init { - template () > = 0> - static U handle_constant(U last_value, flecs::world_t*) { - // Search for constant failed. Pass last valid value through. - return last_value; - } - - template () > = 0> - static U handle_constant(U last_value, flecs::world_t *world) { - // Constant is valid, so fill reflection data. - auto v = Value; - const char *name = enum_constant_to_name(); - - ++enum_type::data.max; // Increment cursor as we build constants array. - - // If the enum was previously contiguous, and continues to be through the current value... - if (enum_type::data.has_contiguous && static_cast(enum_type::data.max) == v && enum_type::data.contiguous_until == v) { - ++enum_type::data.contiguous_until; - } - // else, if the enum was never contiguous and hasn't been set as not contiguous... - else if (!enum_type::data.contiguous_until && enum_type::data.has_contiguous) { - enum_type::data.has_contiguous = false; - } - - ecs_assert(!(last_value > 0 && v < std::numeric_limits::min() + last_value), ECS_UNSUPPORTED, - "Signed integer enums causes integer overflow when recording offset from high positive to" - " low negative. Consider using unsigned integers as underlying type."); - enum_type::data.constants[enum_type::data.max].offset = v - last_value; - if (!enum_type::data.constants[enum_type::data.max].index) { - enum_type::data.constants[enum_type::data.max].index = - flecs_component_ids_index_get(); - } - - flecs::entity_t constant = ecs_cpp_enum_constant_register( - world, type::id(world), 0, name, &v, type::id(world), sizeof(U)); - flecs_component_ids_set(world, - enum_type::data.constants[enum_type::data.max].index, - constant); - - return v; - } - }; -public: - - static enum_data_impl data; - - static enum_type& get() { - static _::enum_type instance; - return instance; - } - - flecs::entity_t entity(E value) const { - int index = index_by_value(value); - if (index >= 0) { - return data.constants[index].id; - } - return 0; - } - - void init(flecs::world_t *world, flecs::entity_t id) { -#if !FLECS_CPP_ENUM_REFLECTION_SUPPORT - ecs_abort(ECS_UNSUPPORTED, "enum reflection requires gcc 7.5 or higher") -#endif - // Initialize/reset reflection data values to default state. - data.min = 0; - data.max = -1; - data.has_contiguous = true; - data.contiguous_until = 0; - - ecs_log_push(); - ecs_cpp_enum_init(world, id, type::id(world)); - // data.id = id; - - // Generate reflection data - enum_reflection::template each_enum< static_cast(enum_last::value) >(world); - ecs_log_pop(); - } -}; - -template -enum_data_impl enum_type::data; - -template ::value > = 0> -inline static void init_enum(flecs::world_t *world, flecs::entity_t id) { - _::enum_type::get().init(world, id); -} - -template ::value > = 0> -inline static void init_enum(flecs::world_t*, flecs::entity_t) { } - -} // namespace _ - -/** Enumeration type data wrapper with world pointer */ -template -struct enum_data { - using U = underlying_type_t; - - enum_data(flecs::world_t *world, _::enum_data_impl& impl) - : world_(world) - , impl_(impl) { } - - /** - * @brief Checks if a given integral value is a valid enum value. - * - * @param value The integral value. - * @return true If the value is a valid enum value. - * @return false If the value is not a valid enum value. - */ - bool is_valid(U value) { - int index = index_by_value(value); - if (index < 0) { - return false; - } - return impl_.constants[index].index != 0; - } - - /** - * @brief Checks if a given enum value is valid. - * - * @param value The enum value. - * @return true If the value is valid. - * @return false If the value is not valid. - */ - bool is_valid(E value) { - return is_valid(static_cast(value)); - } - - /** - * @brief Finds the index into the constants array for a value, if one exists - * - * @param value The enum value. - * @return int The index of the enum value. - */ - int index_by_value(U value) const { - if (impl_.max < 0) { - return -1; - } - // Check if value is in contiguous lookup section - if (impl_.has_contiguous && value < impl_.contiguous_until && value >= 0) { - return static_cast(value); - } - U accumulator = impl_.contiguous_until? impl_.contiguous_until - 1: 0; - for (int i = static_cast(impl_.contiguous_until); i <= impl_.max; ++i) { - accumulator += impl_.constants[i].offset; - if (accumulator == value) { - return i; - } - } - return -1; - } - - /** - * @brief Finds the index into the constants array for an enum value, if one exists - * - * @param value The enum value. - * @return int The index of the enum value. - */ - int index_by_value(E value) const { - return index_by_value(static_cast(value)); - } - - int first() const { - return impl_.min; - } - - int last() const { - return impl_.max; - } - - int next(int cur) const { - return cur + 1; - } - - flecs::entity entity() const; - flecs::entity entity(U value) const; - flecs::entity entity(E value) const; - - flecs::world_t *world_; - _::enum_data_impl& impl_; -}; - -/** Convenience function for getting enum reflection data */ -template -enum_data enum_type(flecs::world_t *world) { - _::type::id(world); // Ensure enum is registered - auto& ref = _::enum_type::get(); - return enum_data(world, ref.data); -} - -} // namespace flecs - -/** - * @file addons/cpp/utils/stringstream.hpp - * @brief Wrapper around ecs_strbuf_t that provides a simple stringstream like API. - */ - -namespace flecs { - -struct stringstream { - explicit stringstream() - : buf_({}) { } - - ~stringstream() { - ecs_strbuf_reset(&buf_); - } - - stringstream(stringstream&& str) noexcept { - ecs_strbuf_reset(&buf_); - buf_ = str.buf_; - str.buf_ = {}; - } - - stringstream& operator=(stringstream&& str) noexcept { - ecs_strbuf_reset(&buf_); - buf_ = str.buf_; - str.buf_ = {}; - return *this; - } - - // Ban implicit copies/allocations - stringstream& operator=(const stringstream& str) = delete; - stringstream(const stringstream& str) = delete; - - stringstream& operator<<(const char* str) { - ecs_strbuf_appendstr(&buf_, str); - return *this; - } - - flecs::string str() { - return flecs::string(ecs_strbuf_get(&buf_)); - } - -private: - ecs_strbuf_t buf_; -}; - -} - -/** - * @file addons/cpp/utils/function_traits.hpp - * @brief Compile time utilities to inspect properties of functions. - * - * Code from: https://stackoverflow.com/questions/27024238/c-template-mechanism-to-get-the-number-of-function-arguments-which-would-work - */ - -namespace flecs { -namespace _ { - -template -struct arg_list { }; - -// Base type that contains the traits -template -struct function_traits_defs -{ - static constexpr bool is_callable = true; - static constexpr size_t arity = sizeof...(Args); - using return_type = ReturnType; - using args = arg_list; -}; - -// Primary template for function_traits_impl -template -struct function_traits_impl { - static constexpr bool is_callable = false; -}; - -// Template specializations for the different kinds of function types (whew) -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -template -struct function_traits_impl - : function_traits_defs {}; - -// Primary template for function_traits_no_cv. If T is not a function, the -// compiler will attempt to instantiate this template and fail, because its base -// is undefined. -template -struct function_traits_no_cv - : function_traits_impl {}; - -// Specialized template for function types -template -struct function_traits_no_cv - : function_traits_impl {}; - -// Front facing template that decays T before ripping it apart. -template -struct function_traits - : function_traits_no_cv< decay_t > {}; - -} // _ - - -template -struct is_callable { - static constexpr bool value = _::function_traits::is_callable; -}; - -template -struct arity { - static constexpr int value = _::function_traits::arity; -}; - -template -using return_type_t = typename _::function_traits::return_type; - -template -using arg_list_t = typename _::function_traits::args; - -// First arg -template -struct first_arg_impl; - -template -struct first_arg_impl > { - using type = T; -}; - -template -struct first_arg { - using type = typename first_arg_impl>::type; -}; - -template -using first_arg_t = typename first_arg::type; - -// Last arg -template -struct second_arg_impl; - -template -struct second_arg_impl > { - using type = T; -}; - -template -struct second_arg { - using type = typename second_arg_impl>::type; -}; - -template -using second_arg_t = typename second_arg::type; - -} // flecs - - - -// Mixin forward declarations -/** - * @file addons/cpp/mixins/id/decl.hpp - * @brief Id class. - */ - -#pragma once - -namespace flecs { - -struct id; -struct entity; - -/** - * @defgroup cpp_ids Ids - * @ingroup cpp_core - * Class for working with entity, component, tag and pair ids. - * - * @{ - */ - -/** Class that wraps around a flecs::id_t. - * A flecs id is an identifier that can be added to entities. Ids can be: - * - entities (including components, tags) - * - pair ids - * - entities with id flags set (like flecs::AUTO_OVERRIDE, flecs::TOGGLE) - */ -struct id { - id() - : world_(nullptr) - , id_(0) { } - - explicit id(flecs::id_t value) - : world_(nullptr) - , id_(value) { } - - explicit id(flecs::world_t *world, flecs::id_t value = 0) - : world_(world) - , id_(value) { } - - explicit id(flecs::world_t *world, flecs::id_t first, flecs::id_t second) - : world_(world) - , id_(ecs_pair(first, second)) { } - - explicit id(flecs::world_t *world, const char *expr) - : world_(world) - , id_(ecs_id_from_str(world, expr)) { } - - explicit id(flecs::id_t first, flecs::id_t second) - : world_(nullptr) - , id_(ecs_pair(first, second)) { } - - explicit id(const flecs::id& first, const flecs::id& second) - : world_(first.world_) - , id_(ecs_pair(first.id_, second.id_)) { } - - /** Test if id is pair (has first, second) */ - bool is_pair() const { - return (id_ & ECS_ID_FLAGS_MASK) == flecs::PAIR; - } - - /** Test if id is a wildcard */ - bool is_wildcard() const { - return ecs_id_is_wildcard(id_); - } - - /** Test if id is entity */ - bool is_entity() const { - return !(id_ & ECS_ID_FLAGS_MASK); - } - - /** Return id as entity (only allowed when id is valid entity) */ - flecs::entity entity() const; - - /** Return id with role added */ - flecs::entity add_flags(flecs::id_t flags) const; - - /** Return id with role removed */ - flecs::entity remove_flags(flecs::id_t flags) const; - - /** Return id without role */ - flecs::entity remove_flags() const; - - /** Return id without role */ - flecs::entity remove_generation() const; - - /** Return component type of id */ - flecs::entity type_id() const; - - /** Test if id has specified role */ - bool has_flags(flecs::id_t flags) const { - return ((id_ & flags) == flags); - } - - /** Test if id has any role */ - bool has_flags() const { - return (id_ & ECS_ID_FLAGS_MASK) != 0; - } - - /** Return id flags set on id */ - flecs::entity flags() const; - - /** Test if id has specified first */ - bool has_relation(flecs::id_t first) const { - if (!is_pair()) { - return false; - } - return ECS_PAIR_FIRST(id_) == first; - } - - /** Get first element from a pair. - * If the id is not a pair, this operation will fail. When the id has a - * world, the operation will ensure that the returned id has the correct - * generation count. */ - flecs::entity first() const; - - /** Get second element from a pair. - * If the id is not a pair, this operation will fail. When the id has a - * world, the operation will ensure that the returned id has the correct - * generation count. */ - flecs::entity second() const; - - /* Convert id to string */ - flecs::string str() const { - return flecs::string(ecs_id_str(world_, id_)); - } - - /** Convert role of id to string. */ - flecs::string flags_str() const { - return flecs::string_view( ecs_id_flag_str(id_ & ECS_ID_FLAGS_MASK)); - } - - /** Return flecs::id_t value */ - flecs::id_t raw_id() const { - return id_; - } - - operator flecs::id_t() const { - return id_; - } - - flecs::world world() const; - -protected: - /* World is optional, but guarantees that entity identifiers extracted from - * the id are valid */ - flecs::world_t *world_; - flecs::id_t id_; -}; - -/** @} */ - -} - -/** - * @file addons/cpp/mixins/term/decl.hpp - * @brief Term declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @ingroup cpp_core_queries - * - * @{ - */ - -struct term; -struct term_builder; - -/** @} */ - -} - -/** - * @file addons/cpp/mixins/query/decl.hpp - * @brief Query declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_core_queries Queries - * @ingroup cpp_core - * - * @{ - */ - -struct query_base; - -template -struct query; - -template -struct query_builder; - -/** @} */ - -} - -/** - * @file addons/cpp/mixins/event/decl.hpp - * @brief Event declarations. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/event/builder.hpp - * @brief Event builder. - */ - -#pragma once - -#define ECS_EVENT_DESC_ID_COUNT_MAX (8) - -namespace flecs { - -/** - * @ingroup cpp_addons_event - * @{ - */ - -/** Event builder interface */ -template -struct event_builder_base { - event_builder_base(flecs::world_t *world, flecs::entity_t event) - : world_(world) - , desc_{} - , ids_{} - , ids_array_{} - { - desc_.event = event; - } - - /** Add component to emit for */ - template - Base& id() { - ids_.array = ids_array_; - ids_.array[ids_.count] = _::type().id(world_); - ids_.count ++; - return *this; - } - - /** - * Add pair to emit for - * @tparam First The first element of the pair. - * @tparam Second the second element of a pair. - */ - template - Base& id() { - return id( - ecs_pair(_::type::id(this->world_), - _::type::id(this->world_))); - } - - /** - * Add pair to emit for - * @tparam First The first element of the pair. - * @param second The second element of the pair id. - */ - template - Base& id(entity_t second) { - return id(ecs_pair(_::type::id(this->world_), second)); - } - - /** - * Add pair to emit for - * @param first The first element of the pair type. - * @param second The second element of the pair id. - */ - Base& id(entity_t first, entity_t second) { - return id(ecs_pair(first, second)); - } - - template ::value> = 0> - Base& id(Enum value) { - const auto& et = enum_type(this->world_); - flecs::entity_t target = et.entity(value); - return id(et.entity(), target); - } - - /** Add (component) id to emit for */ - Base& id(flecs::id_t id) { - ids_.array = ids_array_; - ids_.array[ids_.count] = id; - ids_.count ++; - return *this; - } - - /** Set entity for which to emit event */ - Base& entity(flecs::entity_t e) { - desc_.entity = e; - return *this; - } - - /* Set table for which to emit event */ - Base& table(flecs::table_t *t, int32_t offset = 0, int32_t count = 0) { - desc_.table = t; - desc_.offset = offset; - desc_.count = count; - return *this; - } - - /* Set event data */ - Base& ctx(const E* ptr) { - desc_.const_param = ptr; - return *this; - } - - /* Set event data */ - Base& ctx(E* ptr) { - desc_.param = ptr; - return *this; - } - - void emit() { - ids_.array = ids_array_; - desc_.ids = &ids_; - desc_.observable = const_cast(ecs_get_world(world_)); - ecs_emit(world_, &desc_); - } - - void enqueue() { - ids_.array = ids_array_; - desc_.ids = &ids_; - desc_.observable = const_cast(ecs_get_world(world_)); - ecs_enqueue(world_, &desc_); - } - -protected: - flecs::world_t *world_; - ecs_event_desc_t desc_; - flecs::type_t ids_; - flecs::id_t ids_array_[ECS_EVENT_DESC_ID_COUNT_MAX]; - -private: - operator Base&() { - return *static_cast(this); - } -}; - -struct event_builder : event_builder_base { - using event_builder_base::event_builder_base; -}; - -template -struct event_builder_typed : event_builder_base, E> { -private: - using Class = event_builder_typed; - -public: - using event_builder_base::event_builder_base; - - /* Set event data */ - Class& ctx(const E& ptr) { - this->desc_.const_param = &ptr; - return *this; - } - - /* Set event data */ - Class& ctx(E&& ptr) { - this->desc_.param = &ptr; - return *this; - } -}; - -/** @} */ - -} - - -namespace flecs { -namespace _ { - -// Utility to derive event type from function -template -struct event_from_func; - -// Specialization for observer callbacks with a single argument -template -struct event_from_func::value == 1>> { - using type = decay_t>; -}; - -// Specialization for observer callbacks with an initial entity src argument -template -struct event_from_func::value == 2>> { - using type = decay_t>; -}; - -template -using event_from_func_t = typename event_from_func::type; - -} -} - -/** - * @file addons/cpp/mixins/observer/decl.hpp - * @brief Observer declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_observers Observers - * @ingroup cpp_core - * Observers let applications register callbacks for ECS events. - * - * @{ - */ - -struct observer; - -template -struct observer_builder; - -/** @} */ - -} - -#ifdef FLECS_SYSTEM -/** - * @file addons/cpp/mixins/system/decl.hpp - * @brief System module declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_addons_systems Systems - * @ingroup cpp_addons - * Systems are a query + function that can be ran manually or by a pipeline. - * - * @{ - */ - -using TickSource = EcsTickSource; - -struct system; - -template -struct system_builder; - -namespace _ { - -void system_init(flecs::world& world); - -/** @} */ - -} // namespace _ -} // namespace flecs - -#endif -#ifdef FLECS_PIPELINE -/** - * @file addons/cpp/mixins/pipeline/decl.hpp - * @brief Pipeline module declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_pipelines Pipelines - * @ingroup cpp_addons - * Pipelines order and schedule systems for execution. - * - * @{ - */ - -template -struct pipeline; - -template -struct pipeline_builder; - -/* Builtin pipeline tags */ -static const flecs::entity_t OnStart = EcsOnStart; -static const flecs::entity_t PreFrame = EcsPreFrame; -static const flecs::entity_t OnLoad = EcsOnLoad; -static const flecs::entity_t PostLoad = EcsPostLoad; -static const flecs::entity_t PreUpdate = EcsPreUpdate; -static const flecs::entity_t OnUpdate = EcsOnUpdate; -static const flecs::entity_t OnValidate = EcsOnValidate; -static const flecs::entity_t PostUpdate = EcsPostUpdate; -static const flecs::entity_t PreStore = EcsPreStore; -static const flecs::entity_t OnStore = EcsOnStore; -static const flecs::entity_t PostFrame = EcsPostFrame; - -/** @} */ - -} - -#endif -#ifdef FLECS_TIMER -/** - * @file addons/cpp/mixins/timer/decl.hpp - * @brief Timer module declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_addons_timer Timer - * @ingroup cpp_addons - * Run systems at a time interval. - * - * @{ - */ - -using Timer = EcsTimer; -using RateFilter = EcsRateFilter; - -struct timer; - -/** @} */ - -namespace _ { - -void timer_init(flecs::world& world); - -} // namespace _ -} // namespace flecs - -#endif -#ifdef FLECS_DOC -/** - * @file addons/cpp/mixins/doc/decl.hpp - * @brief Doc mixin declarations. - */ - -#pragma once - -namespace flecs { -namespace doc { - -/** - * @defgroup cpp_addons_doc Doc - * @ingroup cpp_addons - * Utilities for documenting entities, components and systems. - * - * @{ - */ - -/** flecs.doc.Description component */ -using Description = EcsDocDescription; - -/** flecs.doc.Uuid component */ -static const flecs::entity_t Uuid = EcsDocUuid; - -/** flecs.doc.Brief component */ -static const flecs::entity_t Brief = EcsDocBrief; - -/** flecs.doc.Detail component */ -static const flecs::entity_t Detail = EcsDocDetail; - -/** flecs.doc.Link component */ -static const flecs::entity_t Link = EcsDocLink; - -/** flecs.doc.Color component */ -static const flecs::entity_t Color = EcsDocColor; - -/** @private */ -namespace _ { -/** @private */ -void init(flecs::world& world); -} - -/** @} */ - -} -} - -#endif -#ifdef FLECS_REST -/** - * @file addons/cpp/mixins/rest/decl.hpp - * @brief Rest module declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_addons_rest Rest - * @ingroup cpp_addons - * REST API for querying and mutating entities. - * - * @{ - */ - -using Rest = EcsRest; - -namespace rest { - -namespace _ { - -void init(flecs::world& world); - -} -} - -/** @} */ - -} - -#endif -#ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/decl.hpp - * @brief Meta declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_addons_meta Meta - * @ingroup cpp_addons - * Flecs reflection framework. - * - * @{ - */ - -/* Primitive type aliases */ -using bool_t = ecs_bool_t; -using char_t = ecs_char_t; -using u8_t = ecs_u8_t; -using u16_t = ecs_u16_t; -using u32_t = ecs_u32_t; -using u64_t = ecs_u64_t; -using uptr_t = ecs_uptr_t; -using i8_t = ecs_i8_t; -using i16_t = ecs_i16_t; -using i32_t = ecs_i32_t; -using i64_t = ecs_i64_t; -using iptr_t = ecs_iptr_t; -using f32_t = ecs_f32_t; -using f64_t = ecs_f64_t; - -/* Embedded type aliases */ -using member_t = ecs_member_t; -using enum_constant_t = ecs_enum_constant_t; -using bitmask_constant_t = ecs_bitmask_constant_t; - -/* Components */ -using Type = EcsType; -using TypeSerializer = EcsTypeSerializer; -using Primitive = EcsPrimitive; -using Enum = EcsEnum; -using Bitmask = EcsBitmask; -using Member = EcsMember; -using MemberRanges = EcsMemberRanges; -using Struct = EcsStruct; -using Array = EcsArray; -using Vector = EcsVector; -using Unit = EcsUnit; - -/** Base type for bitmasks */ -struct bitmask { - uint32_t value; -}; - -/* Handles to builtin reflection types */ -static const flecs::entity_t Bool = ecs_id(ecs_bool_t); -static const flecs::entity_t Char = ecs_id(ecs_char_t); -static const flecs::entity_t Byte = ecs_id(ecs_byte_t); -static const flecs::entity_t U8 = ecs_id(ecs_u8_t); -static const flecs::entity_t U16 = ecs_id(ecs_u16_t); -static const flecs::entity_t U32 = ecs_id(ecs_u32_t); -static const flecs::entity_t U64 = ecs_id(ecs_u64_t); -static const flecs::entity_t Uptr = ecs_id(ecs_uptr_t); -static const flecs::entity_t I8 = ecs_id(ecs_i8_t); -static const flecs::entity_t I16 = ecs_id(ecs_i16_t); -static const flecs::entity_t I32 = ecs_id(ecs_i32_t); -static const flecs::entity_t I64 = ecs_id(ecs_i64_t); -static const flecs::entity_t Iptr = ecs_id(ecs_iptr_t); -static const flecs::entity_t F32 = ecs_id(ecs_f32_t); -static const flecs::entity_t F64 = ecs_id(ecs_f64_t); -static const flecs::entity_t String = ecs_id(ecs_string_t); -static const flecs::entity_t Entity = ecs_id(ecs_entity_t); -static const flecs::entity_t Constant = EcsConstant; -static const flecs::entity_t Quantity = EcsQuantity; - -namespace meta { - -/* Type kinds supported by reflection system */ -using type_kind_t = ecs_type_kind_t; -static const type_kind_t PrimitiveType = EcsPrimitiveType; -static const type_kind_t BitmaskType = EcsBitmaskType; -static const type_kind_t EnumType = EcsEnumType; -static const type_kind_t StructType = EcsStructType; -static const type_kind_t ArrayType = EcsArrayType; -static const type_kind_t VectorType = EcsVectorType; -static const type_kind_t CustomType = EcsOpaqueType; -static const type_kind_t TypeKindLast = EcsTypeKindLast; - -/* Primitive type kinds supported by reflection system */ -using primitive_kind_t = ecs_primitive_kind_t; -static const primitive_kind_t Bool = EcsBool; -static const primitive_kind_t Char = EcsChar; -static const primitive_kind_t Byte = EcsByte; -static const primitive_kind_t U8 = EcsU8; -static const primitive_kind_t U16 = EcsU16; -static const primitive_kind_t U32 = EcsU32; -static const primitive_kind_t U64 = EcsU64; -static const primitive_kind_t I8 = EcsI8; -static const primitive_kind_t I16 = EcsI16; -static const primitive_kind_t I32 = EcsI32; -static const primitive_kind_t I64 = EcsI64; -static const primitive_kind_t F32 = EcsF32; -static const primitive_kind_t F64 = EcsF64; -static const primitive_kind_t UPtr = EcsUPtr; -static const primitive_kind_t IPtr = EcsIPtr; -static const primitive_kind_t String = EcsString; -static const primitive_kind_t Entity = EcsEntity; -static const primitive_kind_t PrimitiveKindLast = EcsPrimitiveKindLast; - -/** @} */ - -namespace _ { - -void init(flecs::world& world); - -} // namespace _ -} // namespace meta -} // namespace flecs - -/** - * @file addons/cpp/mixins/meta/opaque.hpp - * @brief Helpers for opaque type registration. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_addons_meta Meta - * @ingroup cpp_addons - * Flecs reflection framework. - * - * @{ - */ - -/** Class for reading/writing dynamic values. - * - * @ingroup cpp_addons_meta - */ -struct cursor { - cursor(flecs::world_t *world, flecs::entity_t type_id, void *ptr) { - cursor_ = ecs_meta_cursor(world, type_id, ptr); - } - - /** Push value scope (such as a nested struct) */ - int push() { - return ecs_meta_push(&cursor_); - } - - /** Pop value scope */ - int pop() { - return ecs_meta_pop(&cursor_); - } - - /** Move to next member/element */ - int next() { - return ecs_meta_next(&cursor_); - } - - /** Move to member by name */ - int member(const char *name) { - return ecs_meta_member(&cursor_, name); - } - - /** Move to element by index */ - int elem(int32_t elem) { - return ecs_meta_elem(&cursor_, elem); - } - - /** Test if current scope is a collection type */ - bool is_collection() { - return ecs_meta_is_collection(&cursor_); - } - - /** Get member name */ - flecs::string_view get_member() const { - return flecs::string_view(ecs_meta_get_member(&cursor_)); - } - - /** Get type of value */ - flecs::entity get_type() const; - - /** Get unit of value */ - flecs::entity get_unit() const; - - /** Get untyped pointer to value */ - void* get_ptr() { - return ecs_meta_get_ptr(&cursor_); - } - - /** Set boolean value */ - int set_bool(bool value) { - return ecs_meta_set_bool(&cursor_, value); - } - - /** Set char value */ - int set_char(char value) { - return ecs_meta_set_char(&cursor_, value); - } - - /** Set signed int value */ - int set_int(int64_t value) { - return ecs_meta_set_int(&cursor_, value); - } - - /** Set unsigned int value */ - int set_uint(uint64_t value) { - return ecs_meta_set_uint(&cursor_, value); - } - - /** Set float value */ - int set_float(double value) { - return ecs_meta_set_float(&cursor_, value); - } - - /** Set string value */ - int set_string(const char *value) { - return ecs_meta_set_string(&cursor_, value); - } - - /** Set string literal value */ - int set_string_literal(const char *value) { - return ecs_meta_set_string_literal(&cursor_, value); - } - - /** Set entity value */ - int set_entity(flecs::entity_t value) { - return ecs_meta_set_entity(&cursor_, value); - } - - /** Set (component) id value */ - int set_id(flecs::id_t value) { - return ecs_meta_set_id(&cursor_, value); - } - - /** Set null value */ - int set_null() { - return ecs_meta_set_null(&cursor_); - } - - /** Get boolean value */ - bool get_bool() const { - return ecs_meta_get_bool(&cursor_); - } - - /** Get char value */ - char get_char() const { - return ecs_meta_get_char(&cursor_); - } - - /** Get signed int value */ - int64_t get_int() const { - return ecs_meta_get_int(&cursor_); - } - - /** Get unsigned int value */ - uint64_t get_uint() const { - return ecs_meta_get_uint(&cursor_); - } - - /** Get float value */ - double get_float() const { - return ecs_meta_get_float(&cursor_); - } - - /** Get string value */ - const char *get_string() const { - return ecs_meta_get_string(&cursor_); - } - - /** Get entity value */ - flecs::entity get_entity() const; - - /** Cursor object */ - ecs_meta_cursor_t cursor_; -}; - -/** @} */ - -} - -/** - * @file addons/cpp/mixins/meta/opaque.hpp - * @brief Helpers for opaque type registration. - */ - -#pragma once - -#include - -namespace flecs { - -/** - * @defgroup cpp_addons_meta Meta - * @ingroup cpp_addons - * Flecs reflection framework. - * - * @{ - */ - -/** Serializer object, used for serializing opaque types */ -using serializer = ecs_serializer_t; - -/** Serializer function, used to serialize opaque types */ -using serialize_t = ecs_meta_serialize_t; - -/** Type safe variant of serializer function */ -template -using serialize = int(*)(const serializer *, const T*); - -/** Type safe interface for opaque types */ -template -struct opaque { - opaque(flecs::world_t *w = nullptr) : world(w) { - if (world) { - desc.entity = _::type::id(world); - } - } - - /** Type that describes the type kind/structure of the opaque type */ - opaque& as_type(flecs::id_t func) { - this->desc.type.as_type = func; - return *this; - } - - /** Serialize function */ - opaque& serialize(flecs::serialize func) { - this->desc.type.serialize = - reinterpret_castdesc.type.serialize)>(func); - return *this; - } - - /** Assign bool value */ - opaque& assign_bool(void (*func)(T *dst, bool value)) { - this->desc.type.assign_bool = - reinterpret_castdesc.type.assign_bool)>(func); - return *this; - } - - /** Assign char value */ - opaque& assign_char(void (*func)(T *dst, char value)) { - this->desc.type.assign_char = - reinterpret_castdesc.type.assign_char)>(func); - return *this; - } - - /** Assign int value */ - opaque& assign_int(void (*func)(T *dst, int64_t value)) { - this->desc.type.assign_int = - reinterpret_castdesc.type.assign_int)>(func); - return *this; - } - - /** Assign unsigned int value */ - opaque& assign_uint(void (*func)(T *dst, uint64_t value)) { - this->desc.type.assign_uint = - reinterpret_castdesc.type.assign_uint)>(func); - return *this; - } - - /** Assign float value */ - opaque& assign_float(void (*func)(T *dst, double value)) { - this->desc.type.assign_float = - reinterpret_castdesc.type.assign_float)>(func); - return *this; - } - - /** Assign string value */ - opaque& assign_string(void (*func)(T *dst, const char *value)) { - this->desc.type.assign_string = - reinterpret_castdesc.type.assign_string)>(func); - return *this; - } - - /** Assign entity value */ - opaque& assign_entity( - void (*func)(T *dst, ecs_world_t *world, ecs_entity_t entity)) - { - this->desc.type.assign_entity = - reinterpret_castdesc.type.assign_entity)>(func); - return *this; - } - - /** Assign (component) id value */ - opaque& assign_id( - void (*func)(T *dst, ecs_world_t *world, ecs_id_t id)) - { - this->desc.type.assign_id = - reinterpret_castdesc.type.assign_id)>(func); - return *this; - } - - /** Assign null value */ - opaque& assign_null(void (*func)(T *dst)) { - this->desc.type.assign_null = - reinterpret_castdesc.type.assign_null)>(func); - return *this; - } - - /** Clear collection elements */ - opaque& clear(void (*func)(T *dst)) { - this->desc.type.clear = - reinterpret_castdesc.type.clear)>(func); - return *this; - } - - /** Ensure & get collection element */ - opaque& ensure_element(ElemType* (*func)(T *dst, size_t elem)) { - this->desc.type.ensure_element = - reinterpret_castdesc.type.ensure_element)>(func); - return *this; - } - - /** Ensure & get element */ - opaque& ensure_member(void* (*func)(T *dst, const char *member)) { - this->desc.type.ensure_member = - reinterpret_castdesc.type.ensure_member)>(func); - return *this; - } - - /** Return number of elements */ - opaque& count(size_t (*func)(const T *dst)) { - this->desc.type.count = - reinterpret_castdesc.type.count)>(func); - return *this; - } - - /** Resize to number of elements */ - opaque& resize(void (*func)(T *dst, size_t count)) { - this->desc.type.resize = - reinterpret_castdesc.type.resize)>(func); - return *this; - } - - ~opaque() { - if (world) { - ecs_opaque_init(world, &desc); - } - } - - /** Opaque type descriptor */ - flecs::world_t *world = nullptr; - ecs_opaque_desc_t desc = {}; -}; - -/** @} */ - -} - - -#endif -#ifdef FLECS_UNITS -/** - * @file addons/cpp/mixins/units/decl.hpp - * @brief Units module declarations. - */ - -#pragma once - -namespace flecs { -struct units { - -/** - * @defgroup cpp_addons_units Units - * @ingroup cpp_addons - * Common unit annotations for reflection framework. - * - * @{ - */ - -struct Prefixes { }; - -/** - * @defgroup cpp_addons_units_prefixes Prefixes - * @ingroup cpp_addons_units - * Prefixes to indicate unit count (e.g. Kilo, Mega) - * - * @{ - */ - -struct Yocto { }; -struct Zepto { }; -struct Atto { }; -struct Femto { }; -struct Pico { }; -struct Nano { }; -struct Micro { }; -struct Milli { }; -struct Centi { }; -struct Deci { }; -struct Deca { }; -struct Hecto { }; -struct Kilo { }; -struct Mega { }; -struct Giga { }; -struct Tera { }; -struct Peta { }; -struct Exa { }; -struct Zetta { }; -struct Yotta { }; -struct Kibi { }; -struct Mebi { }; -struct Gibi { }; -struct Tebi { }; -struct Pebi { }; -struct Exbi { }; -struct Zebi { }; -struct Yobi { }; - -/** @} */ - -/** - * @defgroup cpp_addons_units_quantities Quantities - * @ingroup cpp_addons_units - * Quantities that group units (e.g. Length) - * - * @{ - */ - -struct Duration { }; -struct Time { }; -struct Mass { }; -struct ElectricCurrent { }; -struct LuminousIntensity { }; -struct Force { }; -struct Amount { }; -struct Length { }; -struct Pressure { }; -struct Speed { }; -struct Temperature { }; -struct Data { }; -struct DataRate { }; -struct Angle { }; -struct Frequency { }; -struct Uri { }; -struct Color { }; - -/** @} */ - -struct duration { -/** - * @defgroup cpp_addons_units_duration Duration - * @ingroup cpp_addons_units - * @{ - */ - -struct PicoSeconds { }; -struct NanoSeconds { }; -struct MicroSeconds { }; -struct MilliSeconds { }; -struct Seconds { }; -struct Minutes { }; -struct Hours { }; -struct Days { }; - -/** @} */ -}; - -struct angle { -/** - * @defgroup cpp_addons_units_angle Angle - * @ingroup cpp_addons_units - * @{ - */ - -struct Radians { }; -struct Degrees { }; - -/** @} */ -}; - - -struct time { -/** - * @defgroup cpp_addons_units_time Time - * @ingroup cpp_addons_units - * @{ - */ - -struct Date { }; - -/** @} */ -}; - - -struct mass { -/** - * @defgroup cpp_addons_units_mass Mass - * @ingroup cpp_addons_units - * @{ - */ - -struct Grams { }; -struct KiloGrams { }; - -/** @} */ -}; - - -struct electric_current { -/** - * @defgroup cpp_addons_units_electric_current Electric Current - * @ingroup cpp_addons_units - * @{ - */ - -struct Ampere { }; - -/** @} */ -}; - - -struct amount { -/** - * @defgroup cpp_addons_units_amount Amount - * @ingroup cpp_addons_units - * @{ - */ - -struct Mole { }; - -/** @} */ -}; - - -struct luminous_intensity { -/** - * @defgroup cpp_addons_units_luminous_intensity Luminous Intensity - * @ingroup cpp_addons_units - * @{ - */ - -struct Candela { }; - -/** @} */ -}; - - -struct force { -/** - * @defgroup cpp_addons_units_force Force - * @ingroup cpp_addons_units - * @{ - */ - -struct Newton { }; - -/** @} */ -}; - - -struct length { -/** - * @defgroup cpp_addons_units_length Length - * @ingroup cpp_addons_units - * @{ - */ - -struct Meters { }; -struct PicoMeters { }; -struct NanoMeters { }; -struct MicroMeters { }; -struct MilliMeters { }; -struct CentiMeters { }; -struct KiloMeters { }; -struct Miles { }; -struct Pixels { }; - -/** @} */ -}; - - -struct pressure { -/** - * @defgroup cpp_addons_units_pressure Pressure - * @ingroup cpp_addons_units - * @{ - */ - -struct Pascal { }; -struct Bar { }; - -/** @} */ -}; - - -struct speed { -/** - * @defgroup cpp_addons_units_speed Speed - * @ingroup cpp_addons_units - * @{ - */ - -struct MetersPerSecond { }; -struct KiloMetersPerSecond { }; -struct KiloMetersPerHour { }; -struct MilesPerHour { }; - -/** @} */ -}; - - -struct temperature { -/** - * @defgroup cpp_addons_units_temperature Temperature - * @ingroup cpp_addons_units - * @{ - */ - -struct Kelvin { }; -struct Celsius { }; -struct Fahrenheit { }; - -/** @} */ -}; - - -struct data { -/** - * @defgroup cpp_addons_units_data Data - * @ingroup cpp_addons_units - * @{ - */ - -struct Bits { }; -struct KiloBits { }; -struct MegaBits { }; -struct GigaBits { }; -struct Bytes { }; -struct KiloBytes { }; -struct MegaBytes { }; -struct GigaBytes { }; -struct KibiBytes { }; -struct MebiBytes { }; -struct GibiBytes { }; - -/** @} */ -}; - -struct datarate { -/** - * @defgroup cpp_addons_units_datarate Data Rate - * @ingroup cpp_addons_units - * @{ - */ - -struct BitsPerSecond { }; -struct KiloBitsPerSecond { }; -struct MegaBitsPerSecond { }; -struct GigaBitsPerSecond { }; -struct BytesPerSecond { }; -struct KiloBytesPerSecond { }; -struct MegaBytesPerSecond { }; -struct GigaBytesPerSecond { }; - -/** @} */ -}; - - -struct frequency { -/** - * @defgroup cpp_addons_units_frequency Frequency - * @ingroup cpp_addons_units - * @{ - */ - -struct Hertz { }; -struct KiloHertz { }; -struct MegaHertz { }; -struct GigaHertz { }; - -/** @} */ -}; - - -struct uri { -/** - * @defgroup cpp_addons_units_uri Uri - * @ingroup cpp_addons_units - * @{ - */ - -struct Hyperlink { }; -struct Image { }; -struct File { }; - -/** @} */ -}; - - -struct color { -/** - * @defgroup cpp_addons_units_color Color - * @ingroup cpp_addons_units - * @{ - */ - -struct Rgb { }; -struct Hsl { }; -struct Css { }; - -/** @} */ -}; - -struct Percentage { }; -struct Bel { }; -struct DeciBel { }; - -units(flecs::world& world); - -/** @} */ - -}; -} - -#endif -#ifdef FLECS_STATS -/** - * @file addons/cpp/mixins/stats/decl.hpp - * @brief Stats module declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_addons_stats Stats - * @ingroup cpp_addons - * The stats addon tracks statistics for the world and systems. - * - * @{ - */ - -/** Component that stores world statistics */ -using WorldStats = EcsWorldStats; - -/** Component that stores system/pipeline statistics */ -using PipelineStats = EcsPipelineStats; - -/** Component with world summary stats */ -using WorldSummary = EcsWorldSummary; - -struct stats { - stats(flecs::world& world); -}; - -/** @} */ - -} - -#endif -#ifdef FLECS_METRICS -/** - * @file addons/cpp/mixins/metrics/decl.hpp - * @brief Metrics declarations. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/metrics/builder.hpp - * @brief Metric builder. - */ - -#pragma once - -#define ECS_EVENT_DESC_ID_COUNT_MAX (8) - -namespace flecs { - -/** - * @ingroup cpp_addons_metrics - * @{ - */ - -/** Event builder interface */ -struct metric_builder { - metric_builder(flecs::world_t *world, flecs::entity_t entity) - : world_(world) - { - desc_.entity = entity; - } - - ~metric_builder(); - - metric_builder& member(flecs::entity_t e) { - desc_.member = e; - return *this; - } - - metric_builder& member(const char *name); - - template - metric_builder& member(const char *name); - - metric_builder& dotmember(const char *name); - - template - metric_builder& dotmember(const char *name); - - metric_builder& id(flecs::id_t the_id) { - desc_.id = the_id; - return *this; - } - - metric_builder& id(flecs::entity_t first, flecs::entity_t second) { - desc_.id = ecs_pair(first, second); - return *this; - } - - template - metric_builder& id() { - return id(_::type::id(world_)); - } - - template - metric_builder& id(flecs::entity_t second) { - return id(_::type::id(world_), second); - } - - template - metric_builder& id_second(flecs::entity_t first) { - return id(first, _::type::id(world_)); - } - - template - metric_builder& id() { - return id(_::type::id(world_)); - } - - metric_builder& targets(bool value = true) { - desc_.targets = value; - return *this; - } - - metric_builder& kind(flecs::entity_t the_kind) { - desc_.kind = the_kind; - return *this; - } - - template - metric_builder& kind() { - return kind(_::type::id(world_)); - } - - metric_builder& brief(const char *b) { - desc_.brief = b; - return *this; - } - - operator flecs::entity(); - -protected: - flecs::world_t *world_; - ecs_metric_desc_t desc_ = {}; - bool created_ = false; -}; - -/** - * @} - */ - -} - - -namespace flecs { - -/** - * @defgroup cpp_addons_metrics Metrics - * @ingroup cpp_addons - * The metrics module extracts metrics from components and makes them available - * through a unified component interface. - * - * @{ - */ - -struct metrics { - using Value = EcsMetricValue; - using Source = EcsMetricSource; - - struct Instance { }; - struct Metric { }; - struct Counter { }; - struct CounterIncrement { }; - struct CounterId { }; - struct Gauge { }; - - metrics(flecs::world& world); -}; - -/** @} */ - -} - -#endif -#ifdef FLECS_ALERTS -/** - * @file addons/cpp/mixins/alerts/decl.hpp - * @brief Alert declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_addons_alerts Alerts - * @ingroup cpp_addons - * Alert implementation. - * - * @{ - */ - -/** Module */ -struct alerts { - using AlertsActive = EcsAlertsActive; - using Instance = EcsAlertInstance; - - struct Alert { }; - struct Info { }; - struct Warning { }; - struct Error { }; - - alerts(flecs::world& world); -}; - -template -struct alert; - -template -struct alert_builder; - -/** @} */ - -} - -#endif -#ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/decl.hpp - * @brief JSON addon declarations. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_addons_json Json - * @ingroup cpp_addons - * Functions for serializing to/from JSON. - * - * @{ - */ - -using from_json_desc_t = ecs_from_json_desc_t; -using entity_to_json_desc_t = ecs_entity_to_json_desc_t; -using iter_to_json_desc_t = ecs_iter_to_json_desc_t; - -/** @} */ - -} - -#endif -#ifdef FLECS_APP -/** - * @file addons/cpp/mixins/app/decl.hpp - * @brief App addon declarations. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/app/builder.hpp - * @brief App builder. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_addons_app App - * @ingroup cpp_addons - * Optional addon for running the main application loop. - * - * @{ - */ - -/** App builder interface */ -struct app_builder { - app_builder(flecs::world_t *world) - : world_(world) - , desc_{} - { - const ecs_world_info_t *stats = ecs_get_world_info(world); - desc_.target_fps = stats->target_fps; - ecs_ftime_t t_zero = 0.0; - if (ECS_EQ(desc_.target_fps, t_zero)) { - desc_.target_fps = 60; - } - } - - app_builder& target_fps(ecs_ftime_t value) { - desc_.target_fps = value; - return *this; - } - - app_builder& delta_time(ecs_ftime_t value) { - desc_.delta_time = value; - return *this; - } - - app_builder& threads(int32_t value) { - desc_.threads = value; - return *this; - } - - app_builder& frames(int32_t value) { - desc_.frames = value; - return *this; - } - - app_builder& enable_rest(uint16_t port = 0) { - desc_.enable_rest = true; - desc_.port = port; - return *this; - } - - app_builder& enable_stats(bool value = true) { - desc_.enable_stats = value; - return *this; - } - - app_builder& init(ecs_app_init_action_t value) { - desc_.init = value; - return *this; - } - - app_builder& ctx(void *value) { - desc_.ctx = value; - return *this; - } - - int run() { - int result = ecs_app_run(world_, &desc_); - if (ecs_should_quit(world_)) { - // Only free world if quit flag is set. This ensures that we won't - // try to cleanup the world if the app is used in an environment - // that takes over the main loop, like with emscripten. - if (!flecs_poly_release(world_)) { - ecs_fini(world_); - } - } - return result; - } - -private: - flecs::world_t *world_; - ecs_app_desc_t desc_; -}; - -/** @} */ - -} - - -#endif -#ifdef FLECS_SCRIPT -/** - * @file addons/cpp/mixins/script/decl.hpp - * @brief Script declarations. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/script/builder.hpp - * @brief Script builder. - */ - -#pragma once - -namespace flecs { - -/** - * @ingroup cpp_addons_script - * @{ - */ - -/** Script builder interface */ -struct script_builder { - script_builder(flecs::world_t *world, const char *name = nullptr) - : world_(world) - , desc_{} - { - if (name != nullptr) { - ecs_entity_desc_t entity_desc = {}; - entity_desc.name = name; - entity_desc.sep = "::"; - entity_desc.root_sep = "::"; - this->desc_.entity = ecs_entity_init(world, &entity_desc); - } - } - - script_builder& code(const char *str) { - desc_.code = str; - return *this; - } - - script_builder& filename(const char *str) { - desc_.filename = str; - return *this; - } - - flecs::entity run() const; - -protected: - flecs::world_t *world_; - ecs_script_desc_t desc_; -}; - -} - - -namespace flecs { - -/** - * @defgroup cpp_addons_script Script - * @ingroup cpp_addons - * - * @{ - */ - -struct script_builder; - -/** @} */ - -} - -#endif - -/** - * @file addons/cpp/log.hpp - * @brief Logging functions. - */ - -#pragma once - -namespace flecs { -namespace log { - -/** - * @defgroup cpp_log Logging - * @ingroup cpp_addons - * Logging functions. - * - * @{ - */ - -/** Set log level */ -inline void set_level(int level) { - ecs_log_set_level(level); -} - -inline int get_level() { - return ecs_log_get_level(); -} - -/** Enable colors in logging */ -inline void enable_colors(bool enabled = true) { - ecs_log_enable_colors(enabled); -} - -/** Enable timestamps in logging */ -inline void enable_timestamp(bool enabled = true) { - ecs_log_enable_timestamp(enabled); -} - -/** Enable time delta in logging */ -inline void enable_timedelta(bool enabled = true) { - ecs_log_enable_timedelta(enabled); -} - -/** Debug trace (level 1) */ -inline void dbg(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ecs_logv(1, fmt, args); - va_end(args); -} - -/** Trace (level 0) */ -inline void trace(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ecs_logv(0, fmt, args); - va_end(args); -} - -/** Trace (level -2) */ -inline void warn(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ecs_logv(-2, fmt, args); - va_end(args); -} - -/** Trace (level -3) */ -inline void err(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ecs_logv(-3, fmt, args); - va_end(args); -} - -/** Increase log indentation */ -inline void push(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - ecs_logv(0, fmt, args); - va_end(args); - ecs_log_push(); -} - -/** Increase log indentation */ -inline void push() { - ecs_log_push(); -} - -/** Increase log indentation */ -inline void pop() { - ecs_log_pop(); -} - -/** @} */ - -} -} - -/** - * @file addons/cpp/pair.hpp - * @brief Utilities for working with compile time pairs. - */ - -#pragma once - -namespace flecs { - -namespace _ { - struct pair_base { }; -} // _ - - -/** - * @defgroup cpp_pair_type Pair type - * @ingroup cpp_core - * Compile time utilities for working with relationship pairs. - * - * @{ - */ - -/** Type that represents a pair. - * The pair type can be used to represent a pair at compile time, and is able - * to automatically derive the storage type associated with the pair, accessible - * through pair::type. - * - * The storage type is derived using the following rules: - * - if pair::first is non-empty, the storage type is pair::first - * - if pair::first is empty and pair::second is non-empty, the storage type is pair::second - * - * The pair type can hold a temporary value so that it can be used in the - * signatures of queries - */ -template -struct pair : _::pair_base { - using type = conditional_t::value || is_empty::value, First, Second>; - using first = First; - using second = Second; - - pair(type& v) : ref_(v) { } - - // This allows the class to be used as a temporary object - pair(const type& v) : ref_(const_cast(v)) { } - - operator type&() { - return ref_; - } - - operator const type&() const { - return ref_; - } - - type* operator->() { - return &ref_; - } - - const type* operator->() const { - return &ref_; - } - - type& operator*() { - return ref_; - } - - const type& operator*() const { - return ref_; - } - -private: - type& ref_; -}; - -template ::value> = 0> -using pair_object = pair; - -template -using raw_type_t = remove_pointer_t>; - -/** Test if type is a pair. */ -template -struct is_pair { - static constexpr bool value = is_base_of<_::pair_base, raw_type_t >::value; -}; - -/** Get pair::first from pair while preserving cv qualifiers. */ -template -using pair_first_t = transcribe_cv_t, typename raw_type_t

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

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

::type>; - -/** Get actual type from a regular type or pair. */ -template -struct actual_type; - -template -struct actual_type::value >> { - using type = T; -}; - -template -struct actual_type::value >> { - using type = pair_type_t; -}; - -template -using actual_type_t = typename actual_type::type; - - -// Get type without const, *, & -template -struct base_type { - using type = decay_t< actual_type_t >; -}; - -template -using base_type_t = typename base_type::type; - - -// Get type without *, & (retains const which is useful for function args) -template -struct base_arg_type { - using type = remove_pointer_t< remove_reference_t< actual_type_t > >; -}; - -template -using base_arg_type_t = typename base_arg_type::type; - - -// Test if type is the same as its actual type -template -struct is_actual { - static constexpr bool value = - std::is_same >::value && !is_enum::value; -}; - -} // flecs - -/** - * @file addons/cpp/lifecycle_traits.hpp - * @brief Utilities for discovering and registering component lifecycle hooks. - */ - -#pragma once - -namespace flecs -{ - -namespace _ -{ - -// T() -// Can't coexist with T(flecs::entity) or T(flecs::world, flecs::entity) -template -void ctor_impl(void *ptr, int32_t count, const ecs_type_info_t *info) { - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *arr = static_cast(ptr); - for (int i = 0; i < count; i ++) { - FLECS_PLACEMENT_NEW(&arr[i], T); - } -} - -// ~T() -template -void dtor_impl(void *ptr, int32_t count, const ecs_type_info_t *info) { - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *arr = static_cast(ptr); - for (int i = 0; i < count; i ++) { - arr[i].~T(); - } -} - -// T& operator=(const T&) -template -void copy_impl(void *dst_ptr, const void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - const T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - dst_arr[i] = src_arr[i]; - } -} - -// T& operator=(T&&) -template -void move_impl(void *dst_ptr, void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - dst_arr[i] = FLECS_MOV(src_arr[i]); - } -} - -// T(T&) -template -void copy_ctor_impl(void *dst_ptr, const void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - const T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - FLECS_PLACEMENT_NEW(&dst_arr[i], T(src_arr[i])); - } -} - -// T(T&&) -template -void move_ctor_impl(void *dst_ptr, void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - FLECS_PLACEMENT_NEW(&dst_arr[i], T(FLECS_MOV(src_arr[i]))); - } -} - -// T(T&&), ~T() -// Typically used when moving to a new table, and removing from the old table -template -void ctor_move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - FLECS_PLACEMENT_NEW(&dst_arr[i], T(FLECS_MOV(src_arr[i]))); - src_arr[i].~T(); - } -} - -// Move assign + dtor (non-trivial move assignment) -// Typically used when moving a component to a deleted component -template ::value > = 0> -void move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - // Move assignment should free dst & assign dst to src - dst_arr[i] = FLECS_MOV(src_arr[i]); - // Destruct src. Move should have left object in a state where it no - // longer holds resources, but it still needs to be destructed. - src_arr[i].~T(); - } -} - -// Move assign + dtor (trivial move assignment) -// Typically used when moving a component to a deleted component -template ::value > = 0> -void move_dtor_impl(void *dst_ptr, void *src_ptr, int32_t count, - const ecs_type_info_t *info) -{ - (void)info; ecs_assert(info->size == ECS_SIZEOF(T), - ECS_INTERNAL_ERROR, NULL); - T *dst_arr = static_cast(dst_ptr); - T *src_arr = static_cast(src_ptr); - for (int i = 0; i < count; i ++) { - // Cleanup resources of dst - dst_arr[i].~T(); - // Copy src to dst - dst_arr[i] = FLECS_MOV(src_arr[i]); - // No need to destruct src. Since this is a trivial move the code - // should be agnostic to the address of the component which means we - // can pretend nothing got destructed. - } -} - -} // _ - -// Trait to test if type is constructible by flecs -template -struct is_flecs_constructible { - static constexpr bool value = - std::is_default_constructible>::value; -}; - -namespace _ -{ - -// Trivially constructible -template ::value > = 0> -ecs_xtor_t ctor(ecs_flags32_t &) { - return nullptr; -} - -// Not constructible by flecs -template ::value > = 0> -ecs_xtor_t ctor(ecs_flags32_t &flags) { - flags |= ECS_TYPE_HOOK_CTOR_ILLEGAL; - return nullptr; -} - -// Default constructible -template ::value && - std::is_default_constructible::value > = 0> -ecs_xtor_t ctor(ecs_flags32_t &) { - return ctor_impl; -} - -// No dtor -template ::value > = 0> -ecs_xtor_t dtor(ecs_flags32_t &) { - return nullptr; -} - -// Dtor -template ::value && - ! std::is_trivially_destructible::value > = 0> -ecs_xtor_t dtor(ecs_flags32_t &) { - return dtor_impl; -} - -// Assert when the type cannot be destructed -template ::value > = 0> -ecs_xtor_t dtor(ecs_flags32_t &flags) { - flecs_static_assert(always_false::value, - "component type must be destructible"); - flags |= ECS_TYPE_HOOK_DTOR_ILLEGAL; - return nullptr; -} - -// Trivially copyable -template ::value > = 0> -ecs_copy_t copy(ecs_flags32_t &) { - return nullptr; -} - -// Not copyable -template ::value && - ! std::is_copy_assignable::value > = 0> -ecs_copy_t copy(ecs_flags32_t &flags) { - flags |= ECS_TYPE_HOOK_COPY_ILLEGAL; - return nullptr; -} - -// Copy assignment -template ::value && - ! std::is_trivially_copyable::value > = 0> -ecs_copy_t copy(ecs_flags32_t &) { - return copy_impl; -} - -// Trivially move assignable -template ::value > = 0> -ecs_move_t move(ecs_flags32_t &) { - return nullptr; -} - -// Component types must be move assignable -template ::value > = 0> -ecs_move_t move(ecs_flags32_t &flags) { - flags |= ECS_TYPE_HOOK_MOVE_ILLEGAL; - return nullptr; -} - -// Move assignment -template ::value && - ! std::is_trivially_move_assignable::value > = 0> -ecs_move_t move(ecs_flags32_t &) { - return move_impl; -} - -// Trivially copy constructible -template ::value > = 0> -ecs_copy_t copy_ctor(ecs_flags32_t &) { - return nullptr; -} - -// No copy ctor -template ::value > = 0> -ecs_copy_t copy_ctor(ecs_flags32_t &flags) { - flags |= ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL; - return nullptr; - -} - -// Copy ctor -template ::value && - ! std::is_trivially_copy_constructible::value > = 0> -ecs_copy_t copy_ctor(ecs_flags32_t &) { - return copy_ctor_impl; -} - -// Trivially move constructible -template ::value > = 0> -ecs_move_t move_ctor(ecs_flags32_t &) { - return nullptr; -} - -// Component types must be move constructible -template ::value > = 0> -ecs_move_t move_ctor(ecs_flags32_t &flags) { - flags |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; - return nullptr; -} - -// Move ctor -template ::value && - ! std::is_trivially_move_constructible::value > = 0> -ecs_move_t move_ctor(ecs_flags32_t &) { - return move_ctor_impl; -} - -// Trivial merge (move assign + dtor) -template ::value && - std::is_trivially_destructible::value > = 0> -ecs_move_t ctor_move_dtor(ecs_flags32_t &) { - return nullptr; -} - -// Component types must be move constructible and destructible -template ::value || - ! std::is_destructible::value > = 0> -ecs_move_t ctor_move_dtor(ecs_flags32_t &flags) { - flags |= ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL; - return nullptr; -} - -// Merge ctor + dtor -template ::value && - std::is_trivially_destructible::value) && - std::is_move_constructible::value && - std::is_destructible::value > = 0> -ecs_move_t ctor_move_dtor(ecs_flags32_t &) { - return ctor_move_dtor_impl; -} - -// Trivial merge (move assign + dtor) -template ::value && - std::is_trivially_destructible::value > = 0> -ecs_move_t move_dtor(ecs_flags32_t &) { - return nullptr; -} - -// Component types must be move constructible and destructible -template ::value || - ! std::is_destructible::value > = 0> -ecs_move_t move_dtor(ecs_flags32_t &flags) { - flags |= ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL; - return nullptr; -} - -// Merge assign + dtor -template ::value && - std::is_trivially_destructible::value) && - std::is_move_assignable::value && - std::is_destructible::value > = 0> -ecs_move_t move_dtor(ecs_flags32_t &) { - return move_dtor_impl; -} - -} // _ -} // flecs - -/** - * @file addons/cpp/world.hpp - * @brief World class. - */ - -#pragma once - -namespace flecs -{ - -/* Static helper functions to assign a component value */ - -// set(T&&), T = constructible -template ::value > = 0> -inline void set(world_t *world, flecs::entity_t entity, T&& value, flecs::id_t id) { - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - - if (!ecs_is_deferred(world)) { - T& dst = *static_cast(ecs_ensure_id(world, entity, id)); - dst = FLECS_MOV(value); - - ecs_modified_id(world, entity, id); - } else { - T& dst = *static_cast(ecs_ensure_modified_id(world, entity, id)); - dst = FLECS_MOV(value); - } -} - -// set(const T&), T = constructible -template ::value > = 0> -inline void set(world_t *world, flecs::entity_t entity, const T& value, flecs::id_t id) { - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - - if (!ecs_is_deferred(world)) { - T& dst = *static_cast(ecs_ensure_id(world, entity, id)); - dst = FLECS_MOV(value); - - ecs_modified_id(world, entity, id); - } else { - T& dst = *static_cast(ecs_ensure_modified_id(world, entity, id)); - dst = FLECS_MOV(value); - } -} - -// set(T&&), T = not constructible -template ::value > = 0> -inline void set(world_t *world, flecs::entity_t entity, T&& value, flecs::id_t id) { - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - - if (!ecs_is_deferred(world)) { - T& dst = *static_cast*>(ecs_ensure_id(world, entity, id)); - dst = FLECS_MOV(value); - - ecs_modified_id(world, entity, id); - } else { - T& dst = *static_cast*>(ecs_ensure_modified_id(world, entity, id)); - dst = FLECS_MOV(value); - } -} - -// set(const T&), T = not constructible -template ::value > = 0> -inline void set(world_t *world, flecs::entity_t entity, const T& value, flecs::id_t id) { - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - - if (!ecs_is_deferred(world)) { - T& dst = *static_cast*>(ecs_ensure_id(world, entity, id)); - dst = FLECS_MOV(value); - - ecs_modified_id(world, entity, id); - } else { - T& dst = *static_cast*>(ecs_ensure_modified_id(world, entity, id)); - dst = FLECS_MOV(value); - } -} - -// emplace for T(Args...) -template , Args...>::value || - std::is_default_constructible>::value > = 0> -inline void emplace(world_t *world, flecs::entity_t entity, flecs::id_t id, Args&&... args) { - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - T& dst = *static_cast(ecs_emplace_id(world, entity, id, nullptr)); - - FLECS_PLACEMENT_NEW(&dst, T{FLECS_FWD(args)...}); - - ecs_modified_id(world, entity, id); -} - -// set(T&&) -template -inline void set(world_t *world, entity_t entity, A&& value) { - id_t id = _::type::id(world); - flecs::set(world, entity, FLECS_FWD(value), id); -} - -// set(const T&) -template -inline void set(world_t *world, entity_t entity, const A& value) { - id_t id = _::type::id(world); - flecs::set(world, entity, value, id); -} - -/** Return id without generation. - * - * @see ecs_strip_generation() - */ -inline flecs::id_t strip_generation(flecs::entity_t e) { - return ecs_strip_generation(e); -} - -/** Return entity generation. - */ -inline uint32_t get_generation(flecs::entity_t e) { - return ECS_GENERATION(e); -} - -struct scoped_world; - -/** - * @defgroup cpp_world World - * @ingroup cpp_core - * World operations. - * - * @{ - */ - -/** The world. - * The world is the container of all ECS data and systems. If the world is - * deleted, all data in the world will be deleted as well. - */ -struct world { - /** Create world. - */ - explicit world() - : world_( ecs_init() ) { - init_builtin_components(); - } - - /** Create world with command line arguments. - * Currently command line arguments are not interpreted, but they may be - * used in the future to configure Flecs parameters. - */ - explicit world(int argc, char *argv[]) - : world_( ecs_init_w_args(argc, argv) ) { - init_builtin_components(); - } - - /** Create world from C world. - */ - explicit world(world_t *w) - : world_( w ) { - if (w) { - flecs_poly_claim(w); - } - } - - /** Not allowed to copy a world. May only take a reference. - */ - world(const world& obj) { - this->world_ = obj.world_; - flecs_poly_claim(this->world_); - } - - world& operator=(const world& obj) noexcept { - release(); - this->world_ = obj.world_; - flecs_poly_claim(this->world_); - return *this; - } - - world(world&& obj) noexcept { - world_ = obj.world_; - obj.world_ = nullptr; - } - - world& operator=(world&& obj) noexcept { - release(); - world_ = obj.world_; - obj.world_ = nullptr; - return *this; - } - - /* Releases the underlying world object. If this is the last handle, the world - will be finalized. */ - void release() { - if (world_) { - if (!flecs_poly_release(world_)) { - if (ecs_stage_get_id(world_) == -1) { - ecs_stage_free(world_); - } else { - // before we call ecs_fini(), we increment the reference count back to 1 - // otherwise, copies of this object created during ecs_fini (e.g. a component on_remove hook) - // would call again this destructor and ecs_fini(). - flecs_poly_claim(world_); - ecs_fini(world_); - } - } - world_ = nullptr; - } - } - - ~world() { - release(); - } - - /* Implicit conversion to world_t* */ - operator world_t*() const { return world_; } - - /** Make current world object owner of the world. This may only be called on - * one flecs::world object, an may only be called once. Failing to do so - * will result in undefined behavior. - * - * This operation allows a custom (C) world to be wrapped by a C++ object, - * and transfer ownership so that the world is automatically cleaned up. - */ - void make_owner() { - flecs_poly_release(world_); - } - - /** Deletes and recreates the world. */ - void reset() { - /* Make sure there's only one reference to the world */ - ecs_assert(flecs_poly_refcount(world_) == 1, ECS_INVALID_OPERATION, - "reset would invalidate other handles"); - ecs_fini(world_); - world_ = ecs_init(); - } - - /** Obtain pointer to C world object. - */ - world_t* c_ptr() const { - return world_; - } - - /** Signal application should quit. - * After calling this operation, the next call to progress() returns false. - */ - void quit() const { - ecs_quit(world_); - } - - /** Register action to be executed when world is destroyed. - */ - void atfini(ecs_fini_action_t action, void *ctx = nullptr) const { - ecs_atfini(world_, action, ctx); - } - - /** Test if quit() has been called. - */ - bool should_quit() const { - return ecs_should_quit(world_); - } - - /** Begin frame. - * When an application does not use progress() to control the main loop, it - * can still use Flecs features such as FPS limiting and time measurements. - * This operation needs to be invoked whenever a new frame is about to get - * processed. - * - * Calls to frame_begin() must always be followed by frame_end(). - * - * The function accepts a delta_time parameter, which will get passed to - * systems. This value is also used to compute the amount of time the - * function needs to sleep to ensure it does not exceed the target_fps, when - * it is set. When 0 is provided for delta_time, the time will be measured. - * - * This function should only be ran from the main thread. - * - * @param delta_time Time elapsed since the last frame. - * @return The provided delta_time, or measured time if 0 was provided. - * - * @see ecs_frame_begin() - * @see flecs::world::frame_end() - */ - ecs_ftime_t frame_begin(float delta_time = 0) const { - return ecs_frame_begin(world_, delta_time); - } - - /** End frame. - * This operation must be called at the end of the frame, and always after - * frame_begin(). - * - * This function should only be ran from the main thread. - * - * @see ecs_frame_end() - * @see flecs::world::frame_begin() - */ - void frame_end() const { - ecs_frame_end(world_); - } - - /** Begin readonly mode. - * - * @param multi_threaded Whether to enable readonly/multi threaded mode. - * - * @return Whether world is currently readonly. - * - * @see ecs_readonly_begin() - * @see flecs::world::is_readonly() - * @see flecs::world::readonly_end() - */ - bool readonly_begin(bool multi_threaded = false) const { - return ecs_readonly_begin(world_, multi_threaded); - } - - /** End readonly mode. - * - * @see ecs_readonly_end() - * @see flecs::world::is_readonly() - * @see flecs::world::readonly_begin() - */ - void readonly_end() const { - ecs_readonly_end(world_); - } - - /** Defer operations until end of frame. - * When this operation is invoked while iterating, operations inbetween the - * defer_begin() and defer_end() operations are executed at the end of the frame. - * - * This operation is thread safe. - * - * @return true if world changed from non-deferred mode to deferred mode. - * - * @see ecs_defer_begin() - * @see flecs::world::defer() - * @see flecs::world::defer_end() - * @see flecs::world::is_deferred() - * @see flecs::world::defer_resume() - * @see flecs::world::defer_suspend() - */ - bool defer_begin() const { - return ecs_defer_begin(world_); - } - - /** End block of operations to defer. - * See defer_begin(). - * - * This operation is thread safe. - * - * @return true if world changed from deferred mode to non-deferred mode. - * - * @see ecs_defer_end() - * @see flecs::world::defer() - * @see flecs::world::defer_begin() - * @see flecs::world::is_deferred() - * @see flecs::world::defer_resume() - * @see flecs::world::defer_suspend() - */ - bool defer_end() const { - return ecs_defer_end(world_); - } - - /** Test whether deferring is enabled. - * - * @return True if deferred, false if not. - * - * @see ecs_is_deferred() - * @see flecs::world::defer() - * @see flecs::world::defer_begin() - * @see flecs::world::defer_end() - * @see flecs::world::defer_resume() - * @see flecs::world::defer_suspend() - */ - bool is_deferred() const { - return ecs_is_deferred(world_); - } - - /** Configure world to have N stages. - * This initializes N stages, which allows applications to defer operations to - * multiple isolated defer queues. This is typically used for applications with - * multiple threads, where each thread gets its own queue, and commands are - * merged when threads are synchronized. - * - * Note that set_threads() already creates the appropriate number of stages. - * The set_stage_count() operation is useful for applications that want to manage - * their own stages and/or threads. - * - * @param stages The number of stages. - * - * @see ecs_set_stage_count() - * @see flecs::world::get_stage_count() - */ - void set_stage_count(int32_t stages) const { - ecs_set_stage_count(world_, stages); - } - - /** Get number of configured stages. - * Return number of stages set by set_stage_count(). - * - * @return The number of stages used for threading. - * - * @see ecs_get_stage_count() - * @see flecs::world::set_stage_count() - */ - int32_t get_stage_count() const { - return ecs_get_stage_count(world_); - } - - /** Get current stage id. - * The stage id can be used by an application to learn about which stage it - * is using, which typically corresponds with the worker thread id. - * - * @return The stage id. - */ - int32_t get_stage_id() const { - return ecs_stage_get_id(world_); - } - - /** Test if is a stage. - * If this function returns false, it is guaranteed that this is a valid - * world object. - * - * @return True if the world is a stage, false if not. - */ - bool is_stage() const { - ecs_assert( - flecs_poly_is(world_, ecs_world_t) || - flecs_poly_is(world_, ecs_stage_t), - ECS_INVALID_PARAMETER, - "flecs::world instance contains invalid reference to world or stage"); - return flecs_poly_is(world_, ecs_stage_t); - } - - /** Merge world or stage. - * When automatic merging is disabled, an application can call this - * operation on either an individual stage, or on the world which will merge - * all stages. This operation may only be called when staging is not enabled - * (either after progress() or after readonly_end()). - * - * This operation may be called on an already merged stage or world. - * - * @see ecs_merge() - */ - void merge() const { - ecs_merge(world_); - } - - /** Get stage-specific world pointer. - * Flecs threads can safely invoke the API as long as they have a private - * context to write to, also referred to as the stage. This function returns a - * pointer to a stage, disguised as a world pointer. - * - * Note that this function does not(!) create a new world. It simply wraps the - * existing world in a thread-specific context, which the API knows how to - * unwrap. The reason the stage is returned as an ecs_world_t is so that it - * can be passed transparently to the existing API functions, vs. having to - * create a dediated API for threading. - * - * @param stage_id The index of the stage to retrieve. - * @return A thread-specific pointer to the world. - */ - flecs::world get_stage(int32_t stage_id) const { - return flecs::world(ecs_get_stage(world_, stage_id)); - } - - /** Create asynchronous stage. - * An asynchronous stage can be used to asynchronously queue operations for - * later merging with the world. An asynchronous stage is similar to a regular - * stage, except that it does not allow reading from the world. - * - * Asynchronous stages are never merged automatically, and must therefore be - * manually merged with the ecs_merge function. It is not necessary to call - * defer_begin or defer_end before and after enqueuing commands, as an - * asynchronous stage unconditionally defers operations. - * - * The application must ensure that no commands are added to the stage while the - * stage is being merged. - * - * @return The stage. - */ - flecs::world async_stage() const { - ecs_world_t *as = ecs_stage_new(world_); - flecs_poly_release(as); // world object will claim - return flecs::world(as); - } - - /** Get actual world. - * If the current object points to a stage, this operation will return the - * actual world. - * - * @return The actual world. - */ - flecs::world get_world() const { - /* Safe cast, mutability is checked */ - return flecs::world( - world_ ? const_cast(ecs_get_world(world_)) : nullptr); - } - - /** Test whether the current world object is readonly. - * This function allows the code to test whether the currently used world - * object is readonly or whether it allows for writing. - * - * @return True if the world or stage is readonly. - * - * @see ecs_stage_is_readonly() - * @see flecs::world::readonly_begin() - * @see flecs::world::readonly_end() - */ - bool is_readonly() const { - return ecs_stage_is_readonly(world_); - } - - /** Set world context. - * Set a context value that can be accessed by anyone that has a reference - * to the world. - * - * @param ctx A pointer to a user defined structure. - * @param ctx_free A function that is invoked with ctx when the world is freed. - * - * - * @see ecs_set_ctx() - * @see flecs::world::get_ctx() - */ - void set_ctx(void* ctx, ecs_ctx_free_t ctx_free = nullptr) const { - ecs_set_ctx(world_, ctx, ctx_free); - } - - /** Get world context. - * This operation retrieves a previously set world context. - * - * @return The context set with set_binding_ctx(). If no context was set, the - * function returns NULL. - * - * @see ecs_get_ctx() - * @see flecs::world::set_ctx() - */ - void* get_ctx() const { - return ecs_get_ctx(world_); - } - - /** Set world binding context. - * - * Same as set_ctx() but for binding context. A binding context is intended - * specifically for language bindings to store binding specific data. - * - * @param ctx A pointer to a user defined structure. - * @param ctx_free A function that is invoked with ctx when the world is freed. - * - * @see ecs_set_binding_ctx() - * @see flecs::world::get_binding_ctx() - */ - void set_binding_ctx(void* ctx, ecs_ctx_free_t ctx_free = nullptr) const { - ecs_set_binding_ctx(world_, ctx, ctx_free); - } - - /** Get world binding context. - * This operation retrieves a previously set world binding context. - * - * @return The context set with set_binding_ctx(). If no context was set, the - * function returns NULL. - * - * @see ecs_get_binding_ctx() - * @see flecs::world::set_binding_ctx() - */ - void* get_binding_ctx() const { - return ecs_get_binding_ctx(world_); - } - - /** Preallocate memory for number of entities. - * This function preallocates memory for the entity index. - * - * @param entity_count Number of entities to preallocate memory for. - * - * @see ecs_dim() - */ - void dim(int32_t entity_count) const { - ecs_dim(world_, entity_count); - } - - /** Set entity range. - * This function limits the range of issued entity ids between min and max. - * - * @param min Minimum entity id issued. - * @param max Maximum entity id issued. - * - * @see ecs_set_entity_range() - */ - void set_entity_range(entity_t min, entity_t max) const { - ecs_set_entity_range(world_, min, max); - } - - /** Enforce that operations cannot modify entities outside of range. - * This function ensures that only entities within the specified range can - * be modified. Use this function if specific parts of the code only are - * allowed to modify a certain set of entities, as could be the case for - * networked applications. - * - * @param enabled True if range check should be enabled, false if not. - * - * @see ecs_enable_range_check() - */ - void enable_range_check(bool enabled = true) const { - ecs_enable_range_check(world_, enabled); - } - - /** Set current scope. - * - * @param scope The scope to set. - * @return The current scope; - * - * @see ecs_set_scope() - * @see flecs::world::get_scope() - */ - flecs::entity set_scope(const flecs::entity_t scope) const; - - /** Get current scope. - * - * @return The current scope. - * - * @see ecs_get_scope() - * @see flecs::world::set_scope() - */ - flecs::entity get_scope() const; - - /** Same as set_scope but with type. - * - * @see ecs_set_scope() - * @see flecs::world::get_scope() - */ - template - flecs::entity set_scope() const; - - /** Set search path. - * - * @see ecs_set_lookup_path() - * @see flecs::world::lookup() - */ - flecs::entity_t* set_lookup_path(const flecs::entity_t *search_path) const { - return ecs_set_lookup_path(world_, search_path); - } - - /** Lookup entity by name. - * - * @param name Entity name. - * @param recursive When false, only the current scope is searched. - * @result The entity if found, or 0 if not found. - */ - flecs::entity lookup(const char *name, const char *sep = "::", const char *root_sep = "::", bool recursive = true) const; - - /** Set singleton component. - */ - template ::value > = 0> - void set(const T& value) const { - flecs::set(world_, _::type::id(world_), value); - } - - /** Set singleton component. - */ - template ::value > = 0> - void set(T&& value) const { - flecs::set(world_, _::type::id(world_), - FLECS_FWD(value)); - } - - /** Set singleton pair. - */ - template , - typename A = actual_type_t

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

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

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

(world_, _::type::id(world_), FLECS_FWD(value)); - } - - /** Set singleton pair. - */ - template - void set(Second second, const First& value) const; - - /** Set singleton pair. - */ - template - void set(Second second, First&& value) const; - - /** Set singleton component inside a callback. - */ - template ::value > = 0 > - void set(const Func& func) const; - - template - void emplace(Args&&... args) const { - flecs::id_t component_id = _::type::id(world_); - flecs::emplace(world_, component_id, component_id, FLECS_FWD(args)...); - } - - /** Ensure singleton component. - */ - #ifndef ensure - template - T& ensure() const; - #endif - - /** Mark singleton component as modified. - */ - template - void modified() const; - - /** Get ref singleton component. - */ - template - ref get_ref() const; - - /** Get singleton component. - */ - template - const T* get() const; - - /** Get singleton pair. - */ - template , - typename A = actual_type_t

> - const A* get() const; - - /** Get singleton pair. - */ - template - const First* get(Second second) const; - - /** Get singleton component inside a callback. - */ - template ::value > = 0 > - void get(const Func& func) const; - - /** Get mutable singleton component. - */ - template - T* get_mut() const; - - /** Get mutable singleton pair. - */ - template , - typename A = actual_type_t

> - A* get_mut() const; - - /** Get mutable singleton pair. - */ - template - First* get_mut(Second second) const; - - /** Test if world has singleton component. - */ - template - bool has() const; - - /** Test if world has the provided pair. - * - * @tparam First The first element of the pair - * @tparam Second The second element of the pair - */ - template - bool has() const; - - /** Test if world has the provided pair. - * - * @tparam First The first element of the pair - * @param second The second element of the pair. - */ - template - bool has(flecs::id_t second) const; - - /** Test if world has the provided pair. - * - * @param first The first element of the pair - * @param second The second element of the pair - */ - bool has(flecs::id_t first, flecs::id_t second) const; - - /** Add singleton component. - */ - template - void add() const; - - /** Adds a pair to the singleton component. - * - * @tparam First The first element of the pair - * @tparam Second The second element of the pair - */ - template - void add() const; - - /** Adds a pair to the singleton component. - * - * @tparam First The first element of the pair - * @param second The second element of the pair. - */ - template - void add(flecs::entity_t second) const; - - /** Adds a pair to the singleton entity. - * - * @param first The first element of the pair - * @param second The second element of the pair - */ - void add(flecs::entity_t first, flecs::entity_t second) const; - - /** Remove singleton component. - */ - template - void remove() const; - - /** Removes the pair singleton component. - * - * @tparam First The first element of the pair - * @tparam Second The second element of the pair - */ - template - void remove() const; - - /** Removes the pair singleton component. - * - * @tparam First The first element of the pair - * @param second The second element of the pair. - */ - template - void remove(flecs::entity_t second) const; - - /** Removes the pair singleton component. - * - * @param first The first element of the pair - * @param second The second element of the pair - */ - void remove(flecs::entity_t first, flecs::entity_t second) const; - - /** Iterate entities in root of world - * Accepts a callback with the following signature: - * - * @code - * void(*)(flecs::entity e); - * @endcode - */ - template - void children(Func&& f) const; - - /** Get singleton entity for type. - */ - template - flecs::entity singleton() const; - - /** Get target for a given pair from a singleton entity. - * This operation returns the target for a given pair. The optional - * index can be used to iterate through targets, in case the entity has - * multiple instances for the same relationship. - * - * @tparam First The first element of the pair. - * @param index The index (0 for the first instance of the relationship). - */ - template - flecs::entity target(int32_t index = 0) const; - - /** Get target for a given pair from a singleton entity. - * This operation returns the target for a given pair. The optional - * index can be used to iterate through targets, in case the entity has - * multiple instances for the same relationship. - * - * @param first The first element of the pair for which to retrieve the target. - * @param index The index (0 for the first instance of the relationship). - */ - template - flecs::entity target(flecs::entity_t first, int32_t index = 0) const; - - /** Get target for a given pair from a singleton entity. - * This operation returns the target for a given pair. The optional - * index can be used to iterate through targets, in case the entity has - * multiple instances for the same relationship. - * - * @param first The first element of the pair for which to retrieve the target. - * @param index The index (0 for the first instance of the relationship). - */ - flecs::entity target(flecs::entity_t first, int32_t index = 0) const; - - /** Create alias for component. - * - * @tparam T to create an alias for. - * @param alias Alias for the component. - * @return Entity representing the component. - */ - template - flecs::entity use(const char *alias = nullptr) const; - - /** Create alias for entity. - * - * @param name Name of the entity. - * @param alias Alias for the entity. - */ - flecs::entity use(const char *name, const char *alias = nullptr) const; - - /** Create alias for entity. - * - * @param entity Entity for which to create the alias. - * @param alias Alias for the entity. - */ - void use(flecs::entity entity, const char *alias = nullptr) const; - - /** Count entities matching a component. - * - * @param component_id The component id. - */ - int count(flecs::id_t component_id) const { - return ecs_count_id(world_, component_id); - } - - /** Count entities matching a pair. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - int count(flecs::entity_t first, flecs::entity_t second) const { - return ecs_count_id(world_, ecs_pair(first, second)); - } - - /** Count entities matching a component. - * - * @tparam T The component type. - */ - template - int count() const { - return count(_::type::id(world_)); - } - - /** Count entities matching a pair. - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - */ - template - int count(flecs::entity_t second) const { - return count(_::type::id(world_), second); - } - - /** Count entities matching a pair. - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair. - */ - template - int count() const { - return count( - _::type::id(world_), - _::type::id(world_)); - } - - /** All entities created in function are created with id. - */ - template - void with(id_t with_id, const Func& func) const { - ecs_id_t prev = ecs_set_with(world_, with_id); - func(); - ecs_set_with(world_, prev); - } - - /** All entities created in function are created with type. - */ - template - void with(const Func& func) const { - with(this->id(), func); - } - - /** All entities created in function are created with pair. - */ - template - void with(const Func& func) const { - with(ecs_pair(this->id(), this->id()), func); - } - - /** All entities created in function are created with pair. - */ - template - void with(id_t second, const Func& func) const { - with(ecs_pair(this->id(), second), func); - } - - /** All entities created in function are created with pair. - */ - template - void with(id_t first, id_t second, const Func& func) const { - with(ecs_pair(first, second), func); - } - - /** All entities created in function are created in scope. All operations - * called in function (such as lookup) are relative to scope. - */ - template - void scope(id_t parent, const Func& func) const { - ecs_entity_t prev = ecs_set_scope(world_, parent); - func(); - ecs_set_scope(world_, prev); - } - - /** Same as scope(parent, func), but with T as parent. - */ - template - void scope(const Func& func) const { - flecs::id_t parent = _::type::id(world_); - scope(parent, func); - } - - /** Use provided scope for operations ran on returned world. - * Operations need to be ran in a single statement. - */ - flecs::scoped_world scope(id_t parent) const; - - template - flecs::scoped_world scope() const; - - flecs::scoped_world scope(const char* name) const; - - /** Delete all entities with specified id. */ - void delete_with(id_t the_id) const { - ecs_delete_with(world_, the_id); - } - - /** Delete all entities with specified pair. */ - void delete_with(entity_t first, entity_t second) const { - delete_with(ecs_pair(first, second)); - } - - /** Delete all entities with specified component. */ - template - void delete_with() const { - delete_with(_::type::id(world_)); - } - - /** Delete all entities with specified pair. */ - template - void delete_with() const { - delete_with(_::type::id(world_), _::type::id(world_)); - } - - /** Delete all entities with specified pair. */ - template - void delete_with(entity_t second) const { - delete_with(_::type::id(world_), second); - } - - /** Remove all instances of specified id. */ - void remove_all(id_t the_id) const { - ecs_remove_all(world_, the_id); - } - - /** Remove all instances of specified pair. */ - void remove_all(entity_t first, entity_t second) const { - remove_all(ecs_pair(first, second)); - } - - /** Remove all instances of specified component. */ - template - void remove_all() const { - remove_all(_::type::id(world_)); - } - - /** Remove all instances of specified pair. */ - template - void remove_all() const { - remove_all(_::type::id(world_), _::type::id(world_)); - } - - /** Remove all instances of specified pair. */ - template - void remove_all(entity_t second) const { - remove_all(_::type::id(world_), second); - } - - /** Defer all operations called in function. - * - * @see flecs::world::defer_begin() - * @see flecs::world::defer_end() - * @see flecs::world::defer_is_deferred() - * @see flecs::world::defer_resume() - * @see flecs::world::defer_suspend() - */ - template - void defer(const Func& func) const { - ecs_defer_begin(world_); - func(); - ecs_defer_end(world_); - } - - /** Suspend deferring operations. - * - * @see ecs_defer_suspend() - * @see flecs::world::defer() - * @see flecs::world::defer_begin() - * @see flecs::world::defer_end() - * @see flecs::world::defer_is_deferred() - * @see flecs::world::defer_resume() - */ - void defer_suspend() const { - ecs_defer_suspend(world_); - } - - /** Resume deferring operations. - * - * @see ecs_defer_resume() - * @see flecs::world::defer() - * @see flecs::world::defer_begin() - * @see flecs::world::defer_end() - * @see flecs::world::defer_is_deferred() - * @see flecs::world::defer_suspend() - */ - void defer_resume() const { - ecs_defer_resume(world_); - } - - /** Check if entity id exists in the world. - * - * @see ecs_exists() - * @see flecs::world::is_alive() - * @see flecs::world::is_valid() - */ - bool exists(flecs::entity_t e) const { - return ecs_exists(world_, e); - } - - /** Check if entity id exists in the world. - * - * @see ecs_is_alive() - * @see flecs::world::exists() - * @see flecs::world::is_valid() - */ - bool is_alive(flecs::entity_t e) const { - return ecs_is_alive(world_, e); - } - - /** Check if entity id is valid. - * Invalid entities cannot be used with API functions. - * - * @see ecs_is_valid() - * @see flecs::world::exists() - * @see flecs::world::is_alive() - */ - bool is_valid(flecs::entity_t e) const { - return ecs_is_valid(world_, e); - } - - /** Get alive entity for id. - * Returns the entity with the current generation. - * - * @see ecs_get_alive() - */ - flecs::entity get_alive(flecs::entity_t e) const; - - /** - * @see ecs_make_alive() - */ - flecs::entity make_alive(flecs::entity_t e) const; - - /** Set version of entity to provided. - * - * @see ecs_set_version() - */ - void set_version(flecs::entity_t e) const { - ecs_set_version(world_, e); - } - - /* Run callback after completing frame */ - void run_post_frame(ecs_fini_action_t action, void *ctx) const { - ecs_run_post_frame(world_, action, ctx); - } - - /** Get the world info. - * - * @see ecs_get_world_info() - */ - const flecs::world_info_t* get_info() const{ - return ecs_get_world_info(world_); - } - - /** Get delta_time */ - ecs_ftime_t delta_time() const { - return get_info()->delta_time; - } - -/** - * @file addons/cpp/mixins/id/mixin.inl - * @brief Id world mixin. - */ - -/** Get id from a type. - * - * @memberof flecs::world - */ -template -flecs::id id() const; - -/** Id factory. - * - * @memberof flecs::world - */ -template -flecs::id id(Args&&... args) const; - -/** Get pair id from relationship, object. - * - * @memberof flecs::world - */ -template -flecs::id pair() const; - -/** Get pair id from relationship, object. - * - * @memberof flecs::world - */ -template -flecs::id pair(entity_t o) const; - -/** Get pair id from relationship, object. - * - * @memberof flecs::world - */ -flecs::id pair(entity_t r, entity_t o) const; - -/** - * @file addons/cpp/mixins/component/mixin.inl - * @brief Component mixin. - */ - -/** Find or register component. - * - * @ingroup cpp_components - * @memberof flecs::world - */ -template -flecs::component component(Args &&... args) const; - -/** Find or register untyped component. - * Method available on flecs::world class. - * - * @ingroup cpp_components - * @memberof flecs::world - */ -template -flecs::untyped_component component(Args &&... args) const; - -/** - * @file addons/cpp/mixins/entity/mixin.inl - * @brief Entity world mixin. - */ - -/** Create an entity. - * - * @memberof flecs::world - * @ingroup cpp_entities - */ -template -flecs::entity entity(Args &&... args) const; - -/** Convert enum constant to entity. - * - * @memberof flecs::world - * @ingroup cpp_entities - */ -template ::value > = 0> -flecs::id id(E value) const; - -/** Convert enum constant to entity. - * - * @memberof flecs::world - * @ingroup cpp_entities - */ -template ::value > = 0> -flecs::entity entity(E value) const; - -/** Create a prefab. - * - * @memberof flecs::world - * @ingroup cpp_entities - */ -template -flecs::entity prefab(Args &&... args) const; - -/** Create an entity that's associated with a type. - * - * @memberof flecs::world - * @ingroup cpp_entities - */ -template -flecs::entity entity(const char *name = nullptr) const; - -/** Create a prefab that's associated with a type. - * - * @memberof flecs::world - * @ingroup cpp_entities - */ -template -flecs::entity prefab(const char *name = nullptr) const; - -/** - * @file addons/cpp/mixins/event/mixin.inl - * @brief Event world mixin. - */ - -/** - * @defgroup cpp_addons_event Events - * @ingroup cpp_addons - * API for emitting events. - * - * @{ - */ - -/** Create a new event. - * - * @memberof flecs::world - * - * @param evt The event id. - * @return Event builder. - */ -flecs::event_builder event(flecs::entity_t evt) const; - -/** Create a new event. - * - * @memberof flecs::world - * - * @tparam E The event type. - * @return Event builder. - */ -template -flecs::event_builder_typed event() const; - -/** @} */ - -/** - * @file addons/cpp/mixins/term/mixin.inl - * @brief Term world mixin. - */ - -/** - * @memberof flecs::world - * @ingroup cpp_core_queries - * - * @{ - */ - -/** Create a term. - * - */ -template -flecs::term term(Args &&... args) const; - -/** Create a term for a (component) type. - */ -template -flecs::term term() const; - -/** Create a term for a pair. - */ -template -flecs::term term() const; - -/** @} */ - -/** - * @file addons/cpp/mixins/observer/mixin.inl - * @brief Observer world mixin. - */ - -/** Observer builder. - * - * @memberof flecs::world - * @ingroup cpp_observers - * - * @{ - */ - -/** Upcast entity to an observer. - * The provided entity must be an observer. - * - * @param e The entity. - * @return An observer object. - */ -flecs::observer observer(flecs::entity e) const; - -/** Create a new observer. - * - * @tparam Components The components to match on. - * @tparam Args Arguments passed to the constructor of flecs::observer_builder. - * @return Observer builder. - */ -template -flecs::observer_builder observer(Args &&... args) const; - -/** @} */ - -/** - * @file addons/cpp/mixins/query/mixin.inl - * @brief Query world mixin. - */ - -/** - * @memberof flecs::world - * @ingroup cpp_core_queries - * - * @{ - */ - -/** Create a query. - * - * @see ecs_query_init - */ -template -flecs::query query(Args &&... args) const; - -/** Create a query from entity. - * - * @see ecs_query_init - */ -flecs::query<> query(flecs::entity query_entity) const; - -/** Create a query builder. - * - * @see ecs_query_init - */ -template -flecs::query_builder query_builder(Args &&... args) const; - -/** Iterate over all entities with components in argument list of function. - * The function parameter must match the following signature: - * - * @code - * void(*)(T&, U&, ...) - * @endcode - * - * or: - * - * @code - * void(*)(flecs::entity, T&, U&, ...) - * @endcode - * - */ -template -void each(Func&& func) const; - -/** Iterate over all entities with provided component. - * The function parameter must match the following signature: - * - * @code - * void(*)(T&) - * @endcode - * - * or: - * - * @code - * void(*)(flecs::entity, T&) - * @endcode - * - */ -template -void each(Func&& func) const; - -/** Iterate over all entities with provided (component) id. */ -template -void each(flecs::id_t term_id, Func&& func) const; - -/** @} */ - -/** - * @file addons/cpp/mixins/enum/mixin.inl - * @brief Enum world mixin. - */ - -/** Convert enum constant to entity. - * - * @memberof flecs::world - * @ingroup cpp_entities - */ -template ::value > = 0> -flecs::entity to_entity(E constant) const; - - -# ifdef FLECS_MODULE -/** - * @file addons/cpp/mixins/module/mixin.inl - * @brief Module world mixin. - */ - -/** - * @memberof flecs::world - * @ingroup cpp_addons_modules - * - * @{ - */ - -/** Define a module. - * This operation is not mandatory, but can be called inside the module ctor to - * obtain the entity associated with the module, or override the module name. - * - * @tparam Module module class. - * @return Module entity. - */ -template -flecs::entity module(const char *name = nullptr) const; - -/** Import a module. - * - * @tparam Module module class. - * @return Module entity. - */ -template -flecs::entity import(); - -/** @} */ - -# endif -# ifdef FLECS_PIPELINE -/** - * @file addons/cpp/mixins/pipeline/mixin.inl - * @brief Pipeline world mixin. - */ - -/** - * @memberof flecs::world - * @ingroup cpp_pipelines - * - * @{ - */ - -/** Create a new pipeline. - * - * @return A pipeline builder. - */ -flecs::pipeline_builder<> pipeline() const; - -/** Create a new pipeline. - * - * @tparam Pipeline Type associated with pipeline. - * @return A pipeline builder. - */ -template ::value > = 0> -flecs::pipeline_builder<> pipeline() const; - -/** Set pipeline. - * @see ecs_set_pipeline - */ -void set_pipeline(const flecs::entity pip) const; - -/** Set pipeline. - * @see ecs_set_pipeline - */ -template -void set_pipeline() const; - -/** Get pipeline. - * @see ecs_get_pipeline - */ -flecs::entity get_pipeline() const; - -/** Progress world one tick. - * @see ecs_progress - */ -bool progress(ecs_ftime_t delta_time = 0.0) const; - -/** Run pipeline. - * @see ecs_run_pipeline - */ -void run_pipeline(const flecs::entity_t pip, ecs_ftime_t delta_time = 0.0) const; - -/** Run pipeline. - * @tparam Pipeline Type associated with pipeline. - * @see ecs_run_pipeline - */ -template ::value > = 0> -void run_pipeline(ecs_ftime_t delta_time = 0.0) const; - -/** Set timescale. - * @see ecs_set_time_scale - */ -void set_time_scale(ecs_ftime_t mul) const; - -/** Set target FPS. - * @see ecs_set_target_fps - */ -void set_target_fps(ecs_ftime_t target_fps) const; - -/** Reset simulation clock. - * @see ecs_reset_clock - */ -void reset_clock() const; - -/** Set number of threads. - * @see ecs_set_threads - */ -void set_threads(int32_t threads) const; - -/** Set number of threads. - * @see ecs_get_stage_count - */ -int32_t get_threads() const; - -/** Set number of task threads. - * @see ecs_set_task_threads - */ -void set_task_threads(int32_t task_threads) const; - -/** Returns true if task thread use has been requested. - * @see ecs_using_task_threads - */ -bool using_task_threads() const; - -/** @} */ - -# endif -# ifdef FLECS_SYSTEM -/** - * @file addons/cpp/mixins/system/mixin.inl - * @brief System module world mixin. - */ - -/** - * @memberof flecs::world - * @ingroup cpp_addons_systems - * - * @{ -*/ - -/** Upcast entity to a system. - * The provided entity must be a system. - * - * @param e The entity. - * @return A system object. - */ -flecs::system system(flecs::entity e) const; - -/** Create a new system. - * - * @tparam Components The components to match on. - * @tparam Args Arguments passed to the constructor of flecs::system_builder. - * @return System builder. - */ -template -flecs::system_builder system(Args &&... args) const; - -/** @} */ - -# endif -# ifdef FLECS_TIMER -/** - * @file addons/cpp/mixins/timer/mixin.inl - * @brief Timer module mixin. - */ - -/** - * @memberof flecs::world - * @ingroup cpp_addons_timer - */ - -/** Find or register a singleton timer. */ -template -flecs::timer timer() const; - -/** Find or register a timer. */ -template -flecs::timer timer(Args &&... args) const; - -/** Enable randomization of initial time values for timers. - * @see ecs_randomize_timers - */ -void randomize_timers() const; - -# endif -# ifdef FLECS_SCRIPT -/** - * @file addons/cpp/mixins/script/mixin.inl - * @brief Script world mixin. - */ - -/** - * @defgroup cpp_addons_script Script - * @ingroup cpp_addons - * Data definition format for loading entity data. - * - * @{ - */ - -/** Run script. - * @see ecs_script_run - */ -int script_run(const char *name, const char *str) const { - return ecs_script_run(world_, name, str); -} - -/** Run script from file. - * @see ecs_script_run_file - */ -int script_run_file(const char *filename) const { - return ecs_script_run_file(world_, filename); -} - -/** Build script. - * @see ecs_script_init - */ -script_builder script(const char *name = nullptr) const { - return script_builder(world_, name); -} - -/** Convert value to string */ -flecs::string to_expr(flecs::entity_t tid, const void* value) { - char *expr = ecs_ptr_to_expr(world_, tid, value); - return flecs::string(expr); -} - -/** Convert value to string */ -template -flecs::string to_expr(const T* value) { - flecs::entity_t tid = _::type::id(world_); - return to_expr(tid, value); -} - - -/** @} */ - -# endif -# ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/world.inl - * @brief Meta world mixin. - */ - -/** - * @memberof flecs::world - * @ingroup cpp_addons_meta - * - * @{ - */ - -/** Return meta cursor to value */ -flecs::cursor cursor(flecs::entity_t tid, void *ptr) { - return flecs::cursor(world_, tid, ptr); -} - -/** Return meta cursor to value */ -template -flecs::cursor cursor(void *ptr) { - flecs::entity_t tid = _::type::id(world_); - return cursor(tid, ptr); -} - -/** Create primitive type */ -flecs::entity primitive(flecs::meta::primitive_kind_t kind); - -/** Create array type. */ -flecs::entity array(flecs::entity_t elem_id, int32_t array_count); - -/** Create array type. */ -template -flecs::entity array(int32_t array_count); - -/** Create vector type. */ -flecs::entity vector(flecs::entity_t elem_id); - -/** Create vector type. */ -template -flecs::entity vector(); - -/** @} */ - -# endif -# ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/world.inl - * @brief JSON world mixin. - */ - -/** Serialize untyped value to JSON. - * - * @memberof flecs::world - * @ingroup cpp_addons_json - */ -flecs::string to_json(flecs::entity_t tid, const void* value) { - char *json = ecs_ptr_to_json(world_, tid, value); - return flecs::string(json); -} - -/** Serialize value to JSON. - * - * @memberof flecs::world - * @ingroup cpp_addons_json - */ -template -flecs::string to_json(const T* value) { - flecs::entity_t tid = _::type::id(world_); - return to_json(tid, value); -} - -/** Serialize world to JSON. - * - * @memberof flecs::world - * @ingroup cpp_addons_json - */ -flecs::string to_json() { - return flecs::string( ecs_world_to_json(world_, nullptr) ); -} - -/** Deserialize value from JSON. - * - * @memberof flecs::world - * @ingroup cpp_addons_json - */ -const char* from_json(flecs::entity_t tid, void* value, const char *json, flecs::from_json_desc_t *desc = nullptr) { - return ecs_ptr_from_json(world_, tid, value, json, desc); -} - -/** Deserialize value from JSON. - * - * @memberof flecs::world - * @ingroup cpp_addons_json - */ -template -const char* from_json(T* value, const char *json, flecs::from_json_desc_t *desc = nullptr) { - return ecs_ptr_from_json(world_, _::type::id(world_), - value, json, desc); -} - -/** Deserialize JSON into world. - * - * @memberof flecs::world - * @ingroup cpp_addons_json - */ -const char* from_json(const char *json, flecs::from_json_desc_t *desc = nullptr) { - return ecs_world_from_json(world_, json, desc); -} - -/** Deserialize JSON file into world. - * - * @memberof flecs::world - * @ingroup cpp_addons_json - */ -const char* from_json_file(const char *json, flecs::from_json_desc_t *desc = nullptr) { - return ecs_world_from_json_file(world_, json, desc); -} - -# endif -# ifdef FLECS_APP -/** - * @file addons/cpp/mixins/app/mixin.inl - * @brief App world addon mixin. - */ - -/** - * @ingroup cpp_addons_app - * @memberof flecs::world - * - * @{ - */ - -/** Return app builder. - * The app builder is a convenience wrapper around a loop that runs - * world::progress. An app allows for writing platform agnostic code, - * as it provides hooks to modules for overtaking the main loop which is - * required for frameworks like emscripten. - */ -flecs::app_builder app() { - flecs::world_t *w = world_; - world_ = nullptr; // Take ownership - return flecs::app_builder(w); -} - -/** @} */ - -# endif -# ifdef FLECS_METRICS - -/** Create metric. - * - * @ingroup cpp_addons_metrics - * @memberof flecs::world - */ -template -flecs::metric_builder metric(Args &&... args) const; - -# endif -# ifdef FLECS_ALERTS - -/** Create alert. - * - * @ingroup cpp_addons_alerts - * @memberof flecs::world - */ -template -flecs::alert_builder alert(Args &&... args) const; - -# endif - -public: - void init_builtin_components(); - - world_t *world_; -}; - -/** Scoped world. - * Utility class used by the world::scope method to create entities in a scope. - */ -struct scoped_world : world { - scoped_world( - flecs::world_t *w, - flecs::entity_t s) : world(w) - { - prev_scope_ = ecs_set_scope(w, s); - } - - ~scoped_world() { - ecs_set_scope(world_, prev_scope_); - } - - scoped_world(const scoped_world& obj) : world(nullptr) { - prev_scope_ = obj.prev_scope_; - world_ = obj.world_; - flecs_poly_claim(world_); - } - - flecs::entity_t prev_scope_; -}; - -/** @} */ - -} // namespace flecs - - -/** - * @file addons/cpp/field.hpp - * @brief Wrapper classes for fields returned by flecs::iter. - */ - -#pragma once - -/** - * @defgroup cpp_field Fields - * @ingroup cpp_core - * Field helper types. - * - * @{ - */ - -namespace flecs -{ - -/** Unsafe wrapper class around a field. - * This class can be used when a system does not know the type of a field at - * compile time. - * - * @ingroup cpp_iterator - */ -struct untyped_field { - untyped_field(void* array, size_t size, size_t count, bool is_shared = false) - : data_(array) - , size_(size) - , count_(count) - , is_shared_(is_shared) {} - - /** Return element in component array. - * This operator may only be used if the field is not shared. - * - * @param index Index of element. - * @return Reference to element. - */ - void* operator[](size_t index) const { - ecs_assert(!is_shared_ || !index, ECS_INVALID_PARAMETER, - "invalid usage of [] operator for shared component field"); - ecs_assert(index < count_, ECS_COLUMN_INDEX_OUT_OF_RANGE, - "index %d out of range for field", index); - return ECS_OFFSET(data_, size_ * index); - } - -protected: - void* data_; - size_t size_; - size_t count_; - bool is_shared_; -}; - -/** Wrapper class around a field. - * - * @tparam T component type of the field. - * - * @ingroup cpp_iterator - */ -template -struct field { - static_assert(std::is_empty::value == false, - "invalid type for field, cannot iterate empty type"); - - /** Create field from component array. - * - * @param array Pointer to the component array. - * @param count Number of elements in component array. - * @param is_shared Is the component shared or not. - */ - field(T* array, size_t count, bool is_shared = false) - : data_(array) - , count_(count) - , is_shared_(is_shared) {} - - /** Create field from iterator. - * - * @param iter Iterator object. - * @param field Index of the signature of the query being iterated over. - */ - field(iter &iter, int field); - - /** Return element in component array. - * This operator may only be used if the field is not shared. - * - * @param index Index of element. - * @return Reference to element. - */ - T& operator[](size_t index) const; - - /** Return first element of component array. - * This operator is typically used when the field is shared. - * - * @return Reference to the first element. - */ - T& operator*() const; - - /** Return first element of component array. - * This operator is typically used when the field is shared. - * - * @return Pointer to the first element. - */ - T* operator->() const; - -protected: - T* data_; - size_t count_; - bool is_shared_; -}; - -} // namespace flecs - -/** @} */ - -/** - * @file addons/cpp/iter.hpp - * @brief Wrapper classes for ecs_iter_t and component arrays. - */ - -#pragma once - -/** - * @defgroup cpp_iterator Iterators - * @ingroup cpp_core - * Iterator operations. - * - * @{ - */ - -namespace flecs -{ - -//////////////////////////////////////////////////////////////////////////////// - -namespace _ { - -//////////////////////////////////////////////////////////////////////////////// - -/** Iterate over an integer range (used to iterate over entity range). - * - * @tparam T of the iterator - */ -template -struct range_iterator -{ - explicit range_iterator(T value) - : value_(value){} - - bool operator!=(range_iterator const& other) const - { - return value_ != other.value_; - } - - T const& operator*() const - { - return value_; - } - - range_iterator& operator++() - { - ++value_; - return *this; - } - -private: - T value_; -}; - -} // namespace _ - -} // namespace flecs - -namespace flecs -{ - -//////////////////////////////////////////////////////////////////////////////// - -/** Class for iterating over query results. - * - * @ingroup cpp_iterator - */ -struct iter { -private: - using row_iterator = _::range_iterator; - -public: - /** Construct iterator from C iterator object. - * This operation is typically not invoked directly by the user. - * - * @param it Pointer to C iterator. - */ - iter(ecs_iter_t *it) : iter_(it) { } - - row_iterator begin() const { - return row_iterator(0); - } - - row_iterator end() const { - return row_iterator(static_cast(iter_->count)); - } - - flecs::entity system() const; - - flecs::entity event() const; - - flecs::id event_id() const; - - flecs::world world() const; - - const flecs::iter_t* c_ptr() const { - return iter_; - } - - size_t count() const { - ecs_check(iter_->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, - "operation invalid before calling next()"); - return static_cast(iter_->count); - error: - return 0; - } - - ecs_ftime_t delta_time() const { - return iter_->delta_time; - } - - ecs_ftime_t delta_system_time() const { - return iter_->delta_system_time; - } - - flecs::type type() const; - - flecs::table table() const; - - flecs::table other_table() const; - - flecs::table_range range() const; - - /** Access ctx. - * ctx contains the context pointer assigned to a system. - */ - void* ctx() { - return iter_->ctx; - } - - /** Access ctx. - * ctx contains the context pointer assigned to a system. - */ - template - T* ctx() { - return static_cast(iter_->ctx); - } - - /** Access param. - * param contains the pointer passed to the param argument of system::run - */ - void* param() { - return iter_->param; - } - - /** Access param. - * param contains the pointer passed to the param argument of system::run - */ - template - T* param() { - /* TODO: type check */ - return static_cast(iter_->param); - } - - /** Obtain mutable handle to entity being iterated over. - * - * @param row Row being iterated over. - */ - flecs::entity entity(size_t row) const; - - /** Returns whether field is matched on self. - * - * @param index The field index. - */ - bool is_self(int8_t index) const { - return ecs_field_is_self(iter_, index); - } - - /** Returns whether field is set. - * - * @param index The field index. - */ - bool is_set(int8_t index) const { - return ecs_field_is_set(iter_, index); - } - - /** Returns whether field is readonly. - * - * @param index The field index. - */ - bool is_readonly(int8_t index) const { - return ecs_field_is_readonly(iter_, index); - } - - /** Number of fields in iterator. - */ - int32_t field_count() const { - return iter_->field_count; - } - - /** Size of field data type. - * - * @param index The field id. - */ - size_t size(int8_t index) const { - return ecs_field_size(iter_, index); - } - - /** Obtain field source (0 if This). - * - * @param index The field index. - */ - flecs::entity src(int8_t index) const; - - /** Obtain id matched for field. - * - * @param index The field index. - */ - flecs::id id(int8_t index) const; - - /** Obtain pair id matched for field. - * This operation will fail if the id is not a pair. - * - * @param index The field index. - */ - flecs::id pair(int8_t index) const; - - /** Obtain column index for field. - * - * @param index The field index. - */ - int32_t column_index(int8_t index) const { - return ecs_field_column(iter_, index); - } - - /** Obtain term that triggered an observer - */ - int8_t term_index() const { - return iter_->term_index; - } - - /** Convert current iterator result to string. - */ - flecs::string str() const { - char *s = ecs_iter_str(iter_); - return flecs::string(s); - } - - /** Get readonly access to field data. - * If the specified field index does not match with the provided type, the - * function will assert. - * - * This function should not be used in each() callbacks, unless it is to - * access a shared field. For access to non-shared fields in each(), use - * field_at. - * - * @tparam T Type of the field. - * @param index The field index. - * @return The field data. - */ - template , - typename std::enable_if::value, void>::type* = nullptr> - flecs::field field(int8_t index) const; - - /** Get read/write access to field data. - * If the matched id for the specified field does not match with the provided - * type or if the field is readonly, the function will assert. - * - * This function should not be used in each() callbacks, unless it is to - * access a shared field. For access to non-shared fields in each(), use - * field_at. - * - * @tparam T Type of the field. - * @param index The field index. - * @return The field data. - */ - template , - typename std::enable_if< - std::is_const::value == false, void>::type* = nullptr> - flecs::field field(int8_t index) const; - - /** Get unchecked access to field data. - * Unchecked access is required when a system does not know the type of a - * field at compile time. - * - * This function should not be used in each() callbacks, unless it is to - * access a shared field. For access to non-shared fields in each(), use - * field_at. - * - * @param index The field index. - */ - flecs::untyped_field field(int8_t index) const { - ecs_assert(!(iter_->flags & EcsIterCppEach) || - ecs_field_src(iter_, index) != 0, ECS_INVALID_OPERATION, - "cannot .field from .each, use .field_at(%d, row) instead", index); - return get_unchecked_field(index); - } - - /** Get pointer to field at row. - * This function may be used to access shared fields when row is set to 0. - */ - void* field_at(int8_t index, size_t row) const { - if (iter_->row_fields & (1llu << index)) { - return get_unchecked_field_at(index, row)[0]; - } else { - return get_unchecked_field(index)[row]; - } - } - - /** Get reference to field at row. - * This function may be used to access shared fields when row is set to 0. - */ - template , - typename std::enable_if::value, void>::type* = nullptr> - const A& field_at(int8_t index, size_t row) const { - if (iter_->row_fields & (1llu << index)) { - return get_field_at(index, row)[0]; - } else { - return get_field(index)[row]; - } - } - - /** Get reference to field at row. - * This function may be used to access shared fields when row is set to 0. - */ - template , - typename std::enable_if< - std::is_const::value == false, void>::type* = nullptr> - A& field_at(int8_t index, size_t row) const { - ecs_assert(!ecs_field_is_readonly(iter_, index), - ECS_ACCESS_VIOLATION, NULL); - if (iter_->row_fields & (1llu << index)) { - return get_field_at(index, row)[0]; - } else { - return get_field(index)[row]; - } - } - - /** Get readonly access to entity ids. - * - * @return The entity ids. - */ - flecs::field entities() const { - return flecs::field( - iter_->entities, static_cast(iter_->count), false); - } - - /** Check if the current table has changed since the last iteration. - * Can only be used when iterating queries and/or systems. */ - bool changed() { - return ecs_iter_changed(iter_); - } - - /** Skip current table. - * This indicates to the query that the data in the current table is not - * modified. By default, iterating a table with a query will mark the - * iterated components as dirty if they are annotated with InOut or Out. - * - * When this operation is invoked, the components of the current table will - * not be marked dirty. */ - void skip() { - ecs_iter_skip(iter_); - } - - /* Return group id for current table (grouped queries only) */ - uint64_t group_id() const { - return iter_->group_id; - } - - /** Get value of variable by id. - * Get value of a query variable for current result. - */ - flecs::entity get_var(int var_id) const; - - /** Get value of variable by name. - * Get value of a query variable for current result. - */ - flecs::entity get_var(const char *name) const; - - /** Progress iterator. - * This operation should only be called from a context where the iterator is - * not being progressed automatically. An example of a valid context is - * inside of a run() callback. An example of an invalid context is inside of - * an each() callback. - */ - bool next() { - if (iter_->flags & EcsIterIsValid && iter_->table) { - ECS_TABLE_UNLOCK(iter_->world, iter_->table); - } - bool result = iter_->next(iter_); - iter_->flags |= EcsIterIsValid; - if (result && iter_->table) { - ECS_TABLE_LOCK(iter_->world, iter_->table); - } - return result; - } - - /** Forward to each. - * If a system has an each callback registered, this operation will forward - * the current iterator to the each callback. - */ - void each() { - iter_->callback(iter_); - } - - /** Iterate targets for pair field. - * - * @param index The field index. - * @param func Callback invoked for each target - */ - template - void targets(int8_t index, const Func& func); - - /** Free iterator resources. - * This operation only needs to be called when the iterator is not iterated - * until completion (e.g. the last call to next() did not return false). - * - * Failing to call this operation on an unfinished iterator will throw a - * fatal LEAK_DETECTED error. - * - * @see ecs_iter_fini() - */ - void fini() { - if (iter_->flags & EcsIterIsValid && iter_->table) { - ECS_TABLE_UNLOCK(iter_->world, iter_->table); - } - ecs_iter_fini(iter_); - } - -private: - /* Get field, check if correct type is used */ - template > - flecs::field get_field(int8_t index) const { - -#ifndef FLECS_NDEBUG - ecs_entity_t term_id = ecs_field_id(iter_, index); - ecs_assert(ECS_HAS_ID_FLAG(term_id, PAIR) || - term_id == _::type::id(iter_->world), - ECS_COLUMN_TYPE_MISMATCH, NULL); -#endif - - size_t count; - bool is_shared = !ecs_field_is_self(iter_, index); - - /* If a shared column is retrieved with 'column', there will only be a - * single value. Ensure that the application does not accidentally read - * out of bounds. */ - if (is_shared) { - count = 1; - } else { - /* If column is owned, there will be as many values as there are - * entities. */ - count = static_cast(iter_->count); - } - - return flecs::field( - static_cast(ecs_field_w_size(iter_, sizeof(A), index)), - count, is_shared); - } - - /* Get field, check if correct type is used */ - template > - flecs::field get_field_at(int8_t index, int32_t row) const { - -#ifndef FLECS_NDEBUG - ecs_entity_t term_id = ecs_field_id(iter_, index); - ecs_assert(ECS_HAS_ID_FLAG(term_id, PAIR) || - term_id == _::type::id(iter_->world), - ECS_COLUMN_TYPE_MISMATCH, NULL); -#endif - - return flecs::field( - static_cast(ecs_field_at_w_size(iter_, sizeof(A), index, row)), - 1, false); - } - - flecs::untyped_field get_unchecked_field(int8_t index) const { - size_t count; - size_t size = ecs_field_size(iter_, index); - bool is_shared = !ecs_field_is_self(iter_, index); - - /* If a shared column is retrieved with 'column', there will only be a - * single value. Ensure that the application does not accidentally read - * out of bounds. */ - if (is_shared) { - count = 1; - } else { - /* If column is owned, there will be as many values as there are - * entities. */ - count = static_cast(iter_->count); - } - - return flecs::untyped_field( - ecs_field_w_size(iter_, 0, index), size, count, is_shared); - } - - flecs::untyped_field get_unchecked_field_at(int8_t index, size_t row) const { - size_t size = ecs_field_size(iter_, index); - return flecs::untyped_field( - ecs_field_at_w_size(iter_, 0, index, static_cast(row)), - size, 1, false); - } - - flecs::iter_t *iter_; -}; - -} // namespace flecs - -/** @} */ - -/** - * @file addons/cpp/entity.hpp - * @brief Entity class. - * - * This class provides read/write access to entities. - */ - -#pragma once - -/** - * @file addons/cpp/entity_view.hpp - * @brief Entity class with only readonly operations. - * - * This class provides readonly access to entities. Using this class to store - * entities in components ensures valid handles, as this class will always store - * the actual world vs. a stage. The constructors of this class will never - * create a new entity. - * - * To obtain a mutable handle to the entity, use the "mut" function. - */ - -#pragma once - -/** - * @ingroup cpp_entities - * @{ - */ - -namespace flecs -{ - -/** Entity view. - * Class with read operations for entities. Base for flecs::entity. - * - * @ingroup cpp_entities - */ -struct entity_view : public id { - - entity_view() : flecs::id() { } - - /** Wrap an existing entity id. - * - * @param world The world in which the entity is created. - * @param id The entity id. - */ - explicit entity_view(flecs::world_t *world, flecs::id_t id) - : flecs::id(world - ? const_cast(ecs_get_world(world)) - : nullptr - , id ) { } - - /** Implicit conversion from flecs::entity_t to flecs::entity_view. */ - entity_view(entity_t id) - : flecs::id( nullptr, id ) { } - - /** Get entity id. - * @return The integer entity id. - */ - entity_t id() const { - return id_; - } - - /** Check if entity is valid. - * - * @return True if the entity is alive, false otherwise. - */ - bool is_valid() const { - return world_ && ecs_is_valid(world_, id_); - } - - explicit operator bool() const { - return is_valid(); - } - - /** Check if entity is alive. - * - * @return True if the entity is alive, false otherwise. - */ - bool is_alive() const { - return world_ && ecs_is_alive(world_, id_); - } - - /** Return the entity name. - * - * @return The entity name. - */ - flecs::string_view name() const { - return flecs::string_view(ecs_get_name(world_, id_)); - } - - /** Return the entity symbol. - * - * @return The entity symbol. - */ - flecs::string_view symbol() const { - return flecs::string_view(ecs_get_symbol(world_, id_)); - } - - /** Return the entity path. - * - * @return The hierarchical entity path. - */ - flecs::string path(const char *sep = "::", const char *init_sep = "::") const { - return path_from(0, sep, init_sep); - } - - /** Return the entity path relative to a parent. - * - * @return The relative hierarchical entity path. - */ - flecs::string path_from(flecs::entity_t parent, const char *sep = "::", const char *init_sep = "::") const { - char *path = ecs_get_path_w_sep(world_, parent, id_, sep, init_sep); - return flecs::string(path); - } - - /** Return the entity path relative to a parent. - * - * @return The relative hierarchical entity path. - */ - template - flecs::string path_from(const char *sep = "::", const char *init_sep = "::") const { - return path_from(_::type::id(world_), sep, init_sep); - } - - bool enabled() const { - return !ecs_has_id(world_, id_, flecs::Disabled); - } - - /** Get the entity's type. - * - * @return The entity's type. - */ - flecs::type type() const; - - /** Get the entity's table. - * - * @return Returns the entity's table. - */ - flecs::table table() const; - - /** Get table range for the entity. - * Returns a range with the entity's row as offset and count set to 1. If - * the entity is not stored in a table, the function returns a range with - * count 0. - * - * @return Returns the entity's table range. - */ - flecs::table_range range() const; - - /** Iterate (component) ids of an entity. - * The function parameter must match the following signature: - * - * @code - * void(*)(flecs::id id) - * @endcode - * - * @param func The function invoked for each id. - */ - template - void each(const Func& func) const; - - /** Iterate matching pair ids of an entity. - * The function parameter must match the following signature: - * - * @code - * void(*)(flecs::id id) - * @endcode - * - * @param func The function invoked for each id. - */ - template - void each(flecs::id_t first, flecs::id_t second, const Func& func) const; - - /** Iterate targets for a given relationship. - * The function parameter must match the following signature: - * - * @code - * void(*)(flecs::entity target) - * @endcode - * - * @param rel The relationship for which to iterate the targets. - * @param func The function invoked for each target. - */ - template - void each(const flecs::entity_view& rel, const Func& func) const; - - /** Iterate targets for a given relationship. - * The function parameter must match the following signature: - * - * @code - * void(*)(flecs::entity target) - * @endcode - * - * @tparam First The relationship for which to iterate the targets. - * @param func The function invoked for each target. - */ - template - void each(const Func& func) const { - return each(_::type::id(world_), func); - } - - /** Iterate children for entity. - * The function parameter must match the following signature: - * - * @code - * void(*)(flecs::entity target) - * @endcode - * - * @param rel The relationship to follow. - * @param func The function invoked for each child. - */ - template - void children(flecs::entity_t rel, Func&& func) const { - /* When the entity is a wildcard, this would attempt to query for all - * entities with (ChildOf, *) or (ChildOf, _) instead of querying for - * the children of the wildcard entity. */ - if (id_ == flecs::Wildcard || id_ == flecs::Any) { - /* This is correct, wildcard entities don't have children */ - return; - } - - flecs::world world(world_); - - ecs_iter_t it = ecs_each_id(world_, ecs_pair(rel, id_)); - while (ecs_each_next(&it)) { - _::each_delegate(FLECS_MOV(func)).invoke(&it); - } - } - - /** Iterate children for entity. - * The function parameter must match the following signature: - * - * @code - * void(*)(flecs::entity target) - * @endcode - * - * @tparam Rel The relationship to follow. - * @param func The function invoked for each child. - */ - template - void children(Func&& func) const { - children(_::type::id(world_), FLECS_MOV(func)); - } - - /** Iterate children for entity. - * The function parameter must match the following signature: - * - * @code - * void(*)(flecs::entity target) - * @endcode - * - * This operation follows the ChildOf relationship. - * - * @param func The function invoked for each child. - */ - template - void children(Func&& func) const { - children(flecs::ChildOf, FLECS_MOV(func)); - } - - /** Get component value. - * - * @tparam T The component to get. - * @return Pointer to the component value, nullptr if the entity does not - * have the component. - */ - template ::value > = 0> - const T* get() const { - auto comp_id = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return static_cast(ecs_get_id(world_, id_, comp_id)); - } - - /** Get component value. - * Overload for when T is not the same as the actual type, which happens - * when using pair types. - * - * @tparam T The component to get. - * @return Pointer to the component value, nullptr if the entity does not - * have the component. - */ - template , - if_t< flecs::is_pair::value > = 0> - const A* get() const { - auto comp_id = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return static_cast(ecs_get_id(world_, id_, comp_id)); - } - - /** Get a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam First The first element of the pair. - * @tparam Second the second element of a pair. - */ - template , - typename A = actual_type_t

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

(); - } - - /** Get a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - */ - template::value> = 0> - const First* get(Second second) const { - auto first = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return static_cast( - ecs_get_id(world_, id_, ecs_pair(first, second))); - } - - /** Get a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam First The first element of the pair. - * @param constant the enum constant. - */ - template::value> = 0> - const First* get(Second constant) const { - const auto& et = enum_type(this->world_); - flecs::entity_t target = et.entity(constant); - return get(target); - } - - /** Get component value (untyped). - * - * @param comp The component to get. - * @return Pointer to the component value, nullptr if the entity does not - * have the component. - */ - const void* get(flecs::id_t comp) const { - return ecs_get_id(world_, id_, comp); - } - - /** Get a pair (untyped). - * This operation gets the value for a pair from the entity. If neither the - * first nor the second part of the pair are components, the operation - * will fail. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - const void* get(flecs::entity_t first, flecs::entity_t second) const { - return ecs_get_id(world_, id_, ecs_pair(first, second)); - } - - /** Get 1..N components. - * This operation accepts a callback with as arguments the components to - * retrieve. The callback will only be invoked when the entity has all - * the components. - * - * This operation is faster than individually calling get for each component - * as it only obtains entity metadata once. - * - * While the callback is invoked the table in which the components are - * stored is locked, which prevents mutations that could cause invalidation - * of the component references. Note that this is not an actual lock: - * invalid access causes a runtime panic and so it is still up to the - * application to ensure access is protected. - * - * The component arguments must be references and can be either const or - * non-const. When all arguments are const, the function will read-lock the - * table (see ecs_read_begin). If one or more arguments are non-const the - * function will write-lock the table (see ecs_write_begin). - * - * Example: - * - * @code - * e.get([](Position& p, Velocity& v) { // write lock - * p.x += v.x; - * }); - * - * e.get([](const Position& p) { // read lock - * std::cout << p.x << std::endl; - * }); - * @endcode - * - * @param func The callback to invoke. - * @return True if the entity has all components, false if not. - */ - template ::value > = 0> - bool get(const Func& func) const; - - /** Get enum constant. - * - * @tparam T The enum type for which to get the constant - * @return Constant entity if found, 0 entity if not. - */ - template ::value > = 0> - const T* get() const; - - /** Get the second part for a pair. - * This operation gets the value for a pair from the entity. The first - * part of the pair should not be a component. - * - * @tparam Second the second element of a pair. - * @param first The first part of the pair. - */ - template - const Second* get_second(flecs::entity_t first) const { - auto second = _::type::id(world_); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, - ECS_INVALID_PARAMETER, "pair is not a component"); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, - ECS_INVALID_PARAMETER, "type of pair is not Second"); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return static_cast( - ecs_get_id(world_, id_, ecs_pair(first, second))); - } - - /** Get the second part for a pair. - * This operation gets the value for a pair from the entity. The first - * part of the pair should not be a component. - * - * @tparam First The first element of the pair. - * @tparam Second the second element of a pair. - */ - template - const Second* get_second() const { - return get>(); - } - - /** Get mutable component value. - * - * @tparam T The component to get. - * @return Pointer to the component value, nullptr if the entity does not - * have the component. - */ - template ::value > = 0> - T* get_mut() const { - auto comp_id = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return static_cast(ecs_get_mut_id(world_, id_, comp_id)); - } - - /** Get mutable component value. - * Overload for when T is not the same as the actual type, which happens - * when using pair types. - * - * @tparam T The component to get. - * @return Pointer to the component value, nullptr if the entity does not - * have the component. - */ - template , - if_t< flecs::is_pair::value > = 0> - A* get_mut() const { - auto comp_id = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return static_cast(ecs_get_mut_id(world_, id_, comp_id)); - } - - /** Get a mutable pair. - * This operation gets the value for a pair from the entity. - * - * @tparam First The first element of the pair. - * @tparam Second the second element of a pair. - */ - template , - typename A = actual_type_t

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

(); - } - - /** Get a mutable pair. - * This operation gets the value for a pair from the entity. - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - */ - template::value> = 0> - First* get_mut(Second second) const { - auto first = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return static_cast( - ecs_get_mut_id(world_, id_, ecs_pair(first, second))); - } - - /** Get a mutable pair. - * This operation gets the value for a pair from the entity. - * - * @tparam First The first element of the pair. - * @param constant the enum constant. - */ - template::value> = 0> - First* get_mut(Second constant) const { - const auto& et = enum_type(this->world_); - flecs::entity_t target = et.entity(constant); - return get_mut(target); - } - - /** Get mutable component value (untyped). - * - * @param comp The component to get. - * @return Pointer to the component value, nullptr if the entity does not - * have the component. - */ - void* get_mut(flecs::id_t comp) const { - return ecs_get_mut_id(world_, id_, comp); - } - - /** Get a mutable pair (untyped). - * This operation gets the value for a pair from the entity. If neither the - * first nor the second part of the pair are components, the operation - * will fail. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - void* get_mut(flecs::entity_t first, flecs::entity_t second) const { - return ecs_get_mut_id(world_, id_, ecs_pair(first, second)); - } - - /** Get the second part for a pair. - * This operation gets the value for a pair from the entity. The first - * part of the pair should not be a component. - * - * @tparam Second the second element of a pair. - * @param first The first part of the pair. - */ - template - Second* get_mut_second(flecs::entity_t first) const { - auto second = _::type::id(world_); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, - ECS_INVALID_PARAMETER, "pair is not a component"); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, - ECS_INVALID_PARAMETER, "type of pair is not Second"); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return static_cast( - ecs_get_mut_id(world_, id_, ecs_pair(first, second))); - } - - /** Get the second part for a pair. - * This operation gets the value for a pair from the entity. The first - * part of the pair should not be a component. - * - * @tparam First The first element of the pair. - * @tparam Second the second element of a pair. - */ - template - Second* get_mut_second() const { - return get_mut>(); - } - - /** Get target for a given pair. - * This operation returns the target for a given pair. The optional - * index can be used to iterate through targets, in case the entity has - * multiple instances for the same relationship. - * - * @tparam First The first element of the pair. - * @param index The index (0 for the first instance of the relationship). - */ - template - flecs::entity target(int32_t index = 0) const; - - /** Get target for a given pair. - * This operation returns the target for a given pair. The optional - * index can be used to iterate through targets, in case the entity has - * multiple instances for the same relationship. - * - * @param first The first element of the pair for which to retrieve the target. - * @param index The index (0 for the first instance of the relationship). - */ - flecs::entity target(flecs::entity_t first, int32_t index = 0) const; - - /** Get the target of a pair for a given relationship id. - * This operation returns the first entity that has the provided id by following - * the specified relationship. If the entity itself has the id then entity will - * be returned. If the id cannot be found on the entity or by following the - * relationship, the operation will return 0. - * - * This operation can be used to lookup, for example, which prefab is providing - * a component by specifying the IsA pair: - * - * @code - * // Is Position provided by the entity or one of its base entities? - * ecs_get_target_for_id(world, entity, EcsIsA, ecs_id(Position)) - * @endcode - * - * @param relationship The relationship to follow. - * @param id The id to lookup. - * @return The entity for which the target has been found. - */ - flecs::entity target_for(flecs::entity_t relationship, flecs::id_t id) const; - - template - flecs::entity target_for(flecs::entity_t relationship) const; - - template - flecs::entity target_for(flecs::entity_t relationship) const; - - /** Get depth for given relationship. - * - * @param rel The relationship. - * @return The depth. - */ - int32_t depth(flecs::entity_t rel) const { - return ecs_get_depth(world_, id_, rel); - } - - /** Get depth for given relationship. - * - * @tparam Rel The relationship. - * @return The depth. - */ - template - int32_t depth() const { - return this->depth(_::type::id(world_)); - } - - /** Get parent of entity. - * Short for target(flecs::ChildOf). - * - * @return The parent of the entity. - */ - flecs::entity parent() const; - - /** Lookup an entity by name. - * Lookup an entity in the scope of this entity. The provided path may - * contain double colons as scope separators, for example: "Foo::Bar". - * - * @param path The name of the entity to lookup. - * @param search_path When false, only the entity's scope is searched. - * @return The found entity, or entity::null if no entity matched. - */ - flecs::entity lookup(const char *path, bool search_path = false) const; - - /** Check if entity has the provided entity. - * - * @param e The entity to check. - * @return True if the entity has the provided entity, false otherwise. - */ - bool has(flecs::id_t e) const { - return ecs_has_id(world_, id_, e); - } - - /** Check if entity has the provided component. - * - * @tparam T The component to check. - * @return True if the entity has the provided component, false otherwise. - */ - template - bool has() const { - flecs::id_t cid = _::type::id(world_); - bool result = ecs_has_id(world_, id_, cid); - if (result) { - return result; - } - - if (is_enum::value) { - return ecs_has_pair(world_, id_, cid, flecs::Wildcard); - } - - return false; - } - - /** Check if entity has the provided enum constant. - * - * @tparam E The enum type (can be deduced). - * @param value The enum constant to check. - * @return True if the entity has the provided constant, false otherwise. - */ - template ::value > = 0> - bool has(E value) const { - auto r = _::type::id(world_); - auto o = enum_type(world_).entity(value); - ecs_assert(o, ECS_INVALID_PARAMETER, - "Constant was not found in Enum reflection data." - " Did you mean to use has() instead of has(E)?"); - return ecs_has_pair(world_, id_, r, o); - } - - /** Check if entity has the provided pair. - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair. - * @return True if the entity has the provided component, false otherwise. - */ - template - bool has() const { - return this->has(_::type::id(world_)); - } - - /** Check if entity has the provided pair. - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - * @return True if the entity has the provided component, false otherwise. - */ - template::value > = 0> - bool has(Second second) const { - auto comp_id = _::type::id(world_); - return ecs_has_id(world_, id_, ecs_pair(comp_id, second)); - } - - /** Check if entity has the provided pair. - * - * @tparam Second The second element of the pair. - * @param first The first element of the pair. - * @return True if the entity has the provided component, false otherwise. - */ - template - bool has_second(flecs::entity_t first) const { - return this->has(first, _::type::id(world_)); - } - - /** Check if entity has the provided pair. - * - * @tparam First The first element of the pair. - * @param value The enum constant. - * @return True if the entity has the provided component, false otherwise. - */ - template::value > = 0> - bool has(E value) const { - const auto& et = enum_type(this->world_); - flecs::entity_t second = et.entity(value); - return has(second); - } - - /** Check if entity has the provided pair. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - * @return True if the entity has the provided component, false otherwise. - */ - bool has(flecs::id_t first, flecs::id_t second) const { - return ecs_has_id(world_, id_, ecs_pair(first, second)); - } - - /** Check if entity owns the provided entity. - * An entity is owned if it is not shared from a base entity. - * - * @param e The entity to check. - * @return True if the entity owns the provided entity, false otherwise. - */ - bool owns(flecs::id_t e) const { - return ecs_owns_id(world_, id_, e); - } - - /** Check if entity owns the provided pair. - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - * @return True if the entity owns the provided component, false otherwise. - */ - template - bool owns(flecs::id_t second) const { - auto comp_id = _::type::id(world_); - return owns(ecs_pair(comp_id, second)); - } - - /** Check if entity owns the provided pair. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - * @return True if the entity owns the provided component, false otherwise. - */ - bool owns(flecs::id_t first, flecs::id_t second) const { - return owns(ecs_pair(first, second)); - } - - /** Check if entity owns the provided component. - * An component is owned if it is not shared from a base entity. - * - * @tparam T The component to check. - * @return True if the entity owns the provided component, false otherwise. - */ - template - bool owns() const { - return owns(_::type::id(world_)); - } - - /** Check if entity owns the provided pair. - * An pair is owned if it is not shared from a base entity. - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair. - * @return True if the entity owns the provided pair, false otherwise. - */ - template - bool owns() const { - return owns( - _::type::id(world_), - _::type::id(world_)); - } - - /** Test if id is enabled. - * - * @param id The id to test. - * @return True if enabled, false if not. - */ - bool enabled(flecs::id_t id) const { - return ecs_is_enabled_id(world_, id_, id); - } - - /** Test if component is enabled. - * - * @tparam T The component to test. - * @return True if enabled, false if not. - */ - template - bool enabled() const { - return this->enabled(_::type::id(world_)); - } - - /** Test if pair is enabled. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - * @return True if enabled, false if not. - */ - bool enabled(flecs::id_t first, flecs::id_t second) const { - return this->enabled(ecs_pair(first, second)); - } - - /** Test if pair is enabled. - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - * @return True if enabled, false if not. - */ - template - bool enabled(flecs::id_t second) const { - return this->enabled(_::type::id(world_), second); - } - - /** Test if pair is enabled. - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair. - * @return True if enabled, false if not. - */ - template - bool enabled() const { - return this->enabled(_::type::id(world_)); - } - - flecs::entity clone(bool clone_value = true, flecs::entity_t dst_id = 0) const; - - /** Return mutable entity handle for current stage - * When an entity handle created from the world is used while the world is - * in staged mode, it will only allow for readonly operations since - * structural changes are not allowed on the world while in staged mode. - * - * To do mutations on the entity, this operation provides a handle to the - * entity that uses the stage instead of the actual world. - * - * Note that staged entity handles should never be stored persistently, in - * components or elsewhere. An entity handle should always point to the - * main world. - * - * Also note that this operation is not necessary when doing mutations on an - * entity outside of a system. It is allowed to do entity operations - * directly on the world, as long as the world is not in staged mode. - * - * @param stage The current stage. - * @return An entity handle that allows for mutations in the current stage. - */ - flecs::entity mut(const flecs::world& stage) const; - - /** Same as mut(world), but for iterator. - * This operation allows for the construction of a mutable entity handle - * from an iterator. - * - * @param it An iterator that contains a reference to the world or stage. - * @return An entity handle that allows for mutations in the current stage. - */ - flecs::entity mut(const flecs::iter& it) const; - - /** Same as mut(world), but for entity. - * This operation allows for the construction of a mutable entity handle - * from another entity. This is useful in each() functions, which only - * provide a handle to the entity being iterated over. - * - * @param e Another mutable entity. - * @return An entity handle that allows for mutations in the current stage. - */ - flecs::entity mut(const flecs::entity_view& e) const; - -# ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/entity_view.inl - * @brief JSON entity mixin. - */ - -/** Serialize entity to JSON. - * - * @memberof flecs::entity_view - * @ingroup cpp_addons_json - */ -flecs::string to_json(const flecs::entity_to_json_desc_t *desc = nullptr) const { - char *json = ecs_entity_to_json(world_, id_, desc); - return flecs::string(json); -} - -# endif -# ifdef FLECS_DOC -/** - * @file addons/cpp/mixins/doc/entity_view.inl - * @brief Doc entity view mixin. - */ - -/** Get human readable name. - * - * @see ecs_doc_get_name() - * @see flecs::doc::get_name() - * @see flecs::entity_builder::set_doc_name() - * - * @memberof flecs::entity_view - * @ingroup cpp_addons_doc - */ -const char* doc_name() const { - return ecs_doc_get_name(world_, id_); -} - -/** Get brief description. - * - * @see ecs_doc_get_brief() - * @see flecs::doc::get_brief() - * @see flecs::entity_builder::set_doc_brief() - * - * @memberof flecs::entity_view - * @ingroup cpp_addons_doc - */ -const char* doc_brief() const { - return ecs_doc_get_brief(world_, id_); -} - -/** Get detailed description. - * - * @see ecs_doc_get_detail() - * @see flecs::doc::get_detail() - * @see flecs::entity_builder::set_doc_detail() - * - * @memberof flecs::entity_view - * @ingroup cpp_addons_doc - */ -const char* doc_detail() const { - return ecs_doc_get_detail(world_, id_); -} - -/** Get link to external documentation. - * - * @see ecs_doc_get_link() - * @see flecs::doc::get_link() - * @see flecs::entity_builder::set_doc_link() - * - * @memberof flecs::entity_view - * @ingroup cpp_addons_doc - */ -const char* doc_link() const { - return ecs_doc_get_link(world_, id_); -} - -/** Get color. - * - * @see ecs_doc_get_color() - * @see flecs::doc::get_color() - * @see flecs::entity_builder::set_doc_color() - * - * @memberof flecs::entity_view - * @ingroup cpp_addons_doc - */ -const char* doc_color() const { - return ecs_doc_get_color(world_, id_); -} - -/** Get UUID. - * - * @see ecs_doc_get_uuid() - * @see flecs::doc::get_uuid() - * @see flecs::entity_builder::set_doc_uuid() - * - * @memberof flecs::entity_view - * @ingroup cpp_addons_doc - */ -const char* doc_uuid() const { - return ecs_doc_get_uuid(world_, id_); -} - -# endif -# ifdef FLECS_ALERTS -/** - * @file addons/cpp/mixins/alerts/entity_view.inl - * @brief Alerts entity mixin. - */ - -/** Return number of alerts for entity. - * - * @memberof flecs::entity_view - * @ingroup cpp_addons_alerts - */ -int32_t alert_count(flecs::entity_t alert = 0) const { - return ecs_get_alert_count(world_, id_, alert); -} - -# endif - -/** - * @file addons/cpp/mixins/enum/entity_view.inl - * @brief Enum entity view mixin. - */ - -/** Convert entity to enum constant. - * - * @memberof flecs::entity_view - * @ingroup cpp_entities - */ -template -E to_constant() const; - - -/** - * @file addons/cpp/mixins/event/entity_view.inl - * @brief Event entity mixin. - */ - -/** Emit event for entity. - * - * @memberof flecs::entity_view - * - * @param evt The event to emit. - */ -void emit(flecs::entity_t evt) const { - flecs::world(world_) - .event(evt) - .entity(id_) - .emit(); -} - -/** Emit event for entity. - * - * @memberof flecs::entity_view - * - * @param evt The event to emit. - */ -void emit(flecs::entity evt) const; - -/** Emit event for entity. - * - * @memberof flecs::entity_view - * - * @tparam Evt The event to emit. - */ -template ::value> = 0> -void emit() const { - this->emit(_::type::id(world_)); -} - -/** Emit event with payload for entity. - * - * @memberof flecs::entity_view - * - * @tparam Evt The event to emit. - */ -template ::value> = 0> -void emit(const Evt& payload) const { - flecs::world(world_) - .event(_::type::id(world_)) - .entity(id_) - .ctx(&payload) - .emit(); -} - - -/** Enqueue event for entity. - * - * @memberof flecs::entity_view - * - * @param evt The event to enqueue. - */ -void enqueue(flecs::entity_t evt) const { - flecs::world(world_) - .event(evt) - .entity(id_) - .enqueue(); -} - -/** Enqueue event for entity. - * - * @memberof flecs::entity_view - * - * @param evt The event to enqueue. - */ -void enqueue(flecs::entity evt) const; - -/** Enqueue event for entity. - * - * @memberof flecs::entity_view - * - * @tparam Evt The event to enqueue. - */ -template ::value> = 0> -void enqueue() const { - this->enqueue(_::type::id(world_)); -} - -/** Enqueue event with payload for entity. - * - * @memberof flecs::entity_view - * - * @tparam Evt The event to enqueue. - */ -template ::value> = 0> -void enqueue(const Evt& payload) const { - flecs::world(world_) - .event(_::type::id(world_)) - .entity(id_) - .ctx(&payload) - .enqueue(); -} - - -private: - flecs::entity set_stage(world_t *stage); -}; - -} - -/** @} */ - -/** - * @file addons/cpp/mixins/entity/builder.hpp - * @brief Entity builder. - */ - -#pragma once - -namespace flecs -{ - -/** Entity builder. - * @ingroup cpp_entities - */ -template -struct entity_builder : entity_view { - - using entity_view::entity_view; - - /** Add a component to an entity. - * To ensure the component is initialized, it should have a constructor. - * - * @tparam T the component type to add. - */ - template - const Self& add() const { - flecs_static_assert(is_flecs_constructible::value, - "cannot default construct type: add T::T() or use emplace()"); - ecs_add_id(this->world_, this->id_, _::type::id(this->world_)); - return to_base(); - } - - /** Add pair for enum constant. - * This operation will add a pair to the entity where the first element is - * the enumeration type, and the second element the enumeration constant. - * - * The operation may be used with regular (C style) enumerations as well as - * enum classes. - * - * @param value The enumeration value. - */ - template ::value > = 0> - const Self& add(E value) const { - flecs::entity_t first = _::type::id(this->world_); - const auto& et = enum_type(this->world_); - flecs::entity_t second = et.entity(value); - - ecs_assert(second, ECS_INVALID_PARAMETER, "Component was not found in reflection data."); - return this->add(first, second); - } - - /** Add an entity to an entity. - * Add an entity to the entity. This is typically used for tagging. - * - * @param component The component to add. - */ - const Self& add(id_t component) const { - ecs_add_id(this->world_, this->id_, component); - return to_base(); - } - - /** Add a pair. - * This operation adds a pair to the entity. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - const Self& add(entity_t first, entity_t second) const { - ecs_add_pair(this->world_, this->id_, first, second); - return to_base(); - } - - /** Add a pair. - * This operation adds a pair to the entity. - * - * @tparam First The first element of the pair - * @tparam Second The second element of the pair - */ - template - const Self& add() const { - return this->add(_::type::id(this->world_)); - } - - /** Add a pair. - * This operation adds a pair to the entity. - * - * @tparam First The first element of the pair - * @param second The second element of the pair. - */ - template::value > = 0> - const Self& add(Second second) const { - flecs_static_assert(is_flecs_constructible::value, - "cannot default construct type: add T::T() or use emplace()"); - return this->add(_::type::id(this->world_), second); - } - - /** Add a pair. - * This operation adds a pair to the entity that consists out of a tag - * combined with an enum constant. - * - * @tparam First The first element of the pair - * @param constant the enum constant. - */ - template::value > = 0> - const Self& add(Second constant) const { - flecs_static_assert(is_flecs_constructible::value, - "cannot default construct type: add T::T() or use emplace()"); - const auto& et = enum_type(this->world_); - return this->add(et.entity(constant)); - } - - /** Add a pair. - * This operation adds a pair to the entity. - * - * @param first The first element of the pair - * @tparam Second The second element of the pair - */ - template - const Self& add_second(flecs::entity_t first) const { - return this->add(first, _::type::id(this->world_)); - } - - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @param cond The condition to evaluate. - * @param component The component to add. - */ - const Self& add_if(bool cond, flecs::id_t component) const { - if (cond) { - return this->add(component); - } else { - return this->remove(component); - } - } - - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @tparam T The component to add. - * @param cond The condition to evaluate. - */ - template - const Self& add_if(bool cond) const { - if (cond) { - return this->add(); - } else { - return this->remove(); - } - } - - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @param cond The condition to evaluate. - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - const Self& add_if(bool cond, flecs::entity_t first, flecs::entity_t second) const { - if (cond) { - return this->add(first, second); - } else { - /* If second is 0 or if relationship is exclusive, use wildcard for - * second which will remove all instances of the relationship. - * Replacing 0 with Wildcard will make it possible to use the second - * as the condition. */ - if (!second || ecs_has_id(this->world_, first, flecs::Exclusive)) { - second = flecs::Wildcard; - } - return this->remove(first, second); - } - } - - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @tparam First The first element of the pair - * @param cond The condition to evaluate. - * @param second The second element of the pair. - */ - template - const Self& add_if(bool cond, flecs::entity_t second) const { - return this->add_if(cond, _::type::id(this->world_), second); - } - - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @tparam First The first element of the pair - * @tparam Second The second element of the pair - * @param cond The condition to evaluate. - */ - template - const Self& add_if(bool cond) const { - return this->add_if(cond, _::type::id(this->world_)); - } - - /** Conditional add. - * This operation adds if condition is true, removes if condition is false. - * - * @param cond The condition to evaluate. - * @param constant The enumeration constant. - */ - template ::value > = 0> - const Self& add_if(bool cond, E constant) const { - const auto& et = enum_type(this->world_); - return this->add_if(cond, et.entity(constant)); - } - - /** Shortcut for `add(IsA, entity)`. - * - * @param second The second element of the pair. - */ - const Self& is_a(entity_t second) const { - return this->add(flecs::IsA, second); - } - - /** Shortcut for `add(IsA, entity)`. - * - * @tparam T the type associated with the entity. - */ - template - const Self& is_a() const { - return this->add(flecs::IsA, _::type::id(this->world_)); - } - - /** Shortcut for `add(ChildOf, entity)`. - * - * @param second The second element of the pair. - */ - const Self& child_of(entity_t second) const { - return this->add(flecs::ChildOf, second); - } - - /** Shortcut for `add(DependsOn, entity)`. - * - * @param second The second element of the pair. - */ - const Self& depends_on(entity_t second) const { - return this->add(flecs::DependsOn, second); - } - - /** Shortcut for `add(DependsOn, entity)`. - * - * @param second The second element of the pair. - */ - template ::value> = 0> - const Self& depends_on(E second) const { - const auto& et = enum_type(this->world_); - flecs::entity_t target = et.entity(second); - return depends_on(target); - } - - /** Shortcut for `add(SlotOf, entity)`. - * - * @param second The second element of the pair. - */ - const Self& slot_of(entity_t second) const { - return this->add(flecs::SlotOf, second); - } - - /** Shortcut for `add(SlotOf, target(ChildOf))`. - */ - const Self& slot() const { - ecs_check(ecs_get_target(world_, id_, flecs::ChildOf, 0), - ECS_INVALID_PARAMETER, "add ChildOf pair before using slot()"); - return this->slot_of(this->target(flecs::ChildOf)); - error: - return to_base(); - } - - /** Shortcut for `add(ChildOf, entity)`. - * - * @tparam T the type associated with the entity. - */ - template - const Self& child_of() const { - return this->child_of(_::type::id(this->world_)); - } - - /** Shortcut for `add(DependsOn, entity)`. - * - * @tparam T the type associated with the entity. - */ - template - const Self& depends_on() const { - return this->depends_on(_::type::id(this->world_)); - } - - /** Shortcut for `add(SlotOf, entity)`. - * - * @tparam T the type associated with the entity. - */ - template - const Self& slot_of() const { - return this->slot_of(_::type::id(this->world_)); - } - - /** Remove a component from an entity. - * - * @tparam T the type of the component to remove. - */ - template ::value > = 0> - const Self& remove() const { - ecs_remove_id(this->world_, this->id_, _::type::id(this->world_)); - return to_base(); - } - - /** Remove pair for enum. - * This operation will remove any `(Enum, *)` pair from the entity. - * - * @tparam E The enumeration type. - */ - template ::value > = 0> - const Self& remove() const { - flecs::entity_t first = _::type::id(this->world_); - return this->remove(first, flecs::Wildcard); - } - - /** Remove an entity from an entity. - * - * @param entity The entity to remove. - */ - const Self& remove(entity_t entity) const { - ecs_remove_id(this->world_, this->id_, entity); - return to_base(); - } - - /** Remove a pair. - * This operation removes a pair from the entity. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - const Self& remove(entity_t first, entity_t second) const { - ecs_remove_pair(this->world_, this->id_, first, second); - return to_base(); - } - - /** Removes a pair. - * This operation removes a pair from the entity. - * - * @tparam First The first element of the pair - * @tparam Second The second element of the pair - */ - template - const Self& remove() const { - return this->remove(_::type::id(this->world_)); - } - - /** Remove a pair. - * This operation removes the pair from the entity. - * - * @tparam First The first element of the pair - * @param second The second element of the pair. - */ - template::value > = 0> - const Self& remove(Second second) const { - return this->remove(_::type::id(this->world_), second); - } - - /** Removes a pair. - * This operation removes a pair from the entity. - * - * @tparam Second The second element of the pair - * @param first The first element of the pair - */ - template - const Self& remove_second(flecs::entity_t first) const { - return this->remove(first, _::type::id(this->world_)); - } - - /** Remove a pair. - * This operation removes the pair from the entity. - * - * @tparam First The first element of the pair - * @param constant the enum constant. - */ - template::value > = 0> - const Self& remove(Second constant) const { - const auto& et = enum_type(this->world_); - flecs::entity_t second = et.entity(constant); - return this->remove(second); - } - - /** Mark id for auto-overriding. - * When an entity inherits from a base entity (using the `IsA` relationship) - * any ids marked for auto-overriding on the base will be overridden - * automatically by the entity. - * - * @param id The id to mark for overriding. - */ - const Self& auto_override(flecs::id_t id) const { - return this->add(ECS_AUTO_OVERRIDE | id); - } - - /** Mark pair for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - const Self& auto_override(flecs::entity_t first, flecs::entity_t second) const { - return this->auto_override(ecs_pair(first, second)); - } - - /** Mark component for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam T The component to mark for overriding. - */ - template - const Self& auto_override() const { - return this->auto_override(_::type::id(this->world_)); - } - - /** Mark pair for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - */ - template - const Self& auto_override(flecs::entity_t second) const { - return this->auto_override(_::type::id(this->world_), second); - } - - /** Mark pair for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair. - */ - template - const Self& auto_override() const { - return this->auto_override(_::type::id(this->world_)); - } - - /** Set component, mark component for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam T The component to set and for which to add the OVERRIDE flag - * @param val The value to set. - */ - template - const Self& set_auto_override(const T& val) const { - this->auto_override(); - return this->set(val); - } - - /** Set component, mark component for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam T The component to set and for which to add the OVERRIDE flag - * @param val The value to set. - */ - template - const Self& set_auto_override(T&& val) const { - this->auto_override(); - return this->set(FLECS_FWD(val)); - } - - /** Set pair, mark component for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - * @param val The value to set. - */ - template - const Self& set_auto_override(flecs::entity_t second, const First& val) const { - this->auto_override(second); - return this->set(second, val); - } - - /** Set pair, mark component for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - * @param val The value to set. - */ - template - const Self& set_auto_override(flecs::entity_t second, First&& val) const { - this->auto_override(second); - return this->set(second, FLECS_FWD(val)); - } - - /** Set component, mark component for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair. - * @param val The value to set. - */ - template , - typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> - const Self& set_auto_override(const A& val) const { - this->auto_override(); - return this->set(val); - } - - /** Set component, mark component for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair. - * @param val The value to set. - */ - template , - typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> - const Self& set_auto_override(A&& val) const { - this->auto_override(); - return this->set(FLECS_FWD(val)); - } - - /** Emplace component, mark component for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam T The component to emplace and override. - * @param args The arguments to pass to the constructor of `T`. - */ - template - const Self& emplace_auto_override(Args&&... args) const { - this->auto_override(); - - flecs::emplace(this->world_, this->id_, - _::type::id(this->world_), FLECS_FWD(args)...); - - return to_base(); - } - - /** Emplace pair, mark pair for auto-overriding. - * @see auto_override(flecs::id_t) const - * - * @tparam First The first element of the pair to emplace and override. - * @tparam Second The second element of the pair to emplace and override. - * @param args The arguments to pass to the constructor of `Second`. - */ - template , - typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0, - typename ... Args> - const Self& emplace_auto_override(Args&&... args) const { - this->auto_override(); - - flecs::emplace(this->world_, this->id_, - ecs_pair(_::type::id(this->world_), - _::type::id(this->world_)), - FLECS_FWD(args)...); - - return to_base(); - } - - /** Enable an entity. - * Enabled entities are matched with systems and can be searched with - * queries. - */ - const Self& enable() const { - ecs_enable(this->world_, this->id_, true); - return to_base(); - } - - /** Disable an entity. - * Disabled entities are not matched with systems and cannot be searched - * with queries, unless explicitly specified in the query expression. - */ - const Self& disable() const { - ecs_enable(this->world_, this->id_, false); - return to_base(); - } - - /** Enable an id. - * This sets the enabled bit for this component. If this is the first time - * the component is enabled or disabled, the bitset is added. - * - * @param id The id to enable. - * @param toggle True to enable, false to disable (default = true). - * - * @see ecs_enable_id() - */ - const Self& enable(flecs::id_t id, bool toggle = true) const { - ecs_enable_id(this->world_, this->id_, id, toggle); - return to_base(); - } - - /** Enable a component. - * @see enable(flecs::id_t) const - * - * @tparam T The component to enable. - */ - template - const Self& enable() const { - return this->enable(_::type::id(this->world_)); - } - - /** Enable a pair. - * @see enable(flecs::id_t) const - * - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - const Self& enable(flecs::id_t first, flecs::id_t second) const { - return this->enable(ecs_pair(first, second)); - } - - /** Enable a pair. - * @see enable(flecs::id_t) const - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - */ - template - const Self& enable(flecs::id_t second) const { - return this->enable(_::type::id(world_), second); - } - - /** Enable a pair. - * @see enable(flecs::id_t) const - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair. - */ - template - const Self& enable() const { - return this->enable(_::type::id(world_)); - } - - /** Disable an id. - * This sets the enabled bit for this id. If this is the first time - * the id is enabled or disabled, the bitset is added. - * - * @param id The id to disable. - * - * @see ecs_enable_id() - * @see enable(flecs::id_t) const - */ - const Self& disable(flecs::id_t id) const { - return this->enable(id, false); - } - - /** Disable a component. - * @see disable(flecs::id_t) const - * - * @tparam T The component to enable. - */ - template - const Self& disable() const { - return this->disable(_::type::id(world_)); - } - - /** Disable a pair. - * @see disable(flecs::id_t) const - * - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - const Self& disable(flecs::id_t first, flecs::id_t second) const { - return this->disable(ecs_pair(first, second)); - } - - /** Disable a pair. - * @see disable(flecs::id_t) const - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - */ - template - const Self& disable(flecs::id_t second) const { - return this->disable(_::type::id(world_), second); - } - - /** Disable a pair. - * @see disable(flecs::id_t) const - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair. - */ - template - const Self& disable() const { - return this->disable(_::type::id(world_)); - } - - const Self& set_ptr(entity_t comp, size_t size, const void *ptr) const { - ecs_set_id(this->world_, this->id_, comp, size, ptr); - return to_base(); - } - - const Self& set_ptr(entity_t comp, const void *ptr) const { - const flecs::Component *cptr = ecs_get( - this->world_, comp, EcsComponent); - - /* Can't set if it's not a component */ - ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, NULL); - - return set_ptr(comp, cptr->size, ptr); - } - - template::value> = 0 > - const Self& set(T&& value) const { - flecs::set(this->world_, this->id_, FLECS_FWD(value)); - return to_base(); - } - - template::value > = 0> - const Self& set(const T& value) const { - flecs::set(this->world_, this->id_, value); - return to_base(); - } - - template, if_not_t< - is_actual::value > = 0> - const Self& set(A&& value) const { - flecs::set(this->world_, this->id_, FLECS_FWD(value)); - return to_base(); - } - - template, if_not_t< - is_actual::value > = 0> - const Self& set(const A& value) const { - flecs::set(this->world_, this->id_, value); - return to_base(); - } - - /** Set a pair for an entity. - * This operation sets the pair value, and uses First as type. If the - * entity did not yet have the pair, it will be added. - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair - * @param value The value to set. - */ - template , - typename A = actual_type_t

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

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

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

(this->world_, this->id_, value); - return to_base(); - } - - /** Set a pair for an entity. - * This operation sets the pair value, and uses First as type. If the - * entity did not yet have the pair, it will be added. - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - * @param value The value to set. - */ - template ::value > = 0> - const Self& set(Second second, const First& value) const { - auto first = _::type::id(this->world_); - flecs::set(this->world_, this->id_, value, - ecs_pair(first, second)); - return to_base(); - } - - /** Set a pair for an entity. - * This operation sets the pair value, and uses First as type. If the - * entity did not yet have the pair, it will be added. - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - * @param value The value to set. - */ - template ::value > = 0> - const Self& set(Second second, First&& value) const { - auto first = _::type::id(this->world_); - flecs::set(this->world_, this->id_, FLECS_FWD(value), - ecs_pair(first, second)); - return to_base(); - } - - /** Set a pair for an entity. - * This operation sets the pair value, and uses First as type. If the - * entity did not yet have the pair, it will be added. - * - * @tparam First The first element of the pair. - * @param constant The enum constant. - * @param value The value to set. - */ - template ::value > = 0> - const Self& set(Second constant, const First& value) const { - const auto& et = enum_type(this->world_); - flecs::entity_t second = et.entity(constant); - return set(second, value); - } - - /** Set a pair for an entity. - * This operation sets the pair value, and uses Second as type. If the - * entity did not yet have the pair, it will be added. - * - * @tparam Second The second element of the pair - * @param first The first element of the pair. - * @param value The value to set. - */ - template - const Self& set_second(entity_t first, const Second& value) const { - auto second = _::type::id(this->world_); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, - ECS_INVALID_PARAMETER, "pair is not a component"); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, - ECS_INVALID_PARAMETER, "type of pair is not Second"); - flecs::set(this->world_, this->id_, value, - ecs_pair(first, second)); - return to_base(); - } - - /** Set a pair for an entity. - * This operation sets the pair value, and uses Second as type. If the - * entity did not yet have the pair, it will be added. - * - * @tparam Second The second element of the pair - * @param first The first element of the pair. - * @param value The value to set. - */ - template - const Self& set_second(entity_t first, Second&& value) const { - auto second = _::type::id(this->world_); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, - ECS_INVALID_PARAMETER, "pair is not a component"); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, - ECS_INVALID_PARAMETER, "type of pair is not Second"); - flecs::set(this->world_, this->id_, FLECS_FWD(value), - ecs_pair(first, second)); - return to_base(); - } - - template - const Self& set_second(const Second& value) const { - flecs::set>(this->world_, this->id_, value); - return to_base(); - } - - /** Set 1..N components. - * This operation accepts a callback with as arguments the components to - * set. If the entity does not have all of the provided components, they - * will be added. - * - * This operation is faster than individually calling get for each component - * as it only obtains entity metadata once. When this operation is called - * while deferred, its performance is equivalent to that of calling ensure - * for each component separately. - * - * The operation will invoke modified for each component after the callback - * has been invoked. - * - * @param func The callback to invoke. - */ - template - const Self& insert(const Func& func) const; - - /** Emplace component. - * Emplace constructs a component in the storage, which prevents calling the - * destructor on the value passed into the function. - * - * Emplace attempts the following signatures to construct the component: - * - * @code - * T{Args...} - * T{flecs::entity, Args...} - * @endcode - * - * If the second signature matches, emplace will pass in the current entity - * as argument to the constructor, which is useful if the component needs - * to be aware of the entity to which it has been added. - * - * Emplace may only be called for components that have not yet been added - * to the entity. - * - * @tparam T the component to emplace - * @param args The arguments to pass to the constructor of T - */ - template> - const Self& emplace(Args&&... args) const { - flecs::emplace(this->world_, this->id_, - _::type::id(this->world_), FLECS_FWD(args)...); - return to_base(); - } - - template , - typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> - const Self& emplace(Args&&... args) const { - flecs::emplace(this->world_, this->id_, - ecs_pair(_::type::id(this->world_), - _::type::id(this->world_)), - FLECS_FWD(args)...); - return to_base(); - } - - template - const Self& emplace_first(flecs::entity_t second, Args&&... args) const { - auto first = _::type::id(this->world_); - flecs::emplace(this->world_, this->id_, - ecs_pair(first, second), - FLECS_FWD(args)...); - return to_base(); - } - - template - const Self& emplace_second(flecs::entity_t first, Args&&... args) const { - auto second = _::type::id(this->world_); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, - ECS_INVALID_PARAMETER, "pair is not a component"); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, - ECS_INVALID_PARAMETER, "type of pair is not Second"); - flecs::emplace(this->world_, this->id_, - ecs_pair(first, second), - FLECS_FWD(args)...); - return to_base(); - } - - /** Entities created in function will have the current entity. - * This operation is thread safe. - * - * @param func The function to call. - */ - template - const Self& with(const Func& func) const { - ecs_id_t prev = ecs_set_with(this->world_, this->id_); - func(); - ecs_set_with(this->world_, prev); - return to_base(); - } - - /** Entities created in function will have `(First, this)`. - * This operation is thread safe. - * - * @tparam First The first element of the pair - * @param func The function to call. - */ - template - const Self& with(const Func& func) const { - with(_::type::id(this->world_), func); - return to_base(); - } - - /** Entities created in function will have `(first, this)`. - * This operation is thread safe. - * - * @param first The first element of the pair. - * @param func The function to call. - */ - template - const Self& with(entity_t first, const Func& func) const { - ecs_id_t prev = ecs_set_with(this->world_, - ecs_pair(first, this->id_)); - func(); - ecs_set_with(this->world_, prev); - return to_base(); - } - - /** The function will be ran with the scope set to the current entity. */ - template - const Self& scope(const Func& func) const { - ecs_entity_t prev = ecs_set_scope(this->world_, this->id_); - func(); - ecs_set_scope(this->world_, prev); - return to_base(); - } - - /** Return world scoped to entity */ - scoped_world scope() const { - return scoped_world(world_, id_); - } - - /* Set the entity name. - */ - const Self& set_name(const char *name) const { - ecs_set_name(this->world_, this->id_, name); - return to_base(); - } - - /* Set entity alias. - */ - const Self& set_alias(const char *name) const { - ecs_set_alias(this->world_, this->id_, name); - return to_base(); - } - -# ifdef FLECS_DOC -/** - * @file addons/cpp/mixins/doc/entity_builder.inl - * @brief Doc entity builder mixin. - */ - -/** Set human readable name. - * This adds `(flecs.doc.Description, flecs.Name)` to the entity. - * - * @see ecs_doc_set_name() - * @see flecs::doc::set_name() - * @see flecs::entity_view::doc_name() - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_doc - */ -const Self& set_doc_name(const char *name) const { - ecs_doc_set_name(world_, id_, name); - return to_base(); -} - -/** Set brief description. - * This adds `(flecs.doc.Description, flecs.doc.Brief)` to the entity. - * - * @see ecs_doc_set_brief() - * @see flecs::doc::set_brief() - * @see flecs::entity_view::doc_brief() - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_doc - */ -const Self& set_doc_brief(const char *brief) const { - ecs_doc_set_brief(world_, id_, brief); - return to_base(); -} - -/** Set detailed description. - * This adds `(flecs.doc.Description, flecs.doc.Detail)` to the entity. - * - * @see ecs_doc_set_detail() - * @see flecs::doc::set_detail() - * @see flecs::entity_view::doc_detail() - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_doc - */ -const Self& set_doc_detail(const char *detail) const { - ecs_doc_set_detail(world_, id_, detail); - return to_base(); -} - -/** Set link to external documentation. - * This adds `(flecs.doc.Description, flecs.doc.Link)` to the entity. - * - * @see ecs_doc_set_link() - * @see flecs::doc::set_link() - * @see flecs::entity_view::doc_link() - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_doc - */ -const Self& set_doc_link(const char *link) const { - ecs_doc_set_link(world_, id_, link); - return to_base(); -} - -/** Set doc color. - * This adds `(flecs.doc.Description, flecs.doc.Color)` to the entity. - * - * @see ecs_doc_set_color() - * @see flecs::doc::set_color() - * @see flecs::entity_view::doc_color() - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_doc - */ -const Self& set_doc_color(const char *color) const { - ecs_doc_set_color(world_, id_, color); - return to_base(); -} - -/** Set doc UUID. - * This adds `(flecs.doc.Description, flecs.doc.Uuid)` to the entity. - * - * @see ecs_doc_set_uuid() - * @see flecs::doc::set_uuid() - * @see flecs::entity_view::doc_uuid() - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_doc - */ -const Self& set_doc_uuid(const char *uuid) const { - ecs_doc_set_uuid(world_, id_, uuid); - return to_base(); -} - -# endif - -# ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/entity_builder.inl - * @brief Meta entity builder mixin. - */ - -/** - * @memberof flecs::entity_view - * @ingroup cpp_addons_meta - * - * @{ - */ - -/** Make entity a unit */ -const Self& unit( - const char *symbol, - flecs::entity_t prefix = 0, - flecs::entity_t base = 0, - flecs::entity_t over = 0, - int32_t factor = 0, - int32_t power = 0) const -{ - ecs_unit_desc_t desc = {}; - desc.entity = this->id_; - desc.symbol = const_cast(symbol); /* safe, will be copied in */ - desc.base = base; - desc.over = over; - desc.prefix = prefix; - desc.translation.factor = factor; - desc.translation.power = power; - ecs_unit_init(this->world(), &desc); - - return to_base(); -} - -/** Make entity a derived unit */ -const Self& unit( - flecs::entity_t prefix = 0, - flecs::entity_t base = 0, - flecs::entity_t over = 0, - int32_t factor = 0, - int32_t power = 0) const -{ - ecs_unit_desc_t desc = {}; - desc.entity = this->id_; - desc.base = base; - desc.over = over; - desc.prefix = prefix; - desc.translation.factor = factor; - desc.translation.power = power; - ecs_unit_init(this->world(), &desc); - - return to_base(); -} - -/** Make entity a derived unit */ -const Self& unit_prefix( - const char *symbol, - int32_t factor = 0, - int32_t power = 0) const -{ - ecs_unit_prefix_desc_t desc = {}; - desc.entity = this->id_; - desc.symbol = const_cast(symbol); /* safe, will be copied in */ - desc.translation.factor = factor; - desc.translation.power = power; - ecs_unit_prefix_init(this->world(), &desc); - - return to_base(); -} - -/** Add quantity to unit */ -const Self& quantity(flecs::entity_t quantity) const { - ecs_add_pair(this->world(), this->id(), flecs::Quantity, quantity); - return to_base(); -} - -/** Make entity a unity prefix */ -template -const Self& quantity() const { - return this->quantity(_::type::id(this->world())); -} - -/** Make entity a quantity */ -const Self& quantity() const { - ecs_add_id(this->world(), this->id(), flecs::Quantity); - return to_base(); -} - -/** @} */ - -# endif - -# ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/entity_builder.inl - * @brief JSON entity mixin. - */ - -/** Set component from JSON. - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_json - */ -const Self& set_json( - flecs::id_t e, - const char *json, - flecs::from_json_desc_t *desc = nullptr) const -{ - flecs::entity_t type = ecs_get_typeid(world_, e); - if (!type) { - ecs_err("id is not a type"); - return to_base(); - } - - void *ptr = ecs_ensure_id(world_, id_, e); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_ptr_from_json(world_, type, ptr, json, desc); - ecs_modified_id(world_, id_, e); - - return to_base(); -} - -/** Set pair from JSON. - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_json - */ -const Self& set_json( - flecs::entity_t r, - flecs::entity_t t, - const char *json, - flecs::from_json_desc_t *desc = nullptr) const -{ - return set_json(ecs_pair(r, t), json, desc); -} - -/** Set component from JSON. - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_json - */ -template -const Self& set_json( - const char *json, - flecs::from_json_desc_t *desc = nullptr) const -{ - return set_json(_::type::id(world_), json, desc); -} - -/** Set pair from JSON. - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_json - */ -template -const Self& set_json( - const char *json, - flecs::from_json_desc_t *desc = nullptr) const -{ - return set_json( - _::type::id(world_), - _::type::id(world_), - json, desc); -} - -/** Set pair from JSON. - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_json - */ -template -const Self& set_json( - flecs::entity_t t, - const char *json, - flecs::from_json_desc_t *desc = nullptr) const -{ - return set_json( - _::type::id(world_), t, - json, desc); -} - -/** Set pair from JSON. - * - * @memberof flecs::entity_builder - * @ingroup cpp_addons_json - */ -template -const Self& set_json_second( - flecs::entity_t r, - const char *json, - flecs::from_json_desc_t *desc = nullptr) const -{ - return set_json( - r, _::type::id(world_), - json, desc); -} - -# endif - -/** - * @file addons/cpp/mixins/event/entity_builder.inl - * @brief Event entity mixin. - */ - -/** Observe event on entity - * - * @memberof flecs::entity_builder - * - * @param evt The event id. - * @param callback The observer callback. - * @return Event builder. - */ -template -const Self& observe(flecs::entity_t evt, Func&& callback) const; - -/** Observe event on entity - * - * @memberof flecs::entity_builder - * - * @tparam Evt The event type. - * @param callback The observer callback. - * @return Event builder. - */ -template -const Self& observe(Func&& callback) const; - -/** Observe event on entity - * - * @memberof flecs::entity_builder - * - * @param callback The observer callback. - * @return Event builder. - */ -template -const Self& observe(Func&& callback) const; - - - - -protected: - const Self& to_base() const { - return *static_cast(this); - } -}; - -} - - -/** - * @defgroup cpp_entities Entities - * @ingroup cpp_core - * Entity operations. - * - * @{ - */ - -namespace flecs -{ - -/** Entity. - * Class with read/write operations for entities. - * - * @ingroup cpp_entities -*/ -struct entity : entity_builder -{ - entity() : entity_builder() { } - - /** Create entity. - * - * @param world The world in which to create the entity. - */ - explicit entity(world_t *world) - : entity_builder() - { - world_ = world; - if (!ecs_get_scope(world_) && !ecs_get_with(world_)) { - id_ = ecs_new(world); - } else { - ecs_entity_desc_t desc = {}; - id_ = ecs_entity_init(world_, &desc); - } - } - - /** Wrap an existing entity id. - * - * @param world The world in which the entity is created. - * @param id The entity id. - */ - explicit entity(const flecs::world_t *world, flecs::entity_t id) { - world_ = const_cast(world); - id_ = id; - } - - /** Create a named entity. - * Named entities can be looked up with the lookup functions. Entity names - * may be scoped, where each element in the name is separated by "::". - * For example: "Foo::Bar". If parts of the hierarchy in the scoped name do - * not yet exist, they will be automatically created. - * - * @param world The world in which to create the entity. - * @param name The entity name. - */ - explicit entity(world_t *world, const char *name) - : entity_builder() - { - world_ = world; - - ecs_entity_desc_t desc = {}; - desc.name = name; - desc.sep = "::"; - desc.root_sep = "::"; - id_ = ecs_entity_init(world, &desc); - } - - /** Conversion from flecs::entity_t to flecs::entity. - * - * @param id The entity_t value to convert. - */ - explicit entity(entity_t id) - : entity_builder( nullptr, id ) { } - - #ifndef ensure - - /** Get mutable component value. - * This operation returns a mutable pointer to the component. If the entity - * did not yet have the component, it will be added. If a base entity had - * the component, it will be overridden, and the value of the base component - * will be copied to the entity before this function returns. - * - * @tparam T The component to get. - * @return Pointer to the component value. - */ - template - T& ensure() const { - auto comp_id = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return *static_cast(ecs_ensure_id(world_, id_, comp_id)); - } - - /** Get mutable component value (untyped). - * This operation returns a mutable pointer to the component. If the entity - * did not yet have the component, it will be added. If a base entity had - * the component, it will be overridden, and the value of the base component - * will be copied to the entity before this function returns. - * - * @param comp The component to get. - * @return Pointer to the component value. - */ - void* ensure(entity_t comp) const { - return ecs_ensure_id(world_, id_, comp); - } - - /** Get mutable pointer for a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam First The first part of the pair. - * @tparam Second the second part of the pair. - */ - template , - typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> - A& ensure() const { - return *static_cast(ecs_ensure_id(world_, id_, ecs_pair( - _::type::id(world_), - _::type::id(world_)))); - } - - /** Get mutable pointer for the first element of a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam First The first part of the pair. - * @param second The second element of the pair. - */ - template - First& ensure(entity_t second) const { - auto first = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return *static_cast( - ecs_ensure_id(world_, id_, ecs_pair(first, second))); - } - - /** Get mutable pointer for a pair (untyped). - * This operation gets the value for a pair from the entity. If neither the - * first nor second element of the pair is a component, the operation will - * fail. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - void* ensure(entity_t first, entity_t second) const { - return ecs_ensure_id(world_, id_, ecs_pair(first, second)); - } - - /** Get mutable pointer for the second element of a pair. - * This operation gets the value for a pair from the entity. - * - * @tparam Second The second element of the pair. - * @param first The first element of the pair. - */ - template - Second& ensure_second(entity_t first) const { - auto second = _::type::id(world_); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, - ECS_INVALID_PARAMETER, "pair is not a component"); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, - ECS_INVALID_PARAMETER, "type of pair is not Second"); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - return *static_cast( - ecs_ensure_id(world_, id_, ecs_pair(first, second))); - } - - #endif - - /** Signal that component was modified. - * - * @tparam T component that was modified. - */ - template - void modified() const { - auto comp_id = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - this->modified(comp_id); - } - - /** Signal that the first element of a pair was modified. - * - * @tparam First The first part of the pair. - * @tparam Second the second part of the pair. - */ - template >> - void modified() const { - auto first = _::type::id(world_); - auto second = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - this->modified(first, second); - } - - /** Signal that the first part of a pair was modified. - * - * @tparam First The first part of the pair. - * @param second The second element of the pair. - */ - template - void modified(entity_t second) const { - auto first = _::type::id(world_); - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - this->modified(first, second); - } - - /** Signal that a pair has modified (untyped). - * If neither the first or second element of the pair are a component, the - * operation will fail. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - */ - void modified(entity_t first, entity_t second) const { - this->modified(ecs_pair(first, second)); - } - - /** Signal that component was modified. - * - * @param comp component that was modified. - */ - void modified(entity_t comp) const { - ecs_modified_id(world_, id_, comp); - } - - /** Get reference to component. - * A reference allows for quick and safe access to a component value, and is - * a faster alternative to repeatedly calling 'get' for the same component. - * - * @tparam T component for which to get a reference. - * @return The reference. - */ - template ::value > = 0> - ref get_ref() const { - return ref(world_, id_, _::type::id(world_)); - } - - /** Get reference to component. - * Overload for when T is not the same as the actual type, which happens - * when using pair types. - * A reference allows for quick and safe access to a component value, and is - * a faster alternative to repeatedly calling 'get' for the same component. - * - * @tparam T component for which to get a reference. - * @return The reference. - */ - template , if_t< flecs::is_pair::value > = 0> - ref get_ref() const { - return ref(world_, id_, - ecs_pair(_::type::id(world_), - _::type::id(world_))); - } - - - template , - typename A = actual_type_t

> - ref get_ref() const { - return ref(world_, id_, - ecs_pair(_::type::id(world_), _::type::id(world_))); - } - - template - ref get_ref(flecs::entity_t second) const { - auto first = _::type::id(world_); - return ref(world_, id_, ecs_pair(first, second)); - } - - template - ref get_ref_second(flecs::entity_t first) const { - auto second = _::type::id(world_); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second)) != NULL, - ECS_INVALID_PARAMETER, "pair is not a component"); - ecs_assert( ecs_get_type_info(world_, ecs_pair(first, second))->component == second, - ECS_INVALID_PARAMETER, "type of pair is not Second"); - return ref(world_, id_, ecs_pair(first, second)); - } - - /** Clear an entity. - * This operation removes all components from an entity without recycling - * the entity id. - * - * @see ecs_clear() - */ - void clear() const { - ecs_clear(world_, id_); - } - - /** Delete an entity. - * Entities have to be deleted explicitly, and are not deleted when the - * entity object goes out of scope. - * - * @see ecs_delete() - */ - void destruct() const { - ecs_delete(world_, id_); - } - - /** Return entity as entity_view. - * This returns an entity_view instance for the entity which is a readonly - * version of the entity class. - * - * This is similar to a regular upcast, except that this method ensures that - * the entity_view instance is instantiated with a world vs. a stage, which - * a regular upcast does not guarantee. - */ - flecs::entity_view view() const { - return flecs::entity_view( - const_cast(ecs_get_world(world_)), id_); - } - - /** Entity id 0. - * This function is useful when the API must provide an entity that - * belongs to a world, but the entity id is 0. - * - * @param world The world. - */ - static - flecs::entity null(const flecs::world_t *world) { - flecs::entity result; - result.world_ = const_cast(world); - return result; - } - - static - flecs::entity null() { - return flecs::entity(); - } - -# ifdef FLECS_JSON - -/** Deserialize entity to JSON. - * - * @memberof flecs::entity - * @ingroup cpp_addons_json - */ -const char* from_json(const char *json) { - return ecs_entity_from_json(world_, id_, json, nullptr); -} - -# endif -}; - -} // namespace flecs - -/** @} */ - -/** - * @file addons/cpp/delegate.hpp - * @brief Wrappers around C++ functions that provide callbacks for C APIs. - */ - -#pragma once - -#include // std::declval - -namespace flecs -{ - -namespace _ -{ - -// Binding ctx for component hooks -struct component_binding_ctx { - void *on_add = nullptr; - void *on_remove = nullptr; - void *on_set = nullptr; - ecs_ctx_free_t free_on_add = nullptr; - ecs_ctx_free_t free_on_remove = nullptr; - ecs_ctx_free_t free_on_set = nullptr; - - ~component_binding_ctx() { - if (on_add && free_on_add) { - free_on_add(on_add); - } - if (on_remove && free_on_remove) { - free_on_remove(on_remove); - } - if (on_set && free_on_set) { - free_on_set(on_set); - } - } -}; - -// Utility to convert template argument pack to array of term ptrs -struct field_ptr { - void *ptr = nullptr; - int8_t index = 0; - bool is_ref = false; - bool is_row = false; -}; - -template -struct field_ptrs { - using array = flecs::array<_::field_ptr, sizeof...(Components)>; - - void populate(const ecs_iter_t *iter) { - populate(iter, 0, static_cast< - remove_reference_t< - remove_pointer_t> - *>(nullptr)...); - } - - void populate_self(const ecs_iter_t *iter) { - populate_self(iter, 0, static_cast< - remove_reference_t< - remove_pointer_t> - *>(nullptr)...); - } - - array fields_; - -private: - void populate(const ecs_iter_t*, size_t) { } - - template >, - if_not_t< is_empty::value > = 0> - void populate(const ecs_iter_t *iter, size_t index, T, Targs... comps) { - if (iter->row_fields & (1llu << index)) { - /* Need to fetch the value with ecs_field_at() */ - fields_[index].is_row = true; - fields_[index].is_ref = true; - fields_[index].index = static_cast(index); - } else { - fields_[index].ptr = ecs_field_w_size(iter, sizeof(A), - static_cast(index)); - fields_[index].is_ref = iter->sources[index] != 0; - } - - populate(iter, index + 1, comps ...); - } - - template >, - if_t< is_empty::value > = 0> - void populate(const ecs_iter_t *iter, size_t index, T, Targs... comps) { - populate(iter, index + 1, comps ...); - } - - void populate_self(const ecs_iter_t*, size_t) { } - - template >, - if_not_t< is_empty::value > = 0> - void populate_self(const ecs_iter_t *iter, size_t index, T, Targs... comps) { - fields_[index].ptr = ecs_field_w_size(iter, sizeof(A), - static_cast(index)); - fields_[index].is_ref = false; - ecs_assert(iter->sources[index] == 0, ECS_INTERNAL_ERROR, NULL); - populate_self(iter, index + 1, comps ...); - } - - template >, - if_t< is_empty::value > = 0> - void populate_self(const ecs_iter_t *iter, size_t index, T, Targs... comps) { - populate(iter, index + 1, comps ...); - } -}; - -struct delegate { }; - -// Template that figures out from the template parameters of a query/system -// how to pass the value to the each callback -template -struct each_field { }; - -// Base class -struct each_column_base { - each_column_base(const _::field_ptr& field, size_t row) - : field_(field), row_(row) { - } - -protected: - const _::field_ptr& field_; - size_t row_; -}; - -// If type is not a pointer, return a reference to the type (default case) -template -struct each_field::value && - !is_empty>::value && is_actual::value > > - : each_column_base -{ - each_field(const flecs::iter_t*, _::field_ptr& field, size_t row) - : each_column_base(field, row) { } - - T& get_row() { - return static_cast(this->field_.ptr)[this->row_]; - } -}; - -// If argument type is not the same as actual component type, return by value. -// This requires that the actual type can be converted to the type. -// A typical scenario where this happens is when using flecs::pair types. -template -struct each_field::value && - !is_empty>::value && !is_actual::value> > - : each_column_base -{ - each_field(const flecs::iter_t*, _::field_ptr& field, size_t row) - : each_column_base(field, row) { } - - T get_row() { - return static_cast*>(this->field_.ptr)[this->row_]; - } -}; - -// If type is empty (indicating a tag) the query will pass a nullptr. To avoid -// returning nullptr to reference arguments, return a temporary value. -template -struct each_field>::value && - !is_pointer::value > > - : each_column_base -{ - each_field(const flecs::iter_t*, _::field_ptr& field, size_t row) - : each_column_base(field, row) { } - - T get_row() { - return actual_type_t(); - } -}; - -// If type is a pointer (indicating an optional value) don't index with row if -// the field is not set. -template -struct each_field::value && - !is_empty>::value > > - : each_column_base -{ - each_field(const flecs::iter_t*, _::field_ptr& field, size_t row) - : each_column_base(field, row) { } - - actual_type_t get_row() { - if (this->field_.ptr) { - return &static_cast>(this->field_.ptr)[this->row_]; - } else { - // optional argument doesn't have a value - return nullptr; - } - } -}; - -// If the query contains component references to other entities, check if the -// current argument is one. -template -struct each_ref_field : public each_field { - each_ref_field(const flecs::iter_t *iter, _::field_ptr& field, size_t row) - : each_field(iter, field, row) { - - if (field.is_ref) { - // If this is a reference, set the row to 0 as a ref always is a - // single value, not an array. This prevents the application from - // having to do an if-check on whether the column is owned. - // - // This check only happens when the current table being iterated - // over caused the query to match a reference. The check is - // performed once per iterated table. - this->row_ = 0; - } - - if (field.is_row) { - field.ptr = ecs_field_at_w_size(iter, sizeof(T), field.index, - static_cast(row)); - } - } -}; - -// Type that handles passing components to each callbacks -template -struct each_delegate : public delegate { - using Terms = typename field_ptrs::array; - - template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> - explicit each_delegate(Func&& func) noexcept - : func_(FLECS_MOV(func)) { } - - explicit each_delegate(const Func& func) noexcept - : func_(func) { } - - // Invoke object directly. This operation is useful when the calling - // function has just constructed the delegate, such as what happens when - // iterating a query. - void invoke(ecs_iter_t *iter) const { - field_ptrs terms; - - iter->flags |= EcsIterCppEach; - - if (iter->ref_fields | iter->up_fields) { - terms.populate(iter); - invoke_unpack< each_ref_field >(iter, func_, 0, terms.fields_); - } else { - terms.populate_self(iter); - invoke_unpack< each_field >(iter, func_, 0, terms.fields_); - } - } - - // Static function that can be used as callback for systems/triggers - static void run(ecs_iter_t *iter) { - auto self = static_cast(iter->callback_ctx); - ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); - self->invoke(iter); - } - - // Create instance of delegate - static each_delegate* make(const Func& func) { - return FLECS_NEW(each_delegate)(func); - } - - // Function that can be used as callback to free delegate - static void destruct(void *obj) { - _::free_obj(obj); - } - - // Static function to call for component on_add hook - static void run_add(ecs_iter_t *iter) { - component_binding_ctx *ctx = reinterpret_cast( - iter->callback_ctx); - iter->callback_ctx = ctx->on_add; - run(iter); - } - - // Static function to call for component on_remove hook - static void run_remove(ecs_iter_t *iter) { - component_binding_ctx *ctx = reinterpret_cast( - iter->callback_ctx); - iter->callback_ctx = ctx->on_remove; - run(iter); - } - - // Static function to call for component on_set hook - static void run_set(ecs_iter_t *iter) { - component_binding_ctx *ctx = reinterpret_cast( - iter->callback_ctx); - iter->callback_ctx = ctx->on_set; - run(iter); - } - -private: - // func(flecs::entity, Components...) - template class ColumnType, - typename... Args, - typename Fn = Func, - decltype(std::declval()( - std::declval(), - std::declval > >().get_row()...), 0) = 0> - static void invoke_callback( - ecs_iter_t *iter, const Func& func, size_t i, Args... comps) - { - func(flecs::entity(iter->world, iter->entities[i]), - (ColumnType< remove_reference_t >(iter, comps, i) - .get_row())...); - } - - // func(flecs::iter&, size_t row, Components...) - template class ColumnType, - typename... Args, - typename Fn = Func, - decltype(std::declval()( - std::declval(), - std::declval(), - std::declval > >().get_row()...), 0) = 0> - static void invoke_callback( - ecs_iter_t *iter, const Func& func, size_t i, Args... comps) - { - flecs::iter it(iter); - func(it, i, (ColumnType< remove_reference_t >(iter, comps, i) - .get_row())...); - } - - // func(Components...) - template class ColumnType, - typename... Args, - typename Fn = Func, - decltype(std::declval()( - std::declval > >().get_row()...), 0) = 0> - static void invoke_callback( - ecs_iter_t *iter, const Func& func, size_t i, Args... comps) - { - func((ColumnType< remove_reference_t >(iter, comps, i) - .get_row())...); - } - - template class ColumnType, - typename... Args, if_t< - sizeof...(Components) == sizeof...(Args)> = 0> - static void invoke_unpack( - ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) - { - ECS_TABLE_LOCK(iter->world, iter->table); - - size_t count = static_cast(iter->count); - if (count == 0 && !iter->table) { - // If query has no This terms, count can be 0. Since each does not - // have an entity parameter, just pass through components - count = 1; - } - - for (size_t i = 0; i < count; i ++) { - invoke_callback(iter, func, i, comps...); - } - - ECS_TABLE_UNLOCK(iter->world, iter->table); - } - - template class ColumnType, - typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> - static void invoke_unpack(ecs_iter_t *iter, const Func& func, - size_t index, Terms& columns, Args... comps) - { - invoke_unpack( - iter, func, index + 1, columns, comps..., columns[index]); - } - -public: - Func func_; -}; - -template -struct find_delegate : public delegate { - using Terms = typename field_ptrs::array; - - template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> - explicit find_delegate(Func&& func) noexcept - : func_(FLECS_MOV(func)) { } - - explicit find_delegate(const Func& func) noexcept - : func_(func) { } - - // Invoke object directly. This operation is useful when the calling - // function has just constructed the delegate, such as what happens when - // iterating a query. - flecs::entity invoke(ecs_iter_t *iter) const { - field_ptrs terms; - - iter->flags |= EcsIterCppEach; - - if (iter->ref_fields | iter->up_fields) { - terms.populate(iter); - return invoke_callback< each_ref_field >(iter, func_, 0, terms.fields_); - } else { - terms.populate_self(iter); - return invoke_callback< each_field >(iter, func_, 0, terms.fields_); - } - } - -private: - // Number of function arguments is one more than number of components, pass - // entity as argument. - template class ColumnType, - typename... Args, - typename Fn = Func, - if_t = 0, - decltype(bool(std::declval()( - std::declval(), - std::declval > >().get_row()...))) = true> - static flecs::entity invoke_callback( - ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) - { - ECS_TABLE_LOCK(iter->world, iter->table); - - ecs_world_t *world = iter->world; - size_t count = static_cast(iter->count); - flecs::entity result; - - for (size_t i = 0; i < count; i ++) { - if (func(flecs::entity(world, iter->entities[i]), - (ColumnType< remove_reference_t >(iter, comps, i) - .get_row())...)) - { - result = flecs::entity(world, iter->entities[i]); - break; - } - } - - ECS_TABLE_UNLOCK(iter->world, iter->table); - - return result; - } - - // Number of function arguments is two more than number of components, pass - // iter + index as argument. - template class ColumnType, - typename... Args, - typename Fn = Func, - if_t = 0, - decltype(bool(std::declval()( - std::declval(), - std::declval(), - std::declval > >().get_row()...))) = true> - static flecs::entity invoke_callback( - ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) - { - size_t count = static_cast(iter->count); - if (count == 0) { - // If query has no This terms, count can be 0. Since each does not - // have an entity parameter, just pass through components - count = 1; - } - - flecs::iter it(iter); - flecs::entity result; - - ECS_TABLE_LOCK(iter->world, iter->table); - - for (size_t i = 0; i < count; i ++) { - if (func(it, i, - (ColumnType< remove_reference_t >(iter, comps, i) - .get_row())...)) - { - result = flecs::entity(iter->world, iter->entities[i]); - break; - } - } - - ECS_TABLE_UNLOCK(iter->world, iter->table); - - return result; - } - - // Number of function arguments is equal to number of components, no entity - template class ColumnType, - typename... Args, - typename Fn = Func, - if_t = 0, - decltype(bool(std::declval()( - std::declval > >().get_row()...))) = true> - static flecs::entity invoke_callback( - ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps) - { - size_t count = static_cast(iter->count); - if (count == 0) { - // If query has no This terms, count can be 0. Since each does not - // have an entity parameter, just pass through components - count = 1; - } - - flecs::iter it(iter); - flecs::entity result; - - ECS_TABLE_LOCK(iter->world, iter->table); - - for (size_t i = 0; i < count; i ++) { - if (func( - (ColumnType< remove_reference_t >(iter, comps, i) - .get_row())...)) - { - result = flecs::entity(iter->world, iter->entities[i]); - break; - } - } - - ECS_TABLE_UNLOCK(iter->world, iter->table); - - return result; - } - - template class ColumnType, - typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0> - static flecs::entity invoke_callback(ecs_iter_t *iter, const Func& func, - size_t index, Terms& columns, Args... comps) - { - return invoke_callback( - iter, func, index + 1, columns, comps..., columns[index]); - } - - Func func_; -}; - -//////////////////////////////////////////////////////////////////////////////// -//// Utility class to invoke a system iterate action -//////////////////////////////////////////////////////////////////////////////// - -template -struct run_delegate : delegate { - template < if_not_t< is_same< decay_t, decay_t& >::value > = 0> - explicit run_delegate(Func&& func) noexcept - : func_(FLECS_MOV(func)) { } - - explicit run_delegate(const Func& func) noexcept - : func_(func) { } - - // Invoke object directly. This operation is useful when the calling - // function has just constructed the delegate, such as what happens when - // iterating a query. - void invoke(ecs_iter_t *iter) const { - flecs::iter it(iter); - iter->flags &= ~EcsIterIsValid; - func_(it); - } - - // Static function that can be used as callback for systems/triggers - static void run(ecs_iter_t *iter) { - auto self = static_cast(iter->run_ctx); - ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); - self->invoke(iter); - } - - Func func_; -}; - - -//////////////////////////////////////////////////////////////////////////////// -//// Utility class to invoke an entity observer delegate -//////////////////////////////////////////////////////////////////////////////// - -template -struct entity_observer_delegate : delegate { - explicit entity_observer_delegate(Func&& func) noexcept - : func_(FLECS_MOV(func)) { } - - // Static function that can be used as callback for systems/triggers - static void run(ecs_iter_t *iter) { - invoke(iter); - } - -private: - template ()(std::declval()), 0) = 0> - static void invoke(ecs_iter_t *iter) { - auto self = static_cast(iter->callback_ctx); - ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); - self->func_(flecs::entity(iter->world, ecs_field_src(iter, 0))); - } - - template ()(), 0) = 0> - static void invoke(ecs_iter_t *iter) { - auto self = static_cast(iter->callback_ctx); - ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); - self->func_(); - } - - Func func_; -}; - -template -struct entity_payload_observer_delegate : delegate { - explicit entity_payload_observer_delegate(Func&& func) noexcept - : func_(FLECS_MOV(func)) { } - - // Static function that can be used as callback for systems/triggers - static void run(ecs_iter_t *iter) { - invoke(iter); - } - -private: - template ()( - std::declval()), 0) = 0> - static void invoke(ecs_iter_t *iter) { - auto self = static_cast( - iter->callback_ctx); - ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); - ecs_assert(iter->param != nullptr, ECS_INVALID_OPERATION, - "entity observer invoked without payload"); - - Event *data = static_cast(iter->param); - self->func_(*data); - } - - template ()( - std::declval(), - std::declval()), 0) = 0> - static void invoke(ecs_iter_t *iter) { - auto self = static_cast( - iter->callback_ctx); - ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL); - ecs_assert(iter->param != nullptr, ECS_INVALID_OPERATION, - "entity observer invoked without payload"); - - Event *data = static_cast(iter->param); - self->func_(flecs::entity(iter->world, ecs_field_src(iter, 0)), *data); - } - - Func func_; -}; - - -//////////////////////////////////////////////////////////////////////////////// -//// Utility to invoke callback on entity if it has components in signature -//////////////////////////////////////////////////////////////////////////////// - -template -struct entity_with_delegate_impl; - -template -struct entity_with_delegate_impl> { - using ColumnArray = flecs::array; - using ArrayType = flecs::array; - using DummyArray = flecs::array; - using IdArray = flecs::array; - - static bool const_args() { - static flecs::array is_const_args ({ - flecs::is_const>::value... - }); - - for (auto is_const : is_const_args) { - if (!is_const) { - return false; - } - } - return true; - } - - static - bool get_ptrs(world_t *world, flecs::entity_t e, const ecs_record_t *r, ecs_table_t *table, - ArrayType& ptrs) - { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (!ecs_table_column_count(table) && - !ecs_table_has_flags(table, EcsTableHasSparse)) - { - return false; - } - - /* table_index_of needs real world */ - const flecs::world_t *real_world = ecs_get_world(world); - - IdArray ids ({ - _::type().id(world)... - }); - - /* Get column indices for components */ - ColumnArray columns ({ - ecs_table_get_column_index(real_world, table, - _::type().id(world))... - }); - - /* Get pointers for columns for entity */ - size_t i = 0; - for (int32_t column : columns) { - if (column == -1) { - /* Component could be sparse */ - void *ptr = ecs_get_mut_id(world, e, ids[i]); - if (!ptr) { - return false; - } - - ptrs[i ++] = ptr; - continue; - } - - ptrs[i ++] = ecs_record_get_by_column(r, column, 0); - } - - return true; - } - - static bool ensure_ptrs(world_t *world, ecs_entity_t e, ArrayType& ptrs) { - /* Get pointers w/ensure */ - size_t i = 0; - DummyArray dummy ({ - (ptrs[i ++] = ecs_ensure_id(world, e, - _::type().id(world)), 0)... - }); - - return true; - } - - template - static bool invoke_read(world_t *world, entity_t e, const Func& func) { - const ecs_record_t *r = ecs_read_begin(world, e); - if (!r) { - return false; - } - - ecs_table_t *table = r->table; - if (!table) { - return false; - } - - ArrayType ptrs; - bool has_components = get_ptrs(world, e, r, table, ptrs); - if (has_components) { - invoke_callback(func, 0, ptrs); - } - - ecs_read_end(r); - - return has_components; - } - - template - static bool invoke_write(world_t *world, entity_t e, const Func& func) { - ecs_record_t *r = ecs_write_begin(world, e); - if (!r) { - return false; - } - - ecs_table_t *table = r->table; - if (!table) { - return false; - } - - ArrayType ptrs; - bool has_components = get_ptrs(world, e, r, table, ptrs); - if (has_components) { - invoke_callback(func, 0, ptrs); - } - - ecs_write_end(r); - - return has_components; - } - - template - static bool invoke_get(world_t *world, entity_t e, const Func& func) { - if (const_args()) { - return invoke_read(world, e, func); - } else { - return invoke_write(world, e, func); - } - } - - // Utility for storing id in array in pack expansion - static size_t store_added(IdArray& added, size_t elem, ecs_table_t *prev, - ecs_table_t *next, id_t id) - { - // Array should only contain ids for components that are actually added, - // so check if the prev and next tables are different. - if (prev != next) { - added[elem] = id; - elem ++; - } - return elem; - } - - template - static bool invoke_ensure(world_t *world, entity_t id, const Func& func) { - flecs::world w(world); - - ArrayType ptrs; - ecs_table_t *table = NULL; - - // When not deferred take the fast path. - if (!w.is_deferred()) { - // Bit of low level code so we only do at most one table move & one - // entity lookup for the entire operation. - - // Make sure the object is not a stage. Operations on a stage are - // only allowed when the stage is in deferred mode, which is when - // the world is in readonly mode. - ecs_assert(!w.is_stage(), ECS_INVALID_PARAMETER, NULL); - - // Find table for entity - ecs_record_t *r = ecs_record_find(world, id); - if (r) { - table = r->table; - } - - // Find destination table that has all components - ecs_table_t *prev = table, *next; - size_t elem = 0; - IdArray added; - - // Iterate components, only store added component ids in added array - DummyArray dummy_before ({ ( - next = ecs_table_add_id(world, prev, w.id()), - elem = store_added(added, elem, prev, next, w.id()), - prev = next, 0 - )... }); - - (void)dummy_before; - - // If table is different, move entity straight to it - if (table != next) { - ecs_type_t ids; - ids.array = added.ptr(); - ids.count = static_cast(elem); - ecs_commit(world, id, r, next, &ids, NULL); - table = next; - } - - if (!get_ptrs(w, id, r, table, ptrs)) { - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } - - ECS_TABLE_LOCK(world, table); - - // When deferred, obtain pointers with regular ensure - } else { - ensure_ptrs(world, id, ptrs); - } - - invoke_callback(func, 0, ptrs); - - if (!w.is_deferred()) { - ECS_TABLE_UNLOCK(world, table); - } - - // Call modified on each component - DummyArray dummy_after ({ - ( ecs_modified_id(world, id, w.id()), 0)... - }); - (void)dummy_after; - - return true; - } - -private: - template = 0> - static void invoke_callback( - const Func& f, size_t, ArrayType&, TArgs&& ... comps) - { - f(*static_cast::type*>(comps)...); - } - - template = 0> - static void invoke_callback(const Func& f, size_t arg, ArrayType& ptrs, - TArgs&& ... comps) - { - invoke_callback(f, arg + 1, ptrs, comps..., ptrs[arg]); - } -}; - -template -struct entity_with_delegate { - static_assert(function_traits::value, "type is not callable"); -}; - -template -struct entity_with_delegate::value > > - : entity_with_delegate_impl< arg_list_t > -{ - static_assert(function_traits::arity > 0, - "function must have at least one argument"); -}; - -} // namespace _ - -// Experimental: allows using the each delegate for use cases outside of flecs -template -using delegate = _::each_delegate::type, Args...>; - -} // namespace flecs - -/** - * @file addons/cpp/component.hpp - * @brief Registering/obtaining info from components. - */ - -#pragma once - -#include -#include - -/** - * @defgroup cpp_components Components - * @ingroup cpp_core - * Registering and working with components. - * - * @{ - */ - -namespace flecs { - -namespace _ { - -// Trick to obtain typename from type, as described here -// https://blog.molecular-matters.com/2015/12/11/getting-the-type-of-a-template-argument-as-string-without-rtti/ -// -// The code from the link has been modified to work with more types, and across -// multiple compilers. The resulting string should be the same on all platforms -// for all compilers. -// - -#if defined(__GNUC__) || defined(_WIN32) -template -inline const char* type_name() { - static const size_t len = ECS_FUNC_TYPE_LEN(const char*, type_name, ECS_FUNC_NAME); - static char result[len + 1] = {}; - static const size_t front_len = ECS_FUNC_NAME_FRONT(const char*, type_name); - static const char* cppTypeName = ecs_cpp_get_type_name(result, ECS_FUNC_NAME, len, front_len); - return cppTypeName; -} -#else -#error "implicit component registration not supported" -#endif - -// Translate a typename into a language-agnostic identifier. This allows for -// registration of components/modules across language boundaries. -template -inline const char* symbol_name() { - static const size_t len = ECS_FUNC_TYPE_LEN(const char*, symbol_name, ECS_FUNC_NAME); - static char result[len + 1] = {}; - static const char* cppSymbolName = ecs_cpp_get_symbol_name(result, type_name(), len); - return cppSymbolName; -} - -template <> inline const char* symbol_name() { - return "u8"; -} -template <> inline const char* symbol_name() { - return "u16"; -} -template <> inline const char* symbol_name() { - return "u32"; -} -template <> inline const char* symbol_name() { - return "u64"; -} -template <> inline const char* symbol_name() { - return "i8"; -} -template <> inline const char* symbol_name() { - return "i16"; -} -template <> inline const char* symbol_name() { - return "i32"; -} -template <> inline const char* symbol_name() { - return "i64"; -} -template <> inline const char* symbol_name() { - return "f32"; -} -template <> inline const char* symbol_name() { - return "f64"; -} - -// If type is trivial, don't register lifecycle actions. While the functions -// that obtain the lifecycle callback do detect whether the callback is required -// adding a special case for trivial types eases the burden a bit on the -// compiler as it reduces the number of templates to evaluate. -template::value == true - >* = nullptr> -void register_lifecycle_actions(ecs_world_t*, ecs_entity_t) { } - -// If the component is non-trivial, register component lifecycle actions. -// Depending on the type not all callbacks may be available. -template::value == false - >* = nullptr> -void register_lifecycle_actions( - ecs_world_t *world, - ecs_entity_t component) -{ - ecs_type_hooks_t cl{}; - cl.ctor = ctor(cl.flags); - cl.dtor = dtor(cl.flags); - - cl.copy = copy(cl.flags); - cl.copy_ctor = copy_ctor(cl.flags); - cl.move = move(cl.flags); - cl.move_ctor = move_ctor(cl.flags); - - cl.ctor_move_dtor = ctor_move_dtor(cl.flags); - cl.move_dtor = move_dtor(cl.flags); - - ecs_set_hooks_id(world, component, &cl); - - if (cl.flags & (ECS_TYPE_HOOK_MOVE_ILLEGAL|ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL)) - { - ecs_add_id(world, component, flecs::Sparse); - } -} - -template -struct type_impl { - static_assert(is_pointer::value == false, - "pointer types are not allowed for components"); - - // Initialize component identifier - static void init( - bool allow_tag = true) - { - s_index = flecs_component_ids_index_get(); - s_allow_tag = allow_tag; - s_size = sizeof(T); - s_alignment = alignof(T); - if (is_empty::value && allow_tag) { - s_size = 0; - s_alignment = 0; - } - } - - static void init_builtin( - flecs::world_t *world, - flecs::entity_t id, - bool allow_tag = true) - { - init(allow_tag); - flecs_component_ids_set(world, s_index, id); - } - - // Register component id. - static entity_t register_id(world_t *world, - const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0, - bool is_component = false, bool implicit_name = true, const char *n = nullptr, - flecs::entity_t module = 0) - { - if (!s_index) { - // This is the first time (in this binary image) that this type is - // being used. Generate a static index that will identify the type - // across worlds. - init(allow_tag); - ecs_assert(s_index != 0, ECS_INTERNAL_ERROR, NULL); - } - - flecs::entity_t c = flecs_component_ids_get(world, s_index); - - if (!c || !ecs_is_alive(world, c)) { - // 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. - ecs_entity_t prev_scope = ecs_set_scope(world, module); - ecs_entity_t prev_with = ecs_set_with(world, 0); - - // 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 existing = false; - c = ecs_cpp_component_find( - world, id, n, symbol_name(), size(), alignment(), - implicit_name, &existing); - - const char *symbol = nullptr; - if (c) { - symbol = ecs_get_symbol(world, c); - } - if (!symbol) { - symbol = symbol_name(); - } - - c = ecs_cpp_component_register(world, c, c, name, type_name(), - symbol, size(), alignment(), is_component, &existing); - - ecs_set_with(world, prev_with); - ecs_set_scope(world, prev_scope); - - // Register lifecycle callbacks, but only if the component has a - // size. Components that don't have a size are tags, and tags don't - // require construction/destruction/copy/move's. - if (size() && !existing) { - register_lifecycle_actions(world, c); - } - - // Set world local component id - flecs_component_ids_set(world, s_index, c); - - // If component is enum type, register constants. Make sure to do - // this after setting the component id, because the enum code will - // be calling type::id(). - #if FLECS_CPP_ENUM_REFLECTION_SUPPORT - _::init_enum(world, c); - #endif - } - - ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); - - return c; - } - - // Get type (component) id. - // If type was not yet registered and automatic registration is allowed, - // this function will also register the type. - static entity_t id(world_t *world) - { -#ifdef FLECS_CPP_NO_AUTO_REGISTRATION - ecs_assert(registered(world), ECS_INVALID_OPERATION, - "component '%s' must be registered before use", - type_name()); - - flecs::entity_t c = flecs_component_ids_get(world, s_index); - ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_is_alive(world, c), ECS_INVALID_OPERATION, - "component '%s' was deleted, reregister before using", - type_name()); -#else - flecs::entity_t c = flecs_component_ids_get_alive(world, s_index); - if (!c) { - c = register_id(world); - } -#endif - return c; - } - - // Return the size of a component. - static size_t size() { - ecs_assert(s_index != 0, ECS_INTERNAL_ERROR, NULL); - return s_size; - } - - // Return the alignment of a component. - static size_t alignment() { - ecs_assert(s_index != 0, ECS_INTERNAL_ERROR, NULL); - return s_alignment; - } - - // Was the component already registered. - static bool registered(flecs::world_t *world) { - ecs_assert(world != nullptr, ECS_INVALID_PARAMETER, NULL); - - if (s_index == 0) { - return false; - } - - if (!flecs_component_ids_get(world, s_index)) { - return false; - } - - return true; - } - - // This function is only used to test cross-translation unit features. No - // code other than test cases should invoke this function. - static void reset() { - s_index = 0; - s_size = 0; - s_alignment = 0; - s_allow_tag = true; - } - - static int32_t s_index; - static size_t s_size; - static size_t s_alignment; - static bool s_allow_tag; -}; - -// Global templated variables that hold component identifier and other info -template int32_t type_impl::s_index; -template size_t type_impl::s_size; -template size_t type_impl::s_alignment; -template bool type_impl::s_allow_tag( true ); - -// Front facing class for implicitly registering a component & obtaining -// static component data - -// Regular type -template -struct type::value >> - : type_impl> { }; - -// Pair type -template -struct type::value >> -{ - // Override id method to return id of pair - static id_t id(world_t *world = nullptr) { - return ecs_pair( - type< pair_first_t >::id(world), - type< pair_second_t >::id(world)); - } -}; - -} // namespace _ - -/** Untyped component class. - * Generic base class for flecs::component. - * - * @ingroup cpp_components - */ -struct untyped_component : entity { - using entity::entity; - -# ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/untyped_component.inl - * @brief Meta component mixin. - */ - -/** - * @memberof flecs::component - * @ingroup cpp_addons_meta - * - * @{ - */ - -private: - -/** Private method that adds member to component. */ -untyped_component& internal_member( - flecs::entity_t type_id, - flecs::entity_t unit, - const char *name, - int32_t count = 0, - size_t offset = 0, - bool use_offset = false) -{ - ecs_entity_desc_t desc = {}; - desc.name = name; - desc.parent = id_; - ecs_entity_t eid = ecs_entity_init(world_, &desc); - ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); - - flecs::entity e(world_, eid); - - Member m = {}; - m.type = type_id; - m.unit = unit; - m.count = count; - m.offset = static_cast(offset); - m.use_offset = use_offset; - e.set(m); - - return *this; -} - -public: - -/** Add member with unit. */ -untyped_component& member( - flecs::entity_t type_id, - flecs::entity_t unit, - const char *name, - int32_t count = 0) -{ - return internal_member(type_id, unit, name, count, 0, false); -} - -/** Add member with unit, count and offset. */ -untyped_component& member( - flecs::entity_t type_id, - flecs::entity_t unit, - const char *name, - int32_t count, - size_t offset) -{ - return internal_member(type_id, unit, name, count, offset, true); -} - -/** Add member. */ -untyped_component& member( - flecs::entity_t type_id, - const char* name, - int32_t count = 0) -{ - return member(type_id, 0, name, count); -} - -/** Add member with count and offset. */ -untyped_component& member( - flecs::entity_t type_id, - const char* name, - int32_t count, - size_t offset) -{ - return member(type_id, 0, name, count, offset); -} - -/** Add member. */ -template -untyped_component& member( - const char *name, - int32_t count = 0) -{ - flecs::entity_t type_id = _::type::id(world_); - return member(type_id, name, count); -} - -/** Add member. */ -template -untyped_component& member( - const char *name, - int32_t count, - size_t offset) -{ - flecs::entity_t type_id = _::type::id(world_); - return member(type_id, name, count, offset); -} - -/** Add member with unit. */ -template -untyped_component& member( - flecs::entity_t unit, - const char *name, - int32_t count = 0) -{ - flecs::entity_t type_id = _::type::id(world_); - return member(type_id, unit, name, count); -} - -/** Add member with unit. */ -template -untyped_component& member( - flecs::entity_t unit, - const char *name, - int32_t count, - size_t offset) -{ - flecs::entity_t type_id = _::type::id(world_); - return member(type_id, unit, name, count, offset); -} - -/** Add member with unit. */ -template -untyped_component& member( - const char *name, - int32_t count = 0) -{ - flecs::entity_t type_id = _::type::id(world_); - flecs::entity_t unit_id = _::type::id(world_); - return member(type_id, unit_id, name, count); -} - -/** Add member with unit. */ -template -untyped_component& member( - const char *name, - int32_t count, - size_t offset) -{ - flecs::entity_t type_id = _::type::id(world_); - flecs::entity_t unit_id = _::type::id(world_); - return member(type_id, unit_id, name, count, offset); -} - -/** Add member using pointer-to-member. */ -template ::type> -untyped_component& member( - const char* name, - const MemberType ComponentType::* ptr) -{ - flecs::entity_t type_id = _::type::id(world_); - size_t offset = reinterpret_cast(&(static_cast(nullptr)->*ptr)); - return member(type_id, name, std::extent::value, offset); -} - -/** Add member with unit using pointer-to-member. */ -template ::type> -untyped_component& member( - flecs::entity_t unit, - const char* name, - const MemberType ComponentType::* ptr) -{ - flecs::entity_t type_id = _::type::id(world_); - size_t offset = reinterpret_cast(&(static_cast(nullptr)->*ptr)); - return member(type_id, unit, name, std::extent::value, offset); -} - -/** Add member with unit using pointer-to-member. */ -template ::type> -untyped_component& member( - const char* name, - const MemberType ComponentType::* ptr) -{ - flecs::entity_t type_id = _::type::id(world_); - flecs::entity_t unit_id = _::type::id(world_); - size_t offset = reinterpret_cast(&(static_cast(nullptr)->*ptr)); - return member(type_id, unit_id, name, std::extent::value, offset); -} - -/** Add constant. */ -untyped_component& constant( - const char *name, - int32_t value) -{ - ecs_add_id(world_, id_, _::type::id(world_)); - - ecs_entity_desc_t desc = {}; - desc.name = name; - desc.parent = id_; - ecs_entity_t eid = ecs_entity_init(world_, &desc); - ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_set_id(world_, eid, - ecs_pair(flecs::Constant, flecs::I32), sizeof(int32_t), - &value); - - return *this; -} - -/** Add bitmask constant. */ -untyped_component& bit( - const char *name, - uint32_t value) -{ - ecs_add_id(world_, id_, _::type::id(world_)); - - ecs_entity_desc_t desc = {}; - desc.name = name; - desc.parent = id_; - ecs_entity_t eid = ecs_entity_init(world_, &desc); - ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_set_id(world_, eid, - ecs_pair(flecs::Constant, flecs::U32), sizeof(uint32_t), - &value); - - return *this; -} - -/** Register array metadata for component */ -template -untyped_component& array( - int32_t elem_count) -{ - ecs_array_desc_t desc = {}; - desc.entity = id_; - desc.type = _::type::id(world_); - desc.count = elem_count; - ecs_array_init(world_, &desc); - return *this; -} - -/** Add member value range */ -untyped_component& range( - double min, - double max) -{ - const flecs::member_t *m = ecs_cpp_last_member(world_, id_); - if (!m) { - return *this; - } - - flecs::world w(world_); - flecs::entity me = w.entity(m->member); - - // Don't use C++ ensure because Unreal defines a macro called ensure - flecs::MemberRanges *mr = static_cast( - ecs_ensure_id(w, me, w.id())); - mr->value.min = min; - mr->value.max = max; - me.modified(); - return *this; -} - -/** Add member warning range */ -untyped_component& warning_range( - double min, - double max) -{ - const flecs::member_t *m = ecs_cpp_last_member(world_, id_); - if (!m) { - return *this; - } - - flecs::world w(world_); - flecs::entity me = w.entity(m->member); - - // Don't use C++ ensure because Unreal defines a macro called ensure - flecs::MemberRanges *mr = static_cast( - ecs_ensure_id(w, me, w.id())); - mr->warning.min = min; - mr->warning.max = max; - me.modified(); - return *this; -} - -/** Add member error range */ -untyped_component& error_range( - double min, - double max) -{ - const flecs::member_t *m = ecs_cpp_last_member(world_, id_); - if (!m) { - return *this; - } - - flecs::world w(world_); - flecs::entity me = w.entity(m->member); - - // Don't use C++ ensure because Unreal defines a macro called ensure - flecs::MemberRanges *mr = static_cast(ecs_ensure_id( - w, me, w.id())); - mr->error.min = min; - mr->error.max = max; - me.modified(); - return *this; -} - -/** @} */ - -# endif -# ifdef FLECS_METRICS -/** - * @file addons/cpp/mixins/meta/untyped_component.inl - * @brief Metrics component mixin. - */ - -/** - * @memberof flecs::component - * @ingroup cpp_addons_metrics - * - * @{ - */ - -/** Register member as metric. - * When no explicit name is provided, this operation will derive the metric name - * from the member name. When the member name is "value", the operation will use - * the name of the component. - * - * When the brief parameter is provided, it is set on the metric as if - * set_doc_brief is used. The brief description can be obtained with - * get_doc_brief. - * - * @tparam Kind Metric kind (Counter, CounterIncrement or Gauge). - * @param parent Parent entity of the metric (optional). - * @param brief Description for metric (optional). - * @param name Name of metric (optional). - */ -template -untyped_component& metric( - flecs::entity_t parent = 0, - const char *brief = nullptr, - const char *name = nullptr); - -/** @} */ - -# endif -}; - -/** Component class. - * Class used to register components and component metadata. - * - * @ingroup cpp_components - */ -template -struct component : untyped_component { - /** Register a component. - * If the component was already registered, this operation will return a handle - * to the existing component. - * - * @param world The world for which to register the component. - * @param name Optional name (overrides typename). - * @param allow_tag If true, empty types will be registered with size 0. - * @param id Optional id to register component with. - */ - component( - flecs::world_t *world, - const char *name = nullptr, - bool allow_tag = true, - flecs::id_t id = 0) - { - const char *n = name; - bool implicit_name = false; - if (!n) { - n = _::type_name(); - - // Keep track of whether name was explicitly set. If not, and the - // component was already registered, just use the registered name. - // The registered name may differ from the typename as the registered - // name includes the flecs scope. This can in theory be different from - // the C++ namespace though it is good practice to keep them the same */ - implicit_name = true; - } - - // If component is registered by module, ensure it's registered in the - // scope of the module. - flecs::entity_t 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(n, '<'), *last_elem = NULL; - if (start) { - const char *ptr = start; - while (ptr[0] && (ptr[0] != ':') && (ptr > n)) { - ptr --; - } - if (ptr[0] == ':') { - last_elem = ptr; - } - } - - if (last_elem) { - name = last_elem + 1; - } - } - - world_ = world; - id_ = _::type::register_id( - world, name, allow_tag, id, true, implicit_name, n, module); - } - - /** Register on_add hook. */ - template - component& on_add(Func&& func) { - using Delegate = typename _::each_delegate::type, T>; - flecs::type_hooks_t h = get_hooks(); - ecs_assert(h.on_add == nullptr, ECS_INVALID_OPERATION, - "on_add hook is already set"); - BindingCtx *ctx = get_binding_ctx(h); - h.on_add = Delegate::run_add; - ctx->on_add = FLECS_NEW(Delegate)(FLECS_FWD(func)); - ctx->free_on_add = _::free_obj; - ecs_set_hooks_id(world_, id_, &h); - return *this; - } - - /** Register on_remove hook. */ - template - component& on_remove(Func&& func) { - using Delegate = typename _::each_delegate< - typename std::decay::type, T>; - flecs::type_hooks_t h = get_hooks(); - ecs_assert(h.on_remove == nullptr, ECS_INVALID_OPERATION, - "on_remove hook is already set"); - BindingCtx *ctx = get_binding_ctx(h); - h.on_remove = Delegate::run_remove; - ctx->on_remove = FLECS_NEW(Delegate)(FLECS_FWD(func)); - ctx->free_on_remove = _::free_obj; - ecs_set_hooks_id(world_, id_, &h); - return *this; - } - - /** Register on_set hook. */ - template - component& on_set(Func&& func) { - using Delegate = typename _::each_delegate< - typename std::decay::type, T>; - flecs::type_hooks_t h = get_hooks(); - ecs_assert(h.on_set == nullptr, ECS_INVALID_OPERATION, - "on_set hook is already set"); - BindingCtx *ctx = get_binding_ctx(h); - h.on_set = Delegate::run_set; - ctx->on_set = FLECS_NEW(Delegate)(FLECS_FWD(func)); - ctx->free_on_set = _::free_obj; - ecs_set_hooks_id(world_, id_, &h); - return *this; - } - -# ifdef FLECS_META - -/** Register opaque type interface */ -template -component& opaque(const Func& type_support) { - flecs::world world(world_); - auto ts = type_support(world); - ts.desc.entity = _::type::id(world_); - ecs_opaque_init(world_, &ts.desc); - return *this; -} - -flecs::opaque opaque(flecs::entity_t as_type) { - return flecs::opaque(world_).as_type(as_type); -} - -flecs::opaque opaque(flecs::entity as_type) { - return this->opaque(as_type.id()); -} - -flecs::opaque opaque(flecs::untyped_component as_type) { - return this->opaque(as_type.id()); -} - -/** Return opaque type builder for collection type */ -template -flecs::opaque opaque(flecs::id_t as_type) { - return flecs::opaque(world_).as_type(as_type); -} - -/** Add constant. */ -component& constant(const char *name, T value) { - using U = typename std::underlying_type::type; - - ecs_add_id(world_, id_, _::type::id(world_)); - - ecs_entity_desc_t desc = {}; - desc.name = name; - desc.parent = id_; - ecs_entity_t eid = ecs_entity_init(world_, &desc); - ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); - - flecs::id_t pair = ecs_pair(flecs::Constant, _::type::id(world_)); - U *ptr = static_cast(ecs_ensure_id(world_, eid, pair)); - *ptr = static_cast(value); - ecs_modified_id(world_, eid, pair); - - return *this; -} - -# endif - -private: - using BindingCtx = _::component_binding_ctx; - - BindingCtx* get_binding_ctx(flecs::type_hooks_t& h){ - BindingCtx *result = static_cast(h.binding_ctx); - if (!result) { - result = FLECS_NEW(BindingCtx); - h.binding_ctx = result; - h.binding_ctx_free = _::free_obj; - } - return result; - } - - flecs::type_hooks_t get_hooks() { - const flecs::type_hooks_t* h = ecs_get_hooks_id(world_, id_); - if (h) { - return *h; - } else { - return {}; - } - } -}; - -} - -/** @} */ - -/** - * @file addons/cpp/ref.hpp - * @brief Class that caches data to speedup get operations. - */ - -#pragma once - -namespace flecs -{ - -/** - * @defgroup cpp_ref Refs - * @ingroup cpp_core - * Refs are a fast mechanism for referring to a specific entity/component. - * - * @{ - */ - -/** Component reference. - * Reference to a component from a specific entity. - */ -template -struct ref { - ref() : world_(nullptr), ref_{} { } - - ref(world_t *world, entity_t entity, flecs::id_t id = 0) - : ref_() - { - // the world we were called with may be a stage; convert it to a world - // here if that is the case - world_ = world ? const_cast(ecs_get_world(world)) - : nullptr; - if (!id) { - id = _::type::id(world); - } - - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - - ref_ = ecs_ref_init_id(world_, entity, id); - } - - ref(flecs::entity entity, flecs::id_t id = 0) - : ref(entity.world(), entity.id(), id) { } - - T* operator->() { - T* result = static_cast(ecs_ref_get_id( - world_, &ref_, this->ref_.id)); - - ecs_assert(result != NULL, ECS_INVALID_PARAMETER, - "nullptr dereference by flecs::ref"); - - return result; - } - - T* get() { - return static_cast(ecs_ref_get_id( - world_, &ref_, this->ref_.id)); - } - - T* try_get() { - if (!world_ || !ref_.entity) { - return nullptr; - } - - return get(); - } - - bool has() { - return !!try_get(); - } - - /** implicit conversion to bool. return true if there is a valid T* being referred to **/ - operator bool() { - return has(); - } - - flecs::entity entity() const; - -private: - world_t *world_; - flecs::ref_t ref_; -}; - -/** @} */ - -} - -/** - * @file addons/cpp/type.hpp - * @brief Utility functions for id vector. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_types Types - * @ingroup cpp_core - * @brief Type operations. - * - * @{ - */ - -/** Type class. - * A type is a vector of component ids which can be requested from entities or tables. - */ -struct type { - type() : world_(nullptr), type_(nullptr) { } - - type(world_t *world, const type_t *t) - : world_(world) - , type_(t) { } - - /** Convert type to comma-separated string */ - flecs::string str() const { - return flecs::string(ecs_type_str(world_, type_)); - } - - /** Return number of ids in type */ - int32_t count() const { - if (!type_) { - return 0; - } - return type_->count; - } - - /** Return pointer to array. */ - flecs::id_t* array() const { - if (!type_) { - return nullptr; - } - return type_->array; - } - - /** Get id at specified index in type */ - flecs::id get(int32_t index) const { - ecs_assert(type_ != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(type_->count > index, ECS_OUT_OF_RANGE, NULL); - if (!type_) { - return flecs::id(); - } - return flecs::id(world_, type_->array[index]); - } - - const flecs::id_t* begin() const { - if (type_ && type_->count) { - return type_->array; - } else { - return &empty_; - } - } - - const flecs::id_t* end() const { - if (type_ && type_->count) { - return &type_->array[type_->count]; - } else { - return &empty_; - } - } - - /** Implicit conversion to type_t */ - operator const type_t*() const { - return type_; - } -private: - world_t *world_; - const type_t *type_; - flecs::id_t empty_; -}; - -/** #} */ - -} - -/** - * @file addons/cpp/table.hpp - * @brief Direct access to table data. - */ - -#pragma once - -namespace flecs { - -/** - * @defgroup cpp_tables Tables - * @ingroup cpp_core - * Table operations. - * - * @{ - */ - -struct table { - table() : world_(nullptr), table_(nullptr) { } - - table(world_t *world, table_t *t) - : world_(world) - , table_(t) { } - - virtual ~table() { } - - /** Convert table type to string. */ - flecs::string str() const { - return flecs::string(ecs_table_str(world_, table_)); - } - - /** Get table type. */ - flecs::type type() const { - return flecs::type(world_, ecs_table_get_type(table_)); - } - - /** Get table count. */ - int32_t count() const { - return ecs_table_count(table_); - } - - /** Find type index for (component) id. - * - * @param id The (component) id. - * @return The index of the id in the table type, -1 if not found/ - */ - int32_t type_index(flecs::id_t id) const { - return ecs_table_get_type_index(world_, table_, id); - } - - /** Find type index for type. - * - * @tparam T The type. - * @return True if the table has the type, false if not. - */ - template - int32_t type_index() const { - return type_index(_::type::id(world_)); - } - - /** Find type index for pair. - * @param first First element of pair. - * @param second Second element of pair. - * @return True if the table has the pair, false if not. - */ - int32_t type_index(flecs::entity_t first, flecs::entity_t second) const { - return type_index(ecs_pair(first, second)); - } - - /** Find type index for pair. - * @tparam First First element of pair. - * @param second Second element of pair. - * @return True if the table has the pair, false if not. - */ - template - int32_t type_index(flecs::entity_t second) const { - return type_index(_::type::id(world_), second); - } - - /** Find type index for pair. - * @tparam First First element of pair. - * @tparam Second Second element of pair. - * @return True if the table has the pair, false if not. - */ - template - int32_t type_index() const { - return type_index(_::type::id(world_)); - } - - /** Find column index for (component) id. - * - * @param id The (component) id. - * @return The index of the id in the table type, -1 if not found/ - */ - int32_t column_index(flecs::id_t id) const { - return ecs_table_get_column_index(world_, table_, id); - } - - /** Find column index for type. - * - * @tparam T The type. - * @return True if the table has the type, false if not. - */ - template - int32_t column_index() const { - return column_index(_::type::id(world_)); - } - - /** Find column index for pair. - * @param first First element of pair. - * @param second Second element of pair. - * @return True if the table has the pair, false if not. - */ - int32_t column_index(flecs::entity_t first, flecs::entity_t second) const { - return column_index(ecs_pair(first, second)); - } - - /** Find column index for pair. - * @tparam First First element of pair. - * @param second Second element of pair. - * @return True if the table has the pair, false if not. - */ - template - int32_t column_index(flecs::entity_t second) const { - return column_index(_::type::id(world_), second); - } - - /** Find column index for pair. - * @tparam First First element of pair. - * @tparam Second Second element of pair. - * @return True if the table has the pair, false if not. - */ - template - int32_t column_index() const { - return column_index(_::type::id(world_)); - } - - /** Test if table has (component) id. - * - * @param id The (component) id. - * @return True if the table has the id, false if not. - */ - bool has(flecs::id_t id) const { - return type_index(id) != -1; - } - - /** Test if table has the type. - * - * @tparam T The type. - * @return True if the table has the type, false if not. - */ - template - bool has() const { - return type_index() != -1; - } - - /** Test if table has the pair. - * - * @param first First element of pair. - * @param second Second element of pair. - * @return True if the table has the pair, false if not. - */ - bool has(flecs::entity_t first, flecs::entity_t second) const { - return type_index(first, second) != -1; - } - - /** Test if table has the pair. - * - * @tparam First First element of pair. - * @param second Second element of pair. - * @return True if the table has the pair, false if not. - */ - template - bool has(flecs::entity_t second) const { - return type_index(second) != -1; - } - - /** Test if table has the pair. - * - * @tparam First First element of pair. - * @tparam Second Second element of pair. - * @return True if the table has the pair, false if not. - */ - template - bool has() const { - return type_index() != -1; - } - - /** Get pointer to component array by column index. - * - * @param index The column index. - * @return Pointer to the column, NULL if not a component. - */ - virtual void* get_column(int32_t index) const { - return ecs_table_get_column(table_, index, 0); - } - - /** Get pointer to component array by component. - * - * @param id The component id. - * @return Pointer to the column, NULL if not found. - */ - void* get(flecs::id_t id) const { - int32_t index = column_index(id); - if (index == -1) { - return NULL; - } - return get_column(index); - } - - /** Get pointer to component array by pair. - * - * @param first The first element of the pair. - * @param second The second element of the pair. - * @return Pointer to the column, NULL if not found. - */ - void* get(flecs::entity_t first, flecs::entity_t second) const { - return get(ecs_pair(first, second)); - } - - /** Get pointer to component array by component. - * - * @tparam T The component. - * @return Pointer to the column, NULL if not found. - */ - template ::value > = 0> - T* get() const { - return static_cast(get(_::type::id(world_))); - } - - /** Get pointer to component array by (enum) component. - * - * @tparam T The (enum) component. - * @return Pointer to the column, NULL if not found. - */ - template ::value > = 0> - T* get() const { - return static_cast(get(_::type::id(world_))); - } - - /** Get pointer to component array by component. - * - * @tparam T The component. - * @return Pointer to the column, NULL if not found. - */ - template , - if_t< flecs::is_pair::value > = 0> - A* get() const { - return static_cast(get(_::type::id(world_))); - } - - /** Get pointer to component array by pair. - * - * @tparam First The first element of the pair. - * @param second The second element of the pair. - * @return Pointer to the column, NULL if not found. - */ - template - First* get(flecs::entity_t second) const { - return static_cast(get(_::type::id(world_), second)); - } - - /** Get pointer to component array by pair. - * - * @tparam First The first element of the pair. - * @tparam Second The second element of the pair. - * @return Pointer to the column, NULL if not found. - */ - template , - typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> - A* get() const { - return static_cast(get(_::type::id(world_))); - } - - /** Get column size */ - size_t column_size(int32_t index) { - return ecs_table_get_column_size(table_, index); - } - - /** Get depth for given relationship. - * - * @param rel The relationship. - * @return The depth. - */ - int32_t depth(flecs::entity_t rel) { - return ecs_table_get_depth(world_, table_, rel); - } - - /** Get depth for given relationship. - * - * @tparam Rel The relationship. - * @return The depth. - */ - template - int32_t depth() { - return depth(_::type::id(world_)); - } - - /** Get table. - * - * @return The table. - */ - table_t* get_table() const { - return table_; - } - - /* Implicit conversion to table_t */ - operator table_t*() const { - return table_; - } - -protected: - world_t *world_; - table_t *table_; -}; - -struct table_range : table { - table_range() - : table() - , offset_(0) - , count_(0) { } - - table_range(world_t *world, table_t *t, int32_t offset, int32_t count) - : table(world, t) - , offset_(offset) - , count_(count) { } - - int32_t offset() const { - return offset_; - } - - int32_t count() const { - return count_; - } - - /** Get pointer to component array by column index. - * - * @param index The column index. - * @return Pointer to the column, NULL if not a component. - */ - void* get_column(int32_t index) const override { - return ecs_table_get_column(table_, index, offset_); - } - -private: - int32_t offset_ = 0; - int32_t count_ = 0; -}; - -/** @} */ - -} - -/** - * @file addons/cpp/utils/iterable.hpp - * @brief Base class for iterable objects, like queries. - */ - -namespace flecs { - -template -struct iter_iterable; - -template -struct page_iterable; - -template -struct worker_iterable; - -template -struct iterable { - - /** Each iterator. - * The "each" iterator accepts a function that is invoked for each matching - * entity. The following function signatures are valid: - * - func(flecs::entity e, Components& ...) - * - func(flecs::iter& it, size_t index, Components& ....) - * - func(Components& ...) - */ - template - void each(Func&& func) const { - ecs_iter_t it = this->get_iter(nullptr); - ecs_iter_next_action_t next = this->next_action(); - while (next(&it)) { - _::each_delegate(func).invoke(&it); - } - } - - /** Run iterator. - * The "each" iterator accepts a function that is invoked once for a query - * with a valid iterator. The following signature is valid: - * - func(flecs::iter&) - */ - template - void run(Func&& func) const { - ecs_iter_t it = this->get_iter(nullptr); - _::run_delegate(func).invoke(&it); - } - - template - flecs::entity find(Func&& func) const { - ecs_iter_t it = this->get_iter(nullptr); - ecs_iter_next_action_t next = this->next_action(); - - flecs::entity result; - while (!result && next(&it)) { - result = _::find_delegate(func).invoke(&it); - } - - if (result) { - ecs_iter_fini(&it); - } - - return result; - } - - /** Create iterator. - * Create an iterator object that can be modified before iterating. - */ - iter_iterable iter(flecs::world_t *world = nullptr) const; - - /** Create iterator. - * Create an iterator object that can be modified before iterating. - */ - iter_iterable iter(flecs::iter& iter) const; - - /** Create iterator. - * Create an iterator object that can be modified before iterating. - */ - iter_iterable iter(flecs::entity e) const; - - /** Page iterator. - * Create an iterator that limits the returned entities with offset/limit. - * - * @param offset How many entities to skip. - * @param limit The maximum number of entities to return. - * @return Iterable that can be iterated with each/iter. - */ - page_iterable page(int32_t offset, int32_t limit); - - /** Worker iterator. - * Create an iterator that divides the number of matched entities across - * a number of resources. - * - * @param index The index of the current resource. - * @param count The total number of resources to divide entities between. - * @return Iterable that can be iterated with each/iter. - */ - worker_iterable worker(int32_t index, int32_t count); - - /** Return number of entities matched by iterable. */ - int32_t count() const { - return this->iter().count(); - } - - /** Return whether iterable has any matches. */ - bool is_true() const { - return this->iter().is_true(); - } - - /** Return first entity matched by iterable. */ - flecs::entity first() const { - return this->iter().first(); - } - - iter_iterable set_var(int var_id, flecs::entity_t value) const { - return this->iter().set_var(var_id, value); - } - - iter_iterable set_var(const char *name, flecs::entity_t value) const { - return this->iter().set_var(name, value); - } - - iter_iterable set_var(const char *name, flecs::table_t *value) const { - return this->iter().set_var(name, value); - } - - iter_iterable set_var(const char *name, ecs_table_range_t value) const { - return this->iter().set_var(name, value); - } - - iter_iterable set_var(const char *name, flecs::table_range value) const { - return this->iter().set_var(name, value); - } - - // Limit results to tables with specified group id (grouped queries only) - iter_iterable set_group(uint64_t group_id) const { - return this->iter().set_group(group_id); - } - - // Limit results to tables with specified group id (grouped queries only) - template - iter_iterable set_group() const { - return this->iter().template set_group(); - } - - virtual ~iterable() { } -protected: - friend iter_iterable; - friend page_iterable; - friend worker_iterable; - - virtual ecs_iter_t get_iter(flecs::world_t *stage) const = 0; - virtual ecs_iter_next_action_t next_action() const = 0; -}; - -template -struct iter_iterable final : iterable { - template - iter_iterable(Iterable *it, flecs::world_t *world) - { - it_ = it->get_iter(world); - next_ = it->next_action(); - next_each_ = it->next_action(); - ecs_assert(next_ != nullptr, ECS_INTERNAL_ERROR, NULL); - ecs_assert(next_each_ != nullptr, ECS_INTERNAL_ERROR, NULL); - } - - iter_iterable& set_var(int var_id, flecs::entity_t value) { - ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, 0); - ecs_iter_set_var(&it_, var_id, value); - return *this; - } - - iter_iterable& set_var(const char *name, flecs::entity_t value) { - ecs_query_iter_t *qit = &it_.priv_.iter.query; - int var_id = ecs_query_find_var(qit->query, name); - ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, name); - ecs_iter_set_var(&it_, var_id, value); - return *this; - } - - iter_iterable& set_var(const char *name, flecs::table_t *value) { - ecs_query_iter_t *qit = &it_.priv_.iter.query; - int var_id = ecs_query_find_var(qit->query, name); - ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, name); - ecs_iter_set_var_as_table(&it_, var_id, value); - return *this; - } - - iter_iterable& set_var(const char *name, ecs_table_range_t value) { - ecs_query_iter_t *qit = &it_.priv_.iter.query; - int var_id = ecs_query_find_var(qit->query, name); - ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, name); - ecs_iter_set_var_as_range(&it_, var_id, &value); - return *this; - } - - iter_iterable& set_var(const char *name, flecs::table_range value) { - ecs_table_range_t range; - range.table = value.get_table(); - range.offset = value.offset(); - range.count = value.count(); - return set_var(name, range); - } - -# ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/iterable.inl - * @brief JSON iterable mixin. - */ - -/** Serialize iterator result to JSON. - * - * @memberof flecs::iter - * @ingroup cpp_addons_json - */ -flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { - char *json = ecs_iter_to_json(&it_, desc); - return flecs::string(json); -} - -# endif - - // Return total number of entities in result. - int32_t count() { - int32_t result = 0; - while (next_each_(&it_)) { - result += it_.count; - } - return result; - } - - // Returns true if iterator yields at least once result. - bool is_true() { - bool result = next_each_(&it_); - if (result) { - ecs_iter_fini(&it_); - } - return result; - } - - // Return first matching entity. - flecs::entity first() { - flecs::entity result; - if (next_each_(&it_) && it_.count) { - result = flecs::entity(it_.world, it_.entities[0]); - ecs_iter_fini(&it_); - } - return result; - } - - // Limit results to tables with specified group id (grouped queries only) - iter_iterable& set_group(uint64_t group_id) { - ecs_iter_set_group(&it_, group_id); - return *this; - } - - // Limit results to tables with specified group id (grouped queries only) - template - iter_iterable& set_group() { - ecs_iter_set_group(&it_, _::type().id(it_.real_world)); - return *this; - } - -protected: - ecs_iter_t get_iter(flecs::world_t *world) const override { - if (world) { - ecs_iter_t result = it_; - result.world = world; - return result; - } - return it_; - } - - ecs_iter_next_action_t next_action() const override { - return next_; - } - -private: - ecs_iter_t it_; - ecs_iter_next_action_t next_; - ecs_iter_next_action_t next_each_; -}; - -template -iter_iterable iterable::iter(flecs::world_t *world) const -{ - return iter_iterable(this, world); -} - -template -iter_iterable iterable::iter(flecs::iter& it) const -{ - return iter_iterable(this, it.world()); -} - -template -iter_iterable iterable::iter(flecs::entity e) const -{ - return iter_iterable(this, e.world()); -} - -template -struct page_iterable final : iterable { - template - page_iterable(int32_t offset, int32_t limit, Iterable *it) - : offset_(offset) - , limit_(limit) - { - chain_it_ = it->get_iter(nullptr); - } - -protected: - ecs_iter_t get_iter(flecs::world_t*) const { - return ecs_page_iter(&chain_it_, offset_, limit_); - } - - ecs_iter_next_action_t next_action() const { - return ecs_page_next; - } - -private: - ecs_iter_t chain_it_; - int32_t offset_; - int32_t limit_; -}; - -template -page_iterable iterable::page( - int32_t offset, - int32_t limit) -{ - return page_iterable(offset, limit, this); -} - -template -struct worker_iterable final : iterable { - worker_iterable(int32_t offset, int32_t limit, iterable *it) - : offset_(offset) - , limit_(limit) - { - chain_it_ = it->get_iter(nullptr); - } - -protected: - ecs_iter_t get_iter(flecs::world_t*) const { - return ecs_worker_iter(&chain_it_, offset_, limit_); - } - - ecs_iter_next_action_t next_action() const { - return ecs_worker_next; - } - -private: - ecs_iter_t chain_it_; - int32_t offset_; - int32_t limit_; -}; - -template -worker_iterable iterable::worker( - int32_t index, - int32_t count) -{ - return worker_iterable(index, count, this); -} - -} - - -// Mixin implementations -/** - * @file addons/cpp/mixins/id/impl.hpp - * @brief Id class implementation. - */ - -#pragma once - -namespace flecs { - -inline flecs::entity id::entity() const { - ecs_assert(!is_pair(), ECS_INVALID_OPERATION, NULL); - ecs_assert(!flags(), ECS_INVALID_OPERATION, NULL); - return flecs::entity(world_, id_); -} - -inline flecs::entity id::flags() const { - return flecs::entity(world_, id_ & ECS_ID_FLAGS_MASK); -} - -inline flecs::entity id::first() const { - ecs_assert(is_pair(), ECS_INVALID_OPERATION, NULL); - - flecs::entity_t e = ECS_PAIR_FIRST(id_); - if (world_) { - return flecs::entity(world_, ecs_get_alive(world_, e)); - } else { - return flecs::entity(e); - } -} - -inline flecs::entity id::second() const { - flecs::entity_t e = ECS_PAIR_SECOND(id_); - if (world_) { - return flecs::entity(world_, ecs_get_alive(world_, e)); - } else { - return flecs::entity(e); - } -} - -inline flecs::entity id::add_flags(flecs::id_t flags) const { - return flecs::entity(world_, id_ | flags); -} - -inline flecs::entity id::remove_flags(flecs::id_t flags) const { - (void)flags; - ecs_assert((id_ & ECS_ID_FLAGS_MASK) == flags, ECS_INVALID_PARAMETER, NULL); - return flecs::entity(world_, id_ & ECS_COMPONENT_MASK); -} - -inline flecs::entity id::remove_flags() const { - return flecs::entity(world_, id_ & ECS_COMPONENT_MASK); -} - -inline flecs::entity id::remove_generation() const { - return flecs::entity(world_, static_cast(id_)); -} - -inline flecs::world id::world() const { - return flecs::world(world_); -} - -inline flecs::entity id::type_id() const { - return flecs::entity(world_, ecs_get_typeid(world_, id_)); -} - - -// Id mixin implementation - -template -inline flecs::id world::id() const { - return flecs::id(world_, _::type::id(world_)); -} - -template -inline flecs::id world::id(Args&&... args) const { - return flecs::id(world_, FLECS_FWD(args)...); -} - -template -inline flecs::id world::pair() const { - return flecs::id( - world_, - ecs_pair( - _::type::id(world_), - _::type::id(world_))); -} - -template -inline flecs::id world::pair(entity_t o) const { - ecs_assert(!ECS_IS_PAIR(o), ECS_INVALID_PARAMETER, - "cannot create nested pairs"); - - return flecs::id( - world_, - ecs_pair( - _::type::id(world_), - o)); -} - -inline flecs::id world::pair(entity_t r, entity_t o) const { - ecs_assert(!ECS_IS_PAIR(r) && !ECS_IS_PAIR(o), ECS_INVALID_PARAMETER, - "cannot create nested pairs"); - - return flecs::id( - world_, - ecs_pair(r, o)); -} - -} - -/** - * @file addons/cpp/mixins/entity/impl.hpp - * @brief Entity implementation. - */ - -#pragma once - -namespace flecs { - -template -flecs::entity ref::entity() const { - return flecs::entity(world_, ref_.entity); -} - -template -template -inline const Self& entity_builder::insert(const Func& func) const { - _::entity_with_delegate::invoke_ensure( - this->world_, this->id_, func); - return to_base(); -} - -template ::value > > -const T* entity_view::get() const { - entity_t r = _::type::id(world_); - entity_t c = ecs_get_target(world_, id_, r, 0); - - if (c) { -#ifdef FLECS_META - using U = typename std::underlying_type::type; - const T* v = static_cast( - ecs_get_id(world_, c, ecs_pair(flecs::Constant, _::type::id(world_)))); - ecs_assert(v != NULL, ECS_INTERNAL_ERROR, "missing enum constant value"); - return v; -#else - // Fallback if we don't have the reflection addon - return static_cast(ecs_get_id(world_, id_, r)); -#endif - } else { - // If there is no matching pair for (r, *), try just r - return static_cast(ecs_get_id(world_, id_, r)); - } -} - -template -inline flecs::entity entity_view::target(int32_t index) const -{ - return flecs::entity(world_, - ecs_get_target(world_, id_, _::type::id(world_), index)); -} - -inline flecs::entity entity_view::target( - flecs::entity_t relationship, - int32_t index) const -{ - return flecs::entity(world_, - ecs_get_target(world_, id_, relationship, index)); -} - -inline flecs::entity entity_view::target_for( - flecs::entity_t relationship, - flecs::id_t id) const -{ - return flecs::entity(world_, - ecs_get_target_for_id(world_, id_, relationship, id)); -} - -template -inline flecs::entity entity_view::target_for(flecs::entity_t relationship) const { - return target_for(relationship, _::type::id(world_)); -} - -template -inline flecs::entity entity_view::target_for(flecs::entity_t relationship) const { - return target_for(relationship, _::type::id(world_)); -} - -inline flecs::entity entity_view::parent() const { - return target(flecs::ChildOf); -} - -inline flecs::entity entity_view::mut(const flecs::world& stage) const { - ecs_assert(!stage.is_readonly(), ECS_INVALID_PARAMETER, - "cannot use readonly world/stage to create mutable handle"); - return flecs::entity(id_).set_stage(stage.c_ptr()); -} - -inline flecs::entity entity_view::mut(const flecs::iter& it) const { - ecs_assert(!it.world().is_readonly(), ECS_INVALID_PARAMETER, - "cannot use iterator created for readonly world/stage to create mutable handle"); - return flecs::entity(id_).set_stage(it.world().c_ptr()); -} - -inline flecs::entity entity_view::mut(const flecs::entity_view& e) const { - ecs_assert(!e.world().is_readonly(), ECS_INVALID_PARAMETER, - "cannot use entity created for readonly world/stage to create mutable handle"); - return flecs::entity(id_).set_stage(e.world_); -} - -inline flecs::entity entity_view::set_stage(world_t *stage) { - return flecs::entity(stage, id_); -} - -inline flecs::type entity_view::type() const { - return flecs::type(world_, ecs_get_type(world_, id_)); -} - -inline flecs::table entity_view::table() const { - return flecs::table(world_, ecs_get_table(world_, id_)); -} - -inline flecs::table_range entity_view::range() const { - ecs_record_t *r = ecs_record_find(world_, id_); - if (r) { - return flecs::table_range(world_, r->table, - ECS_RECORD_TO_ROW(r->row), 1); - } - return flecs::table_range(); -} - -template -inline void entity_view::each(const Func& func) const { - const ecs_type_t *type = ecs_get_type(world_, id_); - if (!type) { - return; - } - - const ecs_id_t *ids = type->array; - int32_t count = type->count; - - for (int i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - flecs::id ent(world_, id); - func(ent); - } -} - -template -inline void entity_view::each(flecs::id_t pred, flecs::id_t obj, const Func& func) const { - flecs::world_t *real_world = const_cast( - ecs_get_world(world_)); - - const ecs_table_t *table = ecs_get_table(world_, id_); - if (!table) { - return; - } - - const ecs_type_t *type = ecs_table_get_type(table); - if (!type) { - return; - } - - flecs::id_t pattern = pred; - if (obj) { - pattern = ecs_pair(pred, obj); - } - - int32_t cur = 0; - id_t *ids = type->array; - - while (-1 != (cur = ecs_search_offset(real_world, table, cur, pattern, 0))) - { - flecs::id ent(world_, ids[cur]); - func(ent); - cur ++; - } -} - -template -inline void entity_view::each(const flecs::entity_view& rel, const Func& func) const { - return this->each(rel, flecs::Wildcard, [&](flecs::id id) { - flecs::entity obj = id.second(); - func(obj); - }); -} - -template ::value > > -inline bool entity_view::get(const Func& func) const { - return _::entity_with_delegate::invoke_get(world_, id_, func); -} - -inline flecs::entity entity_view::lookup(const char *path, bool search_path) const { - ecs_assert(id_ != 0, ECS_INVALID_PARAMETER, "invalid lookup from null handle"); - auto id = ecs_lookup_path_w_sep(world_, id_, path, "::", "::", search_path); - return flecs::entity(world_, id); -} - -inline flecs::entity entity_view::clone(bool copy_value, flecs::entity_t dst_id) const { - if (!dst_id) { - dst_id = ecs_new(world_); - } - - flecs::entity dst = flecs::entity(world_, dst_id); - ecs_clone(world_, dst_id, id_, copy_value); - return dst; -} - -// Entity mixin implementation -template -inline flecs::entity world::entity(Args &&... args) const { - return flecs::entity(world_, FLECS_FWD(args)...); -} - -template ::value >> -inline flecs::id world::id(E value) const { - flecs::entity_t constant = enum_type(world_).entity(value); - return flecs::id(world_, constant); -} - -template ::value >> -inline flecs::entity world::entity(E value) const { - flecs::entity_t constant = enum_type(world_).entity(value); - return flecs::entity(world_, constant); -} - -template -inline flecs::entity world::entity(const char *name) const { - return flecs::entity(world_, _::type::register_id(world_, name, true) ); -} - -template -inline flecs::entity world::prefab(Args &&... args) const { - flecs::entity result = flecs::entity(world_, FLECS_FWD(args)...); - result.add(flecs::Prefab); - return result; -} - -template -inline flecs::entity world::prefab(const char *name) const { - flecs::entity result = flecs::component(world_, name, true); - result.add(flecs::Prefab); - return result; -} - -} - -/** - * @file addons/cpp/mixins/component/impl.hpp - * @brief Component mixin implementation - */ - -#pragma once - -namespace flecs { - -template -inline flecs::component world::component(Args &&... args) const { - return flecs::component(world_, FLECS_FWD(args)...); -} - -template -inline flecs::untyped_component world::component(Args &&... args) const { - return flecs::untyped_component(world_, FLECS_FWD(args)...); -} - -} // namespace flecs - -/** - * @file addons/cpp/mixins/term/impl.hpp - * @brief Term implementation. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/term/builder_i.hpp - * @brief Term builder interface. - */ - -#pragma once - -/** - * @file addons/cpp/utils/signature.hpp - * @brief Compile time utilities for deriving query attributes from param pack. - */ - -#pragma once - -#include - -namespace flecs { -namespace _ { - - template ::value > = 0> - constexpr flecs::inout_kind_t type_to_inout() { - return flecs::In; - } - - template ::value > = 0> - constexpr flecs::inout_kind_t type_to_inout() { - return flecs::InOut; - } - - template ::value || is_reference::value > = 0> - constexpr flecs::inout_kind_t type_to_inout() { - return flecs::InOutDefault; - } - - template ::value > = 0> - constexpr flecs::oper_kind_t type_to_oper() { - return flecs::Optional; - } - - template ::value > = 0> - constexpr flecs::oper_kind_t type_to_oper() { - return flecs::And; - } - - template - struct sig { - sig(flecs::world_t *world) - : world_(world) - , ids({ (_::type>::id(world))... }) - , inout ({ (type_to_inout())... }) - , oper ({ (type_to_oper())... }) - { } - - flecs::world_t *world_; - flecs::array ids; - flecs::array inout; - flecs::array oper; - - template - void populate(const Builder& b) { - size_t i = 0; - for (auto id : ids) { - if (!(id & ECS_ID_FLAGS_MASK)) { - const flecs::type_info_t *ti = ecs_get_type_info(world_, id); - if (ti) { - // Union relationships always return a value of type - // flecs::entity_t which holds the target id of the - // union relationship. - // If a union component with a non-zero size (like an - // enum) is added to the query signature, the each/iter - // functions would accept a parameter of the component - // type instead of flecs::entity_t, which would cause - // an assert. - ecs_assert( - !ti->size || !ecs_has_id(world_, id, flecs::Union), - ECS_INVALID_PARAMETER, - "use with() method to add union relationship"); - } - } - - b->with(id).inout(inout[i]).oper(oper[i]); - i ++; - } - } - }; - -} // namespace _ -} // namespace flecs - - -namespace flecs -{ - -/** Term identifier builder. - * A term identifier describes a single identifier in a term. Identifier - * descriptions can reference entities by id, name or by variable, which means - * the entity will be resolved when the term is evaluated. - * - * @ingroup cpp_core_queries - */ -template -struct term_ref_builder_i { - term_ref_builder_i() : term_ref_(nullptr) { } - - virtual ~term_ref_builder_i() { } - - /* The self flag indicates the term identifier itself is used */ - Base& self() { - this->assert_term_ref(); - term_ref_->id |= flecs::Self; - return *this; - } - - /* Specify value of identifier by id */ - Base& id(flecs::entity_t id) { - this->assert_term_ref(); - term_ref_->id = id; - return *this; - } - - /* Specify value of identifier by id. Almost the same as id(entity), but this - * operation explicitly sets the flecs::IsEntity flag. This forces the id to - * be interpreted as entity, whereas not setting the flag would implicitly - * convert ids for builtin variables such as flecs::This to a variable. - * - * This function can also be used to disambiguate id(0), which would match - * both id(entity_t) and id(const char*). - */ - Base& entity(flecs::entity_t entity) { - this->assert_term_ref(); - term_ref_->id = entity | flecs::IsEntity; - return *this; - } - - /* Specify value of identifier by name */ - Base& name(const char *name) { - this->assert_term_ref(); - term_ref_->id |= flecs::IsEntity; - term_ref_->name = const_cast(name); - return *this; - } - - /* Specify identifier is a variable (resolved at query evaluation time) */ - Base& var(const char *var_name) { - this->assert_term_ref(); - term_ref_->id |= flecs::IsVariable; - term_ref_->name = const_cast(var_name); - return *this; - } - - /* Override term id flags */ - Base& flags(flecs::flags32_t flags) { - this->assert_term_ref(); - term_ref_->id = flags; - return *this; - } - - ecs_term_ref_t *term_ref_; - -protected: - virtual flecs::world_t* world_v() = 0; - - void assert_term_ref() { - ecs_assert(term_ref_ != NULL, ECS_INVALID_PARAMETER, - "no active term (call .with() first)"); - } - -private: - operator Base&() { - return *static_cast(this); - } -}; - -/** Term builder interface. - * A term is a single element of a query expression. - * - * @ingroup cpp_core_queries - */ -template -struct term_builder_i : term_ref_builder_i { - term_builder_i() : term_(nullptr) { } - - term_builder_i(ecs_term_t *term_ptr) { - set_term(term_ptr); - } - - Base& term(id_t id) { - return this->id(id); - } - - /* Call prior to setting values for src identifier */ - Base& src() { - this->assert_term(); - this->term_ref_ = &term_->src; - return *this; - } - - /* Call prior to setting values for first identifier. This is either the - * component identifier, or first element of a pair (in case second is - * populated as well). */ - Base& first() { - this->assert_term(); - this->term_ref_ = &term_->first; - return *this; - } - - /* Call prior to setting values for second identifier. This is the second - * element of a pair. Requires that first() is populated as well. */ - Base& second() { - this->assert_term(); - this->term_ref_ = &term_->second; - return *this; - } - - /* Select src identifier, initialize it with entity id */ - Base& src(flecs::entity_t id) { - this->src(); - this->id(id); - return *this; - } - - /* Select src identifier, initialize it with id associated with type */ - template - Base& src() { - this->src(_::type::id(this->world_v())); - return *this; - } - - /* Select src identifier, initialize it with name. If name starts with a $ - * the name is interpreted as a variable. */ - Base& src(const char *name) { - ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); - this->src(); - if (name[0] == '$') { - this->var(&name[1]); - } else { - this->name(name); - } - return *this; - } - - /* Select first identifier, initialize it with entity id */ - Base& first(flecs::entity_t id) { - this->first(); - this->id(id); - return *this; - } - - /* Select first identifier, initialize it with id associated with type */ - template - Base& first() { - this->first(_::type::id(this->world_v())); - return *this; - } - - /* Select first identifier, initialize it with name. If name starts with a $ - * the name is interpreted as a variable. */ - Base& first(const char *name) { - ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); - this->first(); - if (name[0] == '$') { - this->var(&name[1]); - } else { - this->name(name); - } - return *this; - } - - /* Select second identifier, initialize it with entity id */ - Base& second(flecs::entity_t id) { - this->second(); - this->id(id); - return *this; - } - - /* Select second identifier, initialize it with id associated with type */ - template - Base& second() { - this->second(_::type::id(this->world_v())); - return *this; - } - - /* Select second identifier, initialize it with name. If name starts with a $ - * the name is interpreted as a variable. */ - Base& second(const char *name) { - ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); - this->second(); - if (name[0] == '$') { - this->var(&name[1]); - } else { - this->name(name); - } - return *this; - } - - /* The up flag indicates that the term identifier may be substituted by - * traversing a relationship upwards. For example: substitute the identifier - * with its parent by traversing the ChildOf relationship. */ - Base& up(flecs::entity_t trav = 0) { - this->assert_term_ref(); - ecs_check(this->term_ref_ != &term_->first, ECS_INVALID_PARAMETER, - "up traversal can only be applied to term source"); - ecs_check(this->term_ref_ != &term_->second, ECS_INVALID_PARAMETER, - "up traversal can only be applied to term source"); - this->term_ref_->id |= flecs::Up; - if (trav) { - term_->trav = trav; - } - error: - return *this; - } - - template - Base& up() { - return this->up(_::type::id(this->world_v())); - } - - /* The cascade flag is like up, but returns results in breadth-first order. - * Only supported for flecs::query */ - Base& cascade(flecs::entity_t trav = 0) { - this->assert_term_ref(); - this->up(); - this->term_ref_->id |= flecs::Cascade; - if (trav) { - term_->trav = trav; - } - return *this; - } - - template - Base& cascade() { - return this->cascade(_::type::id(this->world_v())); - } - - /* Use with cascade to iterate results in descending (bottom -> top) order */ - Base& desc() { - this->assert_term_ref(); - this->term_ref_->id |= flecs::Desc; - return *this; - } - - /* Same as up(), exists for backwards compatibility */ - Base& parent() { - return this->up(); - } - - /* Specify relationship to traverse, and flags to indicate direction */ - Base& trav(flecs::entity_t trav, flecs::flags32_t flags = 0) { - this->assert_term_ref(); - term_->trav = trav; - this->term_ref_->id |= flags; - return *this; - } - - /** Set id flags for term. */ - Base& id_flags(id_t flags) { - this->assert_term(); - term_->id |= flags; - return *this; - } - - /** Set read/write access of term. */ - Base& inout(flecs::inout_kind_t inout) { - this->assert_term(); - term_->inout = static_cast(inout); - return *this; - } - - /** Set read/write access for stage. Use this when a system reads or writes - * components other than the ones provided by the query. This information - * can be used by schedulers to insert sync/merge points between systems - * where deferred operations are flushed. - * - * Setting this is optional. If not set, the value of the accessed component - * may be out of sync for at most one frame. - */ - Base& inout_stage(flecs::inout_kind_t inout) { - this->assert_term(); - term_->inout = static_cast(inout); - if (term_->oper != EcsNot) { - this->src().entity(0); - } - return *this; - } - - /** Short for inout_stage(flecs::Out). - * Use when system uses add, remove or set. - */ - Base& write() { - return this->inout_stage(flecs::Out); - } - - /** Short for inout_stage(flecs::In). - * Use when system uses get. - */ - Base& read() { - return this->inout_stage(flecs::In); - } - - /** Short for inout_stage(flecs::InOut). - * Use when system uses ensure. - */ - Base& read_write() { - return this->inout_stage(flecs::InOut); - } - - /** Short for inout(flecs::In) */ - Base& in() { - return this->inout(flecs::In); - } - - /** Short for inout(flecs::Out) */ - Base& out() { - return this->inout(flecs::Out); - } - - /** Short for inout(flecs::InOut) */ - Base& inout() { - return this->inout(flecs::InOut); - } - - /** Short for inout(flecs::In) */ - Base& inout_none() { - return this->inout(flecs::InOutNone); - } - - /** Set operator of term. */ - Base& oper(flecs::oper_kind_t oper) { - this->assert_term(); - term_->oper = static_cast(oper); - return *this; - } - - /* Short for oper(flecs::And) */ - Base& and_() { - return this->oper(flecs::And); - } - - /* Short for oper(flecs::Or) */ - Base& or_() { - return this->oper(flecs::Or); - } - - /* Short for oper(flecs::Or) */ - Base& not_() { - return this->oper(flecs::Not); - } - - /* Short for oper(flecs::Or) */ - Base& optional() { - return this->oper(flecs::Optional); - } - - /* Short for oper(flecs::AndFrom) */ - Base& and_from() { - return this->oper(flecs::AndFrom); - } - - /* Short for oper(flecs::OrFrom) */ - Base& or_from() { - return this->oper(flecs::OrFrom); - } - - /* Short for oper(flecs::NotFrom) */ - Base& not_from() { - return this->oper(flecs::NotFrom); - } - - /** Match singleton. */ - Base& singleton() { - this->assert_term(); - ecs_assert(term_->id || term_->first.id, ECS_INVALID_PARAMETER, - "no component specified for singleton"); - - flecs::id_t sid = term_->id; - if (!sid) { - sid = term_->first.id; - } - - ecs_assert(sid != 0, ECS_INVALID_PARAMETER, NULL); - - if (!ECS_IS_PAIR(sid)) { - term_->src.id = sid; - } else { - term_->src.id = ecs_pair_first(world(), sid); - } - return *this; - } - - /* Query terms are not triggered on by observers */ - Base& filter() { - term_->inout = EcsInOutFilter; - return *this; - } - - ecs_term_t *term_; - -protected: - virtual flecs::world_t* world_v() override = 0; - - void set_term(ecs_term_t *term) { - term_ = term; - if (term) { - this->term_ref_ = &term_->src; // default to subject - } else { - this->term_ref_ = nullptr; - } - } - -private: - void assert_term() { - ecs_assert(term_ != NULL, ECS_INVALID_PARAMETER, - "no active term (call .with() first)"); - } - - operator Base&() { - return *static_cast(this); - } -}; - -} - - -namespace flecs { - -/** Class that describes a term. - * - * @ingroup cpp_core_queries - */ -struct term final : term_builder_i { - term() - : term_builder_i(&value) - , value({}) - , world_(nullptr) { } - - term(flecs::world_t *world_ptr) - : term_builder_i(&value) - , value({}) - , world_(world_ptr) { } - - term(flecs::world_t *world_ptr, ecs_term_t t) - : term_builder_i(&value) - , value({}) - , world_(world_ptr) { - value = t; - this->set_term(&value); - } - - term(flecs::world_t *world_ptr, id_t id) - : term_builder_i(&value) - , value({}) - , world_(world_ptr) { - if (id & ECS_ID_FLAGS_MASK) { - value.id = id; - } else { - value.first.id = id; - } - this->set_term(&value); - } - - term(flecs::world_t *world_ptr, entity_t r, entity_t o) - : term_builder_i(&value) - , value({}) - , world_(world_ptr) { - value.id = ecs_pair(r, o); - this->set_term(&value); - } - - term(id_t id) - : term_builder_i(&value) - , value({}) - , world_(nullptr) { - if (id & ECS_ID_FLAGS_MASK) { - value.id = id; - } else { - value.first.id = id; - } - } - - term(id_t r, id_t o) - : term_builder_i(&value) - , value({}) - , world_(nullptr) { - value.id = ecs_pair(r, o); - } - - void reset() { - value = {}; - this->set_term(nullptr); - } - - bool is_set() { - return ecs_term_is_initialized(&value); - } - - flecs::id id() { - return flecs::id(world_, value.id); - } - - flecs::inout_kind_t inout() { - return static_cast(value.inout); - } - - flecs::oper_kind_t oper() { - return static_cast(value.oper); - } - - flecs::entity get_src() { - return flecs::entity(world_, ECS_TERM_REF_ID(&value.src)); - } - - flecs::entity get_first() { - return flecs::entity(world_, ECS_TERM_REF_ID(&value.first)); - } - - flecs::entity get_second() { - return flecs::entity(world_, ECS_TERM_REF_ID(&value.second)); - } - - operator flecs::term_t() const { - return value; - } - - flecs::term_t value; - -protected: - flecs::world_t* world_v() override { return world_; } - -private: - flecs::world_t *world_; -}; - -// Term mixin implementation -template -inline flecs::term world::term(Args &&... args) const { - return flecs::term(world_, FLECS_FWD(args)...); -} - -template -inline flecs::term world::term() const { - return flecs::term(world_, _::type::id(world_)); -} - -template -inline flecs::term world::term() const { - return flecs::term(world_, ecs_pair( - _::type::id(world_), - _::type::id(world_))); -} - -} - -/** - * @file addons/cpp/mixins/query/impl.hpp - * @brief Query implementation. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/query/builder.hpp - * @brief Query builder. - */ - -#pragma once - -/** - * @file addons/cpp/utils/builder.hpp - * @brief Builder base class. - * - * Generic functionality for builder classes. - */ - -#pragma once - -namespace flecs { -namespace _ { - -// Macros for template types so we don't go cross-eyed -#define FLECS_TBUILDER template class -#define FLECS_IBUILDER template class - -template -struct builder : IBuilder -{ - using IBase = IBuilder; - -public: - builder(flecs::world_t *world) - : IBase(&desc_) - , desc_{} - , world_(world) { } - - builder(const builder& f) - : IBase(&desc_, f.term_index_) - { - world_ = f.world_; - desc_ = f.desc_; - } - - builder(builder&& f) noexcept - : builder(f) { } - - operator TDesc*() { - return &desc_; - } - - T build() { - return T(world_, *static_cast(this)); - } - -protected: - flecs::world_t* world_v() override { return world_; } - TDesc desc_; - flecs::world_t *world_; -}; - -#undef FLECS_TBUILDER -#undef FLECS_IBUILDER - -} // namespace _ -} // namespace flecs - -/** - * @file addons/cpp/mixins/query/builder_i.hpp - * @brief Query builder interface. - */ - -#pragma once - - -namespace flecs -{ - -/** Query builder interface. - * - * @ingroup cpp_core_queries - */ -template -struct query_builder_i : term_builder_i { - query_builder_i(ecs_query_desc_t *desc, int32_t term_index = 0) - : term_index_(term_index) - , expr_count_(0) - , desc_(desc) { } - - Base& query_flags(ecs_flags32_t flags) { - desc_->flags |= flags; - return *this; - } - - Base& cache_kind(query_cache_kind_t kind) { - desc_->cache_kind = static_cast(kind); - return *this; - } - - Base& cached() { - return cache_kind(flecs::QueryCacheAuto); - } - - Base& expr(const char *expr) { - ecs_check(expr_count_ == 0, ECS_INVALID_OPERATION, - "query_builder::expr() called more than once"); - desc_->expr = expr; - expr_count_ ++; - - error: - return *this; - } - - /* With methods */ - - template - Base& with() { - this->term(); - *this->term_ = flecs::term(_::type::id(this->world_v())); - this->term_->inout = static_cast( - _::type_to_inout()); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } - return *this; - } - - Base& with(id_t id) { - this->term(); - *this->term_ = flecs::term(id); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } - return *this; - } - - Base& with(const char *name) { - this->term(); - *this->term_ = flecs::term().first(name); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } - return *this; - } - - Base& with(const char *first, const char *second) { - this->term(); - *this->term_ = flecs::term().first(first).second(second); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } - return *this; - } - - Base& with(entity_t r, entity_t o) { - this->term(); - *this->term_ = flecs::term(r, o); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } - return *this; - } - - Base& with(entity_t r, const char *o) { - this->term(); - *this->term_ = flecs::term(r).second(o); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } - return *this; - } - - Base& with(const char *r, entity_t o) { - this->term(); - *this->term_ = flecs::term().first(r).second(o); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } - return *this; - } - - template - Base& with(id_t o) { - return this->with(_::type::id(this->world_v()), o); - } - - template - Base& with(const char *second) { - return this->with(_::type::id(this->world_v())).second(second); - } - - template - Base& with() { - return this->with(_::type::id(this->world_v())); - } - - template ::value > = 0> - Base& with(E value) { - flecs::entity_t r = _::type::id(this->world_v()); - auto o = enum_type(this->world_v()).entity(value); - return this->with(r, o); - } - - Base& with(flecs::term& term) { - this->term(); - *this->term_ = term; - return *this; - } - - Base& with(flecs::term&& term) { - this->term(); - *this->term_ = term; - return *this; - } - - /* Without methods, shorthand for .with(...).not_(). */ - - template - Base& without(Args&&... args) { - return this->with(FLECS_FWD(args)...).not_(); - } - - template - Base& without(Args&&... args) { - return this->with(FLECS_FWD(args)...).not_(); - } - - template - Base& without() { - return this->with().not_(); - } - - /* Write/read methods */ - - Base& write() { - term_builder_i::write(); - return *this; - } - - template - Base& write(Args&&... args) { - return this->with(FLECS_FWD(args)...).write(); - } - - template - Base& write(Args&&... args) { - return this->with(FLECS_FWD(args)...).write(); - } - - template - Base& write() { - return this->with().write(); - } - - Base& read() { - term_builder_i::read(); - return *this; - } - - template - Base& read(Args&&... args) { - return this->with(FLECS_FWD(args)...).read(); - } - - template - Base& read(Args&&... args) { - return this->with(FLECS_FWD(args)...).read(); - } - - template - Base& read() { - return this->with().read(); - } - - /* Scope_open/scope_close shorthand notation. */ - Base& scope_open() { - return this->with(flecs::ScopeOpen).entity(0); - } - - Base& scope_close() { - return this->with(flecs::ScopeClose).entity(0); - } - - /* Term notation for more complex query features */ - - Base& term() { - if (this->term_) { - ecs_check(ecs_term_is_initialized(this->term_), - ECS_INVALID_OPERATION, - "query_builder::term() called without initializing term"); - } - - ecs_check(term_index_ < FLECS_TERM_COUNT_MAX, - ECS_INVALID_PARAMETER, "maximum number of terms exceeded"); - - this->set_term(&desc_->terms[term_index_]); - - term_index_ ++; - - error: - return *this; - } - - Base& term_at(int32_t term_index) { - ecs_assert(term_index >= 0, ECS_INVALID_PARAMETER, NULL); - int32_t prev_index = term_index_; - term_index_ = term_index; - this->term(); - term_index_ = prev_index; - ecs_assert(ecs_term_is_initialized(this->term_), - ECS_INVALID_PARAMETER, NULL); - return *this; - } - - /** Sort the output of a query. - * This enables sorting of entities across matched tables. As a result of this - * operation, the order of entities in the matched tables may be changed. - * Resorting happens when a query iterator is obtained, and only if the table - * data has changed. - * - * If multiple queries that match the same (down)set of tables specify different - * sorting functions, resorting is likely to happen every time an iterator is - * obtained, which can significantly slow down iterations. - * - * The sorting function will be applied to the specified component. Resorting - * only happens if that component has changed, or when the entity order in the - * table has changed. If no component is provided, resorting only happens when - * the entity order changes. - * - * @tparam T The component used to sort. - * @param compare The compare function used to sort the components. - */ - template - Base& order_by(int(*compare)(flecs::entity_t, const T*, flecs::entity_t, const T*)) { - ecs_order_by_action_t cmp = reinterpret_cast(compare); - return this->order_by(_::type::id(this->world_v()), cmp); - } - - /** Sort the output of a query. - * Same as order_by, but with component identifier. - * - * @param component The component used to sort. - * @param compare The compare function used to sort the components. - */ - Base& order_by(flecs::entity_t component, int(*compare)(flecs::entity_t, const void*, flecs::entity_t, const void*)) { - desc_->order_by_callback = reinterpret_cast(compare); - desc_->order_by = component; - return *this; - } - - /** Group and sort matched tables. - * Similar to ecs_query_order_by(), but instead of sorting individual entities, this - * operation only sorts matched tables. This can be useful of a query needs to - * enforce a certain iteration order upon the tables it is iterating, for - * example by giving a certain component or tag a higher priority. - * - * The sorting function assigns a "rank" to each type, which is then used to - * sort the tables. Tables with higher ranks will appear later in the iteration. - * - * Resorting happens when a query iterator is obtained, and only if the set of - * matched tables for a query has changed. If table sorting is enabled together - * with entity sorting, table sorting takes precedence, and entities will be - * sorted within each set of tables that are assigned the same rank. - * - * @tparam T The component used to determine the group rank. - * @param group_by_action Callback that determines group id for table. - */ - template - Base& group_by(uint64_t(*group_by_action)(flecs::world_t*, flecs::table_t *table, flecs::id_t id, void* ctx)) { - ecs_group_by_action_t action = reinterpret_cast(group_by_action); - return this->group_by(_::type::id(this->world_v()), action); - } - - /** Group and sort matched tables. - * Same as group_by, but with component identifier. - * - * @param component The component used to determine the group rank. - * @param group_by_action Callback that determines group id for table. - */ - Base& group_by(flecs::entity_t component, uint64_t(*group_by_action)(flecs::world_t*, flecs::table_t *table, flecs::id_t id, void* ctx)) { - desc_->group_by_callback = reinterpret_cast(group_by_action); - desc_->group_by = component; - return *this; - } - - /** Group and sort matched tables. - * Same as group_by, but with default group_by action. - * - * @tparam T The component used to determine the group rank. - */ - template - Base& group_by() { - return this->group_by(_::type::id(this->world_v()), nullptr); - } - - /** Group and sort matched tables. - * Same as group_by, but with default group_by action. - * - * @param component The component used to determine the group rank. - */ - Base& group_by(flecs::entity_t component) { - return this->group_by(component, nullptr); - } - - /** Specify context to be passed to group_by function. - * - * @param ctx Context to pass to group_by function. - * @param ctx_free Function to cleanup context (called when query is deleted). - */ - Base& group_by_ctx(void *ctx, ecs_ctx_free_t ctx_free = nullptr) { - desc_->group_by_ctx = ctx; - desc_->group_by_ctx_free = ctx_free; - return *this; - } - - /** Specify on_group_create action. - */ - Base& on_group_create(ecs_group_create_action_t action) { - desc_->on_group_create = action; - return *this; - } - - /** Specify on_group_delete action. - */ - Base& on_group_delete(ecs_group_delete_action_t action) { - desc_->on_group_delete = action; - return *this; - } - -protected: - virtual flecs::world_t* world_v() override = 0; - int32_t term_index_; - int32_t expr_count_; - -private: - operator Base&() { - return *static_cast(this); - } - - ecs_query_desc_t *desc_; -}; - -} - - -namespace flecs { -namespace _ { - template - using query_builder_base = builder< - query, ecs_query_desc_t, query_builder, - query_builder_i, Components ...>; -} - -/** Query builder. - * - * @ingroup cpp_core_queries - */ -template -struct query_builder final : _::query_builder_base { - query_builder(flecs::world_t* world, flecs::entity query_entity) - : _::query_builder_base(world) - { - _::sig(world).populate(this); - this->desc_.entity = query_entity.id(); - } - - query_builder(flecs::world_t* world, const char *name = nullptr) - : _::query_builder_base(world) - { - _::sig(world).populate(this); - if (name != nullptr) { - ecs_entity_desc_t entity_desc = {}; - entity_desc.name = name; - entity_desc.sep = "::"; - entity_desc.root_sep = "::"; - this->desc_.entity = ecs_entity_init(world, &entity_desc); - } - } - - template - void each(Func&& func) { - this->build().each(FLECS_FWD(func)); - } -}; - -} - - -namespace flecs -{ - -struct query_base { - query_base() { } - - query_base(query_t *q) - : query_(q) { - flecs_poly_claim(q); - } - - query_base(const query_t *q) - : query_(ECS_CONST_CAST(query_t*, q)) { - flecs_poly_claim(q); - } - - query_base(world_t *world, ecs_query_desc_t *desc) { - if (desc->entity && desc->terms[0].id == 0) { - const flecs::Poly *query_poly = ecs_get_pair( - world, desc->entity, EcsPoly, EcsQuery); - if (query_poly) { - query_ = static_cast(query_poly->poly); - flecs_poly_claim(query_); - return; - } - } - - query_ = ecs_query_init(world, desc); - } - - query_base(const query_base& obj) { - this->query_ = obj.query_; - flecs_poly_claim(this->query_); - } - - query_base& operator=(const query_base& obj) { - this->query_ = obj.query_; - flecs_poly_claim(this->query_); - return *this; - } - - query_base(query_base&& obj) noexcept { - this->query_ = obj.query_; - obj.query_ = nullptr; - } - - query_base& operator=(query_base&& obj) noexcept { - this->query_ = obj.query_; - obj.query_ = nullptr; - return *this; - } - - flecs::entity entity() { - return flecs::entity(query_->world, query_->entity); - } - - const flecs::query_t* c_ptr() const { - return query_; - } - - operator const flecs::query_t*() const { - return query_; - } - - operator bool() const { - return query_ != nullptr; - } - - /** Free persistent query. - * A persistent query is a query that is associated with an entity, such as - * system queries and named queries. Persistent queries must be deleted with - * destruct(), or will be deleted automatically at world cleanup. - */ - void destruct() { - ecs_assert(query_->entity != 0, ECS_INVALID_OPERATION, "destruct() " - "should only be called on queries associated with entities"); - ecs_query_fini(query_); - query_ = nullptr; - } - - ~query_base() { - /* Only free if query is not associated with entity, such as system - * queries and named queries. Named queries have to be either explicitly - * deleted with the .destruct() method, or will be deleted when the - * world is deleted. */ - if (query_ && !query_->entity) { - if (!flecs_poly_release(query_)) { - ecs_query_fini(query_); - query_ = nullptr; - } - } - } - - /** Returns whether the query data changed since the last iteration. - * This operation must be invoked before obtaining the iterator, as this will - * reset the changed state. The operation will return true after: - * - new entities have been matched with - * - matched entities were deleted - * - matched components were changed - * - * @return true if entities changed, otherwise false. - */ - bool changed() const { - return ecs_query_changed(query_); - } - - /** Get info for group. - * - * @param group_id The group id for which to retrieve the info. - * @return The group info. - */ - const flecs::query_group_info_t* group_info(uint64_t group_id) const { - return ecs_query_get_group_info(query_, group_id); - } - - /** Get context for group. - * - * @param group_id The group id for which to retrieve the context. - * @return The group context. - */ - void* group_ctx(uint64_t group_id) const { - const flecs::query_group_info_t *gi = group_info(group_id); - if (gi) { - return gi->ctx; - } else { - return NULL; - } - } - - template - void each_term(const Func& func) { - for (int i = 0; i < query_->term_count; i ++) { - flecs::term t(query_->world, query_->terms[i]); - func(t); - t.reset(); // prevent freeing resources - } - } - - flecs::term term(int32_t index) { - return flecs::term(query_->world, query_->terms[index]); - } - - int32_t term_count() { - return query_->term_count; - } - - int32_t field_count() { - return query_->field_count; - } - - int32_t find_var(const char *name) { - return ecs_query_find_var(query_, name); - } - - flecs::string str() { - char *result = ecs_query_str(query_); - return flecs::string(result); - } - - /** Returns a string representing the query plan. - * This can be used to analyze the behavior & performance of the query. - * @see ecs_query_plan - */ - flecs::string plan() const { - char *result = ecs_query_plan(query_); - return flecs::string(result); - } - - operator query<>() const; - -# ifdef FLECS_JSON - -/** Serialize query to JSON. - * - * @memberof flecs::query_base - * @ingroup cpp_addons_json - */ -flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { - ecs_iter_t it = ecs_query_iter(ecs_get_world(query_), query_); - char *json = ecs_iter_to_json(&it, desc); - return flecs::string(json); -} -# endif - -protected: - query_t *query_ = nullptr; -}; - -template -struct query : query_base, iterable { -private: - using Fields = typename _::field_ptrs::array; - -public: - using query_base::query_base; - - query() : query_base() { } // necessary not to confuse msvc - - query(const query& obj) : query_base(obj) { } - - query& operator=(const query& obj) { - query_base::operator=(obj); - return *this; - } - - query(query&& obj) noexcept : query_base(FLECS_MOV(obj)) { } - - query& operator=(query&& obj) noexcept { - query_base::operator=(FLECS_FWD(obj)); - return *this; - } - -private: - ecs_iter_t get_iter(flecs::world_t *world) const override { - ecs_assert(query_ != nullptr, ECS_INVALID_PARAMETER, - "cannot iterate invalid query"); - if (!world) { - world = query_->world; - } - return ecs_query_iter(world, query_); - } - - ecs_iter_next_action_t next_action() const override { - return ecs_query_next; - } -}; - -// World mixin implementation -template -inline flecs::query world::query(Args &&... args) const { - return flecs::query_builder(world_, FLECS_FWD(args)...) - .build(); -} - -inline flecs::query<> world::query(flecs::entity query_entity) const { - ecs_query_desc_t desc = {}; - desc.entity = query_entity; - return flecs::query<>(world_, &desc); -} - -template -inline flecs::query_builder world::query_builder(Args &&... args) const { - return flecs::query_builder(world_, FLECS_FWD(args)...); -} - -// world::each -namespace _ { - -// Each with entity parameter -template -struct query_delegate_w_ent; - -template -struct query_delegate_w_ent > -{ - query_delegate_w_ent(const flecs::world& world, Func&& func) { - auto f = world.query(); - f.each(FLECS_MOV(func)); - } -}; - -// Each without entity parameter -template -struct query_delegate_no_ent; - -template -struct query_delegate_no_ent > -{ - query_delegate_no_ent(const flecs::world& world, Func&& func) { - auto f = world.query(); - f.each(FLECS_MOV(func)); - } -}; - -// Switch between function with & without entity parameter -template -struct query_delegate; - -template -struct query_delegate, flecs::entity>::value> > { - query_delegate(const flecs::world& world, Func&& func) { - query_delegate_w_ent>(world, FLECS_MOV(func)); - } -}; - -template -struct query_delegate, flecs::entity>::value> > { - query_delegate(const flecs::world& world, Func&& func) { - query_delegate_no_ent>(world, FLECS_MOV(func)); - } -}; - -} - -template -inline void world::each(Func&& func) const { - _::query_delegate f_delegate(*this, FLECS_MOV(func)); -} - -template -inline void world::each(Func&& func) const { - ecs_iter_t it = ecs_each_id(world_, _::type::id(world_)); - - while (ecs_each_next(&it)) { - _::each_delegate(func).invoke(&it); - } -} - -template -inline void world::each(flecs::id_t each_id, Func&& func) const { - ecs_iter_t it = ecs_each_id(world_, each_id); - - while (ecs_each_next(&it)) { - _::each_delegate(func).invoke(&it); - } -} - -// query_base implementation -inline query_base::operator flecs::query<> () const { - return flecs::query<>(query_); -} - -} - -/** - * @file addons/cpp/mixins/observer/impl.hpp - * @brief Observer implementation. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/observer/builder.hpp - * @brief Observer builder. - */ - -#pragma once - -/** - * @file addons/cpp/utils/node_builder.hpp - * @brief Base builder class for node objects, like systems, observers. - */ - -#pragma once - -namespace flecs { -namespace _ { - -// Macros for template types so we don't go cross-eyed -#define FLECS_IBUILDER template class - -template -struct node_builder : IBuilder -{ - using IBase = IBuilder; - -public: - explicit node_builder(flecs::world_t* world, const char *name = nullptr) - : IBase(&desc_) - , desc_{} - , world_(world) - { - ecs_entity_desc_t entity_desc = {}; - entity_desc.name = name; - entity_desc.sep = "::"; - entity_desc.root_sep = "::"; - desc_.entity = ecs_entity_init(world_, &entity_desc); - } - - template - T run(Func&& func) { - using Delegate = typename _::run_delegate< - typename std::decay::type>; - - auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(func)); - desc_.run = Delegate::run; - desc_.run_ctx = ctx; - desc_.run_ctx_free = _::free_obj; - return T(world_, &desc_); - } - - template - T run(Func&& func, EachFunc&& each_func) { - using Delegate = typename _::run_delegate< - typename std::decay::type>; - - auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(func)); - desc_.run = Delegate::run; - desc_.run_ctx = ctx; - desc_.run_ctx_free = _::free_obj; - return each(FLECS_FWD(each_func)); - } - - template - T each(Func&& func) { - using Delegate = typename _::each_delegate< - typename std::decay::type, Components...>; - auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(func)); - desc_.callback = Delegate::run; - desc_.callback_ctx = ctx; - desc_.callback_ctx_free = _::free_obj; - return T(world_, &desc_); - } - -protected: - flecs::world_t* world_v() override { return world_; } - TDesc desc_; - flecs::world_t *world_; -}; - -#undef FLECS_IBUILDER - -} // namespace _ -} // namespace flecs - -/** - * @file addons/cpp/mixins/observer/builder_i.hpp - * @brief Observer builder interface. - */ - -#pragma once - - -namespace flecs { - -/** Observer builder interface. - * - * @ingroup cpp_observers - */ -template -struct observer_builder_i : query_builder_i { - using BaseClass = query_builder_i; - observer_builder_i() - : BaseClass(nullptr) - , desc_(nullptr) - , event_count_(0) { } - - observer_builder_i(ecs_observer_desc_t *desc) - : BaseClass(&desc->query) - , desc_(desc) - , event_count_(0) { } - - /** Specify the event(s) for when the observer should run. - * @param evt The event. - */ - Base& event(entity_t evt) { - desc_->events[event_count_ ++] = evt; - return *this; - } - - /** Specify the event(s) for when the observer should run. - * @tparam E The event. - */ - template - Base& event() { - desc_->events[event_count_ ++] = _::type().id(world_v()); - return *this; - } - - /** Invoke observer for anything that matches its query on creation */ - Base& yield_existing(bool value = true) { - desc_->yield_existing = value; - return *this; - } - - /** Set observer flags */ - Base& observer_flags(ecs_flags32_t flags) { - desc_->flags_ |= flags; - return *this; - } - - /** Set observer context */ - Base& ctx(void *ptr) { - desc_->ctx = ptr; - return *this; - } - - /** Set observer run callback */ - Base& run(ecs_iter_action_t action) { - desc_->run = action; - return *this; - } - -protected: - virtual flecs::world_t* world_v() override = 0; - -private: - operator Base&() { - return *static_cast(this); - } - - ecs_observer_desc_t *desc_; - int32_t event_count_; -}; - -} - - -namespace flecs { -namespace _ { - template - using observer_builder_base = node_builder< - observer, ecs_observer_desc_t, observer_builder, - observer_builder_i, Components ...>; -} - -/** Observer builder. - * - * @ingroup cpp_observers - */ -template -struct observer_builder final : _::observer_builder_base { - observer_builder(flecs::world_t* world, const char *name = nullptr) - : _::observer_builder_base(world, name) - { - _::sig(world).populate(this); - } -}; - -} - - -namespace flecs -{ - -struct observer final : entity -{ - using entity::entity; - - explicit observer() : entity() { } - - observer(flecs::world_t *world, ecs_observer_desc_t *desc) { - world_ = world; - id_ = ecs_observer_init(world, desc); - } - - void ctx(void *ctx) { - ecs_observer_desc_t desc = {}; - desc.entity = id_; - desc.ctx = ctx; - ecs_observer_init(world_, &desc); - } - - void* ctx() const { - return ecs_observer_get(world_, id_)->ctx; - } - - flecs::query<> query() const { - return flecs::query<>(ecs_observer_get(world_, id_)->query); - } -}; - -// Mixin implementation -inline observer world::observer(flecs::entity e) const { - return flecs::observer(world_, e); -} - -template -inline observer_builder world::observer(Args &&... args) const { - return flecs::observer_builder(world_, FLECS_FWD(args)...); -} - -} // namespace flecs - -/** - * @file addons/cpp/mixins/event/impl.hpp - * @brief Event implementation. - */ - -#pragma once - - -namespace flecs -{ - -// Mixin implementation - -inline flecs::event_builder world::event(flecs::entity_t evt) const { - return flecs::event_builder(world_, evt); -} - -template -inline flecs::event_builder_typed world::event() const { - return flecs::event_builder_typed(world_, _::type().id(world_)); -} - -namespace _ { - inline void entity_observer_create( - flecs::world_t *world, - flecs::entity_t event, - flecs::entity_t entity, - ecs_iter_action_t callback, - void *callback_ctx, - ecs_ctx_free_t callback_ctx_free) - { - ecs_observer_desc_t desc = {}; - desc.events[0] = event; - desc.query.terms[0].id = EcsAny; - desc.query.terms[0].src.id = entity; - desc.callback = callback; - desc.callback_ctx = callback_ctx; - desc.callback_ctx_free = callback_ctx_free; - - flecs::entity_t o = ecs_observer_init(world, &desc); - ecs_add_pair(world, o, EcsChildOf, entity); - } - - template - struct entity_observer_factory { - template ::value> = 0> - static void create( - flecs::world_t *world, - flecs::entity_t entity, - Func&& f) - { - using Delegate = _::entity_observer_delegate; - auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(f)); - entity_observer_create(world, _::type::id(world), entity, Delegate::run, ctx, _::free_obj); - } - - template ::value> = 0> - static void create( - flecs::world_t *world, - flecs::entity_t entity, - Func&& f) - { - using Delegate = _::entity_payload_observer_delegate; - auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(f)); - entity_observer_create(world, _::type::id(world), entity, Delegate::run, ctx, _::free_obj); - } - }; -} - -template -template -inline const Self& entity_builder::observe(flecs::entity_t evt, Func&& f) const { - using Delegate = _::entity_observer_delegate; - auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(f)); - - _::entity_observer_create(world_, evt, id_, Delegate::run, ctx, _::free_obj); - - return to_base(); -} - -template -template -inline const Self& entity_builder::observe(Func&& f) const { - _::entity_observer_factory::template create( - world_, id_, FLECS_FWD(f)); - return to_base(); -} - -template -template -inline const Self& entity_builder::observe(Func&& f) const { - return this->observe<_::event_from_func_t>(FLECS_FWD(f)); -} - -inline void entity_view::emit(flecs::entity evt) const { - this->emit(evt.id()); -} - -inline void entity_view::enqueue(flecs::entity evt) const { - this->enqueue(evt.id()); -} - -} // namespace flecs - -/** - * @file addons/cpp/mixins/enum/impl.hpp - * @brief Enum implementation. - */ - -#pragma once - -namespace flecs { - -template -inline E entity_view::to_constant() const { -#ifdef FLECS_META - using U = typename std::underlying_type::type; - const E* ptr = static_cast(ecs_get_id(world_, id_, - ecs_pair(flecs::Constant, _::type::id(world_)))); - ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, "entity is not a constant"); - return ptr[0]; -#else - ecs_assert(false, ECS_UNSUPPORTED, - "operation not supported without FLECS_META addon"); - return E(); -#endif -} - -template ::value >> -inline flecs::entity world::to_entity(E constant) const { - const auto& et = enum_type(world_); - return flecs::entity(world_, et.entity(constant)); -} - -} -#ifdef FLECS_MODULE -/** - * @file addons/cpp/mixins/module/impl.hpp - * @brief Module implementation. - */ - -#pragma once - -namespace flecs { - -namespace _ { - -template -ecs_entity_t do_import(world& world, const char *symbol) { - ecs_trace("#[magenta]import#[reset] %s", _::type_name()); - ecs_log_push(); - - ecs_entity_t scope = ecs_set_scope(world, 0); - - // Initialize module component type & don't allow it to be registered as a - // tag, as this would prevent calling emplace() - auto c_ = component(world, nullptr, false); - - // Make module component sparse so that it'll never move in memory. This - // guarantees that a module destructor can be reliably used to cleanup - // module resources. - c_.add(flecs::Sparse); - - ecs_set_scope(world, c_); - world.emplace(world); - ecs_set_scope(world, scope); - - ecs_add_id(world, c_, EcsModule); - - // It should now be possible to lookup the module - ecs_entity_t m = ecs_lookup_symbol(world, symbol, false, false); - ecs_assert(m != 0, ECS_MODULE_UNDEFINED, symbol); - ecs_assert(m == c_, ECS_INTERNAL_ERROR, NULL); - - ecs_log_pop(); - - return m; -} - -template -flecs::entity import(world& world) { - const char *symbol = _::symbol_name(); - - ecs_entity_t m = ecs_lookup_symbol(world, symbol, true, false); - - if (!_::type::registered(world)) { - /* Module is registered with world, initialize static data */ - if (m) { - _::type::init_builtin(world, m, false); - - /* Module is not yet registered, register it now */ - } else { - m = _::do_import(world, symbol); - } - - /* Module has been registered, but could have been for another world. Import - * if module hasn't been registered for this world. */ - } else if (!m) { - m = _::do_import(world, symbol); - } - - return flecs::entity(world, m); -} - -} - -/** - * @defgroup cpp_addons_modules Modules - * @ingroup cpp_addons - * Modules organize components, systems and more in reusable units of code. - * - * @{ - */ - -template -inline flecs::entity world::module(const char *name) const { - flecs::entity result = this->entity(_::type::register_id( - world_, nullptr, false)); - - if (name) { - flecs::entity prev_parent = result.parent(); - ecs_add_path_w_sep(world_, result, 0, name, "::", "::"); - flecs::entity parent = result.parent(); - if (prev_parent != parent) { - // Module was reparented, cleanup old parent(s) - flecs::entity cur = prev_parent, next; - while (cur) { - next = cur.parent(); - - ecs_iter_t it = ecs_each_id(world_, ecs_pair(EcsChildOf, cur)); - if (!ecs_iter_is_true(&it)) { - cur.destruct(); - - // Prevent increasing the generation count of the temporary - // parent. This allows entities created during - // initialization to keep non-recycled ids. - this->set_version(cur); - } - - cur = next; - } - } - } - - return result; -} - -template -inline flecs::entity world::import() { - return flecs::_::import(*this); -} - -/** @} */ - -} - -#endif -#ifdef FLECS_SYSTEM -/** - * @file addons/cpp/mixins/system/impl.hpp - * @brief System module implementation. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/system/builder.hpp - * @brief System builder. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/system/builder_i.hpp - * @brief System builder interface. - */ - -#pragma once - - -namespace flecs -{ - -/** System builder interface. - * - * @ingroup cpp_addons_systems - */ -template -struct system_builder_i : query_builder_i { -private: - using BaseClass = query_builder_i; - -public: - system_builder_i(ecs_system_desc_t *desc) - : BaseClass(&desc->query) - , desc_(desc) { } - - /** Specify in which phase the system should run. - * - * @param phase The phase. - */ - Base& kind(entity_t phase) { - flecs::entity_t cur_phase = ecs_get_target( - world_v(), desc_->entity, EcsDependsOn, 0); - if (cur_phase) { - ecs_remove_id(world_v(), desc_->entity, ecs_dependson(cur_phase)); - ecs_remove_id(world_v(), desc_->entity, cur_phase); - } - if (phase) { - ecs_add_id(world_v(), desc_->entity, ecs_dependson(phase)); - ecs_add_id(world_v(), desc_->entity, phase); - } - return *this; - } - - template ::value> = 0> - Base& kind(E phase) - { - const auto& et = enum_type(this->world_v()); - flecs::entity_t target = et.entity(phase); - return this->kind(target); - } - - /** Specify in which phase the system should run. - * - * @tparam Phase The phase. - */ - template - Base& kind() { - return this->kind(_::type::id(world_v())); - } - - /** Specify whether system can run on multiple threads. - * - * @param value If false system will always run on a single thread. - */ - Base& multi_threaded(bool value = true) { - desc_->multi_threaded = value; - return *this; - } - - /** Specify whether system should be ran in staged context. - * - * @param value If false system will always run staged. - */ - Base& immediate(bool value = true) { - desc_->immediate = value; - return *this; - } - - /** Set system interval. - * This operation will cause the system to be ran at the specified interval. - * - * The timer is synchronous, and is incremented each frame by delta_time. - * - * @param interval The interval value. - */ - Base& interval(ecs_ftime_t interval) { - desc_->interval = interval; - return *this; - } - - /** Set system rate. - * This operation will cause the system to be ran at a multiple of the - * provided tick source. The tick source may be any entity, including - * another system. - * - * @param tick_source The tick source. - * @param rate The multiple at which to run the system. - */ - Base& rate(const entity_t tick_source, int32_t rate) { - desc_->rate = rate; - desc_->tick_source = tick_source; - return *this; - } - - /** Set system rate. - * This operation will cause the system to be ran at a multiple of the - * frame tick frequency. If a tick source was provided, this just updates - * the rate of the system. - * - * @param rate The multiple at which to run the system. - */ - Base& rate(int32_t rate) { - desc_->rate = rate; - return *this; - } - - /** Set tick source. - * This operation sets a shared tick source for the system. - * - * @tparam T The type associated with the singleton tick source to use for the system. - */ - template - Base& tick_source() { - desc_->tick_source = _::type::id(world_v()); - return *this; - } - - /** Set tick source. - * This operation sets a shared tick source for the system. - * - * @param tick_source The tick source to use for the system. - */ - Base& tick_source(flecs::entity_t tick_source) { - desc_->tick_source = tick_source; - return *this; - } - - /** Set system context */ - Base& ctx(void *ptr) { - desc_->ctx = ptr; - return *this; - } - - /** Set system run callback */ - Base& run(ecs_iter_action_t action) { - desc_->run = action; - return *this; - } - -protected: - virtual flecs::world_t* world_v() override = 0; - -private: - operator Base&() { - return *static_cast(this); - } - - ecs_system_desc_t *desc_; -}; - -} - - -namespace flecs { -namespace _ { - template - using system_builder_base = node_builder< - system, ecs_system_desc_t, system_builder, - system_builder_i, Components ...>; -} - -/** System builder. - * - * @ingroup cpp_addons_systems - */ -template -struct system_builder final : _::system_builder_base { - system_builder(flecs::world_t* world, const char *name = nullptr) - : _::system_builder_base(world, name) - { - _::sig(world).populate(this); - -#ifdef FLECS_PIPELINE - ecs_add_id(world, this->desc_.entity, ecs_dependson(flecs::OnUpdate)); - ecs_add_id(world, this->desc_.entity, flecs::OnUpdate); -#endif - } -}; - -} - - -namespace flecs -{ - -struct system_runner_fluent { - system_runner_fluent( - world_t *world, - entity_t id, - int32_t stage_current, - int32_t stage_count, - ecs_ftime_t delta_time, - void *param) - : stage_(world) - , id_(id) - , delta_time_(delta_time) - , param_(param) - , stage_current_(stage_current) - , stage_count_(stage_count) { } - - system_runner_fluent& offset(int32_t offset) { - offset_ = offset; - return *this; - } - - system_runner_fluent& limit(int32_t limit) { - limit_ = limit; - return *this; - } - - system_runner_fluent& stage(flecs::world& stage) { - stage_ = stage.c_ptr(); - return *this; - } - - ~system_runner_fluent() { - if (stage_count_) { - ecs_run_worker( - stage_, id_, stage_current_, stage_count_, delta_time_, - param_); - } else { - ecs_run(stage_, id_, delta_time_, param_); - } - } - -private: - world_t *stage_; - entity_t id_; - ecs_ftime_t delta_time_; - void *param_; - int32_t offset_; - int32_t limit_; - int32_t stage_current_; - int32_t stage_count_; -}; - -struct system final : entity -{ - using entity::entity; - - explicit system() { - id_ = 0; - world_ = nullptr; - } - - explicit system(flecs::world_t *world, ecs_system_desc_t *desc) { - world_ = world; - id_ = ecs_system_init(world, desc); - } - - void ctx(void *ctx) { - ecs_system_desc_t desc = {}; - desc.entity = id_; - desc.ctx = ctx; - ecs_system_init(world_, &desc); - } - - void* ctx() const { - return ecs_system_get(world_, id_)->ctx; - } - - flecs::query<> query() const { - return flecs::query<>(ecs_system_get(world_, id_)->query); - } - - system_runner_fluent run(ecs_ftime_t delta_time = 0.0f, void *param = nullptr) const { - return system_runner_fluent(world_, id_, 0, 0, delta_time, param); - } - - system_runner_fluent run_worker( - int32_t stage_current, - int32_t stage_count, - ecs_ftime_t delta_time = 0.0f, - void *param = nullptr) const - { - return system_runner_fluent( - world_, id_, stage_current, stage_count, delta_time, param); - } - -# ifdef FLECS_TIMER -/** - * @file addons/cpp/mixins/timer/system_mixin.inl - * @brief Timer module system mixin. - */ - -/** - * @memberof flecs::system - * @ingroup cpp_addons_timer - * - * @{ - */ - -/** Set interval. - * @see ecs_set_interval - */ -void interval(ecs_ftime_t interval); - -/** Get interval. - * @see ecs_get_interval. - */ -ecs_ftime_t interval(); - -/** Set timeout. - * @see ecs_set_timeout - */ -void timeout(ecs_ftime_t timeout); - -/** Get timeout. - * @see ecs_get_timeout - */ -ecs_ftime_t timeout(); - -/** Set system rate (system is its own tick source). - * @see ecs_set_rate - */ -void rate(int32_t rate); - -/** Start timer. - * @see ecs_start_timer - */ -void start(); - -/** Stop timer. - * @see ecs_start_timer - */ -void stop(); - -/** Set external tick source. - * @see ecs_set_tick_source - */ -template -void set_tick_source(); - -/** Set external tick source. - * @see ecs_set_tick_source - */ -void set_tick_source(flecs::entity e); - -/** @} */ - -# endif - -}; - -// Mixin implementation -inline system world::system(flecs::entity e) const { - return flecs::system(world_, e); -} - -template -inline system_builder world::system(Args &&... args) const { - return flecs::system_builder(world_, FLECS_FWD(args)...); -} - -namespace _ { - -inline void system_init(flecs::world& world) { - world.component("flecs::system::TickSource"); -} - -} // namespace _ -} // namespace flecs - -#endif -#ifdef FLECS_PIPELINE -/** - * @file addons/cpp/mixins/pipeline/impl.hpp - * @brief Pipeline module implementation. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/pipeline/builder.hpp - * @brief Pipeline builder. - */ - -#pragma once - -/** - * @file addons/cpp/mixins/pipeline/builder_i.hpp - * @brief Pipeline builder interface. - */ - -#pragma once - - -namespace flecs { - -/** Pipeline builder interface. - * - * @ingroup cpp_pipelines - */ -template -struct pipeline_builder_i : query_builder_i { - pipeline_builder_i(ecs_pipeline_desc_t *desc, int32_t term_index = 0) - : query_builder_i(&desc->query, term_index) - , desc_(desc) { } - -private: - ecs_pipeline_desc_t *desc_; -}; - -} - - -namespace flecs { -namespace _ { - template - using pipeline_builder_base = builder< - pipeline, ecs_pipeline_desc_t, pipeline_builder, - pipeline_builder_i, Components ...>; -} - -/** Pipeline builder. - * - * @ingroup cpp_pipelines - */ -template -struct pipeline_builder final : _::pipeline_builder_base { - pipeline_builder(flecs::world_t* world, flecs::entity_t id = 0) - : _::pipeline_builder_base(world) - { - _::sig(world).populate(this); - this->desc_.entity = id; - } -}; - -} - - -namespace flecs { - -template -struct pipeline : entity { - pipeline(world_t *world, ecs_pipeline_desc_t *desc) - : entity(world) - { - id_ = ecs_pipeline_init(world, desc); - - if (!id_) { - ecs_abort(ECS_INVALID_PARAMETER, NULL); - } - } -}; - -inline flecs::pipeline_builder<> world::pipeline() const { - return flecs::pipeline_builder<>(world_); -} - -template ::value >> -inline flecs::pipeline_builder<> world::pipeline() const { - return flecs::pipeline_builder<>(world_, _::type::id(world_)); -} - -inline void world::set_pipeline(const flecs::entity pip) const { - return ecs_set_pipeline(world_, pip); -} - -template -inline void world::set_pipeline() const { - return ecs_set_pipeline(world_, _::type::id(world_)); -} - -inline flecs::entity world::get_pipeline() const { - return flecs::entity(world_, ecs_get_pipeline(world_)); -} - -inline bool world::progress(ecs_ftime_t delta_time) const { - return ecs_progress(world_, delta_time); -} - -inline void world::run_pipeline(const flecs::entity_t pip, ecs_ftime_t delta_time) const { - return ecs_run_pipeline(world_, pip, delta_time); -} - -template ::value >> -inline void world::run_pipeline(ecs_ftime_t delta_time) const { - return ecs_run_pipeline(world_, _::type::id(world_), delta_time); -} - -inline void world::set_time_scale(ecs_ftime_t mul) const { - ecs_set_time_scale(world_, mul); -} - -inline void world::set_target_fps(ecs_ftime_t target_fps) const { - ecs_set_target_fps(world_, target_fps); -} - -inline void world::reset_clock() const { - ecs_reset_clock(world_); -} - -inline void world::set_threads(int32_t threads) const { - ecs_set_threads(world_, threads); -} - -inline int32_t world::get_threads() const { - return ecs_get_stage_count(world_); -} - -inline void world::set_task_threads(int32_t task_threads) const { - ecs_set_task_threads(world_, task_threads); -} - -inline bool world::using_task_threads() const { - return ecs_using_task_threads(world_); -} - -} - -#endif -#ifdef FLECS_TIMER -/** - * @file addons/cpp/mixins/timer/impl.hpp - * @brief Timer module implementation. - */ - -#pragma once - -namespace flecs { - -// Timer class -struct timer final : entity { - using entity::entity; - - timer& interval(ecs_ftime_t interval) { - ecs_set_interval(world_, id_, interval); - return *this; - } - - ecs_ftime_t interval() { - return ecs_get_interval(world_, id_); - } - - timer& timeout(ecs_ftime_t timeout) { - ecs_set_timeout(world_, id_, timeout); - return *this; - } - - ecs_ftime_t timeout() { - return ecs_get_timeout(world_, id_); - } - - timer& rate(int32_t rate, flecs::entity_t tick_source = 0) { - ecs_set_rate(world_, id_, rate, tick_source); - return *this; - } - - void start() { - ecs_start_timer(world_, id_); - } - - void stop() { - ecs_stop_timer(world_, id_); - } -}; - -template -inline flecs::timer world::timer() const { - return flecs::timer(world_, _::type::id(world_)); -} - -template -inline flecs::timer world::timer(Args &&... args) const { - return flecs::timer(world_, FLECS_FWD(args)...); -} - -inline void world::randomize_timers() const { - ecs_randomize_timers(world_); -} - -inline void system::interval(ecs_ftime_t interval) { - ecs_set_interval(world_, id_, interval); -} - -inline ecs_ftime_t system::interval() { - return ecs_get_interval(world_, id_); -} - -inline void system::timeout(ecs_ftime_t timeout) { - ecs_set_timeout(world_, id_, timeout); -} - -inline ecs_ftime_t system::timeout() { - return ecs_get_timeout(world_, id_); -} - -inline void system::rate(int32_t rate) { - ecs_set_rate(world_, id_, rate, 0); -} - -inline void system::start() { - ecs_start_timer(world_, id_); -} - -inline void system::stop() { - ecs_stop_timer(world_, id_); -} - -template -inline void system::set_tick_source() { - ecs_set_tick_source(world_, id_, _::type::id(world_)); -} - -inline void system::set_tick_source(flecs::entity e) { - ecs_set_tick_source(world_, id_, e); -} - -namespace _ { - -inline void timer_init(flecs::world& world) { - world.component("flecs::timer::RateFilter"); - world.component("flecs::timer::Timer"); -} - -} -} - -#endif -#ifdef FLECS_DOC -/** - * @file addons/cpp/mixins/doc/impl.hpp - * @brief Doc mixin implementation. - */ - -#pragma once - -namespace flecs { -namespace doc { - -/** Get UUID for an entity. - * - * @see ecs_doc_get_uuid() - * @see flecs::doc::set_uuid() - * @see flecs::entity_view::doc_uuid() - * - * @ingroup cpp_addons_doc - */ -inline const char* get_uuid(const flecs::entity_view& e) { - return ecs_doc_get_uuid(e.world(), e); -} - -/** Get human readable name for an entity. - * - * @see ecs_doc_get_name() - * @see flecs::doc::set_name() - * @see flecs::entity_view::doc_name() - * - * @ingroup cpp_addons_doc - */ -inline const char* get_name(const flecs::entity_view& e) { - return ecs_doc_get_name(e.world(), e); -} - -/** Get brief description for an entity. - * - * @see ecs_doc_get_brief() - * @see flecs::doc::set_brief() - * @see flecs::entity_view::doc_brief() - * - * @ingroup cpp_addons_doc - */ -inline const char* get_brief(const flecs::entity_view& e) { - return ecs_doc_get_brief(e.world(), e); -} - -/** Get detailed description for an entity. - * - * @see ecs_doc_get_detail() - * @see flecs::doc::set_detail() - * @see flecs::entity_view::doc_detail() - * - * @ingroup cpp_addons_doc - */ -inline const char* get_detail(const flecs::entity_view& e) { - return ecs_doc_get_detail(e.world(), e); -} - -/** Get link to external documentation for an entity. - * - * @see ecs_doc_get_link() - * @see flecs::doc::set_link() - * @see flecs::entity_view::doc_link() - * - * @ingroup cpp_addons_doc - */ -inline const char* get_link(const flecs::entity_view& e) { - return ecs_doc_get_link(e.world(), e); -} - -/** Get color for an entity. - * - * @see ecs_doc_get_color() - * @see flecs::doc::set_color() - * @see flecs::entity_view::doc_color() - * - * @ingroup cpp_addons_doc - */ -inline const char* get_color(const flecs::entity_view& e) { - return ecs_doc_get_color(e.world(), e); -} - -/** Set UUID for an entity. - * - * @see ecs_doc_set_uuid() - * @see flecs::doc::get_uuid() - * @see flecs::entity_builder::set_doc_uuid() - * - * @ingroup cpp_addons_doc - */ -inline void set_uuid(flecs::entity& e, const char *uuid) { - ecs_doc_set_uuid(e.world(), e, uuid); -} - -/** Set human readable name for an entity. - * - * @see ecs_doc_set_name() - * @see flecs::doc::get_name() - * @see flecs::entity_builder::set_doc_name() - * - * @ingroup cpp_addons_doc - */ -inline void set_name(flecs::entity& e, const char *name) { - ecs_doc_set_name(e.world(), e, name); -} - -/** Set brief description for an entity. - * - * @see ecs_doc_set_brief() - * @see flecs::doc::get_brief() - * @see flecs::entity_builder::set_doc_brief() - * - * @ingroup cpp_addons_doc - */ -inline void set_brief(flecs::entity& e, const char *description) { - ecs_doc_set_brief(e.world(), e, description); -} - -/** Set detailed description for an entity. - * - * @see ecs_doc_set_detail() - * @see flecs::doc::get_detail() - * @see flecs::entity_builder::set_doc_detail() - * - * @ingroup cpp_addons_doc - */ -inline void set_detail(flecs::entity& e, const char *description) { - ecs_doc_set_detail(e.world(), e, description); -} - -/** Set link to external documentation for an entity. - * - * @see ecs_doc_set_link() - * @see flecs::doc::get_link() - * @see flecs::entity_builder::set_doc_link() - * - * @ingroup cpp_addons_doc - */ -inline void set_link(flecs::entity& e, const char *link) { - ecs_doc_set_link(e.world(), e, link); -} - -/** Set color for an entity. - * - * @see ecs_doc_set_color() - * @see flecs::doc::get_color() - * @see flecs::entity_builder::set_doc_color() - * - * @ingroup cpp_addons_doc - */ -inline void set_color(flecs::entity& e, const char *color) { - ecs_doc_set_color(e.world(), e, color); -} - -/** @private */ -namespace _ { - -/** @private */ -inline void init(flecs::world& world) { - world.component("flecs::doc::Description"); -} - -} // namespace _ -} // namespace doc -} // namespace flecs - -#endif -#ifdef FLECS_DOC -#endif -#ifdef FLECS_REST -/** - * @file addons/cpp/mixins/rest/impl.hpp - * @brief Rest module implementation. - */ - -#pragma once - -namespace flecs { -namespace rest { -namespace _ { - -inline void init(flecs::world& world) { - world.component("flecs::rest::Rest"); -} - -} // namespace _ -} // namespace rest -} // namespace flecs - -#endif -#ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/impl.hpp - * @brief Meta implementation. - */ - -#pragma once - -FLECS_ENUM_LAST(flecs::meta::type_kind_t, flecs::meta::TypeKindLast) -FLECS_ENUM_LAST(flecs::meta::primitive_kind_t, flecs::meta::PrimitiveKindLast) - -namespace flecs { -namespace meta { -namespace _ { - -/* Type support for entity wrappers */ -template -inline flecs::opaque flecs_entity_support(flecs::world&) { - return flecs::opaque() - .as_type(flecs::Entity) - .serialize([](const flecs::serializer *ser, const EntityType *data) { - flecs::entity_t id = data->id(); - return ser->value(flecs::Entity, &id); - }) - .assign_entity( - [](EntityType *dst, flecs::world_t *world, flecs::entity_t e) { - *dst = EntityType(world, e); - }); -} - -inline void init(flecs::world& world) { - world.component("flecs::meta::bool"); - world.component("flecs::meta::char"); - world.component("flecs::meta::u8"); - world.component("flecs::meta::u16"); - world.component("flecs::meta::u32"); - world.component("flecs::meta::u64"); - world.component("flecs::meta::i8"); - world.component("flecs::meta::i16"); - world.component("flecs::meta::i32"); - world.component("flecs::meta::i64"); - world.component("flecs::meta::f32"); - world.component("flecs::meta::f64"); - - world.component("flecs::meta::type_kind"); - world.component("flecs::meta::primitive_kind"); - world.component("flecs::meta::member_t"); - world.component("flecs::meta::enum_constant"); - world.component("flecs::meta::bitmask_constant"); - - world.component("flecs::meta::type"); - world.component("flecs::meta::TypeSerializer"); - world.component("flecs::meta::primitive"); - world.component("flecs::meta::enum"); - world.component("flecs::meta::bitmask"); - world.component("flecs::meta::member"); - world.component("flecs::meta::member_ranges"); - world.component("flecs::meta::struct"); - world.component("flecs::meta::array"); - world.component("flecs::meta::vector"); - - world.component("flecs::meta::unit"); - - // To support member and member register components - // (that do not have conflicting symbols with builtin ones) for platform - // specific types. - - if (!flecs::is_same() && !flecs::is_same()) { - flecs::_::type::init_builtin(world, flecs::Iptr, true); - // Remove symbol to prevent validation errors, as it doesn't match with - // the typename - ecs_remove_pair(world, flecs::Iptr, ecs_id(EcsIdentifier), EcsSymbol); - } - - if (!flecs::is_same() && !flecs::is_same()) { - flecs::_::type::init_builtin(world, flecs::Uptr, true); - // Remove symbol to prevent validation errors, as it doesn't match with - // the typename - ecs_remove_pair(world, flecs::Uptr, ecs_id(EcsIdentifier), EcsSymbol); - } - - // Register opaque type support for C++ entity wrappers - world.entity("::flecs::cpp").add(flecs::Module).scope([&]{ - world.component() - .opaque(flecs_entity_support); - world.component() - .opaque(flecs_entity_support); - }); -} - -} // namespace _ - -} // namespace meta - - -inline flecs::entity cursor::get_type() const { - return flecs::entity(cursor_.world, ecs_meta_get_type(&cursor_)); -} - -inline flecs::entity cursor::get_unit() const { - return flecs::entity(cursor_.world, ecs_meta_get_unit(&cursor_)); -} - -inline flecs::entity cursor::get_entity() const { - return flecs::entity(cursor_.world, ecs_meta_get_entity(&cursor_)); -} - -/** Create primitive type */ -inline flecs::entity world::primitive(flecs::meta::primitive_kind_t kind) { - ecs_primitive_desc_t desc = {}; - desc.kind = kind; - flecs::entity_t eid = ecs_primitive_init(world_, &desc); - ecs_assert(eid != 0, ECS_INVALID_OPERATION, NULL); - return flecs::entity(world_, eid); -} - -/** Create array type. */ -inline flecs::entity world::array(flecs::entity_t elem_id, int32_t array_count) { - ecs_array_desc_t desc = {}; - desc.type = elem_id; - desc.count = array_count; - flecs::entity_t eid = ecs_array_init(world_, &desc); - ecs_assert(eid != 0, ECS_INVALID_OPERATION, NULL); - return flecs::entity(world_, eid); -} - -/** Create array type. */ -template -inline flecs::entity world::array(int32_t array_count) { - return this->array(_::type::id(world_), array_count); -} - -inline flecs::entity world::vector(flecs::entity_t elem_id) { - ecs_vector_desc_t desc = {}; - desc.type = elem_id; - flecs::entity_t eid = ecs_vector_init(world_, &desc); - ecs_assert(eid != 0, ECS_INVALID_OPERATION, NULL); - return flecs::entity(world_, eid); -} - -template -inline flecs::entity world::vector() { - return this->vector(_::type::id(world_)); -} - -} // namespace flecs - -inline int ecs_serializer_t::value(ecs_entity_t type, const void *v) const { - return this->value_(this, type, v); -} - -template -inline int ecs_serializer_t::value(const T& v) const { - return this->value(flecs::_::type::id( - const_cast(this->world)), &v); -} - -inline int ecs_serializer_t::member(const char *name) const { - return this->member_(this, name); -} - -#endif -#ifdef FLECS_UNITS -/** - * @file addons/cpp/mixins/units/impl.hpp - * @brief Units module implementation. - */ - -#pragma once - -namespace flecs { - -inline units::units(flecs::world& world) { - /* Import C module */ - FlecsUnitsImport(world); - - /* Bridge between C++ types and flecs.units entities */ - world.module(); - - // Initialize world.entity(prefixes) scope - world.entity("::flecs::units::prefixes"); - - // Initialize prefixes - world.entity("::flecs::units::prefixes::Yocto"); - world.entity("::flecs::units::prefixes::Zepto"); - world.entity("::flecs::units::prefixes::Atto"); - world.entity("::flecs::units::prefixes::Femto"); - world.entity("::flecs::units::prefixes::Pico"); - world.entity("::flecs::units::prefixes::Nano"); - world.entity("::flecs::units::prefixes::Micro"); - world.entity("::flecs::units::prefixes::Milli"); - world.entity("::flecs::units::prefixes::Centi"); - world.entity("::flecs::units::prefixes::Deci"); - world.entity("::flecs::units::prefixes::Deca"); - world.entity("::flecs::units::prefixes::Hecto"); - world.entity("::flecs::units::prefixes::Kilo"); - world.entity("::flecs::units::prefixes::Mega"); - world.entity("::flecs::units::prefixes::Giga"); - world.entity("::flecs::units::prefixes::Tera"); - world.entity("::flecs::units::prefixes::Peta"); - world.entity("::flecs::units::prefixes::Exa"); - world.entity("::flecs::units::prefixes::Zetta"); - world.entity("::flecs::units::prefixes::Yotta"); - world.entity("::flecs::units::prefixes::Kibi"); - world.entity("::flecs::units::prefixes::Mebi"); - world.entity("::flecs::units::prefixes::Gibi"); - world.entity("::flecs::units::prefixes::Tebi"); - world.entity("::flecs::units::prefixes::Pebi"); - world.entity("::flecs::units::prefixes::Exbi"); - world.entity("::flecs::units::prefixes::Zebi"); - world.entity("::flecs::units::prefixes::Yobi"); - - // Initialize quantities - world.entity("::flecs::units::Duration"); - world.entity iter::field(int8_t index) const { - ecs_assert(!(iter_->flags & EcsIterCppEach) || - ecs_field_src(iter_, index) != 0, ECS_INVALID_OPERATION, - "cannot .field from .each, use .field_at<%s>(%d, row) instead", - _::type_name(), index); - return get_field(index); -} - -template ::value == false, void>::type*> -inline flecs::field iter::field(int8_t index) const { - ecs_assert(!(iter_->flags & EcsIterCppEach) || - ecs_field_src(iter_, index) != 0, ECS_INVALID_OPERATION, - "cannot .field from .each, use .field_at<%s>(%d, row) instead", - _::type_name(), index); - ecs_assert(!ecs_field_is_readonly(iter_, index), - ECS_ACCESS_VIOLATION, NULL); - return get_field(index); -} - -inline flecs::entity iter::get_var(int var_id) const { - ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, 0); - return flecs::entity(iter_->world, ecs_iter_get_var(iter_, var_id)); -} - -/** Get value of variable by name. - * Get value of a query variable for current result. - */ -inline flecs::entity iter::get_var(const char *name) const { - ecs_query_iter_t *qit = &iter_->priv_.iter.query; - const flecs::query_t *q = qit->query; - int var_id = ecs_query_find_var(q, name); - ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, name); - return flecs::entity(iter_->world, ecs_iter_get_var(iter_, var_id)); -} - -template -void iter::targets(int8_t index, const Func& func) { - ecs_assert(iter_->table != nullptr, ECS_INVALID_OPERATION, NULL); - ecs_assert(index < iter_->field_count, ECS_INVALID_PARAMETER, NULL); - ecs_assert(ecs_field_is_set(iter_, index), ECS_INVALID_PARAMETER, NULL); - const ecs_type_t *table_type = ecs_table_get_type(iter_->table); - const ecs_table_record_t *tr = iter_->trs[index]; - int32_t i = tr->index, end = i + tr->count; - for (; i < end; i ++) { - ecs_id_t id = table_type->array[i]; - ecs_assert(ECS_IS_PAIR(id), ECS_INVALID_PARAMETER, - "field does not match a pair"); - flecs::entity tgt(iter_->world, - ecs_pair_second(iter_->real_world, id)); - func(tgt); - } -} - -} // namespace flecs - -/** - * @file addons/cpp/impl/world.hpp - * @brief World implementation. - */ - -#pragma once - -namespace flecs -{ - -inline void world::init_builtin_components() { - this->component(); - this->component(); - this->component(); - -# ifdef FLECS_SYSTEM - _::system_init(*this); -# endif -# ifdef FLECS_TIMER - _::timer_init(*this); -# endif -# ifdef FLECS_DOC - doc::_::init(*this); -# endif -# ifdef FLECS_REST - rest::_::init(*this); -# endif -# ifdef FLECS_META - meta::_::init(*this); -# endif -} - -template -inline flecs::entity world::use(const char *alias) const { - entity_t e = _::type::id(world_); - const char *name = alias; - if (!name) { - // If no name is defined, use the entity name without the scope - name = ecs_get_name(world_, e); - } - ecs_set_alias(world_, e, name); - return flecs::entity(world_, e); -} - -inline flecs::entity world::use(const char *name, const char *alias) const { - entity_t e = ecs_lookup_path_w_sep(world_, 0, name, "::", "::", true); - ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL); - - ecs_set_alias(world_, e, alias); - return flecs::entity(world_, e); -} - -inline void world::use(flecs::entity e, const char *alias) const { - entity_t eid = e.id(); - const char *name = alias; - if (!name) { - // If no name is defined, use the entity name without the scope - name = ecs_get_name(world_, eid); - } - ecs_set_alias(world_, eid, name); -} - -inline flecs::entity world::set_scope(const flecs::entity_t s) const { - return flecs::entity(ecs_set_scope(world_, s)); -} - -inline flecs::entity world::get_scope() const { - return flecs::entity(world_, ecs_get_scope(world_)); -} - -template -inline flecs::entity world::set_scope() const { - return set_scope( _::type::id(world_) ); -} - -inline entity world::lookup(const char *name, const char *sep, const char *root_sep, bool recursive) const { - auto e = ecs_lookup_path_w_sep(world_, 0, name, sep, root_sep, recursive); - return flecs::entity(*this, e); -} - -#ifndef ensure -template -inline T& world::ensure() const { - flecs::entity e(world_, _::type::id(world_)); - return e.ensure(); -} -#endif - -template -inline void world::modified() const { - flecs::entity e(world_, _::type::id(world_)); - e.modified(); -} - -template -inline void world::set(Second second, const First& value) const { - flecs::entity e(world_, _::type::id(world_)); - e.set(second, value); -} - -template -inline void world::set(Second second, First&& value) const { - flecs::entity e(world_, _::type::id(world_)); - e.set(second, value); -} - -template -inline ref world::get_ref() const { - flecs::entity e(world_, _::type::id(world_)); - return e.get_ref(); -} - -template -inline const T* world::get() const { - flecs::entity e(world_, _::type::id(world_)); - return e.get(); -} - -template -const A* world::get() const { - flecs::entity e(world_, _::type::id(world_)); - return e.get(); -} - -template -const First* world::get(Second second) const { - flecs::entity e(world_, _::type::id(world_)); - return e.get(second); -} - -template -T* world::get_mut() const { - flecs::entity e(world_, _::type::id(world_)); - return e.get_mut(); -} - -template -A* world::get_mut() const { - flecs::entity e(world_, _::type::id(world_)); - return e.get_mut(); -} - -template -First* world::get_mut(Second second) const { - flecs::entity e(world_, _::type::id(world_)); - return e.get_mut(second); -} - -template -inline bool world::has() const { - flecs::entity e(world_, _::type::id(world_)); - return e.has(); -} - -template -inline bool world::has() const { - flecs::entity e(world_, _::type::id(world_)); - return e.has(); -} - -template -inline bool world::has(flecs::id_t second) const { - flecs::entity e(world_, _::type::id(world_)); - return e.has(second); -} - -inline bool world::has(flecs::id_t first, flecs::id_t second) const { - flecs::entity e(world_, first); - return e.has(first, second); -} - -template -inline void world::add() const { - flecs::entity e(world_, _::type::id(world_)); - e.add(); -} - -template -inline void world::add() const { - flecs::entity e(world_, _::type::id(world_)); - e.add(); -} - -template -inline void world::add(flecs::entity_t second) const { - flecs::entity e(world_, _::type::id(world_)); - e.add(second); -} - -inline void world::add(flecs::entity_t first, flecs::entity_t second) const { - flecs::entity e(world_, first); - e.add(first, second); -} - -template -inline void world::remove() const { - flecs::entity e(world_, _::type::id(world_)); - e.remove(); -} - -template -inline void world::remove() const { - flecs::entity e(world_, _::type::id(world_)); - e.remove(); -} - -template -inline void world::remove(flecs::entity_t second) const { - flecs::entity e(world_, _::type::id(world_)); - e.remove(second); -} - -inline void world::remove(flecs::entity_t first, flecs::entity_t second) const { - flecs::entity e(world_, first); - e.remove(first, second); -} - -template -inline void world::children(Func&& f) const { - this->entity(0).children(FLECS_FWD(f)); -} - -template -inline flecs::entity world::singleton() const { - return flecs::entity(world_, _::type::id(world_)); -} - -template -inline flecs::entity world::target(int32_t index) const -{ - return flecs::entity(world_, - ecs_get_target(world_, _::type::id(world_), _::type::id(world_), index)); -} - -template -inline flecs::entity world::target( - flecs::entity_t relationship, - int32_t index) const -{ - return flecs::entity(world_, - ecs_get_target(world_, _::type::id(world_), relationship, index)); -} - -inline flecs::entity world::target( - flecs::entity_t relationship, - int32_t index) const -{ - return flecs::entity(world_, - ecs_get_target(world_, relationship, relationship, index)); -} - -template ::value > > -inline void world::get(const Func& func) const { - static_assert(arity::value == 1, "singleton component must be the only argument"); - _::entity_with_delegate::invoke_get( - this->world_, this->singleton>(), func); -} - -template ::value > > -inline void world::set(const Func& func) const { - static_assert(arity::value == 1, "singleton component must be the only argument"); - _::entity_with_delegate::invoke_ensure( - this->world_, this->singleton>(), func); -} - -inline flecs::entity world::get_alive(flecs::entity_t e) const { - e = ecs_get_alive(world_, e); - return flecs::entity(world_, e); -} - -inline flecs::entity world::make_alive(flecs::entity_t e) const { - ecs_make_alive(world_, e); - return flecs::entity(world_, e); -} - -template -inline flecs::entity enum_data::entity() const { - return flecs::entity(world_, _::type::id(world_)); -} - -template -inline flecs::entity enum_data::entity(underlying_type_t value) const { - int index = index_by_value(value); - if (index >= 0) { - int32_t constant_i = impl_.constants[index].index; - flecs::entity_t entity = flecs_component_ids_get(world_, constant_i); - return flecs::entity(world_, entity); - } -#ifdef FLECS_META - // Reflection data lookup failed. Try value lookup amongst flecs::Constant relationships - flecs::world world = flecs::world(world_); - return world.query_builder() - .with(flecs::ChildOf, world.id()) - .with(flecs::Constant, world.id()) - .build() - .find([value](flecs::entity constant) { - const int32_t *constant_value = constant.get_second(flecs::Constant); - ecs_assert(constant_value, ECS_INTERNAL_ERROR, NULL); - return value == static_cast>(*constant_value); - }); -#else - return flecs::entity::null(world_); -#endif -} - -template -inline flecs::entity enum_data::entity(E value) const { - return entity(static_cast>(value)); -} - -/** Use provided scope for operations ran on returned world. - * Operations need to be ran in a single statement. - */ -inline flecs::scoped_world world::scope(id_t parent) const { - return scoped_world(world_, parent); -} - -template -inline flecs::scoped_world world::scope() const { - flecs::id_t parent = _::type::id(world_); - return scoped_world(world_, parent); -} - -inline flecs::scoped_world world::scope(const char* name) const { - return scope(entity(name)); -} - -} // namespace flecs - - -/** - * @defgroup cpp_core Core - * Core ECS functionality (entities, storage, queries) - * - * @{ - * @} - */ - -/** - * @defgroup cpp_addons Addons - * C++ APIs for addons. - * - * @{ - * @} - */ - -/** @} */ - -#endif // __cplusplus - -#endif // FLECS_CPP - -#endif - - -#endif - diff --git a/include/colysis.h b/include/colysis.h index 80c9363..4ba3ec6 100644 --- a/include/colysis.h +++ b/include/colysis.h @@ -1,16 +1,12 @@ #ifndef COLYSIS_H #define COLYSIS_H -/* This generated file contains includes for project dependencies */ -#include "colysis/bake_config.h" -#include "raylib.h" -#include "raymath.h" -#include "rlgl.h" - #ifdef __cplusplus extern "C" { #endif + + #ifdef __cplusplus } #endif diff --git a/include/colysis/bake_config.h b/include/colysis/bake_config.h deleted file mode 100644 index 626162f..0000000 --- a/include/colysis/bake_config.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - ) - (.) - .|. - | | - _.--| |--._ - .-'; ;`-'& ; `&. - \ & ; & &_/ - |"""---...---"""| - \ | | | | | | | / - `---.|.|.|.---' - - * This file is generated by bake.lang.c for your convenience. Headers of - * dependencies will automatically show up in this file. Include bake_config.h - * in your main project file. Do not edit! */ - -#ifndef COLYSIS_BAKE_CONFIG_H -#define COLYSIS_BAKE_CONFIG_H - -/* Headers of public dependencies */ -#include "../../deps/flecs.h" - -#endif - diff --git a/include/raylib.h b/include/raylib.h deleted file mode 100644 index 56abfa7..0000000 --- a/include/raylib.h +++ /dev/null @@ -1,1708 +0,0 @@ -/********************************************************************************************** -* -* raylib v5.6-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) -* -* FEATURES: -* - NO external dependencies, all required libraries included with raylib -* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, -* MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5. -* - Written in plain C code (C99) in PascalCase/camelCase notation -* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) -* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] -* - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) -* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) -* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! -* - Flexible Materials system, supporting classic maps and PBR maps -* - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) -* - Shaders support, including Model shaders and Postprocessing shaders -* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] -* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD) -* - VR stereo rendering with configurable HMD device parameters -* - Bindings to multiple programming languages available! -* -* NOTES: -* - One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] -* - One default Texture2D is loaded on rlglInit(), 1x1 white pixel R8G8B8A8 [rlgl] (OpenGL 3.3 or ES2) -* - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) -* - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) -* -* DEPENDENCIES (included): -* [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input -* [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input -* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading -* [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management -* -* OPTIONAL DEPENDENCIES (included): -* [rcore] msf_gif (Miles Fogle) for GIF recording -* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm -* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm -* [rcore] rprand (Ramon Snatamaria) for pseudo-random numbers generation -* [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image manage -* [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) -* [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) -* [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms -* [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation -* [rtext] stb_truetype (Sean Barret) for ttf fonts loading -* [rtext] stb_rect_pack (Sean Barret) for rectangles packing -* [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation -* [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) -* [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) -* [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) -* [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX) -* [raudio] dr_wav (David Reid) for WAV audio file loading -* [raudio] dr_flac (David Reid) for FLAC audio file loading -* [raudio] dr_mp3 (David Reid) for MP3 audio file loading -* [raudio] stb_vorbis (Sean Barret) for OGG audio loading -* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading -* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading -* [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio manage -* -* -* LICENSE: zlib/libpng -* -* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, -* BSD-like license that allows static linking with closed source software: -* -* Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) -* -* 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. -* -**********************************************************************************************/ - -#ifndef RAYLIB_H -#define RAYLIB_H - -#include // Required for: va_list - Only used by TraceLogCallback - -#define RAYLIB_VERSION_MAJOR 5 -#define RAYLIB_VERSION_MINOR 6 -#define RAYLIB_VERSION_PATCH 0 -#define RAYLIB_VERSION "5.6-dev" - -// Function specifiers in case library is build/used as a shared library -// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll -// NOTE: visibility("default") attribute makes symbols "visible" when compiled with -fvisibility=hidden -#if defined(_WIN32) - #if defined(__TINYC__) - #define __declspec(x) __attribute__((x)) - #endif - #if defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) - #elif defined(USE_LIBTYPE_SHARED) - #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) - #endif -#else - #if defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __attribute__((visibility("default"))) // We are building as a Unix shared library (.so/.dylib) - #endif -#endif - -#ifndef RLAPI - #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) -#endif - -//---------------------------------------------------------------------------------- -// Some basic Defines -//---------------------------------------------------------------------------------- -#ifndef PI - #define PI 3.14159265358979323846f -#endif -#ifndef DEG2RAD - #define DEG2RAD (PI/180.0f) -#endif -#ifndef RAD2DEG - #define RAD2DEG (180.0f/PI) -#endif - -// Allow custom memory allocators -// NOTE: Require recompiling raylib sources -#ifndef RL_MALLOC - #define RL_MALLOC(sz) malloc(sz) -#endif -#ifndef RL_CALLOC - #define RL_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RL_REALLOC - #define RL_REALLOC(ptr,sz) realloc(ptr,sz) -#endif -#ifndef RL_FREE - #define RL_FREE(ptr) free(ptr) -#endif - -// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) -// Plain structures in C++ (without constructors) can be initialized with { } -// This is called aggregate initialization (C++11 feature) -#if defined(__cplusplus) - #define CLITERAL(type) type -#else - #define CLITERAL(type) (type) -#endif - -// Some compilers (mostly macos clang) default to C++98, -// where aggregate initialization can't be used -// So, give a more clear error stating how to fix this -#if !defined(_MSC_VER) && (defined(__cplusplus) && __cplusplus < 201103L) - #error "C++11 or later is required. Add -std=c++11" -#endif - -// NOTE: We set some defines with some data types declared by raylib -// Other modules (raymath, rlgl) also require some of those types, so, -// to be able to use those other modules as standalone (not depending on raylib) -// this defines are very useful for internal check and avoid type (re)definitions -#define RL_COLOR_TYPE -#define RL_RECTANGLE_TYPE -#define RL_VECTOR2_TYPE -#define RL_VECTOR3_TYPE -#define RL_VECTOR4_TYPE -#define RL_QUATERNION_TYPE -#define RL_MATRIX_TYPE - -// Some Basic Colors -// NOTE: Custom raylib color palette for amazing visuals on WHITE background -#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray -#define GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray -#define DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray -#define YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow -#define GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold -#define ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange -#define PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink -#define RED CLITERAL(Color){ 230, 41, 55, 255 } // Red -#define MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon -#define GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green -#define LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime -#define DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green -#define SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue -#define BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue -#define DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue -#define PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple -#define VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet -#define DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple -#define BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige -#define BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown -#define DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown - -#define WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White -#define BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black -#define BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) -#define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta -#define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) - -//---------------------------------------------------------------------------------- -// Structures Definition -//---------------------------------------------------------------------------------- -// Boolean type -#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) - #include -#elif !defined(__cplusplus) && !defined(bool) - typedef enum bool { false = 0, true = !false } bool; - #define RL_BOOL_TYPE -#endif - -// Vector2, 2 components -typedef struct Vector2 { - float x; // Vector x component - float y; // Vector y component -} Vector2; - -// Vector3, 3 components -typedef struct Vector3 { - float x; // Vector x component - float y; // Vector y component - float z; // Vector z component -} Vector3; - -// Vector4, 4 components -typedef struct Vector4 { - float x; // Vector x component - float y; // Vector y component - float z; // Vector z component - float w; // Vector w component -} Vector4; - -// Quaternion, 4 components (Vector4 alias) -typedef Vector4 Quaternion; - -// Matrix, 4x4 components, column major, OpenGL style, right-handed -typedef struct Matrix { - float m0, m4, m8, m12; // Matrix first row (4 components) - float m1, m5, m9, m13; // Matrix second row (4 components) - float m2, m6, m10, m14; // Matrix third row (4 components) - float m3, m7, m11, m15; // Matrix fourth row (4 components) -} Matrix; - -// Color, 4 components, R8G8B8A8 (32bit) -typedef struct Color { - unsigned char r; // Color red value - unsigned char g; // Color green value - unsigned char b; // Color blue value - unsigned char a; // Color alpha value -} Color; - -// Rectangle, 4 components -typedef struct Rectangle { - float x; // Rectangle top-left corner position x - float y; // Rectangle top-left corner position y - float width; // Rectangle width - float height; // Rectangle height -} Rectangle; - -// Image, pixel data stored in CPU memory (RAM) -typedef struct Image { - void *data; // Image raw data - int width; // Image base width - int height; // Image base height - int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (PixelFormat type) -} Image; - -// Texture, tex data stored in GPU memory (VRAM) -typedef struct Texture { - unsigned int id; // OpenGL texture id - int width; // Texture base width - int height; // Texture base height - int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (PixelFormat type) -} Texture; - -// Texture2D, same as Texture -typedef Texture Texture2D; - -// TextureCubemap, same as Texture -typedef Texture TextureCubemap; - -// RenderTexture, fbo for texture rendering -typedef struct RenderTexture { - unsigned int id; // OpenGL framebuffer object id - Texture texture; // Color buffer attachment texture - Texture depth; // Depth buffer attachment texture -} RenderTexture; - -// RenderTexture2D, same as RenderTexture -typedef RenderTexture RenderTexture2D; - -// NPatchInfo, n-patch layout info -typedef struct NPatchInfo { - Rectangle source; // Texture source rectangle - int left; // Left border offset - int top; // Top border offset - int right; // Right border offset - int bottom; // Bottom border offset - int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 -} NPatchInfo; - -// GlyphInfo, font characters glyphs info -typedef struct GlyphInfo { - int value; // Character value (Unicode) - int offsetX; // Character offset X when drawing - int offsetY; // Character offset Y when drawing - int advanceX; // Character advance position X - Image image; // Character image data -} GlyphInfo; - -// Font, font texture and GlyphInfo array data -typedef struct Font { - int baseSize; // Base size (default chars height) - int glyphCount; // Number of glyph characters - int glyphPadding; // Padding around the glyph characters - Texture2D texture; // Texture atlas containing the glyphs - Rectangle *recs; // Rectangles in texture for the glyphs - GlyphInfo *glyphs; // Glyphs info data -} Font; - -// Camera, defines position/orientation in 3d space -typedef struct Camera3D { - Vector3 position; // Camera position - Vector3 target; // Camera target it looks-at - Vector3 up; // Camera up vector (rotation over its axis) - float fovy; // Camera field-of-view aperture in Y (degrees) in perspective, used as near plane width in orthographic - int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC -} Camera3D; - -typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D - -// Camera2D, defines position/orientation in 2d space -typedef struct Camera2D { - Vector2 offset; // Camera offset (displacement from target) - Vector2 target; // Camera target (rotation and zoom origin) - float rotation; // Camera rotation in degrees - float zoom; // Camera zoom (scaling), should be 1.0f by default -} Camera2D; - -// Mesh, vertex data and vao/vbo -typedef struct Mesh { - int vertexCount; // Number of vertices stored in arrays - int triangleCount; // Number of triangles stored (indexed or not) - - // Vertex attributes data - float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) - float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) - float *texcoords2; // Vertex texture second coordinates (UV - 2 components per vertex) (shader-location = 5) - float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) - float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) - unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) - unsigned short *indices; // Vertex indices (in case vertex data comes indexed) - - // Animation vertex data - float *animVertices; // Animated vertex positions (after bones transformations) - float *animNormals; // Animated normals (after bones transformations) - unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6) - float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) - Matrix *boneMatrices; // Bones animated transformation matrices - int boneCount; // Number of bones - - // OpenGL identifiers - unsigned int vaoId; // OpenGL Vertex Array Object id - unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) -} Mesh; - -// Shader -typedef struct Shader { - unsigned int id; // Shader program id - int *locs; // Shader locations array (RL_MAX_SHADER_LOCATIONS) -} Shader; - -// MaterialMap -typedef struct MaterialMap { - Texture2D texture; // Material map texture - Color color; // Material map color - float value; // Material map value -} MaterialMap; - -// Material, includes shader and maps -typedef struct Material { - Shader shader; // Material shader - MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) - float params[4]; // Material generic parameters (if required) -} Material; - -// Transform, vertex transformation data -typedef struct Transform { - Vector3 translation; // Translation - Quaternion rotation; // Rotation - Vector3 scale; // Scale -} Transform; - -// Bone, skeletal animation bone -typedef struct BoneInfo { - char name[32]; // Bone name - int parent; // Bone parent -} BoneInfo; - -// Model, meshes, materials and animation data -typedef struct Model { - Matrix transform; // Local transform matrix - - int meshCount; // Number of meshes - int materialCount; // Number of materials - Mesh *meshes; // Meshes array - Material *materials; // Materials array - int *meshMaterial; // Mesh material number - - // Animation data - int boneCount; // Number of bones - BoneInfo *bones; // Bones information (skeleton) - Transform *bindPose; // Bones base transformation (pose) -} Model; - -// ModelAnimation -typedef struct ModelAnimation { - int boneCount; // Number of bones - int frameCount; // Number of animation frames - BoneInfo *bones; // Bones information (skeleton) - Transform **framePoses; // Poses array by frame - char name[32]; // Animation name -} ModelAnimation; - -// Ray, ray for raycasting -typedef struct Ray { - Vector3 position; // Ray position (origin) - Vector3 direction; // Ray direction (normalized) -} Ray; - -// RayCollision, ray hit information -typedef struct RayCollision { - bool hit; // Did the ray hit something? - float distance; // Distance to the nearest hit - Vector3 point; // Point of the nearest hit - Vector3 normal; // Surface normal of hit -} RayCollision; - -// BoundingBox -typedef struct BoundingBox { - Vector3 min; // Minimum vertex box-corner - Vector3 max; // Maximum vertex box-corner -} BoundingBox; - -// Wave, audio wave data -typedef struct Wave { - unsigned int frameCount; // Total number of frames (considering channels) - unsigned int sampleRate; // Frequency (samples per second) - unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) - unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) - void *data; // Buffer data pointer -} Wave; - -// Opaque structs declaration -// NOTE: Actual structs are defined internally in raudio module -typedef struct rAudioBuffer rAudioBuffer; -typedef struct rAudioProcessor rAudioProcessor; - -// AudioStream, custom audio stream -typedef struct AudioStream { - rAudioBuffer *buffer; // Pointer to internal data used by the audio system - rAudioProcessor *processor; // Pointer to internal data processor, useful for audio effects - - unsigned int sampleRate; // Frequency (samples per second) - unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) - unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) -} AudioStream; - -// Sound -typedef struct Sound { - AudioStream stream; // Audio stream - unsigned int frameCount; // Total number of frames (considering channels) -} Sound; - -// Music, audio stream, anything longer than ~10 seconds should be streamed -typedef struct Music { - AudioStream stream; // Audio stream - unsigned int frameCount; // Total number of frames (considering channels) - bool looping; // Music looping enable - - int ctxType; // Type of music context (audio filetype) - void *ctxData; // Audio context data, depends on type -} Music; - -// VrDeviceInfo, Head-Mounted-Display device parameters -typedef struct VrDeviceInfo { - int hResolution; // Horizontal resolution in pixels - int vResolution; // Vertical resolution in pixels - float hScreenSize; // Horizontal size in meters - float vScreenSize; // Vertical size in meters - float eyeToScreenDistance; // Distance between eye and display in meters - float lensSeparationDistance; // Lens separation distance in meters - float interpupillaryDistance; // IPD (distance between pupils) in meters - float lensDistortionValues[4]; // Lens distortion constant parameters - float chromaAbCorrection[4]; // Chromatic aberration correction parameters -} VrDeviceInfo; - -// VrStereoConfig, VR stereo rendering configuration for simulator -typedef struct VrStereoConfig { - Matrix projection[2]; // VR projection matrices (per eye) - Matrix viewOffset[2]; // VR view offset matrices (per eye) - float leftLensCenter[2]; // VR left lens center - float rightLensCenter[2]; // VR right lens center - float leftScreenCenter[2]; // VR left screen center - float rightScreenCenter[2]; // VR right screen center - float scale[2]; // VR distortion scale - float scaleIn[2]; // VR distortion scale in -} VrStereoConfig; - -// File path list -typedef struct FilePathList { - unsigned int capacity; // Filepaths max entries - unsigned int count; // Filepaths entries count - char **paths; // Filepaths entries -} FilePathList; - -// Automation event -typedef struct AutomationEvent { - unsigned int frame; // Event frame - unsigned int type; // Event type (AutomationEventType) - int params[4]; // Event parameters (if required) -} AutomationEvent; - -// Automation event list -typedef struct AutomationEventList { - unsigned int capacity; // Events max entries (MAX_AUTOMATION_EVENTS) - unsigned int count; // Events entries count - AutomationEvent *events; // Events entries -} AutomationEventList; - -//---------------------------------------------------------------------------------- -// Enumerators Definition -//---------------------------------------------------------------------------------- -// System/Window config flags -// NOTE: Every bit registers one state (use it with bit masks) -// By default all flags are set to 0 -typedef enum { - FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU - FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen - FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window - FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) - FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window - FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) - FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) - FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused - FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top - FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized - FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer - FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI - FLAG_WINDOW_MOUSE_PASSTHROUGH = 0x00004000, // Set to support mouse passthrough, only supported when FLAG_WINDOW_UNDECORATED - FLAG_BORDERLESS_WINDOWED_MODE = 0x00008000, // Set to run program in borderless windowed mode - FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X - FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) -} ConfigFlags; - -// Trace log level -// NOTE: Organized by priority level -typedef enum { - LOG_ALL = 0, // Display all logs - LOG_TRACE, // Trace logging, intended for internal use only - LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds - LOG_INFO, // Info logging, used for program execution info - LOG_WARNING, // Warning logging, used on recoverable failures - LOG_ERROR, // Error logging, used on unrecoverable failures - LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) - LOG_NONE // Disable logging -} TraceLogLevel; - -// Keyboard keys (US keyboard layout) -// NOTE: Use GetKeyPressed() to allow redefining -// required keys for alternative layouts -typedef enum { - KEY_NULL = 0, // Key: NULL, used for no key pressed - // Alphanumeric keys - KEY_APOSTROPHE = 39, // Key: ' - KEY_COMMA = 44, // Key: , - KEY_MINUS = 45, // Key: - - KEY_PERIOD = 46, // Key: . - KEY_SLASH = 47, // Key: / - KEY_ZERO = 48, // Key: 0 - KEY_ONE = 49, // Key: 1 - KEY_TWO = 50, // Key: 2 - KEY_THREE = 51, // Key: 3 - KEY_FOUR = 52, // Key: 4 - KEY_FIVE = 53, // Key: 5 - KEY_SIX = 54, // Key: 6 - KEY_SEVEN = 55, // Key: 7 - KEY_EIGHT = 56, // Key: 8 - KEY_NINE = 57, // Key: 9 - KEY_SEMICOLON = 59, // Key: ; - KEY_EQUAL = 61, // Key: = - KEY_A = 65, // Key: A | a - KEY_B = 66, // Key: B | b - KEY_C = 67, // Key: C | c - KEY_D = 68, // Key: D | d - KEY_E = 69, // Key: E | e - KEY_F = 70, // Key: F | f - KEY_G = 71, // Key: G | g - KEY_H = 72, // Key: H | h - KEY_I = 73, // Key: I | i - KEY_J = 74, // Key: J | j - KEY_K = 75, // Key: K | k - KEY_L = 76, // Key: L | l - KEY_M = 77, // Key: M | m - KEY_N = 78, // Key: N | n - KEY_O = 79, // Key: O | o - KEY_P = 80, // Key: P | p - KEY_Q = 81, // Key: Q | q - KEY_R = 82, // Key: R | r - KEY_S = 83, // Key: S | s - KEY_T = 84, // Key: T | t - KEY_U = 85, // Key: U | u - KEY_V = 86, // Key: V | v - KEY_W = 87, // Key: W | w - KEY_X = 88, // Key: X | x - KEY_Y = 89, // Key: Y | y - KEY_Z = 90, // Key: Z | z - KEY_LEFT_BRACKET = 91, // Key: [ - KEY_BACKSLASH = 92, // Key: '\' - KEY_RIGHT_BRACKET = 93, // Key: ] - KEY_GRAVE = 96, // Key: ` - // Function keys - KEY_SPACE = 32, // Key: Space - KEY_ESCAPE = 256, // Key: Esc - KEY_ENTER = 257, // Key: Enter - KEY_TAB = 258, // Key: Tab - KEY_BACKSPACE = 259, // Key: Backspace - KEY_INSERT = 260, // Key: Ins - KEY_DELETE = 261, // Key: Del - KEY_RIGHT = 262, // Key: Cursor right - KEY_LEFT = 263, // Key: Cursor left - KEY_DOWN = 264, // Key: Cursor down - KEY_UP = 265, // Key: Cursor up - KEY_PAGE_UP = 266, // Key: Page up - KEY_PAGE_DOWN = 267, // Key: Page down - KEY_HOME = 268, // Key: Home - KEY_END = 269, // Key: End - KEY_CAPS_LOCK = 280, // Key: Caps lock - KEY_SCROLL_LOCK = 281, // Key: Scroll down - KEY_NUM_LOCK = 282, // Key: Num lock - KEY_PRINT_SCREEN = 283, // Key: Print screen - KEY_PAUSE = 284, // Key: Pause - KEY_F1 = 290, // Key: F1 - KEY_F2 = 291, // Key: F2 - KEY_F3 = 292, // Key: F3 - KEY_F4 = 293, // Key: F4 - KEY_F5 = 294, // Key: F5 - KEY_F6 = 295, // Key: F6 - KEY_F7 = 296, // Key: F7 - KEY_F8 = 297, // Key: F8 - KEY_F9 = 298, // Key: F9 - KEY_F10 = 299, // Key: F10 - KEY_F11 = 300, // Key: F11 - KEY_F12 = 301, // Key: F12 - KEY_LEFT_SHIFT = 340, // Key: Shift left - KEY_LEFT_CONTROL = 341, // Key: Control left - KEY_LEFT_ALT = 342, // Key: Alt left - KEY_LEFT_SUPER = 343, // Key: Super left - KEY_RIGHT_SHIFT = 344, // Key: Shift right - KEY_RIGHT_CONTROL = 345, // Key: Control right - KEY_RIGHT_ALT = 346, // Key: Alt right - KEY_RIGHT_SUPER = 347, // Key: Super right - KEY_KB_MENU = 348, // Key: KB menu - // Keypad keys - KEY_KP_0 = 320, // Key: Keypad 0 - KEY_KP_1 = 321, // Key: Keypad 1 - KEY_KP_2 = 322, // Key: Keypad 2 - KEY_KP_3 = 323, // Key: Keypad 3 - KEY_KP_4 = 324, // Key: Keypad 4 - KEY_KP_5 = 325, // Key: Keypad 5 - KEY_KP_6 = 326, // Key: Keypad 6 - KEY_KP_7 = 327, // Key: Keypad 7 - KEY_KP_8 = 328, // Key: Keypad 8 - KEY_KP_9 = 329, // Key: Keypad 9 - KEY_KP_DECIMAL = 330, // Key: Keypad . - KEY_KP_DIVIDE = 331, // Key: Keypad / - KEY_KP_MULTIPLY = 332, // Key: Keypad * - KEY_KP_SUBTRACT = 333, // Key: Keypad - - KEY_KP_ADD = 334, // Key: Keypad + - KEY_KP_ENTER = 335, // Key: Keypad Enter - KEY_KP_EQUAL = 336, // Key: Keypad = - // Android key buttons - KEY_BACK = 4, // Key: Android back button - KEY_MENU = 5, // Key: Android menu button - KEY_VOLUME_UP = 24, // Key: Android volume up button - KEY_VOLUME_DOWN = 25 // Key: Android volume down button -} KeyboardKey; - -// Add backwards compatibility support for deprecated names -#define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT -#define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT -#define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE - -// Mouse buttons -typedef enum { - MOUSE_BUTTON_LEFT = 0, // Mouse button left - MOUSE_BUTTON_RIGHT = 1, // Mouse button right - MOUSE_BUTTON_MIDDLE = 2, // Mouse button middle (pressed wheel) - MOUSE_BUTTON_SIDE = 3, // Mouse button side (advanced mouse device) - MOUSE_BUTTON_EXTRA = 4, // Mouse button extra (advanced mouse device) - MOUSE_BUTTON_FORWARD = 5, // Mouse button forward (advanced mouse device) - MOUSE_BUTTON_BACK = 6, // Mouse button back (advanced mouse device) -} MouseButton; - -// Mouse cursor -typedef enum { - MOUSE_CURSOR_DEFAULT = 0, // Default pointer shape - MOUSE_CURSOR_ARROW = 1, // Arrow shape - MOUSE_CURSOR_IBEAM = 2, // Text writing cursor shape - MOUSE_CURSOR_CROSSHAIR = 3, // Cross shape - MOUSE_CURSOR_POINTING_HAND = 4, // Pointing hand cursor - MOUSE_CURSOR_RESIZE_EW = 5, // Horizontal resize/move arrow shape - MOUSE_CURSOR_RESIZE_NS = 6, // Vertical resize/move arrow shape - MOUSE_CURSOR_RESIZE_NWSE = 7, // Top-left to bottom-right diagonal resize/move arrow shape - MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape - MOUSE_CURSOR_RESIZE_ALL = 9, // The omnidirectional resize/move cursor shape - MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape -} MouseCursor; - -// Gamepad buttons -typedef enum { - GAMEPAD_BUTTON_UNKNOWN = 0, // Unknown button, just for error checking - GAMEPAD_BUTTON_LEFT_FACE_UP, // Gamepad left DPAD up button - GAMEPAD_BUTTON_LEFT_FACE_RIGHT, // Gamepad left DPAD right button - GAMEPAD_BUTTON_LEFT_FACE_DOWN, // Gamepad left DPAD down button - GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Gamepad left DPAD left button - GAMEPAD_BUTTON_RIGHT_FACE_UP, // Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) - GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, // Gamepad right button right (i.e. PS3: Circle, Xbox: B) - GAMEPAD_BUTTON_RIGHT_FACE_DOWN, // Gamepad right button down (i.e. PS3: Cross, Xbox: A) - GAMEPAD_BUTTON_RIGHT_FACE_LEFT, // Gamepad right button left (i.e. PS3: Square, Xbox: X) - GAMEPAD_BUTTON_LEFT_TRIGGER_1, // Gamepad top/back trigger left (first), it could be a trailing button - GAMEPAD_BUTTON_LEFT_TRIGGER_2, // Gamepad top/back trigger left (second), it could be a trailing button - GAMEPAD_BUTTON_RIGHT_TRIGGER_1, // Gamepad top/back trigger right (first), it could be a trailing button - GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // Gamepad top/back trigger right (second), it could be a trailing button - GAMEPAD_BUTTON_MIDDLE_LEFT, // Gamepad center buttons, left one (i.e. PS3: Select) - GAMEPAD_BUTTON_MIDDLE, // Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) - GAMEPAD_BUTTON_MIDDLE_RIGHT, // Gamepad center buttons, right one (i.e. PS3: Start) - GAMEPAD_BUTTON_LEFT_THUMB, // Gamepad joystick pressed button left - GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right -} GamepadButton; - -// Gamepad axis -typedef enum { - GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis - GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis - GAMEPAD_AXIS_RIGHT_X = 2, // Gamepad right stick X axis - GAMEPAD_AXIS_RIGHT_Y = 3, // Gamepad right stick Y axis - GAMEPAD_AXIS_LEFT_TRIGGER = 4, // Gamepad back trigger left, pressure level: [1..-1] - GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // Gamepad back trigger right, pressure level: [1..-1] -} GamepadAxis; - -// Material map index -typedef enum { - MATERIAL_MAP_ALBEDO = 0, // Albedo material (same as: MATERIAL_MAP_DIFFUSE) - MATERIAL_MAP_METALNESS, // Metalness material (same as: MATERIAL_MAP_SPECULAR) - MATERIAL_MAP_NORMAL, // Normal material - MATERIAL_MAP_ROUGHNESS, // Roughness material - MATERIAL_MAP_OCCLUSION, // Ambient occlusion material - MATERIAL_MAP_EMISSION, // Emission material - MATERIAL_MAP_HEIGHT, // Heightmap material - MATERIAL_MAP_CUBEMAP, // Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) - MATERIAL_MAP_IRRADIANCE, // Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) - MATERIAL_MAP_PREFILTER, // Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) - MATERIAL_MAP_BRDF // Brdf material -} MaterialMapIndex; - -#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO -#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS - -// Shader location index -typedef enum { - SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position - SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 - SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 - SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal - SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent - SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color - SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection - SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) - SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection - SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) - SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal - SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view - SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color - SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color - SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color - SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) - SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) - SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal - SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness - SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion - SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission - SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height - SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap - SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance - SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter - SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf - SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds - SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights - SHADER_LOC_BONE_MATRICES // Shader location: array of matrices uniform: boneMatrices -} ShaderLocationIndex; - -#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO -#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS - -// Shader uniform data type -typedef enum { - SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float - SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) - SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) - SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) - SHADER_UNIFORM_INT, // Shader uniform type: int - SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) - SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) - SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) - SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d -} ShaderUniformDataType; - -// Shader attribute data types -typedef enum { - SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float - SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) - SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) - SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) -} ShaderAttributeDataType; - -// Pixel formats -// NOTE: Support depends on OpenGL version and platform -typedef enum { - PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) - PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) - PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp - PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp - PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) - PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) - PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp - PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) - PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) - PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) - PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) - PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) - PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) - PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) - PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) - PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp - PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp - PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp - PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp - PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp -} PixelFormat; - -// Texture parameters: filter mode -// NOTE 1: Filtering considers mipmaps if available in the texture -// NOTE 2: Filter is accordingly set for minification and magnification -typedef enum { - TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation - TEXTURE_FILTER_BILINEAR, // Linear filtering - TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) - TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x - TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x - TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x -} TextureFilter; - -// Texture parameters: wrap mode -typedef enum { - TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode - TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode - TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode - TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode -} TextureWrap; - -// Cubemap layouts -typedef enum { - CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type - CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces - CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces - CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces - CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces -} CubemapLayout; - -// Font type, defines generation method -typedef enum { - FONT_DEFAULT = 0, // Default font generation, anti-aliased - FONT_BITMAP, // Bitmap font generation, no anti-aliasing - FONT_SDF // SDF font generation, requires external shader -} FontType; - -// Color blending modes (pre-defined) -typedef enum { - BLEND_ALPHA = 0, // Blend textures considering alpha (default) - BLEND_ADDITIVE, // Blend textures adding colors - BLEND_MULTIPLIED, // Blend textures multiplying colors - BLEND_ADD_COLORS, // Blend textures adding colors (alternative) - BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) - BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha - BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) - BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) -} BlendMode; - -// Gesture -// NOTE: Provided as bit-wise flags to enable only desired gestures -typedef enum { - GESTURE_NONE = 0, // No gesture - GESTURE_TAP = 1, // Tap gesture - GESTURE_DOUBLETAP = 2, // Double tap gesture - GESTURE_HOLD = 4, // Hold gesture - GESTURE_DRAG = 8, // Drag gesture - GESTURE_SWIPE_RIGHT = 16, // Swipe right gesture - GESTURE_SWIPE_LEFT = 32, // Swipe left gesture - GESTURE_SWIPE_UP = 64, // Swipe up gesture - GESTURE_SWIPE_DOWN = 128, // Swipe down gesture - GESTURE_PINCH_IN = 256, // Pinch in gesture - GESTURE_PINCH_OUT = 512 // Pinch out gesture -} Gesture; - -// Camera system modes -typedef enum { - CAMERA_CUSTOM = 0, // Camera custom, controlled by user (UpdateCamera() does nothing) - CAMERA_FREE, // Camera free mode - CAMERA_ORBITAL, // Camera orbital, around target, zoom supported - CAMERA_FIRST_PERSON, // Camera first person - CAMERA_THIRD_PERSON // Camera third person -} CameraMode; - -// Camera projection -typedef enum { - CAMERA_PERSPECTIVE = 0, // Perspective projection - CAMERA_ORTHOGRAPHIC // Orthographic projection -} CameraProjection; - -// N-patch layout -typedef enum { - NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles - NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles - NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles -} NPatchLayout; - -// Callbacks to hook some internal functions -// WARNING: These callbacks are intended for advanced users -typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages -typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, int *dataSize); // FileIO: Load binary data -typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, int dataSize); // FileIO: Save binary data -typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data -typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data - -//------------------------------------------------------------------------------------ -// Global Variables Definition -//------------------------------------------------------------------------------------ -// It's lonely here... - -//------------------------------------------------------------------------------------ -// Window and Graphics Device Functions (Module: core) -//------------------------------------------------------------------------------------ - -#if defined(__cplusplus) -extern "C" { // Prevents name mangling of functions -#endif - -// Window-related functions -RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context -RLAPI void CloseWindow(void); // Close window and unload OpenGL context -RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked) -RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully -RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen -RLAPI bool IsWindowHidden(void); // Check if window is currently hidden -RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized -RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized -RLAPI bool IsWindowFocused(void); // Check if window is currently focused -RLAPI bool IsWindowResized(void); // Check if window has been resized last frame -RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled -RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags -RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags -RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution -RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution -RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable -RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable -RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized -RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit) -RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit) -RLAPI void SetWindowTitle(const char *title); // Set title for window -RLAPI void SetWindowPosition(int x, int y); // Set window position on screen -RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window -RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) -RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) -RLAPI void SetWindowSize(int width, int height); // Set window dimensions -RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] -RLAPI void SetWindowFocused(void); // Set window focused -RLAPI void *GetWindowHandle(void); // Get native window handle -RLAPI int GetScreenWidth(void); // Get current screen width -RLAPI int GetScreenHeight(void); // Get current screen height -RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) -RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) -RLAPI int GetMonitorCount(void); // Get number of connected monitors -RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed -RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position -RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor) -RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor) -RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres -RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres -RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate -RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor -RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor -RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor -RLAPI void SetClipboardText(const char *text); // Set clipboard text content -RLAPI const char *GetClipboardText(void); // Get clipboard text content -RLAPI Image GetClipboardImage(void); // Get clipboard image content -RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling -RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling - -// Cursor-related functions -RLAPI void ShowCursor(void); // Shows cursor -RLAPI void HideCursor(void); // Hides cursor -RLAPI bool IsCursorHidden(void); // Check if cursor is not visible -RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) -RLAPI void DisableCursor(void); // Disables cursor (lock cursor) -RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen - -// Drawing-related functions -RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) -RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing -RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) -RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) -RLAPI void EndMode2D(void); // Ends 2D mode with custom camera -RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) -RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode -RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture -RLAPI void EndTextureMode(void); // Ends drawing to render texture -RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing -RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) -RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied, subtract, custom) -RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) -RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) -RLAPI void EndScissorMode(void); // End scissor mode -RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) -RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) - -// VR stereo config functions for VR simulator -RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters -RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config - -// Shader management functions -// NOTE: Shader functionality is not available on OpenGL 1.1 -RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations -RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations -RLAPI bool IsShaderValid(Shader shader); // Check if a shader is valid (loaded on GPU) -RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location -RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value -RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector -RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) -RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) -RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) - -// Screen-space-related functions -#define GetMouseRay GetScreenToWorldRay // Compatibility hack for previous raylib versions -RLAPI Ray GetScreenToWorldRay(Vector2 position, Camera camera); // Get a ray trace from screen position (i.e mouse) -RLAPI Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height); // Get a ray trace from screen position (i.e mouse) in a viewport -RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get the screen space position for a 3d world space position -RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Get size position for a 3d world space position -RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Get the screen space position for a 2d camera world space position -RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get the world space position for a 2d camera screen space position -RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) -RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix - -// Timing-related functions -RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) -RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) -RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() -RLAPI int GetFPS(void); // Get current FPS - -// Custom frame control functions -// NOTE: Those functions are intended for advanced users that want full control over the frame processing -// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() -// To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL -RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) -RLAPI void PollInputEvents(void); // Register all input events -RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) - -// Random values generation functions -RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator -RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) -RLAPI int *LoadRandomSequence(unsigned int count, int min, int max); // Load random values sequence, no values repeated -RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence - -// Misc. functions -RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) -RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) -RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) - -// NOTE: Following functions implemented in module [utils] -//------------------------------------------------------------------ -RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) -RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level -RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator -RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator -RLAPI void MemFree(void *ptr); // Internal memory free - -// Set custom callbacks -// WARNING: Callbacks setup is intended for advanced users -RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log -RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader -RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver -RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader -RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver - -// Files management functions -RLAPI unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) -RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() -RLAPI bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write), returns true on success -RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName); // Export data to code (.h), returns true on success -RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string -RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() -RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success -//------------------------------------------------------------------ - -// File system functions -RLAPI bool FileExists(const char *fileName); // Check if file exists -RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists -RLAPI bool IsFileExtension(const char *fileName, const char *ext); // Check file extension (including point: .png, .wav) -RLAPI int GetFileLength(const char *fileName); // Get file length in bytes (NOTE: GetFileSize() conflicts with windows.h) -RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: '.png') -RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string -RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) -RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) -RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) -RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) -RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) -RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success -RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success -RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory -RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS -RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths -RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result -RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths -RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window -RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths -RLAPI void UnloadDroppedFiles(FilePathList files); // Unload dropped filepaths -RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) - -// Compression/Encoding functionality -RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() -RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() -RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string, memory must be MemFree() -RLAPI unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() -RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code -RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) -RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) - - -// Automation events functionality -RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS -RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file -RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file -RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to -RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording -RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) -RLAPI void StopAutomationEventRecording(void); // Stop recording automation events -RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event - -//------------------------------------------------------------------------------------ -// Input Handling Functions (Module: core) -//------------------------------------------------------------------------------------ - -// Input-related functions: keyboard -RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once -RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again -RLAPI bool IsKeyDown(int key); // Check if a key is being pressed -RLAPI bool IsKeyReleased(int key); // Check if a key has been released once -RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed -RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty -RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty -RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) - -// Input-related functions: gamepads -RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available -RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id -RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once -RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed -RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once -RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed -RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed -RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad -RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis -RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) -RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) - -// Input-related functions: mouse -RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once -RLAPI bool IsMouseButtonDown(int button); // Check if a mouse button is being pressed -RLAPI bool IsMouseButtonReleased(int button); // Check if a mouse button has been released once -RLAPI bool IsMouseButtonUp(int button); // Check if a mouse button is NOT being pressed -RLAPI int GetMouseX(void); // Get mouse position X -RLAPI int GetMouseY(void); // Get mouse position Y -RLAPI Vector2 GetMousePosition(void); // Get mouse position XY -RLAPI Vector2 GetMouseDelta(void); // Get mouse delta between frames -RLAPI void SetMousePosition(int x, int y); // Set mouse position XY -RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset -RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling -RLAPI float GetMouseWheelMove(void); // Get mouse wheel movement for X or Y, whichever is larger -RLAPI Vector2 GetMouseWheelMoveV(void); // Get mouse wheel movement for both X and Y -RLAPI void SetMouseCursor(int cursor); // Set mouse cursor - -// Input-related functions: touch -RLAPI int GetTouchX(void); // Get touch position X for touch point 0 (relative to screen size) -RLAPI int GetTouchY(void); // Get touch position Y for touch point 0 (relative to screen size) -RLAPI Vector2 GetTouchPosition(int index); // Get touch position XY for a touch point index (relative to screen size) -RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index -RLAPI int GetTouchPointCount(void); // Get number of touch points - -//------------------------------------------------------------------------------------ -// Gestures and Touch Handling Functions (Module: rgestures) -//------------------------------------------------------------------------------------ -RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags -RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected -RLAPI int GetGestureDetected(void); // Get latest detected gesture -RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds -RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector -RLAPI float GetGestureDragAngle(void); // Get gesture drag angle -RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta -RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle - -//------------------------------------------------------------------------------------ -// Camera System Functions (Module: rcamera) -//------------------------------------------------------------------------------------ -RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode -RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom); // Update camera movement/rotation - -//------------------------------------------------------------------------------------ -// Basic Shapes Drawing Functions (Module: shapes) -//------------------------------------------------------------------------------------ -// Set texture and rectangle to be used on shapes drawing -// NOTE: It can be useful when using basic shapes and one single font, -// defining a font char white rectangle would allow drawing everything in a single draw call -RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing -RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing -RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing - -// Basic shapes drawing functions -RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] -RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care] -RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line -RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) -RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) -RLAPI void DrawLineStrip(const Vector2 *points, int pointCount, Color color); // Draw lines sequence (using gl lines) -RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw line segment cubic-bezier in-out interpolation -RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle -RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle -RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline -RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle -RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) -RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline -RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) -RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse -RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline -RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring -RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline -RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle -RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) -RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle -RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters -RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle -RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle -RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors -RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline -RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters -RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges -RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle lines with rounded edges -RLAPI void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float lineThick, Color color); // Draw rectangle with rounded edges outline -RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) -RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) -RLAPI void DrawTriangleFan(const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) -RLAPI void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points -RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) -RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides -RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters - -// Splines drawing functions -RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points -RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points -RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points -RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] -RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] -RLAPI void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color); // Draw spline segment: Linear, 2 points -RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: B-Spline, 4 points -RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points -RLAPI void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color); // Draw spline segment: Quadratic Bezier, 2 points, 1 control point -RLAPI void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color); // Draw spline segment: Cubic Bezier, 2 points, 2 control points - -// Spline segment point evaluation functions, for a given t [0.0f .. 1.0f] -RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: Linear -RLAPI Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: B-Spline -RLAPI Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: Catmull-Rom -RLAPI Vector2 GetSplinePointBezierQuad(Vector2 p1, Vector2 c2, Vector2 p3, float t); // Get (evaluate) spline point: Quadratic Bezier -RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float t); // Get (evaluate) spline point: Cubic Bezier - -// Basic shapes collision detection functions -RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles -RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles -RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle -RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] -RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle -RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle -RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle -RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] -RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices -RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference -RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision - -//------------------------------------------------------------------------------------ -// Texture Loading and Drawing Functions (Module: textures) -//------------------------------------------------------------------------------------ - -// Image loading functions -// NOTE: These functions do not require GPU access -RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) -RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data -RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) -RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames); // Load image sequence from memory buffer -RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' -RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data -RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot) -RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters) -RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) -RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success -RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer -RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success - -// Image generation functions -RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color -RLAPI Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end); // Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient -RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient -RLAPI Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer); // Generate image: square gradient -RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked -RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise -RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise -RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm, bigger tileSize means bigger cells -RLAPI Image GenImageText(int width, int height, const char *text); // Generate image: grayscale image from text data - -// Image manipulation functions -RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) -RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece -RLAPI Image ImageFromChannel(Image image, int selectedChannel); // Create an image from a selected channel of another image (GRAYSCALE) -RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) -RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) -RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format -RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) -RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle -RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value -RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color -RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image -RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel -RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation -RLAPI void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize); // Apply custom square convolution kernel to image -RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) -RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) -RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color -RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image -RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) -RLAPI void ImageFlipVertical(Image *image); // Flip image vertically -RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally -RLAPI void ImageRotate(Image *image, int degrees); // Rotate image by input angle in degrees (-359 to 359) -RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg -RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg -RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint -RLAPI void ImageColorInvert(Image *image); // Modify image color: invert -RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale -RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) -RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) -RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color -RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) -RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount); // Load colors palette from image as a Color array (RGBA - 32bit) -RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() -RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() -RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle -RLAPI Color GetImageColor(Image image, int x, int y); // Get image pixel color at (x, y) position - -// Image drawing functions -// NOTE: Image software-rendering functions (CPU) -RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color -RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image -RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) -RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image -RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) -RLAPI void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color); // Draw a line defining thickness within an image -RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw a filled circle within an image -RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw a filled circle within an image (Vector version) -RLAPI void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle outline within an image -RLAPI void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color); // Draw circle outline within an image (Vector version) -RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image -RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) -RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image -RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image -RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image -RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image -RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image -RLAPI void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) -RLAPI void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image -RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) -RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) -RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) - -// Texture loading functions -// NOTE: These functions require GPU access -RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) -RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data -RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported -RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) -RLAPI bool IsTextureValid(Texture2D texture); // Check if a texture is valid (loaded in GPU) -RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) -RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU) -RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) -RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data -RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data - -// Texture configuration functions -RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture -RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode -RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode - -// Texture drawing functions -RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D -RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 -RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters -RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle -RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters -RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely - -// Color/pixel related functions -RLAPI bool ColorIsEqual(Color col1, Color col2); // Check if two colors are equal -RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f -RLAPI int ColorToInt(Color color); // Get hexadecimal value for a Color (0xRRGGBBAA) -RLAPI Vector4 ColorNormalize(Color color); // Get Color normalized as float [0..1] -RLAPI Color ColorFromNormalized(Vector4 normalized); // Get Color from normalized values [0..1] -RLAPI Vector3 ColorToHSV(Color color); // Get HSV values for a Color, hue [0..360], saturation/value [0..1] -RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Get a Color from HSV values, hue [0..360], saturation/value [0..1] -RLAPI Color ColorTint(Color color, Color tint); // Get color multiplied with another color -RLAPI Color ColorBrightness(Color color, float factor); // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f -RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f -RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f -RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint -RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f] -RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value -RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format -RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer -RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format - -//------------------------------------------------------------------------------------ -// Font Loading and Text Drawing Functions (Module: text) -//------------------------------------------------------------------------------------ - -// Font loading/unloading functions -RLAPI Font GetFontDefault(void); // Get the default Font -RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) -RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height -RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) -RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' -RLAPI bool IsFontValid(Font font); // Check if a font is valid (font data loaded, WARNING: GPU texture not checked) -RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount, int type); // Load font data for further use -RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info -RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM) -RLAPI void UnloadFont(Font font); // Unload font from GPU memory (VRAM) -RLAPI bool ExportFontAsCode(Font font, const char *fileName); // Export font as code file, returns true on success - -// Text drawing functions -RLAPI void DrawFPS(int posX, int posY); // Draw current FPS -RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) -RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters -RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) -RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) -RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint) - -// Text font info functions -RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks -RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font -RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font -RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found -RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found -RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found - -// Text codepoints management functions (unicode characters) -RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array -RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array -RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter -RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory -RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string -RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) - -// Text strings management functions (no UTF-8 strings, only byte chars) -// NOTE: Some strings allocate memory internally for returned strings, just be careful! -RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied -RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal -RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending -RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf() style) -RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string -RLAPI char *TextReplace(const char *text, const char *replace, const char *by); // Replace text string (WARNING: memory must be freed!) -RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (WARNING: memory must be freed!) -RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter -RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings -RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! -RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string -RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string -RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string -RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string -RLAPI const char *TextToSnake(const char *text); // Get Snake case notation version of provided string -RLAPI const char *TextToCamel(const char *text); // Get Camel case notation version of provided string - -RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) -RLAPI float TextToFloat(const char *text); // Get float value from text (negative values not supported) - -//------------------------------------------------------------------------------------ -// Basic 3d Shapes Drawing Functions (Module: models) -//------------------------------------------------------------------------------------ - -// Basic geometric 3D shapes drawing functions -RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space -RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line -RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space -RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) -RLAPI void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points -RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube -RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) -RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires -RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) -RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere -RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters -RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires -RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone -RLAPI void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder with base at startPos and top at endPos -RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires -RLAPI void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder wires with base at startPos and top at endPos -RLAPI void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw a capsule with the center of its sphere caps at startPos and endPos -RLAPI void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw capsule wireframe with the center of its sphere caps at startPos and endPos -RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ -RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line -RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) - -//------------------------------------------------------------------------------------ -// Model 3d Loading and Drawing Functions (Module: models) -//------------------------------------------------------------------------------------ - -// Model management functions -RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) -RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) -RLAPI bool IsModelValid(Model model); // Check if a model is valid (loaded in GPU, VAO/VBOs) -RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) -RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) - -// Model drawing functions -RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) -RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters -RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) -RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters -RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points -RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters -RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) -RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture -RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source -RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation - -// Mesh management functions -RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids -RLAPI void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index -RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU -RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform -RLAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms -RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits -RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents -RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success -RLAPI bool ExportMeshAsCode(Mesh mesh, const char *fileName); // Export mesh as code file (.h) defining multiple arrays of vertex attributes - -// Mesh generation functions -RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh -RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) -RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh -RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) -RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) -RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh -RLAPI Mesh GenMeshCone(float radius, float height, int slices); // Generate cone/pyramid mesh -RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh -RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh -RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data -RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data - -// Material loading/unloading functions -RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file -RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) -RLAPI bool IsMaterialValid(Material material); // Check if a material is valid (shader assigned, map textures loaded in GPU) -RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) -RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) -RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh - -// Model animations loading/unloading functions -RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file -RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU) -RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning) -RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data -RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data -RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match - -// Collision detection functions -RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres -RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes -RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere -RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere -RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box -RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh -RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle -RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad - -//------------------------------------------------------------------------------------ -// Audio Loading and Playing Functions (Module: audio) -//------------------------------------------------------------------------------------ -typedef void (*AudioCallback)(void *bufferData, unsigned int frames); - -// Audio device management functions -RLAPI void InitAudioDevice(void); // Initialize audio device and context -RLAPI void CloseAudioDevice(void); // Close the audio device and context -RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully -RLAPI void SetMasterVolume(float volume); // Set master volume (listener) -RLAPI float GetMasterVolume(void); // Get master volume (listener) - -// Wave/Sound loading/unloading functions -RLAPI Wave LoadWave(const char *fileName); // Load wave data from file -RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' -RLAPI bool IsWaveValid(Wave wave); // Checks if wave data is valid (data loaded and parameters) -RLAPI Sound LoadSound(const char *fileName); // Load sound from file -RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data -RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data -RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) -RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data -RLAPI void UnloadWave(Wave wave); // Unload wave data -RLAPI void UnloadSound(Sound sound); // Unload sound -RLAPI void UnloadSoundAlias(Sound alias); // Unload a sound alias (does not deallocate sample data) -RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success -RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success - -// Wave/Sound management functions -RLAPI void PlaySound(Sound sound); // Play a sound -RLAPI void StopSound(Sound sound); // Stop playing a sound -RLAPI void PauseSound(Sound sound); // Pause a sound -RLAPI void ResumeSound(Sound sound); // Resume a paused sound -RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing -RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) -RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) -RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (0.5 is center) -RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave -RLAPI void WaveCrop(Wave *wave, int initFrame, int finalFrame); // Crop a wave to defined frames range -RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format -RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a 32bit float data array -RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() - -// Music management functions -RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file -RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data -RLAPI bool IsMusicValid(Music music); // Checks if a music stream is valid (context and buffers initialized) -RLAPI void UnloadMusicStream(Music music); // Unload music stream -RLAPI void PlayMusicStream(Music music); // Start music playing -RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing -RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming -RLAPI void StopMusicStream(Music music); // Stop music playing -RLAPI void PauseMusicStream(Music music); // Pause music playing -RLAPI void ResumeMusicStream(Music music); // Resume playing paused music -RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) -RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) -RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) -RLAPI void SetMusicPan(Music music, float pan); // Set pan for a music (0.5 is center) -RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) -RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) - -// AudioStream management functions -RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) -RLAPI bool IsAudioStreamValid(AudioStream stream); // Checks if an audio stream is valid (buffers initialized) -RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory -RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data -RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill -RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream -RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream -RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream -RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing -RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream -RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) -RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) -RLAPI void SetAudioStreamPan(AudioStream stream, float pan); // Set pan for audio stream (0.5 is centered) -RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams -RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); // Audio thread callback to request new data - -RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives the samples as 'float' -RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream - -RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' -RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline - -#if defined(__cplusplus) -} -#endif - -#endif // RAYLIB_H diff --git a/include/raymath.h b/include/raymath.h deleted file mode 100644 index 5b5e4c7..0000000 --- a/include/raymath.h +++ /dev/null @@ -1,2949 +0,0 @@ -/********************************************************************************************** -* -* raymath v2.0 - Math functions to work with Vector2, Vector3, Matrix and Quaternions -* -* CONVENTIONS: -* - Matrix structure is defined as row-major (memory layout) but parameters naming AND all -* math operations performed by the library consider the structure as it was column-major -* It is like transposed versions of the matrices are used for all the maths -* It benefits some functions making them cache-friendly and also avoids matrix -* transpositions sometimes required by OpenGL -* Example: In memory order, row0 is [m0 m4 m8 m12] but in semantic math row0 is [m0 m1 m2 m3] -* - Functions are always self-contained, no function use another raymath function inside, -* required code is directly re-implemented inside -* - Functions input parameters are always received by value (2 unavoidable exceptions) -* - Functions use always a "result" variable for return (except C++ operators) -* - Functions are always defined inline -* - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) -* - No compound literals used to make sure libray is compatible with C++ -* -* CONFIGURATION: -* #define RAYMATH_IMPLEMENTATION -* Generates the implementation of the library into the included file. -* If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. -* -* #define RAYMATH_STATIC_INLINE -* Define static inline functions code, so #include header suffices for use. -* This may use up lots of memory. -* -* #define RAYMATH_DISABLE_CPP_OPERATORS -* Disables C++ operator overloads for raymath types. -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2015-2024 Ramon Santamaria (@raysan5) -* -* 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. -* -**********************************************************************************************/ - -#ifndef RAYMATH_H -#define RAYMATH_H - -#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_STATIC_INLINE) - #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_STATIC_INLINE is contradictory" -#endif - -// Function specifiers definition -#if defined(RAYMATH_IMPLEMENTATION) - #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) - #define RMAPI __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll) - #elif defined(BUILD_LIBTYPE_SHARED) - #define RMAPI __attribute__((visibility("default"))) // We are building raylib as a Unix shared library (.so/.dylib) - #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) - #define RMAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) - #else - #define RMAPI extern inline // Provide external definition - #endif -#elif defined(RAYMATH_STATIC_INLINE) - #define RMAPI static inline // Functions may be inlined, no external out-of-line definition -#else - #if defined(__TINYC__) - #define RMAPI static inline // plain inline not supported by tinycc (See issue #435) - #else - #define RMAPI inline // Functions may be inlined or external definition used - #endif -#endif - - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#ifndef PI - #define PI 3.14159265358979323846f -#endif - -#ifndef EPSILON - #define EPSILON 0.000001f -#endif - -#ifndef DEG2RAD - #define DEG2RAD (PI/180.0f) -#endif - -#ifndef RAD2DEG - #define RAD2DEG (180.0f/PI) -#endif - -// Get float vector for Matrix -#ifndef MatrixToFloat - #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) -#endif - -// Get float vector for Vector3 -#ifndef Vector3ToFloat - #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -#if !defined(RL_VECTOR2_TYPE) -// Vector2 type -typedef struct Vector2 { - float x; - float y; -} Vector2; -#define RL_VECTOR2_TYPE -#endif - -#if !defined(RL_VECTOR3_TYPE) -// Vector3 type -typedef struct Vector3 { - float x; - float y; - float z; -} Vector3; -#define RL_VECTOR3_TYPE -#endif - -#if !defined(RL_VECTOR4_TYPE) -// Vector4 type -typedef struct Vector4 { - float x; - float y; - float z; - float w; -} Vector4; -#define RL_VECTOR4_TYPE -#endif - -#if !defined(RL_QUATERNION_TYPE) -// Quaternion type -typedef Vector4 Quaternion; -#define RL_QUATERNION_TYPE -#endif - -#if !defined(RL_MATRIX_TYPE) -// Matrix type (OpenGL style 4x4 - right handed, column major) -typedef struct Matrix { - float m0, m4, m8, m12; // Matrix first row (4 components) - float m1, m5, m9, m13; // Matrix second row (4 components) - float m2, m6, m10, m14; // Matrix third row (4 components) - float m3, m7, m11, m15; // Matrix fourth row (4 components) -} Matrix; -#define RL_MATRIX_TYPE -#endif - -// NOTE: Helper types to be used instead of array return types for *ToFloat functions -typedef struct float3 { - float v[3]; -} float3; - -typedef struct float16 { - float v[16]; -} float16; - -#include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Utils math -//---------------------------------------------------------------------------------- - -// Clamp float value -RMAPI float Clamp(float value, float min, float max) -{ - float result = (value < min)? min : value; - - if (result > max) result = max; - - return result; -} - -// Calculate linear interpolation between two floats -RMAPI float Lerp(float start, float end, float amount) -{ - float result = start + amount*(end - start); - - return result; -} - -// Normalize input value within input range -RMAPI float Normalize(float value, float start, float end) -{ - float result = (value - start)/(end - start); - - return result; -} - -// Remap input value within input range to output range -RMAPI float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) -{ - float result = (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; - - return result; -} - -// Wrap input value from min to max -RMAPI float Wrap(float value, float min, float max) -{ - float result = value - (max - min)*floorf((value - min)/(max - min)); - - return result; -} - -// Check whether two given floats are almost equal -RMAPI int FloatEquals(float x, float y) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = (fabsf(x - y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(x), fabsf(y)))); - - return result; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vector2 math -//---------------------------------------------------------------------------------- - -// Vector with components value 0.0f -RMAPI Vector2 Vector2Zero(void) -{ - Vector2 result = { 0.0f, 0.0f }; - - return result; -} - -// Vector with components value 1.0f -RMAPI Vector2 Vector2One(void) -{ - Vector2 result = { 1.0f, 1.0f }; - - return result; -} - -// Add two vectors (v1 + v2) -RMAPI Vector2 Vector2Add(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x + v2.x, v1.y + v2.y }; - - return result; -} - -// Add vector and float value -RMAPI Vector2 Vector2AddValue(Vector2 v, float add) -{ - Vector2 result = { v.x + add, v.y + add }; - - return result; -} - -// Subtract two vectors (v1 - v2) -RMAPI Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x - v2.x, v1.y - v2.y }; - - return result; -} - -// Subtract vector by float value -RMAPI Vector2 Vector2SubtractValue(Vector2 v, float sub) -{ - Vector2 result = { v.x - sub, v.y - sub }; - - return result; -} - -// Calculate vector length -RMAPI float Vector2Length(Vector2 v) -{ - float result = sqrtf((v.x*v.x) + (v.y*v.y)); - - return result; -} - -// Calculate vector square length -RMAPI float Vector2LengthSqr(Vector2 v) -{ - float result = (v.x*v.x) + (v.y*v.y); - - return result; -} - -// Calculate two vectors dot product -RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) -{ - float result = (v1.x*v2.x + v1.y*v2.y); - - return result; -} - -// Calculate two vectors cross product -RMAPI float Vector2CrossProduct(Vector2 v1, Vector2 v2) -{ - float result = (v1.x*v2.y - v1.y*v2.x); - - return result; -} - -// Calculate distance between two vectors -RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) -{ - float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); - - return result; -} - -// Calculate square distance between two vectors -RMAPI float Vector2DistanceSqr(Vector2 v1, Vector2 v2) -{ - float result = ((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); - - return result; -} - -// Calculate angle between two vectors -// NOTE: Angle is calculated from origin point (0, 0) -RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) -{ - float result = 0.0f; - - float dot = v1.x*v2.x + v1.y*v2.y; - float det = v1.x*v2.y - v1.y*v2.x; - - result = atan2f(det, dot); - - return result; -} - -// Calculate angle defined by a two vectors line -// NOTE: Parameters need to be normalized -// Current implementation should be aligned with glm::angle -RMAPI float Vector2LineAngle(Vector2 start, Vector2 end) -{ - float result = 0.0f; - - // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior - result = -atan2f(end.y - start.y, end.x - start.x); - - return result; -} - -// Scale vector (multiply by value) -RMAPI Vector2 Vector2Scale(Vector2 v, float scale) -{ - Vector2 result = { v.x*scale, v.y*scale }; - - return result; -} - -// Multiply vector by vector -RMAPI Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x*v2.x, v1.y*v2.y }; - - return result; -} - -// Negate vector -RMAPI Vector2 Vector2Negate(Vector2 v) -{ - Vector2 result = { -v.x, -v.y }; - - return result; -} - -// Divide vector by vector -RMAPI Vector2 Vector2Divide(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x/v2.x, v1.y/v2.y }; - - return result; -} - -// Normalize provided vector -RMAPI Vector2 Vector2Normalize(Vector2 v) -{ - Vector2 result = { 0 }; - float length = sqrtf((v.x*v.x) + (v.y*v.y)); - - if (length > 0) - { - float ilength = 1.0f/length; - result.x = v.x*ilength; - result.y = v.y*ilength; - } - - return result; -} - -// Transforms a Vector2 by a given Matrix -RMAPI Vector2 Vector2Transform(Vector2 v, Matrix mat) -{ - Vector2 result = { 0 }; - - float x = v.x; - float y = v.y; - float z = 0; - - result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; - result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; - - return result; -} - -// Calculate linear interpolation between two vectors -RMAPI Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) -{ - Vector2 result = { 0 }; - - result.x = v1.x + amount*(v2.x - v1.x); - result.y = v1.y + amount*(v2.y - v1.y); - - return result; -} - -// Calculate reflected vector to normal -RMAPI Vector2 Vector2Reflect(Vector2 v, Vector2 normal) -{ - Vector2 result = { 0 }; - - float dotProduct = (v.x*normal.x + v.y*normal.y); // Dot product - - result.x = v.x - (2.0f*normal.x)*dotProduct; - result.y = v.y - (2.0f*normal.y)*dotProduct; - - return result; -} - -// Get min value for each pair of components -RMAPI Vector2 Vector2Min(Vector2 v1, Vector2 v2) -{ - Vector2 result = { 0 }; - - result.x = fminf(v1.x, v2.x); - result.y = fminf(v1.y, v2.y); - - return result; -} - -// Get max value for each pair of components -RMAPI Vector2 Vector2Max(Vector2 v1, Vector2 v2) -{ - Vector2 result = { 0 }; - - result.x = fmaxf(v1.x, v2.x); - result.y = fmaxf(v1.y, v2.y); - - return result; -} - -// Rotate vector by angle -RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) -{ - Vector2 result = { 0 }; - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.x = v.x*cosres - v.y*sinres; - result.y = v.x*sinres + v.y*cosres; - - return result; -} - -// Move Vector towards target -RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) -{ - Vector2 result = { 0 }; - - float dx = target.x - v.x; - float dy = target.y - v.y; - float value = (dx*dx) + (dy*dy); - - if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; - - float dist = sqrtf(value); - - result.x = v.x + dx/dist*maxDistance; - result.y = v.y + dy/dist*maxDistance; - - return result; -} - -// Invert the given vector -RMAPI Vector2 Vector2Invert(Vector2 v) -{ - Vector2 result = { 1.0f/v.x, 1.0f/v.y }; - - return result; -} - -// Clamp the components of the vector between -// min and max values specified by the given vectors -RMAPI Vector2 Vector2Clamp(Vector2 v, Vector2 min, Vector2 max) -{ - Vector2 result = { 0 }; - - result.x = fminf(max.x, fmaxf(min.x, v.x)); - result.y = fminf(max.y, fmaxf(min.y, v.y)); - - return result; -} - -// Clamp the magnitude of the vector between two min and max values -RMAPI Vector2 Vector2ClampValue(Vector2 v, float min, float max) -{ - Vector2 result = v; - - float length = (v.x*v.x) + (v.y*v.y); - if (length > 0.0f) - { - length = sqrtf(length); - - float scale = 1; // By default, 1 as the neutral element. - if (length < min) - { - scale = min/length; - } - else if (length > max) - { - scale = max/length; - } - - result.x = v.x*scale; - result.y = v.y*scale; - } - - return result; -} - -// Check whether two given vectors are almost equal -RMAPI int Vector2Equals(Vector2 p, Vector2 q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))); - - return result; -} - -// Compute the direction of a refracted ray -// v: normalized direction of the incoming ray -// n: normalized normal vector of the interface of two optical media -// r: ratio of the refractive index of the medium from where the ray comes -// to the refractive index of the medium on the other side of the surface -RMAPI Vector2 Vector2Refract(Vector2 v, Vector2 n, float r) -{ - Vector2 result = { 0 }; - - float dot = v.x*n.x + v.y*n.y; - float d = 1.0f - r*r*(1.0f - dot*dot); - - if (d >= 0.0f) - { - d = sqrtf(d); - v.x = r*v.x - (r*dot + d)*n.x; - v.y = r*v.y - (r*dot + d)*n.y; - - result = v; - } - - return result; -} - - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vector3 math -//---------------------------------------------------------------------------------- - -// Vector with components value 0.0f -RMAPI Vector3 Vector3Zero(void) -{ - Vector3 result = { 0.0f, 0.0f, 0.0f }; - - return result; -} - -// Vector with components value 1.0f -RMAPI Vector3 Vector3One(void) -{ - Vector3 result = { 1.0f, 1.0f, 1.0f }; - - return result; -} - -// Add two vectors -RMAPI Vector3 Vector3Add(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; - - return result; -} - -// Add vector and float value -RMAPI Vector3 Vector3AddValue(Vector3 v, float add) -{ - Vector3 result = { v.x + add, v.y + add, v.z + add }; - - return result; -} - -// Subtract two vectors -RMAPI Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; - - return result; -} - -// Subtract vector by float value -RMAPI Vector3 Vector3SubtractValue(Vector3 v, float sub) -{ - Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; - - return result; -} - -// Multiply vector by scalar -RMAPI Vector3 Vector3Scale(Vector3 v, float scalar) -{ - Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; - - return result; -} - -// Multiply vector by vector -RMAPI Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; - - return result; -} - -// Calculate two vectors cross product -RMAPI Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; - - return result; -} - -// Calculate one vector perpendicular vector -RMAPI Vector3 Vector3Perpendicular(Vector3 v) -{ - Vector3 result = { 0 }; - - float min = fabsf(v.x); - Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; - - if (fabsf(v.y) < min) - { - min = fabsf(v.y); - Vector3 tmp = {0.0f, 1.0f, 0.0f}; - cardinalAxis = tmp; - } - - if (fabsf(v.z) < min) - { - Vector3 tmp = {0.0f, 0.0f, 1.0f}; - cardinalAxis = tmp; - } - - // Cross product between vectors - result.x = v.y*cardinalAxis.z - v.z*cardinalAxis.y; - result.y = v.z*cardinalAxis.x - v.x*cardinalAxis.z; - result.z = v.x*cardinalAxis.y - v.y*cardinalAxis.x; - - return result; -} - -// Calculate vector length -RMAPI float Vector3Length(const Vector3 v) -{ - float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - - return result; -} - -// Calculate vector square length -RMAPI float Vector3LengthSqr(const Vector3 v) -{ - float result = v.x*v.x + v.y*v.y + v.z*v.z; - - return result; -} - -// Calculate two vectors dot product -RMAPI float Vector3DotProduct(Vector3 v1, Vector3 v2) -{ - float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - - return result; -} - -// Calculate distance between two vectors -RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) -{ - float result = 0.0f; - - float dx = v2.x - v1.x; - float dy = v2.y - v1.y; - float dz = v2.z - v1.z; - result = sqrtf(dx*dx + dy*dy + dz*dz); - - return result; -} - -// Calculate square distance between two vectors -RMAPI float Vector3DistanceSqr(Vector3 v1, Vector3 v2) -{ - float result = 0.0f; - - float dx = v2.x - v1.x; - float dy = v2.y - v1.y; - float dz = v2.z - v1.z; - result = dx*dx + dy*dy + dz*dz; - - return result; -} - -// Calculate angle between two vectors -RMAPI float Vector3Angle(Vector3 v1, Vector3 v2) -{ - float result = 0.0f; - - Vector3 cross = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; - float len = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); - float dot = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - result = atan2f(len, dot); - - return result; -} - -// Negate provided vector (invert direction) -RMAPI Vector3 Vector3Negate(Vector3 v) -{ - Vector3 result = { -v.x, -v.y, -v.z }; - - return result; -} - -// Divide vector by vector -RMAPI Vector3 Vector3Divide(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; - - return result; -} - -// Normalize provided vector -RMAPI Vector3 Vector3Normalize(Vector3 v) -{ - Vector3 result = v; - - float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length != 0.0f) - { - float ilength = 1.0f/length; - - result.x *= ilength; - result.y *= ilength; - result.z *= ilength; - } - - return result; -} - -//Calculate the projection of the vector v1 on to v2 -RMAPI Vector3 Vector3Project(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); - - float mag = v1dv2/v2dv2; - - result.x = v2.x*mag; - result.y = v2.y*mag; - result.z = v2.z*mag; - - return result; -} - -//Calculate the rejection of the vector v1 on to v2 -RMAPI Vector3 Vector3Reject(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); - - float mag = v1dv2/v2dv2; - - result.x = v1.x - (v2.x*mag); - result.y = v1.y - (v2.y*mag); - result.z = v1.z - (v2.z*mag); - - return result; -} - -// Orthonormalize provided vectors -// Makes vectors normalized and orthogonal to each other -// Gram-Schmidt function implementation -RMAPI void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) -{ - float length = 0.0f; - float ilength = 0.0f; - - // Vector3Normalize(*v1); - Vector3 v = *v1; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - v1->x *= ilength; - v1->y *= ilength; - v1->z *= ilength; - - // Vector3CrossProduct(*v1, *v2) - Vector3 vn1 = { v1->y*v2->z - v1->z*v2->y, v1->z*v2->x - v1->x*v2->z, v1->x*v2->y - v1->y*v2->x }; - - // Vector3Normalize(vn1); - v = vn1; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - vn1.x *= ilength; - vn1.y *= ilength; - vn1.z *= ilength; - - // Vector3CrossProduct(vn1, *v1) - Vector3 vn2 = { vn1.y*v1->z - vn1.z*v1->y, vn1.z*v1->x - vn1.x*v1->z, vn1.x*v1->y - vn1.y*v1->x }; - - *v2 = vn2; -} - -// Transforms a Vector3 by a given Matrix -RMAPI Vector3 Vector3Transform(Vector3 v, Matrix mat) -{ - Vector3 result = { 0 }; - - float x = v.x; - float y = v.y; - float z = v.z; - - result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; - result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; - result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; - - return result; -} - -// Transform a vector by quaternion rotation -RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) -{ - Vector3 result = { 0 }; - - result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); - result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); - result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); - - return result; -} - -// Rotates a vector around an axis -RMAPI Vector3 Vector3RotateByAxisAngle(Vector3 v, Vector3 axis, float angle) -{ - // Using Euler-Rodrigues Formula - // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula - - Vector3 result = v; - - // Vector3Normalize(axis); - float length = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - axis.x *= ilength; - axis.y *= ilength; - axis.z *= ilength; - - angle /= 2.0f; - float a = sinf(angle); - float b = axis.x*a; - float c = axis.y*a; - float d = axis.z*a; - a = cosf(angle); - Vector3 w = { b, c, d }; - - // Vector3CrossProduct(w, v) - Vector3 wv = { w.y*v.z - w.z*v.y, w.z*v.x - w.x*v.z, w.x*v.y - w.y*v.x }; - - // Vector3CrossProduct(w, wv) - Vector3 wwv = { w.y*wv.z - w.z*wv.y, w.z*wv.x - w.x*wv.z, w.x*wv.y - w.y*wv.x }; - - // Vector3Scale(wv, 2*a) - a *= 2; - wv.x *= a; - wv.y *= a; - wv.z *= a; - - // Vector3Scale(wwv, 2) - wwv.x *= 2; - wwv.y *= 2; - wwv.z *= 2; - - result.x += wv.x; - result.y += wv.y; - result.z += wv.z; - - result.x += wwv.x; - result.y += wwv.y; - result.z += wwv.z; - - return result; -} - -// Move Vector towards target -RMAPI Vector3 Vector3MoveTowards(Vector3 v, Vector3 target, float maxDistance) -{ - Vector3 result = { 0 }; - - float dx = target.x - v.x; - float dy = target.y - v.y; - float dz = target.z - v.z; - float value = (dx*dx) + (dy*dy) + (dz*dz); - - if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; - - float dist = sqrtf(value); - - result.x = v.x + dx/dist*maxDistance; - result.y = v.y + dy/dist*maxDistance; - result.z = v.z + dz/dist*maxDistance; - - return result; -} - -// Calculate linear interpolation between two vectors -RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) -{ - Vector3 result = { 0 }; - - result.x = v1.x + amount*(v2.x - v1.x); - result.y = v1.y + amount*(v2.y - v1.y); - result.z = v1.z + amount*(v2.z - v1.z); - - return result; -} - -// Calculate cubic hermite interpolation between two vectors and their tangents -// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic -RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount) -{ - Vector3 result = { 0 }; - - float amountPow2 = amount*amount; - float amountPow3 = amount*amount*amount; - - result.x = (2*amountPow3 - 3*amountPow2 + 1)*v1.x + (amountPow3 - 2*amountPow2 + amount)*tangent1.x + (-2*amountPow3 + 3*amountPow2)*v2.x + (amountPow3 - amountPow2)*tangent2.x; - result.y = (2*amountPow3 - 3*amountPow2 + 1)*v1.y + (amountPow3 - 2*amountPow2 + amount)*tangent1.y + (-2*amountPow3 + 3*amountPow2)*v2.y + (amountPow3 - amountPow2)*tangent2.y; - result.z = (2*amountPow3 - 3*amountPow2 + 1)*v1.z + (amountPow3 - 2*amountPow2 + amount)*tangent1.z + (-2*amountPow3 + 3*amountPow2)*v2.z + (amountPow3 - amountPow2)*tangent2.z; - - return result; -} - -// Calculate reflected vector to normal -RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal) -{ - Vector3 result = { 0 }; - - // I is the original vector - // N is the normal of the incident plane - // R = I - (2*N*(DotProduct[I, N])) - - float dotProduct = (v.x*normal.x + v.y*normal.y + v.z*normal.z); - - result.x = v.x - (2.0f*normal.x)*dotProduct; - result.y = v.y - (2.0f*normal.y)*dotProduct; - result.z = v.z - (2.0f*normal.z)*dotProduct; - - return result; -} - -// Get min value for each pair of components -RMAPI Vector3 Vector3Min(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - result.x = fminf(v1.x, v2.x); - result.y = fminf(v1.y, v2.y); - result.z = fminf(v1.z, v2.z); - - return result; -} - -// Get max value for each pair of components -RMAPI Vector3 Vector3Max(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - result.x = fmaxf(v1.x, v2.x); - result.y = fmaxf(v1.y, v2.y); - result.z = fmaxf(v1.z, v2.z); - - return result; -} - -// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) -// NOTE: Assumes P is on the plane of the triangle -RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) -{ - Vector3 result = { 0 }; - - Vector3 v0 = { b.x - a.x, b.y - a.y, b.z - a.z }; // Vector3Subtract(b, a) - Vector3 v1 = { c.x - a.x, c.y - a.y, c.z - a.z }; // Vector3Subtract(c, a) - Vector3 v2 = { p.x - a.x, p.y - a.y, p.z - a.z }; // Vector3Subtract(p, a) - float d00 = (v0.x*v0.x + v0.y*v0.y + v0.z*v0.z); // Vector3DotProduct(v0, v0) - float d01 = (v0.x*v1.x + v0.y*v1.y + v0.z*v1.z); // Vector3DotProduct(v0, v1) - float d11 = (v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); // Vector3DotProduct(v1, v1) - float d20 = (v2.x*v0.x + v2.y*v0.y + v2.z*v0.z); // Vector3DotProduct(v2, v0) - float d21 = (v2.x*v1.x + v2.y*v1.y + v2.z*v1.z); // Vector3DotProduct(v2, v1) - - float denom = d00*d11 - d01*d01; - - result.y = (d11*d20 - d01*d21)/denom; - result.z = (d00*d21 - d01*d20)/denom; - result.x = 1.0f - (result.z + result.y); - - return result; -} - -// Projects a Vector3 from screen space into object space -// NOTE: We are avoiding calling other raymath functions despite available -RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) -{ - Vector3 result = { 0 }; - - // Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it - Matrix matViewProj = { // MatrixMultiply(view, projection); - view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12, - view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13, - view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14, - view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15, - view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12, - view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13, - view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14, - view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15, - view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12, - view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13, - view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14, - view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15, - view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12, - view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13, - view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14, - view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 }; - - // Calculate inverted matrix -> MatrixInvert(matViewProj); - // Cache the matrix values (speed optimization) - float a00 = matViewProj.m0, a01 = matViewProj.m1, a02 = matViewProj.m2, a03 = matViewProj.m3; - float a10 = matViewProj.m4, a11 = matViewProj.m5, a12 = matViewProj.m6, a13 = matViewProj.m7; - float a20 = matViewProj.m8, a21 = matViewProj.m9, a22 = matViewProj.m10, a23 = matViewProj.m11; - float a30 = matViewProj.m12, a31 = matViewProj.m13, a32 = matViewProj.m14, a33 = matViewProj.m15; - - float b00 = a00*a11 - a01*a10; - float b01 = a00*a12 - a02*a10; - float b02 = a00*a13 - a03*a10; - float b03 = a01*a12 - a02*a11; - float b04 = a01*a13 - a03*a11; - float b05 = a02*a13 - a03*a12; - float b06 = a20*a31 - a21*a30; - float b07 = a20*a32 - a22*a30; - float b08 = a20*a33 - a23*a30; - float b09 = a21*a32 - a22*a31; - float b10 = a21*a33 - a23*a31; - float b11 = a22*a33 - a23*a32; - - // Calculate the invert determinant (inlined to avoid double-caching) - float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); - - Matrix matViewProjInv = { - (a11*b11 - a12*b10 + a13*b09)*invDet, - (-a01*b11 + a02*b10 - a03*b09)*invDet, - (a31*b05 - a32*b04 + a33*b03)*invDet, - (-a21*b05 + a22*b04 - a23*b03)*invDet, - (-a10*b11 + a12*b08 - a13*b07)*invDet, - (a00*b11 - a02*b08 + a03*b07)*invDet, - (-a30*b05 + a32*b02 - a33*b01)*invDet, - (a20*b05 - a22*b02 + a23*b01)*invDet, - (a10*b10 - a11*b08 + a13*b06)*invDet, - (-a00*b10 + a01*b08 - a03*b06)*invDet, - (a30*b04 - a31*b02 + a33*b00)*invDet, - (-a20*b04 + a21*b02 - a23*b00)*invDet, - (-a10*b09 + a11*b07 - a12*b06)*invDet, - (a00*b09 - a01*b07 + a02*b06)*invDet, - (-a30*b03 + a31*b01 - a32*b00)*invDet, - (a20*b03 - a21*b01 + a22*b00)*invDet }; - - // Create quaternion from source point - Quaternion quat = { source.x, source.y, source.z, 1.0f }; - - // Multiply quat point by unprojecte matrix - Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv) - matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w, - matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w, - matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w, - matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w }; - - // Normalized world points in vectors - result.x = qtransformed.x/qtransformed.w; - result.y = qtransformed.y/qtransformed.w; - result.z = qtransformed.z/qtransformed.w; - - return result; -} - -// Get Vector3 as float array -RMAPI float3 Vector3ToFloatV(Vector3 v) -{ - float3 buffer = { 0 }; - - buffer.v[0] = v.x; - buffer.v[1] = v.y; - buffer.v[2] = v.z; - - return buffer; -} - -// Invert the given vector -RMAPI Vector3 Vector3Invert(Vector3 v) -{ - Vector3 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z }; - - return result; -} - -// Clamp the components of the vector between -// min and max values specified by the given vectors -RMAPI Vector3 Vector3Clamp(Vector3 v, Vector3 min, Vector3 max) -{ - Vector3 result = { 0 }; - - result.x = fminf(max.x, fmaxf(min.x, v.x)); - result.y = fminf(max.y, fmaxf(min.y, v.y)); - result.z = fminf(max.z, fmaxf(min.z, v.z)); - - return result; -} - -// Clamp the magnitude of the vector between two values -RMAPI Vector3 Vector3ClampValue(Vector3 v, float min, float max) -{ - Vector3 result = v; - - float length = (v.x*v.x) + (v.y*v.y) + (v.z*v.z); - if (length > 0.0f) - { - length = sqrtf(length); - - float scale = 1; // By default, 1 as the neutral element. - if (length < min) - { - scale = min/length; - } - else if (length > max) - { - scale = max/length; - } - - result.x = v.x*scale; - result.y = v.y*scale; - result.z = v.z*scale; - } - - return result; -} - -// Check whether two given vectors are almost equal -RMAPI int Vector3Equals(Vector3 p, Vector3 q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))); - - return result; -} - -// Compute the direction of a refracted ray -// v: normalized direction of the incoming ray -// n: normalized normal vector of the interface of two optical media -// r: ratio of the refractive index of the medium from where the ray comes -// to the refractive index of the medium on the other side of the surface -RMAPI Vector3 Vector3Refract(Vector3 v, Vector3 n, float r) -{ - Vector3 result = { 0 }; - - float dot = v.x*n.x + v.y*n.y + v.z*n.z; - float d = 1.0f - r*r*(1.0f - dot*dot); - - if (d >= 0.0f) - { - d = sqrtf(d); - v.x = r*v.x - (r*dot + d)*n.x; - v.y = r*v.y - (r*dot + d)*n.y; - v.z = r*v.z - (r*dot + d)*n.z; - - result = v; - } - - return result; -} - - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vector4 math -//---------------------------------------------------------------------------------- - -RMAPI Vector4 Vector4Zero(void) -{ - Vector4 result = { 0.0f, 0.0f, 0.0f, 0.0f }; - return result; -} - -RMAPI Vector4 Vector4One(void) -{ - Vector4 result = { 1.0f, 1.0f, 1.0f, 1.0f }; - return result; -} - -RMAPI Vector4 Vector4Add(Vector4 v1, Vector4 v2) -{ - Vector4 result = { - v1.x + v2.x, - v1.y + v2.y, - v1.z + v2.z, - v1.w + v2.w - }; - return result; -} - -RMAPI Vector4 Vector4AddValue(Vector4 v, float add) -{ - Vector4 result = { - v.x + add, - v.y + add, - v.z + add, - v.w + add - }; - return result; -} - -RMAPI Vector4 Vector4Subtract(Vector4 v1, Vector4 v2) -{ - Vector4 result = { - v1.x - v2.x, - v1.y - v2.y, - v1.z - v2.z, - v1.w - v2.w - }; - return result; -} - -RMAPI Vector4 Vector4SubtractValue(Vector4 v, float add) -{ - Vector4 result = { - v.x - add, - v.y - add, - v.z - add, - v.w - add - }; - return result; -} - -RMAPI float Vector4Length(Vector4 v) -{ - float result = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); - return result; -} - -RMAPI float Vector4LengthSqr(Vector4 v) -{ - float result = (v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w); - return result; -} - -RMAPI float Vector4DotProduct(Vector4 v1, Vector4 v2) -{ - float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w); - return result; -} - -// Calculate distance between two vectors -RMAPI float Vector4Distance(Vector4 v1, Vector4 v2) -{ - float result = sqrtf( - (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + - (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w)); - return result; -} - -// Calculate square distance between two vectors -RMAPI float Vector4DistanceSqr(Vector4 v1, Vector4 v2) -{ - float result = - (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + - (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w); - - return result; -} - -RMAPI Vector4 Vector4Scale(Vector4 v, float scale) -{ - Vector4 result = { v.x*scale, v.y*scale, v.z*scale, v.w*scale }; - return result; -} - -// Multiply vector by vector -RMAPI Vector4 Vector4Multiply(Vector4 v1, Vector4 v2) -{ - Vector4 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z, v1.w*v2.w }; - return result; -} - -// Negate vector -RMAPI Vector4 Vector4Negate(Vector4 v) -{ - Vector4 result = { -v.x, -v.y, -v.z, -v.w }; - return result; -} - -// Divide vector by vector -RMAPI Vector4 Vector4Divide(Vector4 v1, Vector4 v2) -{ - Vector4 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z, v1.w/v2.w }; - return result; -} - -// Normalize provided vector -RMAPI Vector4 Vector4Normalize(Vector4 v) -{ - Vector4 result = { 0 }; - float length = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); - - if (length > 0) - { - float ilength = 1.0f/length; - result.x = v.x*ilength; - result.y = v.y*ilength; - result.z = v.z*ilength; - result.w = v.w*ilength; - } - - return result; -} - -// Get min value for each pair of components -RMAPI Vector4 Vector4Min(Vector4 v1, Vector4 v2) -{ - Vector4 result = { 0 }; - - result.x = fminf(v1.x, v2.x); - result.y = fminf(v1.y, v2.y); - result.z = fminf(v1.z, v2.z); - result.w = fminf(v1.w, v2.w); - - return result; -} - -// Get max value for each pair of components -RMAPI Vector4 Vector4Max(Vector4 v1, Vector4 v2) -{ - Vector4 result = { 0 }; - - result.x = fmaxf(v1.x, v2.x); - result.y = fmaxf(v1.y, v2.y); - result.z = fmaxf(v1.z, v2.z); - result.w = fmaxf(v1.w, v2.w); - - return result; -} - -// Calculate linear interpolation between two vectors -RMAPI Vector4 Vector4Lerp(Vector4 v1, Vector4 v2, float amount) -{ - Vector4 result = { 0 }; - - result.x = v1.x + amount*(v2.x - v1.x); - result.y = v1.y + amount*(v2.y - v1.y); - result.z = v1.z + amount*(v2.z - v1.z); - result.w = v1.w + amount*(v2.w - v1.w); - - return result; -} - -// Move Vector towards target -RMAPI Vector4 Vector4MoveTowards(Vector4 v, Vector4 target, float maxDistance) -{ - Vector4 result = { 0 }; - - float dx = target.x - v.x; - float dy = target.y - v.y; - float dz = target.z - v.z; - float dw = target.w - v.w; - float value = (dx*dx) + (dy*dy) + (dz*dz) + (dw*dw); - - if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; - - float dist = sqrtf(value); - - result.x = v.x + dx/dist*maxDistance; - result.y = v.y + dy/dist*maxDistance; - result.z = v.z + dz/dist*maxDistance; - result.w = v.w + dw/dist*maxDistance; - - return result; -} - -// Invert the given vector -RMAPI Vector4 Vector4Invert(Vector4 v) -{ - Vector4 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z, 1.0f/v.w }; - return result; -} - -// Check whether two given vectors are almost equal -RMAPI int Vector4Equals(Vector4 p, Vector4 q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && - ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w))))); - return result; -} - - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Matrix math -//---------------------------------------------------------------------------------- - -// Compute matrix determinant -RMAPI float MatrixDeterminant(Matrix mat) -{ - float result = 0.0f; - - // Cache the matrix values (speed optimization) - float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; - float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; - float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; - float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; - - result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + - a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + - a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + - a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + - a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + - a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; - - return result; -} - -// Get the trace of the matrix (sum of the values along the diagonal) -RMAPI float MatrixTrace(Matrix mat) -{ - float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); - - return result; -} - -// Transposes provided matrix -RMAPI Matrix MatrixTranspose(Matrix mat) -{ - Matrix result = { 0 }; - - result.m0 = mat.m0; - result.m1 = mat.m4; - result.m2 = mat.m8; - result.m3 = mat.m12; - result.m4 = mat.m1; - result.m5 = mat.m5; - result.m6 = mat.m9; - result.m7 = mat.m13; - result.m8 = mat.m2; - result.m9 = mat.m6; - result.m10 = mat.m10; - result.m11 = mat.m14; - result.m12 = mat.m3; - result.m13 = mat.m7; - result.m14 = mat.m11; - result.m15 = mat.m15; - - return result; -} - -// Invert provided matrix -RMAPI Matrix MatrixInvert(Matrix mat) -{ - Matrix result = { 0 }; - - // Cache the matrix values (speed optimization) - float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; - float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; - float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; - float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; - - float b00 = a00*a11 - a01*a10; - float b01 = a00*a12 - a02*a10; - float b02 = a00*a13 - a03*a10; - float b03 = a01*a12 - a02*a11; - float b04 = a01*a13 - a03*a11; - float b05 = a02*a13 - a03*a12; - float b06 = a20*a31 - a21*a30; - float b07 = a20*a32 - a22*a30; - float b08 = a20*a33 - a23*a30; - float b09 = a21*a32 - a22*a31; - float b10 = a21*a33 - a23*a31; - float b11 = a22*a33 - a23*a32; - - // Calculate the invert determinant (inlined to avoid double-caching) - float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); - - result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; - result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; - result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; - result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; - result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; - result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; - result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; - result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; - result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; - result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; - result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; - result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; - result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; - result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; - result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; - result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; - - return result; -} - -// Get identity matrix -RMAPI Matrix MatrixIdentity(void) -{ - Matrix result = { 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 }; - - return result; -} - -// Add two matrices -RMAPI Matrix MatrixAdd(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0 + right.m0; - result.m1 = left.m1 + right.m1; - result.m2 = left.m2 + right.m2; - result.m3 = left.m3 + right.m3; - result.m4 = left.m4 + right.m4; - result.m5 = left.m5 + right.m5; - result.m6 = left.m6 + right.m6; - result.m7 = left.m7 + right.m7; - result.m8 = left.m8 + right.m8; - result.m9 = left.m9 + right.m9; - result.m10 = left.m10 + right.m10; - result.m11 = left.m11 + right.m11; - result.m12 = left.m12 + right.m12; - result.m13 = left.m13 + right.m13; - result.m14 = left.m14 + right.m14; - result.m15 = left.m15 + right.m15; - - return result; -} - -// Subtract two matrices (left - right) -RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0 - right.m0; - result.m1 = left.m1 - right.m1; - result.m2 = left.m2 - right.m2; - result.m3 = left.m3 - right.m3; - result.m4 = left.m4 - right.m4; - result.m5 = left.m5 - right.m5; - result.m6 = left.m6 - right.m6; - result.m7 = left.m7 - right.m7; - result.m8 = left.m8 - right.m8; - result.m9 = left.m9 - right.m9; - result.m10 = left.m10 - right.m10; - result.m11 = left.m11 - right.m11; - result.m12 = left.m12 - right.m12; - result.m13 = left.m13 - right.m13; - result.m14 = left.m14 - right.m14; - result.m15 = left.m15 - right.m15; - - return result; -} - -// Get two matrix multiplication -// NOTE: When multiplying matrices... the order matters! -RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; - result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; - result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; - result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; - result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; - result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; - result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; - result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; - result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; - result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; - result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; - result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; - result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; - result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; - result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; - result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; - - return result; -} - -// Get translation matrix -RMAPI Matrix MatrixTranslate(float x, float y, float z) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, x, - 0.0f, 1.0f, 0.0f, y, - 0.0f, 0.0f, 1.0f, z, - 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Create rotation matrix from axis and angle -// NOTE: Angle should be provided in radians -RMAPI Matrix MatrixRotate(Vector3 axis, float angle) -{ - Matrix result = { 0 }; - - float x = axis.x, y = axis.y, z = axis.z; - - float lengthSquared = x*x + y*y + z*z; - - if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) - { - float ilength = 1.0f/sqrtf(lengthSquared); - x *= ilength; - y *= ilength; - z *= ilength; - } - - float sinres = sinf(angle); - float cosres = cosf(angle); - float t = 1.0f - cosres; - - result.m0 = x*x*t + cosres; - result.m1 = y*x*t + z*sinres; - result.m2 = z*x*t - y*sinres; - result.m3 = 0.0f; - - result.m4 = x*y*t - z*sinres; - result.m5 = y*y*t + cosres; - result.m6 = z*y*t + x*sinres; - result.m7 = 0.0f; - - result.m8 = x*z*t + y*sinres; - result.m9 = y*z*t - x*sinres; - result.m10 = z*z*t + cosres; - result.m11 = 0.0f; - - result.m12 = 0.0f; - result.m13 = 0.0f; - result.m14 = 0.0f; - result.m15 = 1.0f; - - return result; -} - -// Get x-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateX(float angle) -{ - Matrix result = { 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 }; // MatrixIdentity() - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.m5 = cosres; - result.m6 = sinres; - result.m9 = -sinres; - result.m10 = cosres; - - return result; -} - -// Get y-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateY(float angle) -{ - Matrix result = { 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 }; // MatrixIdentity() - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.m0 = cosres; - result.m2 = -sinres; - result.m8 = sinres; - result.m10 = cosres; - - return result; -} - -// Get z-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateZ(float angle) -{ - Matrix result = { 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 }; // MatrixIdentity() - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.m0 = cosres; - result.m1 = sinres; - result.m4 = -sinres; - result.m5 = cosres; - - return result; -} - - -// Get xyz-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateXYZ(Vector3 angle) -{ - Matrix result = { 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 }; // MatrixIdentity() - - float cosz = cosf(-angle.z); - float sinz = sinf(-angle.z); - float cosy = cosf(-angle.y); - float siny = sinf(-angle.y); - float cosx = cosf(-angle.x); - float sinx = sinf(-angle.x); - - result.m0 = cosz*cosy; - result.m1 = (cosz*siny*sinx) - (sinz*cosx); - result.m2 = (cosz*siny*cosx) + (sinz*sinx); - - result.m4 = sinz*cosy; - result.m5 = (sinz*siny*sinx) + (cosz*cosx); - result.m6 = (sinz*siny*cosx) - (cosz*sinx); - - result.m8 = -siny; - result.m9 = cosy*sinx; - result.m10= cosy*cosx; - - return result; -} - -// Get zyx-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateZYX(Vector3 angle) -{ - Matrix result = { 0 }; - - float cz = cosf(angle.z); - float sz = sinf(angle.z); - float cy = cosf(angle.y); - float sy = sinf(angle.y); - float cx = cosf(angle.x); - float sx = sinf(angle.x); - - result.m0 = cz*cy; - result.m4 = cz*sy*sx - cx*sz; - result.m8 = sz*sx + cz*cx*sy; - result.m12 = 0; - - result.m1 = cy*sz; - result.m5 = cz*cx + sz*sy*sx; - result.m9 = cx*sz*sy - cz*sx; - result.m13 = 0; - - result.m2 = -sy; - result.m6 = cy*sx; - result.m10 = cy*cx; - result.m14 = 0; - - result.m3 = 0; - result.m7 = 0; - result.m11 = 0; - result.m15 = 1; - - return result; -} - -// Get scaling matrix -RMAPI Matrix MatrixScale(float x, float y, float z) -{ - Matrix result = { x, 0.0f, 0.0f, 0.0f, - 0.0f, y, 0.0f, 0.0f, - 0.0f, 0.0f, z, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Get perspective projection matrix -RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, double nearPlane, double farPlane) -{ - Matrix result = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(farPlane - nearPlane); - - result.m0 = ((float)nearPlane*2.0f)/rl; - result.m1 = 0.0f; - result.m2 = 0.0f; - result.m3 = 0.0f; - - result.m4 = 0.0f; - result.m5 = ((float)nearPlane*2.0f)/tb; - result.m6 = 0.0f; - result.m7 = 0.0f; - - result.m8 = ((float)right + (float)left)/rl; - result.m9 = ((float)top + (float)bottom)/tb; - result.m10 = -((float)farPlane + (float)nearPlane)/fn; - result.m11 = -1.0f; - - result.m12 = 0.0f; - result.m13 = 0.0f; - result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; - result.m15 = 0.0f; - - return result; -} - -// Get perspective projection matrix -// NOTE: Fovy angle must be provided in radians -RMAPI Matrix MatrixPerspective(double fovY, double aspect, double nearPlane, double farPlane) -{ - Matrix result = { 0 }; - - double top = nearPlane*tan(fovY*0.5); - double bottom = -top; - double right = top*aspect; - double left = -right; - - // MatrixFrustum(-right, right, -top, top, near, far); - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(farPlane - nearPlane); - - result.m0 = ((float)nearPlane*2.0f)/rl; - result.m5 = ((float)nearPlane*2.0f)/tb; - result.m8 = ((float)right + (float)left)/rl; - result.m9 = ((float)top + (float)bottom)/tb; - result.m10 = -((float)farPlane + (float)nearPlane)/fn; - result.m11 = -1.0f; - result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; - - return result; -} - -// Get orthographic projection matrix -RMAPI Matrix MatrixOrtho(double left, double right, double bottom, double top, double nearPlane, double farPlane) -{ - Matrix result = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(farPlane - nearPlane); - - result.m0 = 2.0f/rl; - result.m1 = 0.0f; - result.m2 = 0.0f; - result.m3 = 0.0f; - result.m4 = 0.0f; - result.m5 = 2.0f/tb; - result.m6 = 0.0f; - result.m7 = 0.0f; - result.m8 = 0.0f; - result.m9 = 0.0f; - result.m10 = -2.0f/fn; - result.m11 = 0.0f; - result.m12 = -((float)left + (float)right)/rl; - result.m13 = -((float)top + (float)bottom)/tb; - result.m14 = -((float)farPlane + (float)nearPlane)/fn; - result.m15 = 1.0f; - - return result; -} - -// Get camera look-at matrix (view matrix) -RMAPI Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) -{ - Matrix result = { 0 }; - - float length = 0.0f; - float ilength = 0.0f; - - // Vector3Subtract(eye, target) - Vector3 vz = { eye.x - target.x, eye.y - target.y, eye.z - target.z }; - - // Vector3Normalize(vz) - Vector3 v = vz; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - vz.x *= ilength; - vz.y *= ilength; - vz.z *= ilength; - - // Vector3CrossProduct(up, vz) - Vector3 vx = { up.y*vz.z - up.z*vz.y, up.z*vz.x - up.x*vz.z, up.x*vz.y - up.y*vz.x }; - - // Vector3Normalize(x) - v = vx; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - vx.x *= ilength; - vx.y *= ilength; - vx.z *= ilength; - - // Vector3CrossProduct(vz, vx) - Vector3 vy = { vz.y*vx.z - vz.z*vx.y, vz.z*vx.x - vz.x*vx.z, vz.x*vx.y - vz.y*vx.x }; - - result.m0 = vx.x; - result.m1 = vy.x; - result.m2 = vz.x; - result.m3 = 0.0f; - result.m4 = vx.y; - result.m5 = vy.y; - result.m6 = vz.y; - result.m7 = 0.0f; - result.m8 = vx.z; - result.m9 = vy.z; - result.m10 = vz.z; - result.m11 = 0.0f; - result.m12 = -(vx.x*eye.x + vx.y*eye.y + vx.z*eye.z); // Vector3DotProduct(vx, eye) - result.m13 = -(vy.x*eye.x + vy.y*eye.y + vy.z*eye.z); // Vector3DotProduct(vy, eye) - result.m14 = -(vz.x*eye.x + vz.y*eye.y + vz.z*eye.z); // Vector3DotProduct(vz, eye) - result.m15 = 1.0f; - - return result; -} - -// Get float array of matrix data -RMAPI float16 MatrixToFloatV(Matrix mat) -{ - float16 result = { 0 }; - - result.v[0] = mat.m0; - result.v[1] = mat.m1; - result.v[2] = mat.m2; - result.v[3] = mat.m3; - result.v[4] = mat.m4; - result.v[5] = mat.m5; - result.v[6] = mat.m6; - result.v[7] = mat.m7; - result.v[8] = mat.m8; - result.v[9] = mat.m9; - result.v[10] = mat.m10; - result.v[11] = mat.m11; - result.v[12] = mat.m12; - result.v[13] = mat.m13; - result.v[14] = mat.m14; - result.v[15] = mat.m15; - - return result; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Quaternion math -//---------------------------------------------------------------------------------- - -// Add two quaternions -RMAPI Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) -{ - Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; - - return result; -} - -// Add quaternion and float value -RMAPI Quaternion QuaternionAddValue(Quaternion q, float add) -{ - Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; - - return result; -} - -// Subtract two quaternions -RMAPI Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) -{ - Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; - - return result; -} - -// Subtract quaternion and float value -RMAPI Quaternion QuaternionSubtractValue(Quaternion q, float sub) -{ - Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; - - return result; -} - -// Get identity quaternion -RMAPI Quaternion QuaternionIdentity(void) -{ - Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Computes the length of a quaternion -RMAPI float QuaternionLength(Quaternion q) -{ - float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - - return result; -} - -// Normalize provided quaternion -RMAPI Quaternion QuaternionNormalize(Quaternion q) -{ - Quaternion result = { 0 }; - - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - - return result; -} - -// Invert provided quaternion -RMAPI Quaternion QuaternionInvert(Quaternion q) -{ - Quaternion result = q; - - float lengthSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w; - - if (lengthSq != 0.0f) - { - float invLength = 1.0f/lengthSq; - - result.x *= -invLength; - result.y *= -invLength; - result.z *= -invLength; - result.w *= invLength; - } - - return result; -} - -// Calculate two quaternion multiplication -RMAPI Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) -{ - Quaternion result = { 0 }; - - float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; - float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; - - result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; - result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; - result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; - result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; - - return result; -} - -// Scale quaternion by float value -RMAPI Quaternion QuaternionScale(Quaternion q, float mul) -{ - Quaternion result = { 0 }; - - result.x = q.x*mul; - result.y = q.y*mul; - result.z = q.z*mul; - result.w = q.w*mul; - - return result; -} - -// Divide two quaternions -RMAPI Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) -{ - Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; - - return result; -} - -// Calculate linear interpolation between two quaternions -RMAPI Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) -{ - Quaternion result = { 0 }; - - result.x = q1.x + amount*(q2.x - q1.x); - result.y = q1.y + amount*(q2.y - q1.y); - result.z = q1.z + amount*(q2.z - q1.z); - result.w = q1.w + amount*(q2.w - q1.w); - - return result; -} - -// Calculate slerp-optimized interpolation between two quaternions -RMAPI Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) -{ - Quaternion result = { 0 }; - - // QuaternionLerp(q1, q2, amount) - result.x = q1.x + amount*(q2.x - q1.x); - result.y = q1.y + amount*(q2.y - q1.y); - result.z = q1.z + amount*(q2.z - q1.z); - result.w = q1.w + amount*(q2.w - q1.w); - - // QuaternionNormalize(q); - Quaternion q = result; - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - - return result; -} - -// Calculates spherical linear interpolation between two quaternions -RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) -{ - Quaternion result = { 0 }; - -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; - - if (cosHalfTheta < 0) - { - q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; - cosHalfTheta = -cosHalfTheta; - } - - if (fabsf(cosHalfTheta) >= 1.0f) result = q1; - else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); - else - { - float halfTheta = acosf(cosHalfTheta); - float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); - - if (fabsf(sinHalfTheta) < EPSILON) - { - result.x = (q1.x*0.5f + q2.x*0.5f); - result.y = (q1.y*0.5f + q2.y*0.5f); - result.z = (q1.z*0.5f + q2.z*0.5f); - result.w = (q1.w*0.5f + q2.w*0.5f); - } - else - { - float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; - float ratioB = sinf(amount*halfTheta)/sinHalfTheta; - - result.x = (q1.x*ratioA + q2.x*ratioB); - result.y = (q1.y*ratioA + q2.y*ratioB); - result.z = (q1.z*ratioA + q2.z*ratioB); - result.w = (q1.w*ratioA + q2.w*ratioB); - } - } - - return result; -} - -// Calculate quaternion cubic spline interpolation using Cubic Hermite Spline algorithm -// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic -RMAPI Quaternion QuaternionCubicHermiteSpline(Quaternion q1, Quaternion outTangent1, Quaternion q2, Quaternion inTangent2, float t) -{ - float t2 = t*t; - float t3 = t2*t; - float h00 = 2*t3 - 3*t2 + 1; - float h10 = t3 - 2*t2 + t; - float h01 = -2*t3 + 3*t2; - float h11 = t3 - t2; - - Quaternion p0 = QuaternionScale(q1, h00); - Quaternion m0 = QuaternionScale(outTangent1, h10); - Quaternion p1 = QuaternionScale(q2, h01); - Quaternion m1 = QuaternionScale(inTangent2, h11); - - Quaternion result = { 0 }; - - result = QuaternionAdd(p0, m0); - result = QuaternionAdd(result, p1); - result = QuaternionAdd(result, m1); - result = QuaternionNormalize(result); - - return result; -} - -// Calculate quaternion based on the rotation from one vector to another -RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) -{ - Quaternion result = { 0 }; - - float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) - Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) - - result.x = cross.x; - result.y = cross.y; - result.z = cross.z; - result.w = 1.0f + cos2Theta; - - // QuaternionNormalize(q); - // NOTE: Normalize to essentially nlerp the original and identity to 0.5 - Quaternion q = result; - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - - return result; -} - -// Get a quaternion for a given rotation matrix -RMAPI Quaternion QuaternionFromMatrix(Matrix mat) -{ - Quaternion result = { 0 }; - - float fourWSquaredMinus1 = mat.m0 + mat.m5 + mat.m10; - float fourXSquaredMinus1 = mat.m0 - mat.m5 - mat.m10; - float fourYSquaredMinus1 = mat.m5 - mat.m0 - mat.m10; - float fourZSquaredMinus1 = mat.m10 - mat.m0 - mat.m5; - - int biggestIndex = 0; - float fourBiggestSquaredMinus1 = fourWSquaredMinus1; - if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourXSquaredMinus1; - biggestIndex = 1; - } - - if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourYSquaredMinus1; - biggestIndex = 2; - } - - if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourZSquaredMinus1; - biggestIndex = 3; - } - - float biggestVal = sqrtf(fourBiggestSquaredMinus1 + 1.0f)*0.5f; - float mult = 0.25f/biggestVal; - - switch (biggestIndex) - { - case 0: - result.w = biggestVal; - result.x = (mat.m6 - mat.m9)*mult; - result.y = (mat.m8 - mat.m2)*mult; - result.z = (mat.m1 - mat.m4)*mult; - break; - case 1: - result.x = biggestVal; - result.w = (mat.m6 - mat.m9)*mult; - result.y = (mat.m1 + mat.m4)*mult; - result.z = (mat.m8 + mat.m2)*mult; - break; - case 2: - result.y = biggestVal; - result.w = (mat.m8 - mat.m2)*mult; - result.x = (mat.m1 + mat.m4)*mult; - result.z = (mat.m6 + mat.m9)*mult; - break; - case 3: - result.z = biggestVal; - result.w = (mat.m1 - mat.m4)*mult; - result.x = (mat.m8 + mat.m2)*mult; - result.y = (mat.m6 + mat.m9)*mult; - break; - } - - return result; -} - -// Get a matrix for a given quaternion -RMAPI Matrix QuaternionToMatrix(Quaternion q) -{ - Matrix result = { 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 }; // MatrixIdentity() - - float a2 = q.x*q.x; - float b2 = q.y*q.y; - float c2 = q.z*q.z; - float ac = q.x*q.z; - float ab = q.x*q.y; - float bc = q.y*q.z; - float ad = q.w*q.x; - float bd = q.w*q.y; - float cd = q.w*q.z; - - result.m0 = 1 - 2*(b2 + c2); - result.m1 = 2*(ab + cd); - result.m2 = 2*(ac - bd); - - result.m4 = 2*(ab - cd); - result.m5 = 1 - 2*(a2 + c2); - result.m6 = 2*(bc + ad); - - result.m8 = 2*(ac + bd); - result.m9 = 2*(bc - ad); - result.m10 = 1 - 2*(a2 + b2); - - return result; -} - -// Get rotation quaternion for an angle and axis -// NOTE: Angle must be provided in radians -RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) -{ - Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; - - float axisLength = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); - - if (axisLength != 0.0f) - { - angle *= 0.5f; - - float length = 0.0f; - float ilength = 0.0f; - - // Vector3Normalize(axis) - length = axisLength; - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - axis.x *= ilength; - axis.y *= ilength; - axis.z *= ilength; - - float sinres = sinf(angle); - float cosres = cosf(angle); - - result.x = axis.x*sinres; - result.y = axis.y*sinres; - result.z = axis.z*sinres; - result.w = cosres; - - // QuaternionNormalize(q); - Quaternion q = result; - length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - } - - return result; -} - -// Get the rotation angle and axis for a given quaternion -RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) -{ - if (fabsf(q.w) > 1.0f) - { - // QuaternionNormalize(q); - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - q.x = q.x*ilength; - q.y = q.y*ilength; - q.z = q.z*ilength; - q.w = q.w*ilength; - } - - Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; - float resAngle = 2.0f*acosf(q.w); - float den = sqrtf(1.0f - q.w*q.w); - - if (den > EPSILON) - { - resAxis.x = q.x/den; - resAxis.y = q.y/den; - resAxis.z = q.z/den; - } - else - { - // This occurs when the angle is zero. - // Not a problem: just set an arbitrary normalized axis. - resAxis.x = 1.0f; - } - - *outAxis = resAxis; - *outAngle = resAngle; -} - -// Get the quaternion equivalent to Euler angles -// NOTE: Rotation order is ZYX -RMAPI Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) -{ - Quaternion result = { 0 }; - - float x0 = cosf(pitch*0.5f); - float x1 = sinf(pitch*0.5f); - float y0 = cosf(yaw*0.5f); - float y1 = sinf(yaw*0.5f); - float z0 = cosf(roll*0.5f); - float z1 = sinf(roll*0.5f); - - result.x = x1*y0*z0 - x0*y1*z1; - result.y = x0*y1*z0 + x1*y0*z1; - result.z = x0*y0*z1 - x1*y1*z0; - result.w = x0*y0*z0 + x1*y1*z1; - - return result; -} - -// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) -// NOTE: Angles are returned in a Vector3 struct in radians -RMAPI Vector3 QuaternionToEuler(Quaternion q) -{ - Vector3 result = { 0 }; - - // Roll (x-axis rotation) - float x0 = 2.0f*(q.w*q.x + q.y*q.z); - float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); - result.x = atan2f(x0, x1); - - // Pitch (y-axis rotation) - float y0 = 2.0f*(q.w*q.y - q.z*q.x); - y0 = y0 > 1.0f ? 1.0f : y0; - y0 = y0 < -1.0f ? -1.0f : y0; - result.y = asinf(y0); - - // Yaw (z-axis rotation) - float z0 = 2.0f*(q.w*q.z + q.x*q.y); - float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); - result.z = atan2f(z0, z1); - - return result; -} - -// Transform a quaternion given a transformation matrix -RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) -{ - Quaternion result = { 0 }; - - result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; - result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; - result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; - result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; - - return result; -} - -// Check whether two given quaternions are almost equal -RMAPI int QuaternionEquals(Quaternion p, Quaternion q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = (((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && - ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))) || - (((fabsf(p.x + q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y + q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z + q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && - ((fabsf(p.w + q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))); - - return result; -} - -// Decompose a transformation matrix into its rotational, translational and scaling components -RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) -{ - // Extract translation. - translation->x = mat.m12; - translation->y = mat.m13; - translation->z = mat.m14; - - // Extract upper-left for determinant computation - const float a = mat.m0; - const float b = mat.m4; - const float c = mat.m8; - const float d = mat.m1; - const float e = mat.m5; - const float f = mat.m9; - const float g = mat.m2; - const float h = mat.m6; - const float i = mat.m10; - const float A = e*i - f*h; - const float B = f*g - d*i; - const float C = d*h - e*g; - - // Extract scale - const float det = a*A + b*B + c*C; - Vector3 abc = { a, b, c }; - Vector3 def = { d, e, f }; - Vector3 ghi = { g, h, i }; - - float scalex = Vector3Length(abc); - float scaley = Vector3Length(def); - float scalez = Vector3Length(ghi); - Vector3 s = { scalex, scaley, scalez }; - - if (det < 0) s = Vector3Negate(s); - - *scale = s; - - // Remove scale from the matrix if it is not close to zero - Matrix clone = mat; - if (!FloatEquals(det, 0)) - { - clone.m0 /= s.x; - clone.m4 /= s.x; - clone.m8 /= s.x; - clone.m1 /= s.y; - clone.m5 /= s.y; - clone.m9 /= s.y; - clone.m2 /= s.z; - clone.m6 /= s.z; - clone.m10 /= s.z; - - // Extract rotation - *rotation = QuaternionFromMatrix(clone); - } - else - { - // Set to identity if close to zero - *rotation = QuaternionIdentity(); - } -} - -#if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS) - -// Optional C++ math operators -//------------------------------------------------------------------------------- - -// Vector2 operators -static constexpr Vector2 Vector2Zeros = { 0, 0 }; -static constexpr Vector2 Vector2Ones = { 1, 1 }; -static constexpr Vector2 Vector2UnitX = { 1, 0 }; -static constexpr Vector2 Vector2UnitY = { 0, 1 }; - -inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Add(lhs, rhs); -} - -inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Add(lhs, rhs); - return lhs; -} - -inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Subtract(lhs, rhs); -} - -inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Subtract(lhs, rhs); - return lhs; -} - -inline Vector2 operator * (const Vector2& lhs, const float& rhs) -{ - return Vector2Scale(lhs, rhs); -} - -inline const Vector2& operator *= (Vector2& lhs, const float& rhs) -{ - lhs = Vector2Scale(lhs, rhs); - return lhs; -} - -inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Multiply(lhs, rhs); -} - -inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Multiply(lhs, rhs); - return lhs; -} - -inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs) -{ - return Vector2Transform(lhs, rhs); -} - -inline const Vector2& operator *= (Vector2& lhs, const Matrix& rhs) -{ - lhs = Vector2Transform(lhs, rhs); - return lhs; -} - -inline Vector2 operator / (const Vector2& lhs, const float& rhs) -{ - return Vector2Scale(lhs, 1.0f / rhs); -} - -inline const Vector2& operator /= (Vector2& lhs, const float& rhs) -{ - lhs = Vector2Scale(lhs, 1.0f / rhs); - return lhs; -} - -inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Divide(lhs, rhs); -} - -inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Divide(lhs, rhs); - return lhs; -} - -inline bool operator == (const Vector2& lhs, const Vector2& rhs) -{ - return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y); -} - -inline bool operator != (const Vector2& lhs, const Vector2& rhs) -{ - return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y); -} - -// Vector3 operators -static constexpr Vector3 Vector3Zeros = { 0, 0, 0 }; -static constexpr Vector3 Vector3Ones = { 1, 1, 1 }; -static constexpr Vector3 Vector3UnitX = { 1, 0, 0 }; -static constexpr Vector3 Vector3UnitY = { 0, 1, 0 }; -static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 }; - -inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Add(lhs, rhs); -} - -inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Add(lhs, rhs); - return lhs; -} - -inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Subtract(lhs, rhs); -} - -inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Subtract(lhs, rhs); - return lhs; -} - -inline Vector3 operator * (const Vector3& lhs, const float& rhs) -{ - return Vector3Scale(lhs, rhs); -} - -inline const Vector3& operator *= (Vector3& lhs, const float& rhs) -{ - lhs = Vector3Scale(lhs, rhs); - return lhs; -} - -inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Multiply(lhs, rhs); -} - -inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Multiply(lhs, rhs); - return lhs; -} - -inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs) -{ - return Vector3Transform(lhs, rhs); -} - -inline const Vector3& operator *= (Vector3& lhs, const Matrix& rhs) -{ - lhs = Vector3Transform(lhs, rhs); - return lhs; -} - -inline Vector3 operator / (const Vector3& lhs, const float& rhs) -{ - return Vector3Scale(lhs, 1.0f / rhs); -} - -inline const Vector3& operator /= (Vector3& lhs, const float& rhs) -{ - lhs = Vector3Scale(lhs, 1.0f / rhs); - return lhs; -} - -inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Divide(lhs, rhs); -} - -inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Divide(lhs, rhs); - return lhs; -} - -inline bool operator == (const Vector3& lhs, const Vector3& rhs) -{ - return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z); -} - -inline bool operator != (const Vector3& lhs, const Vector3& rhs) -{ - return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z); -} - -// Vector4 operators -static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 }; -static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 }; -static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 }; -static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 }; -static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 }; -static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 }; - -inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Add(lhs, rhs); -} - -inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Add(lhs, rhs); - return lhs; -} - -inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Subtract(lhs, rhs); -} - -inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Subtract(lhs, rhs); - return lhs; -} - -inline Vector4 operator * (const Vector4& lhs, const float& rhs) -{ - return Vector4Scale(lhs, rhs); -} - -inline const Vector4& operator *= (Vector4& lhs, const float& rhs) -{ - lhs = Vector4Scale(lhs, rhs); - return lhs; -} - -inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Multiply(lhs, rhs); -} - -inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Multiply(lhs, rhs); - return lhs; -} - -inline Vector4 operator / (const Vector4& lhs, const float& rhs) -{ - return Vector4Scale(lhs, 1.0f / rhs); -} - -inline const Vector4& operator /= (Vector4& lhs, const float& rhs) -{ - lhs = Vector4Scale(lhs, 1.0f / rhs); - return lhs; -} - -inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Divide(lhs, rhs); -} - -inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Divide(lhs, rhs); - return lhs; -} - -inline bool operator == (const Vector4& lhs, const Vector4& rhs) -{ - return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w); -} - -inline bool operator != (const Vector4& lhs, const Vector4& rhs) -{ - return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w); -} - -// Quaternion operators -static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 }; -static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 }; -static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 }; - -inline Quaternion operator + (const Quaternion& lhs, const float& rhs) -{ - return QuaternionAddValue(lhs, rhs); -} - -inline const Quaternion& operator += (Quaternion& lhs, const float& rhs) -{ - lhs = QuaternionAddValue(lhs, rhs); - return lhs; -} - -inline Quaternion operator - (const Quaternion& lhs, const float& rhs) -{ - return QuaternionSubtractValue(lhs, rhs); -} - -inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs) -{ - lhs = QuaternionSubtractValue(lhs, rhs); - return lhs; -} - -inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs) -{ - return QuaternionTransform(lhs, rhs); -} - -inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs) -{ - lhs = QuaternionTransform(lhs, rhs); - return lhs; -} - -// Matrix operators -inline Matrix operator + (const Matrix& lhs, const Matrix& rhs) -{ - return MatrixAdd(lhs, rhs); -} - -inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs) -{ - lhs = MatrixAdd(lhs, rhs); - return lhs; -} - -inline Matrix operator - (const Matrix& lhs, const Matrix& rhs) -{ - return MatrixSubtract(lhs, rhs); -} - -inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs) -{ - lhs = MatrixSubtract(lhs, rhs); - return lhs; -} - -inline Matrix operator * (const Matrix& lhs, const Matrix& rhs) -{ - return MatrixMultiply(lhs, rhs); -} - -inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs) -{ - lhs = MatrixMultiply(lhs, rhs); - return lhs; -} -//------------------------------------------------------------------------------- -#endif // C++ operators - -#endif // RAYMATH_H diff --git a/include/rlgl.h b/include/rlgl.h deleted file mode 100644 index 92971df..0000000 --- a/include/rlgl.h +++ /dev/null @@ -1,5262 +0,0 @@ -/********************************************************************************************** -* -* rlgl v5.0 - A multi-OpenGL abstraction layer with an immediate-mode style API -* -* DESCRIPTION: -* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0, ES 3.0) -* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) -* -* ADDITIONAL NOTES: -* When choosing an OpenGL backend different than OpenGL 1.1, some internal buffer are -* initialized on rlglInit() to accumulate vertex data -* -* When an internal state change is required all the stored vertex data is renderer in batch, -* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch -* -* Some resources are also loaded for convenience, here the complete list: -* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data -* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 -* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) -* -* Internal buffer (and resources) must be manually unloaded calling rlglClose() -* -* CONFIGURATION: -* #define GRAPHICS_API_OPENGL_11 -* #define GRAPHICS_API_OPENGL_21 -* #define GRAPHICS_API_OPENGL_33 -* #define GRAPHICS_API_OPENGL_43 -* #define GRAPHICS_API_OPENGL_ES2 -* #define GRAPHICS_API_OPENGL_ES3 -* Use selected OpenGL graphics backend, should be supported by platform -* Those preprocessor defines are only used on rlgl module, if OpenGL version is -* required by any other module, use rlGetVersion() to check it -* -* #define RLGL_IMPLEMENTATION -* Generates the implementation of the library into the included file -* If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation -* -* #define RLGL_RENDER_TEXTURES_HINT -* Enable framebuffer objects (fbo) support (enabled by default) -* Some GPUs could not support them despite the OpenGL version -* -* #define RLGL_SHOW_GL_DETAILS_INFO -* Show OpenGL extensions and capabilities detailed logs on init -* -* #define RLGL_ENABLE_OPENGL_DEBUG_CONTEXT -* Enable debug context (only available on OpenGL 4.3) -* -* rlgl capabilities could be customized just defining some internal -* values before library inclusion (default values listed): -* -* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits -* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) -* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) -* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) -* -* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack -* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported -* #define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance -* #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance -* -* When loading a shader, the following vertex attributes and uniform -* location names are tried to be set automatically: -* -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView))) -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) -* -* DEPENDENCIES: -* - OpenGL libraries (depending on platform and OpenGL version selected) -* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) -* -* 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. -* -**********************************************************************************************/ - -#ifndef RLGL_H -#define RLGL_H - -#define RLGL_VERSION "5.0" - -// Function specifiers in case library is build/used as a shared library -// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll -// NOTE: visibility(default) attribute makes symbols "visible" when compiled with -fvisibility=hidden -#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) -#elif defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __attribute__((visibility("default"))) // We are building the library as a Unix shared library (.so/.dylib) -#elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) - #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) -#endif - -// Function specifiers definition -#ifndef RLAPI - #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) -#endif - -// Support TRACELOG macros -#ifndef TRACELOG - #define TRACELOG(level, ...) (void)0 - #define TRACELOGD(...) (void)0 -#endif - -// Allow custom memory allocators -#ifndef RL_MALLOC - #define RL_MALLOC(sz) malloc(sz) -#endif -#ifndef RL_CALLOC - #define RL_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RL_REALLOC - #define RL_REALLOC(n,sz) realloc(n,sz) -#endif -#ifndef RL_FREE - #define RL_FREE(p) free(p) -#endif - -// Security check in case no GRAPHICS_API_OPENGL_* defined -#if !defined(GRAPHICS_API_OPENGL_11) && \ - !defined(GRAPHICS_API_OPENGL_21) && \ - !defined(GRAPHICS_API_OPENGL_33) && \ - !defined(GRAPHICS_API_OPENGL_43) && \ - !defined(GRAPHICS_API_OPENGL_ES2) && \ - !defined(GRAPHICS_API_OPENGL_ES3) - #define GRAPHICS_API_OPENGL_33 -#endif - -// Security check in case multiple GRAPHICS_API_OPENGL_* defined -#if defined(GRAPHICS_API_OPENGL_11) - #if defined(GRAPHICS_API_OPENGL_21) - #undef GRAPHICS_API_OPENGL_21 - #endif - #if defined(GRAPHICS_API_OPENGL_33) - #undef GRAPHICS_API_OPENGL_33 - #endif - #if defined(GRAPHICS_API_OPENGL_43) - #undef GRAPHICS_API_OPENGL_43 - #endif - #if defined(GRAPHICS_API_OPENGL_ES2) - #undef GRAPHICS_API_OPENGL_ES2 - #endif -#endif - -// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality -// WARNING: Specific parts are checked with #if defines -#if defined(GRAPHICS_API_OPENGL_21) - #define GRAPHICS_API_OPENGL_33 -#endif - -// OpenGL 4.3 uses OpenGL 3.3 Core functionality -#if defined(GRAPHICS_API_OPENGL_43) - #define GRAPHICS_API_OPENGL_33 -#endif - -// OpenGL ES 3.0 uses OpenGL ES 2.0 functionality (and more) -#if defined(GRAPHICS_API_OPENGL_ES3) - #define GRAPHICS_API_OPENGL_ES2 -#endif - -// Support framebuffer objects by default -// NOTE: Some driver implementation do not support it, despite they should -#define RLGL_RENDER_TEXTURES_HINT - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- - -// Default internal render batch elements limits -#ifndef RL_DEFAULT_BATCH_BUFFER_ELEMENTS - #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // This is the maximum amount of elements (quads) per batch - // NOTE: Be careful with text, every letter maps to a quad - #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 - #endif - #if defined(GRAPHICS_API_OPENGL_ES2) - // We reduce memory sizes for embedded systems (RPI and HTML5) - // NOTE: On HTML5 (emscripten) this is allocated on heap, - // by default it's only 16MB!...just take care... - #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 2048 - #endif -#endif -#ifndef RL_DEFAULT_BATCH_BUFFERS - #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) -#endif -#ifndef RL_DEFAULT_BATCH_DRAWCALLS - #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) -#endif -#ifndef RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS - #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) -#endif - -// Internal Matrix stack -#ifndef RL_MAX_MATRIX_STACK_SIZE - #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack -#endif - -// Shader limits -#ifndef RL_MAX_SHADER_LOCATIONS - #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported -#endif - -// Projection matrix culling -#ifndef RL_CULL_DISTANCE_NEAR - #define RL_CULL_DISTANCE_NEAR 0.01 // Default near cull distance -#endif -#ifndef RL_CULL_DISTANCE_FAR - #define RL_CULL_DISTANCE_FAR 1000.0 // Default far cull distance -#endif - -// Texture parameters (equivalent to OpenGL defines) -#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S -#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T -#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER -#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER - -#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST -#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR -#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST -#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR -#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST -#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR -#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) -#define RL_TEXTURE_MIPMAP_BIAS_RATIO 0x4000 // Texture mipmap bias, percentage ratio (custom identifier) - -#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT -#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE -#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT -#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT - -// Matrix modes (equivalent to OpenGL) -#define RL_MODELVIEW 0x1700 // GL_MODELVIEW -#define RL_PROJECTION 0x1701 // GL_PROJECTION -#define RL_TEXTURE 0x1702 // GL_TEXTURE - -// Primitive assembly draw modes -#define RL_LINES 0x0001 // GL_LINES -#define RL_TRIANGLES 0x0004 // GL_TRIANGLES -#define RL_QUADS 0x0007 // GL_QUADS - -// GL equivalent data types -#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE -#define RL_FLOAT 0x1406 // GL_FLOAT - -// GL buffer usage hint -#define RL_STREAM_DRAW 0x88E0 // GL_STREAM_DRAW -#define RL_STREAM_READ 0x88E1 // GL_STREAM_READ -#define RL_STREAM_COPY 0x88E2 // GL_STREAM_COPY -#define RL_STATIC_DRAW 0x88E4 // GL_STATIC_DRAW -#define RL_STATIC_READ 0x88E5 // GL_STATIC_READ -#define RL_STATIC_COPY 0x88E6 // GL_STATIC_COPY -#define RL_DYNAMIC_DRAW 0x88E8 // GL_DYNAMIC_DRAW -#define RL_DYNAMIC_READ 0x88E9 // GL_DYNAMIC_READ -#define RL_DYNAMIC_COPY 0x88EA // GL_DYNAMIC_COPY - -// GL Shader type -#define RL_FRAGMENT_SHADER 0x8B30 // GL_FRAGMENT_SHADER -#define RL_VERTEX_SHADER 0x8B31 // GL_VERTEX_SHADER -#define RL_COMPUTE_SHADER 0x91B9 // GL_COMPUTE_SHADER - -// GL blending factors -#define RL_ZERO 0 // GL_ZERO -#define RL_ONE 1 // GL_ONE -#define RL_SRC_COLOR 0x0300 // GL_SRC_COLOR -#define RL_ONE_MINUS_SRC_COLOR 0x0301 // GL_ONE_MINUS_SRC_COLOR -#define RL_SRC_ALPHA 0x0302 // GL_SRC_ALPHA -#define RL_ONE_MINUS_SRC_ALPHA 0x0303 // GL_ONE_MINUS_SRC_ALPHA -#define RL_DST_ALPHA 0x0304 // GL_DST_ALPHA -#define RL_ONE_MINUS_DST_ALPHA 0x0305 // GL_ONE_MINUS_DST_ALPHA -#define RL_DST_COLOR 0x0306 // GL_DST_COLOR -#define RL_ONE_MINUS_DST_COLOR 0x0307 // GL_ONE_MINUS_DST_COLOR -#define RL_SRC_ALPHA_SATURATE 0x0308 // GL_SRC_ALPHA_SATURATE -#define RL_CONSTANT_COLOR 0x8001 // GL_CONSTANT_COLOR -#define RL_ONE_MINUS_CONSTANT_COLOR 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR -#define RL_CONSTANT_ALPHA 0x8003 // GL_CONSTANT_ALPHA -#define RL_ONE_MINUS_CONSTANT_ALPHA 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA - -// GL blending functions/equations -#define RL_FUNC_ADD 0x8006 // GL_FUNC_ADD -#define RL_MIN 0x8007 // GL_MIN -#define RL_MAX 0x8008 // GL_MAX -#define RL_FUNC_SUBTRACT 0x800A // GL_FUNC_SUBTRACT -#define RL_FUNC_REVERSE_SUBTRACT 0x800B // GL_FUNC_REVERSE_SUBTRACT -#define RL_BLEND_EQUATION 0x8009 // GL_BLEND_EQUATION -#define RL_BLEND_EQUATION_RGB 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) -#define RL_BLEND_EQUATION_ALPHA 0x883D // GL_BLEND_EQUATION_ALPHA -#define RL_BLEND_DST_RGB 0x80C8 // GL_BLEND_DST_RGB -#define RL_BLEND_SRC_RGB 0x80C9 // GL_BLEND_SRC_RGB -#define RL_BLEND_DST_ALPHA 0x80CA // GL_BLEND_DST_ALPHA -#define RL_BLEND_SRC_ALPHA 0x80CB // GL_BLEND_SRC_ALPHA -#define RL_BLEND_COLOR 0x8005 // GL_BLEND_COLOR - -#define RL_READ_FRAMEBUFFER 0x8CA8 // GL_READ_FRAMEBUFFER -#define RL_DRAW_FRAMEBUFFER 0x8CA9 // GL_DRAW_FRAMEBUFFER - -// Default shader vertex attribute locations -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 -#endif - #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 -#endif -#ifdef RL_SUPPORT_MESH_GPU_SKINNING -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 -#endif -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) - #include -#elif !defined(__cplusplus) && !defined(bool) && !defined(RL_BOOL_TYPE) - // Boolean type -typedef enum bool { false = 0, true = !false } bool; -#endif - -#if !defined(RL_MATRIX_TYPE) -// Matrix, 4x4 components, column major, OpenGL style, right handed -typedef struct Matrix { - float m0, m4, m8, m12; // Matrix first row (4 components) - float m1, m5, m9, m13; // Matrix second row (4 components) - float m2, m6, m10, m14; // Matrix third row (4 components) - float m3, m7, m11, m15; // Matrix fourth row (4 components) -} Matrix; -#define RL_MATRIX_TYPE -#endif - -// Dynamic vertex buffers (position + texcoords + colors + indices arrays) -typedef struct rlVertexBuffer { - int elementCount; // Number of elements in the buffer (QUADS) - - float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) - float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) - float *normals; // Vertex normal (XYZ - 3 components per vertex) (shader-location = 2) - unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) - unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) -#endif - unsigned int vaoId; // OpenGL Vertex Array Object id - unsigned int vboId[5]; // OpenGL Vertex Buffer Objects id (5 types of vertex data) -} rlVertexBuffer; - -// Draw call type -// NOTE: Only texture changes register a new draw, other state-change-related elements are not -// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any -// of those state-change happens (this is done in core module) -typedef struct rlDrawCall { - int mode; // Drawing mode: LINES, TRIANGLES, QUADS - int vertexCount; // Number of vertex of the draw - int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) - //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId - //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShaderId - unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes - - //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default - //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default -} rlDrawCall; - -// rlRenderBatch type -typedef struct rlRenderBatch { - int bufferCount; // Number of vertex buffers (multi-buffering support) - int currentBuffer; // Current buffer tracking in case of multi-buffering - rlVertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data - - rlDrawCall *draws; // Draw calls array, depends on textureId - int drawCounter; // Draw calls counter - float currentDepth; // Current depth value for next draw -} rlRenderBatch; - -// OpenGL version -typedef enum { - RL_OPENGL_11 = 1, // OpenGL 1.1 - RL_OPENGL_21, // OpenGL 2.1 (GLSL 120) - RL_OPENGL_33, // OpenGL 3.3 (GLSL 330) - RL_OPENGL_43, // OpenGL 4.3 (using GLSL 330) - RL_OPENGL_ES_20, // OpenGL ES 2.0 (GLSL 100) - RL_OPENGL_ES_30 // OpenGL ES 3.0 (GLSL 300 es) -} rlGlVersion; - -// Trace log level -// NOTE: Organized by priority level -typedef enum { - RL_LOG_ALL = 0, // Display all logs - RL_LOG_TRACE, // Trace logging, intended for internal use only - RL_LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds - RL_LOG_INFO, // Info logging, used for program execution info - RL_LOG_WARNING, // Warning logging, used on recoverable failures - RL_LOG_ERROR, // Error logging, used on unrecoverable failures - RL_LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) - RL_LOG_NONE // Disable logging -} rlTraceLogLevel; - -// Texture pixel formats -// NOTE: Support depends on OpenGL version -typedef enum { - RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) - RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) - RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp - RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp - RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) - RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) - RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp - RL_PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) - RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) - RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) - RL_PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) - RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) - RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) - RL_PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) - RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) - RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp -} rlPixelFormat; - -// Texture parameters: filter mode -// NOTE 1: Filtering considers mipmaps if available in the texture -// NOTE 2: Filter is accordingly set for minification and magnification -typedef enum { - RL_TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation - RL_TEXTURE_FILTER_BILINEAR, // Linear filtering - RL_TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) - RL_TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x - RL_TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x - RL_TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x -} rlTextureFilter; - -// Color blending modes (pre-defined) -typedef enum { - RL_BLEND_ALPHA = 0, // Blend textures considering alpha (default) - RL_BLEND_ADDITIVE, // Blend textures adding colors - RL_BLEND_MULTIPLIED, // Blend textures multiplying colors - RL_BLEND_ADD_COLORS, // Blend textures adding colors (alternative) - RL_BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) - RL_BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha - RL_BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) - RL_BLEND_CUSTOM_SEPARATE // Blend textures using custom src/dst factors (use rlSetBlendFactorsSeparate()) -} rlBlendMode; - -// Shader location point type -typedef enum { - RL_SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position - RL_SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 - RL_SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 - RL_SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal - RL_SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent - RL_SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color - RL_SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection - RL_SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) - RL_SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection - RL_SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) - RL_SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal - RL_SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view - RL_SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color - RL_SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color - RL_SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color - RL_SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: RL_SHADER_LOC_MAP_DIFFUSE) - RL_SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: RL_SHADER_LOC_MAP_SPECULAR) - RL_SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal - RL_SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness - RL_SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion - RL_SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission - RL_SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height - RL_SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap - RL_SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance - RL_SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter - RL_SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf -} rlShaderLocationIndex; - -#define RL_SHADER_LOC_MAP_DIFFUSE RL_SHADER_LOC_MAP_ALBEDO -#define RL_SHADER_LOC_MAP_SPECULAR RL_SHADER_LOC_MAP_METALNESS - -// Shader uniform data type -typedef enum { - RL_SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float - RL_SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) - RL_SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) - RL_SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) - RL_SHADER_UNIFORM_INT, // Shader uniform type: int - RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) - RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) - RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) - RL_SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int - RL_SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) - RL_SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) - RL_SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) - RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d -} rlShaderUniformDataType; - -// Shader attribute data types -typedef enum { - RL_SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float - RL_SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) - RL_SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) - RL_SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) -} rlShaderAttributeDataType; - -// Framebuffer attachment type -// NOTE: By default up to 8 color channels defined, but it can be more -typedef enum { - RL_ATTACHMENT_COLOR_CHANNEL0 = 0, // Framebuffer attachment type: color 0 - RL_ATTACHMENT_COLOR_CHANNEL1 = 1, // Framebuffer attachment type: color 1 - RL_ATTACHMENT_COLOR_CHANNEL2 = 2, // Framebuffer attachment type: color 2 - RL_ATTACHMENT_COLOR_CHANNEL3 = 3, // Framebuffer attachment type: color 3 - RL_ATTACHMENT_COLOR_CHANNEL4 = 4, // Framebuffer attachment type: color 4 - RL_ATTACHMENT_COLOR_CHANNEL5 = 5, // Framebuffer attachment type: color 5 - RL_ATTACHMENT_COLOR_CHANNEL6 = 6, // Framebuffer attachment type: color 6 - RL_ATTACHMENT_COLOR_CHANNEL7 = 7, // Framebuffer attachment type: color 7 - RL_ATTACHMENT_DEPTH = 100, // Framebuffer attachment type: depth - RL_ATTACHMENT_STENCIL = 200, // Framebuffer attachment type: stencil -} rlFramebufferAttachType; - -// Framebuffer texture attachment type -typedef enum { - RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, // Framebuffer texture attachment type: cubemap, +X side - RL_ATTACHMENT_CUBEMAP_NEGATIVE_X = 1, // Framebuffer texture attachment type: cubemap, -X side - RL_ATTACHMENT_CUBEMAP_POSITIVE_Y = 2, // Framebuffer texture attachment type: cubemap, +Y side - RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y = 3, // Framebuffer texture attachment type: cubemap, -Y side - RL_ATTACHMENT_CUBEMAP_POSITIVE_Z = 4, // Framebuffer texture attachment type: cubemap, +Z side - RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z = 5, // Framebuffer texture attachment type: cubemap, -Z side - RL_ATTACHMENT_TEXTURE2D = 100, // Framebuffer texture attachment type: texture2d - RL_ATTACHMENT_RENDERBUFFER = 200, // Framebuffer texture attachment type: renderbuffer -} rlFramebufferAttachTextureType; - -// Face culling mode -typedef enum { - RL_CULL_FACE_FRONT = 0, - RL_CULL_FACE_BACK -} rlCullMode; - -//------------------------------------------------------------------------------------ -// Functions Declaration - Matrix operations -//------------------------------------------------------------------------------------ - -#if defined(__cplusplus) -extern "C" { // Prevents name mangling of functions -#endif - -RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed -RLAPI void rlPushMatrix(void); // Push the current matrix to stack -RLAPI void rlPopMatrix(void); // Pop latest inserted matrix from stack -RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix -RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix -RLAPI void rlRotatef(float angle, float x, float y, float z); // Multiply the current matrix by a rotation matrix -RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix -RLAPI void rlMultMatrixf(const float *matf); // Multiply the current matrix by another matrix -RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); -RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); -RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area -RLAPI void rlSetClipPlanes(double nearPlane, double farPlane); // Set clip planes distances -RLAPI double rlGetCullDistanceNear(void); // Get cull plane distance near -RLAPI double rlGetCullDistanceFar(void); // Get cull plane distance far - -//------------------------------------------------------------------------------------ -// Functions Declaration - Vertex level operations -//------------------------------------------------------------------------------------ -RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) -RLAPI void rlEnd(void); // Finish vertex providing -RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int -RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float -RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float -RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float -RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float -RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte -RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float -RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float - -//------------------------------------------------------------------------------------ -// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) -// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, -// some of them are direct wrappers over OpenGL calls, some others are custom -//------------------------------------------------------------------------------------ - -// Vertex buffers state -RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) -RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) -RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) -RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) -RLAPI void rlEnableVertexBufferElement(unsigned int id); // Enable vertex buffer element (VBO element) -RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) -RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index -RLAPI void rlDisableVertexAttribute(unsigned int index); // Disable vertex attribute index -#if defined(GRAPHICS_API_OPENGL_11) -RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer -RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer -#endif - -// Textures state -RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot -RLAPI void rlEnableTexture(unsigned int id); // Enable texture -RLAPI void rlDisableTexture(void); // Disable texture -RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap -RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap -RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) -RLAPI void rlCubemapParameters(unsigned int id, int param, int value); // Set cubemap parameters (filter, wrap) - -// Shader state -RLAPI void rlEnableShader(unsigned int id); // Enable shader program -RLAPI void rlDisableShader(void); // Disable shader program - -// Framebuffer state -RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) -RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer -RLAPI unsigned int rlGetActiveFramebuffer(void); // Get the currently active render texture (fbo), 0 for default framebuffer -RLAPI void rlActiveDrawBuffers(int count); // Activate multiple draw color buffers -RLAPI void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask); // Blit active framebuffer to main framebuffer -RLAPI void rlBindFramebuffer(unsigned int target, unsigned int framebuffer); // Bind framebuffer (FBO) - -// General render state -RLAPI void rlEnableColorBlend(void); // Enable color blending -RLAPI void rlDisableColorBlend(void); // Disable color blending -RLAPI void rlEnableDepthTest(void); // Enable depth test -RLAPI void rlDisableDepthTest(void); // Disable depth test -RLAPI void rlEnableDepthMask(void); // Enable depth write -RLAPI void rlDisableDepthMask(void); // Disable depth write -RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling -RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling -RLAPI void rlColorMask(bool r, bool g, bool b, bool a); // Color mask control -RLAPI void rlSetCullFace(int mode); // Set face culling mode -RLAPI void rlEnableScissorTest(void); // Enable scissor test -RLAPI void rlDisableScissorTest(void); // Disable scissor test -RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test -RLAPI void rlEnableWireMode(void); // Enable wire mode -RLAPI void rlEnablePointMode(void); // Enable point mode -RLAPI void rlDisableWireMode(void); // Disable wire (and point) mode -RLAPI void rlSetLineWidth(float width); // Set the line drawing width -RLAPI float rlGetLineWidth(void); // Get the line drawing width -RLAPI void rlEnableSmoothLines(void); // Enable line aliasing -RLAPI void rlDisableSmoothLines(void); // Disable line aliasing -RLAPI void rlEnableStereoRender(void); // Enable stereo rendering -RLAPI void rlDisableStereoRender(void); // Disable stereo rendering -RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled - -RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color -RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) -RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes -RLAPI void rlSetBlendMode(int mode); // Set blending mode -RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) -RLAPI void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha); // Set blending mode factors and equations separately (using OpenGL factors) - -//------------------------------------------------------------------------------------ -// Functions Declaration - rlgl functionality -//------------------------------------------------------------------------------------ -// rlgl initialization functions -RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) -RLAPI void rlglClose(void); // De-initialize rlgl (buffers, shaders, textures) -RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) -RLAPI int rlGetVersion(void); // Get current OpenGL version -RLAPI void rlSetFramebufferWidth(int width); // Set current framebuffer width -RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width -RLAPI void rlSetFramebufferHeight(int height); // Set current framebuffer height -RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height - -RLAPI unsigned int rlGetTextureIdDefault(void); // Get default texture id -RLAPI unsigned int rlGetShaderIdDefault(void); // Get default shader id -RLAPI int *rlGetShaderLocsDefault(void); // Get default shader locations - -// Render batch management -// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode -// but this render batch API is exposed in case of custom batches are required -RLAPI rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system -RLAPI void rlUnloadRenderBatch(rlRenderBatch batch); // Unload render batch system -RLAPI void rlDrawRenderBatch(rlRenderBatch *batch); // Draw render batch data (Update->Draw->Reset) -RLAPI void rlSetRenderBatchActive(rlRenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) -RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch -RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex - -RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits - -//------------------------------------------------------------------------------------------------------------------------ - -// Vertex buffers management -RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported -RLAPI unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic); // Load a vertex buffer object -RLAPI unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic); // Load vertex buffer elements object -RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, const void *data, int dataSize, int offset); // Update vertex buffer object data on GPU buffer -RLAPI void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset); // Update vertex buffer elements data on GPU buffer -RLAPI void rlUnloadVertexArray(unsigned int vaoId); // Unload vertex array (vao) -RLAPI void rlUnloadVertexBuffer(unsigned int vboId); // Unload vertex buffer object -RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset); // Set vertex attribute data configuration -RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); // Set vertex attribute data divisor -RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value, when attribute to provided -RLAPI void rlDrawVertexArray(int offset, int count); // Draw vertex array (currently active vao) -RLAPI void rlDrawVertexArrayElements(int offset, int count, const void *buffer); // Draw vertex array elements -RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); // Draw vertex array (currently active vao) with instancing -RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances); // Draw vertex array elements with instancing - -// Textures management -RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture data -RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) -RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount); // Load texture cubemap data -RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update texture with new data on GPU -RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats -RLAPI const char *rlGetPixelFormatName(unsigned int format); // Get name string for pixel format -RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory -RLAPI void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps); // Generate mipmap data for selected texture -RLAPI void *rlReadTexturePixels(unsigned int id, int width, int height, int format); // Read texture pixel data -RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) - -// Framebuffer management (fbo) -RLAPI unsigned int rlLoadFramebuffer(void); // Load an empty framebuffer -RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer -RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete -RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU - -// Shaders management -RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings -RLAPI unsigned int rlCompileShader(const char *shaderCode, int type); // Compile custom shader and return shader id (type: RL_VERTEX_SHADER, RL_FRAGMENT_SHADER, RL_COMPUTE_SHADER) -RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program -RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program -RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform -RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute -RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform -RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix -RLAPI void rlSetUniformMatrices(int locIndex, const Matrix *mat, int count); // Set shader value matrices -RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler -RLAPI void rlSetShader(unsigned int id, int *locs); // Set shader currently active (id and locations) - -// Compute shader management -RLAPI unsigned int rlLoadComputeShaderProgram(unsigned int shaderId); // Load compute shader program -RLAPI void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ); // Dispatch compute shader (equivalent to *draw* for graphics pipeline) - -// Shader buffer storage object management (ssbo) -RLAPI unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint); // Load shader storage buffer object (SSBO) -RLAPI void rlUnloadShaderBuffer(unsigned int ssboId); // Unload shader storage buffer object (SSBO) -RLAPI void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset); // Update SSBO buffer data -RLAPI void rlBindShaderBuffer(unsigned int id, unsigned int index); // Bind SSBO buffer -RLAPI void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset); // Read SSBO buffer data (GPU->CPU) -RLAPI void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count); // Copy SSBO data between buffers -RLAPI unsigned int rlGetShaderBufferSize(unsigned int id); // Get SSBO buffer size - -// Buffer management -RLAPI void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly); // Bind image texture - -// Matrix state management -RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix -RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix -RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix -RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) -RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) -RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) -RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) -RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering -RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering - -// Quick and dirty cube/quad buffers load->draw->unload -RLAPI void rlLoadDrawCube(void); // Load and draw a cube -RLAPI void rlLoadDrawQuad(void); // Load and draw a quad - -#if defined(__cplusplus) -} -#endif - -#endif // RLGL_H - -/*********************************************************************************** -* -* RLGL IMPLEMENTATION -* -************************************************************************************/ - -#if defined(RLGL_IMPLEMENTATION) - -// Expose OpenGL functions from glad in raylib -#if defined(BUILD_LIBTYPE_SHARED) - #define GLAD_API_CALL_EXPORT - #define GLAD_API_CALL_EXPORT_BUILD -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - #if defined(__APPLE__) - #include // OpenGL 1.1 library for OSX - #include // OpenGL extensions library - #else - // APIENTRY for OpenGL function pointer declarations is required - #if !defined(APIENTRY) - #if defined(_WIN32) - #define APIENTRY __stdcall - #else - #define APIENTRY - #endif - #endif - // WINGDIAPI definition. Some Windows OpenGL headers need it - #if !defined(WINGDIAPI) && defined(_WIN32) - #define WINGDIAPI __declspec(dllimport) - #endif - - #include // OpenGL 1.1 library - #endif -#endif - -#if defined(GRAPHICS_API_OPENGL_33) - #define GLAD_MALLOC RL_MALLOC - #define GLAD_FREE RL_FREE - - #define GLAD_GL_IMPLEMENTATION - #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers -#endif - -#if defined(GRAPHICS_API_OPENGL_ES3) - #include // OpenGL ES 3.0 library - #define GL_GLEXT_PROTOTYPES - #include // OpenGL ES 2.0 extensions library -#elif defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: OpenGL ES 2.0 can be enabled on Desktop platforms, - // in that case, functions are loaded from a custom glad for OpenGL ES 2.0 - #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) - #define GLAD_GLES2_IMPLEMENTATION - #include "external/glad_gles2.h" - #else - #define GL_GLEXT_PROTOTYPES - //#include // EGL library -> not required, platform layer - #include // OpenGL ES 2.0 library - #include // OpenGL ES 2.0 extensions library - #endif - - // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi - // provided headers (despite being defined in official Khronos GLES2 headers) - #if defined(PLATFORM_DRM) - typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); - typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); - typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); - #endif -#endif - -#include // Required for: malloc(), free() -#include // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] -#include // Required for: sqrtf(), sinf(), cosf(), floor(), log() - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#ifndef PI - #define PI 3.14159265358979323846f -#endif -#ifndef DEG2RAD - #define DEG2RAD (PI/180.0f) -#endif -#ifndef RAD2DEG - #define RAD2DEG (180.0f/PI) -#endif - -#ifndef GL_SHADING_LANGUAGE_VERSION - #define GL_SHADING_LANGUAGE_VERSION 0x8B8C -#endif - -#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT - #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 -#endif -#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 -#endif -#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 -#endif -#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 -#endif -#ifndef GL_ETC1_RGB8_OES - #define GL_ETC1_RGB8_OES 0x8D64 -#endif -#ifndef GL_COMPRESSED_RGB8_ETC2 - #define GL_COMPRESSED_RGB8_ETC2 0x9274 -#endif -#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC - #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 -#endif -#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG - #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 -#endif -#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG - #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 -#endif -#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR - #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 -#endif -#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR - #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 -#endif - -#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF -#endif -#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE -#endif - -#ifndef GL_PROGRAM_POINT_SIZE - #define GL_PROGRAM_POINT_SIZE 0x8642 -#endif - -#ifndef GL_LINE_WIDTH - #define GL_LINE_WIDTH 0x0B21 -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - #define GL_UNSIGNED_SHORT_5_6_5 0x8363 - #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 - #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 -#endif - -#if defined(GRAPHICS_API_OPENGL_21) - #define GL_LUMINANCE 0x1909 - #define GL_LUMINANCE_ALPHA 0x190A -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - #define glClearDepth glClearDepthf - #if !defined(GRAPHICS_API_OPENGL_ES3) - #define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER - #define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER - #endif -#endif - -// Default shader vertex attribute names to set location points -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION - #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL - #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR - #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS - #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS - #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS -#endif - -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MVP - #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW - #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION - #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL - #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL - #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR - #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES - #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices -#endif -#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 - #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) -#endif -#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 - #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) -#endif -#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 - #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -typedef struct rlglData { - rlRenderBatch *currentBatch; // Current render batch - rlRenderBatch defaultBatch; // Default internal render batch - - struct { - int vertexCounter; // Current active render batch vertex counter (generic, used for all batches) - float texcoordx, texcoordy; // Current active texture coordinate (added on glVertex*()) - float normalx, normaly, normalz; // Current active normal (added on glVertex*()) - unsigned char colorr, colorg, colorb, colora; // Current active color (added on glVertex*()) - - int currentMatrixMode; // Current matrix mode - Matrix *currentMatrix; // Current matrix pointer - Matrix modelview; // Default modelview matrix - Matrix projection; // Default projection matrix - Matrix transform; // Transform matrix to be used with rlTranslate, rlRotate, rlScale - bool transformRequired; // Require transform matrix application to current draw-call vertex (if required) - Matrix stack[RL_MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop - int stackCounter; // Matrix stack counter - - unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) - unsigned int activeTextureId[RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS]; // Active texture ids to be enabled on batch drawing (0 active by default) - unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) - unsigned int defaultFShaderId; // Default fragment shader id (used by default shader program) - unsigned int defaultShaderId; // Default shader program id, supports vertex color and diffuse texture - int *defaultShaderLocs; // Default shader locations pointer to be used on rendering - unsigned int currentShaderId; // Current shader id to be used on rendering (by default, defaultShaderId) - int *currentShaderLocs; // Current shader locations pointer to be used on rendering (by default, defaultShaderLocs) - - bool stereoRender; // Stereo rendering flag - Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices - Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices - - // Blending variables - int currentBlendMode; // Blending mode active - int glBlendSrcFactor; // Blending source factor - int glBlendDstFactor; // Blending destination factor - int glBlendEquation; // Blending equation - int glBlendSrcFactorRGB; // Blending source RGB factor - int glBlendDestFactorRGB; // Blending destination RGB factor - int glBlendSrcFactorAlpha; // Blending source alpha factor - int glBlendDestFactorAlpha; // Blending destination alpha factor - int glBlendEquationRGB; // Blending equation for RGB - int glBlendEquationAlpha; // Blending equation for alpha - bool glCustomBlendModeModified; // Custom blending factor and equation modification status - - int framebufferWidth; // Current framebuffer width - int framebufferHeight; // Current framebuffer height - - } State; // Renderer state - struct { - bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) - bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) - bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) - bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_OES_depth_texture) - bool texDepthWebGL; // Depth textures supported WebGL specific (GL_WEBGL_depth_texture) - bool texFloat32; // float textures support (32 bit per channel) (GL_OES_texture_float) - bool texFloat16; // half float textures support (16 bit per channel) (GL_OES_texture_half_float) - bool texCompDXT; // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc) - bool texCompETC1; // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1) - bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) - bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) - bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) - bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) - bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) - bool computeShader; // Compute shaders support (GL_ARB_compute_shader) - bool ssbo; // Shader storage buffer object support (GL_ARB_shader_storage_buffer_object) - - float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) - int maxDepthBits; // Maximum bits for depth component - - } ExtSupported; // Extensions supported flags -} rlglData; - -typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) - -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -static double rlCullDistanceNear = RL_CULL_DISTANCE_NEAR; -static double rlCullDistanceFar = RL_CULL_DISTANCE_FAR; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -static rlglData RLGL = { 0 }; -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -#if defined(GRAPHICS_API_OPENGL_ES2) && !defined(GRAPHICS_API_OPENGL_ES3) -// NOTE: VAO functionality is exposed through extensions (OES) -static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; -static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; -static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; - -// NOTE: Instancing functionality could also be available through extension -static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; -static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; -static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; -#endif - -//---------------------------------------------------------------------------------- -// Module specific Functions Declaration -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -static void rlLoadShaderDefault(void); // Load default shader -static void rlUnloadShaderDefault(void); // Unload default shader -#if defined(RLGL_SHOW_GL_DETAILS_INFO) -static const char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name -#endif // RLGL_SHOW_GL_DETAILS_INFO -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) - -// Auxiliar matrix math functions -typedef struct rl_float16 { - float v[16]; -} rl_float16; -static rl_float16 rlMatrixToFloatV(Matrix mat); // Get float array of matrix data -#define rlMatrixToFloat(mat) (rlMatrixToFloatV(mat).v) // Get float vector for Matrix -static Matrix rlMatrixIdentity(void); // Get identity matrix -static Matrix rlMatrixMultiply(Matrix left, Matrix right); // Multiply two matrices -static Matrix rlMatrixTranspose(Matrix mat); // Transposes provided matrix -static Matrix rlMatrixInvert(Matrix mat); // Invert provided matrix - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Matrix operations -//---------------------------------------------------------------------------------- - -#if defined(GRAPHICS_API_OPENGL_11) -// Fallback to OpenGL 1.1 function calls -//--------------------------------------- -void rlMatrixMode(int mode) -{ - switch (mode) - { - case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break; - case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break; - case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break; - default: break; - } -} - -void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) -{ - glFrustum(left, right, bottom, top, znear, zfar); -} - -void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) -{ - glOrtho(left, right, bottom, top, znear, zfar); -} - -void rlPushMatrix(void) { glPushMatrix(); } -void rlPopMatrix(void) { glPopMatrix(); } -void rlLoadIdentity(void) { glLoadIdentity(); } -void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } -void rlRotatef(float angle, float x, float y, float z) { glRotatef(angle, x, y, z); } -void rlScalef(float x, float y, float z) { glScalef(x, y, z); } -void rlMultMatrixf(const float *matf) { glMultMatrixf(matf); } -#endif -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Choose the current matrix to be transformed -void rlMatrixMode(int mode) -{ - if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection; - else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview; - //else if (mode == RL_TEXTURE) // Not supported - - RLGL.State.currentMatrixMode = mode; -} - -// Push the current matrix into RLGL.State.stack -void rlPushMatrix(void) -{ - if (RLGL.State.stackCounter >= RL_MAX_MATRIX_STACK_SIZE) TRACELOG(RL_LOG_ERROR, "RLGL: Matrix stack overflow (RL_MAX_MATRIX_STACK_SIZE)"); - - if (RLGL.State.currentMatrixMode == RL_MODELVIEW) - { - RLGL.State.transformRequired = true; - RLGL.State.currentMatrix = &RLGL.State.transform; - } - - RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix; - RLGL.State.stackCounter++; -} - -// Pop lattest inserted matrix from RLGL.State.stack -void rlPopMatrix(void) -{ - if (RLGL.State.stackCounter > 0) - { - Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1]; - *RLGL.State.currentMatrix = mat; - RLGL.State.stackCounter--; - } - - if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW)) - { - RLGL.State.currentMatrix = &RLGL.State.modelview; - RLGL.State.transformRequired = false; - } -} - -// Reset current matrix to identity matrix -void rlLoadIdentity(void) -{ - *RLGL.State.currentMatrix = rlMatrixIdentity(); -} - -// Multiply the current matrix by a translation matrix -void rlTranslatef(float x, float y, float z) -{ - Matrix matTranslation = { - 1.0f, 0.0f, 0.0f, x, - 0.0f, 1.0f, 0.0f, y, - 0.0f, 0.0f, 1.0f, z, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - // NOTE: We transpose matrix with multiplication order - *RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by a rotation matrix -// NOTE: The provided angle must be in degrees -void rlRotatef(float angle, float x, float y, float z) -{ - Matrix matRotation = rlMatrixIdentity(); - - // Axis vector (x, y, z) normalization - float lengthSquared = x*x + y*y + z*z; - if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) - { - float inverseLength = 1.0f/sqrtf(lengthSquared); - x *= inverseLength; - y *= inverseLength; - z *= inverseLength; - } - - // Rotation matrix generation - float sinres = sinf(DEG2RAD*angle); - float cosres = cosf(DEG2RAD*angle); - float t = 1.0f - cosres; - - matRotation.m0 = x*x*t + cosres; - matRotation.m1 = y*x*t + z*sinres; - matRotation.m2 = z*x*t - y*sinres; - matRotation.m3 = 0.0f; - - matRotation.m4 = x*y*t - z*sinres; - matRotation.m5 = y*y*t + cosres; - matRotation.m6 = z*y*t + x*sinres; - matRotation.m7 = 0.0f; - - matRotation.m8 = x*z*t + y*sinres; - matRotation.m9 = y*z*t - x*sinres; - matRotation.m10 = z*z*t + cosres; - matRotation.m11 = 0.0f; - - matRotation.m12 = 0.0f; - matRotation.m13 = 0.0f; - matRotation.m14 = 0.0f; - matRotation.m15 = 1.0f; - - // NOTE: We transpose matrix with multiplication order - *RLGL.State.currentMatrix = rlMatrixMultiply(matRotation, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by a scaling matrix -void rlScalef(float x, float y, float z) -{ - Matrix matScale = { - x, 0.0f, 0.0f, 0.0f, - 0.0f, y, 0.0f, 0.0f, - 0.0f, 0.0f, z, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - // NOTE: We transpose matrix with multiplication order - *RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by another matrix -void rlMultMatrixf(const float *matf) -{ - // Matrix creation from array - Matrix mat = { matf[0], matf[4], matf[8], matf[12], - matf[1], matf[5], matf[9], matf[13], - matf[2], matf[6], matf[10], matf[14], - matf[3], matf[7], matf[11], matf[15] }; - - *RLGL.State.currentMatrix = rlMatrixMultiply(mat, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by a perspective matrix generated by parameters -void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) -{ - Matrix matFrustum = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(zfar - znear); - - matFrustum.m0 = ((float) znear*2.0f)/rl; - matFrustum.m1 = 0.0f; - matFrustum.m2 = 0.0f; - matFrustum.m3 = 0.0f; - - matFrustum.m4 = 0.0f; - matFrustum.m5 = ((float) znear*2.0f)/tb; - matFrustum.m6 = 0.0f; - matFrustum.m7 = 0.0f; - - matFrustum.m8 = ((float)right + (float)left)/rl; - matFrustum.m9 = ((float)top + (float)bottom)/tb; - matFrustum.m10 = -((float)zfar + (float)znear)/fn; - matFrustum.m11 = -1.0f; - - matFrustum.m12 = 0.0f; - matFrustum.m13 = 0.0f; - matFrustum.m14 = -((float)zfar*(float)znear*2.0f)/fn; - matFrustum.m15 = 0.0f; - - *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matFrustum); -} - -// Multiply the current matrix by an orthographic matrix generated by parameters -void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) -{ - // NOTE: If left-right and top-botton values are equal it could create a division by zero, - // response to it is platform/compiler dependant - Matrix matOrtho = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(zfar - znear); - - matOrtho.m0 = 2.0f/rl; - matOrtho.m1 = 0.0f; - matOrtho.m2 = 0.0f; - matOrtho.m3 = 0.0f; - matOrtho.m4 = 0.0f; - matOrtho.m5 = 2.0f/tb; - matOrtho.m6 = 0.0f; - matOrtho.m7 = 0.0f; - matOrtho.m8 = 0.0f; - matOrtho.m9 = 0.0f; - matOrtho.m10 = -2.0f/fn; - matOrtho.m11 = 0.0f; - matOrtho.m12 = -((float)left + (float)right)/rl; - matOrtho.m13 = -((float)top + (float)bottom)/tb; - matOrtho.m14 = -((float)zfar + (float)znear)/fn; - matOrtho.m15 = 1.0f; - - *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matOrtho); -} -#endif - -// Set the viewport area (transformation from normalized device coordinates to window coordinates) -// NOTE: We store current viewport dimensions -void rlViewport(int x, int y, int width, int height) -{ - glViewport(x, y, width, height); -} - -// Set clip planes distances -void rlSetClipPlanes(double nearPlane, double farPlane) -{ - rlCullDistanceNear = nearPlane; - rlCullDistanceFar = farPlane; -} - -// Get cull plane distance near -double rlGetCullDistanceNear(void) -{ - return rlCullDistanceNear; -} - -// Get cull plane distance far -double rlGetCullDistanceFar(void) -{ - return rlCullDistanceFar; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vertex level operations -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_11) -// Fallback to OpenGL 1.1 function calls -//--------------------------------------- -void rlBegin(int mode) -{ - switch (mode) - { - case RL_LINES: glBegin(GL_LINES); break; - case RL_TRIANGLES: glBegin(GL_TRIANGLES); break; - case RL_QUADS: glBegin(GL_QUADS); break; - default: break; - } -} - -void rlEnd(void) { glEnd(); } -void rlVertex2i(int x, int y) { glVertex2i(x, y); } -void rlVertex2f(float x, float y) { glVertex2f(x, y); } -void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } -void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } -void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } -void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } -void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } -void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } -#endif -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Initialize drawing mode (how to organize vertex) -void rlBegin(int mode) -{ - // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS - // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode != mode) - { - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) - { - // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, - // that way, following QUADS drawing will keep aligned with index processing - // It implies adding some extra alignment vertex at the end of the draw, - // those vertex are not processed but they are considered as an additional offset - // for the next set of vertex to be drawn - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); - else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); - else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; - - if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) - { - RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; - RLGL.currentBatch->drawCounter++; - } - } - - if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); - - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = mode; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = RLGL.State.defaultTextureId; - } -} - -// Finish vertex providing -void rlEnd(void) -{ - // NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values, - // as well as depth buffer bit-depth (16bit or 24bit or 32bit) - // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) - RLGL.currentBatch->currentDepth += (1.0f/20000.0f); -} - -// Define one vertex (position) -// NOTE: Vertex position data is the basic information required for drawing -void rlVertex3f(float x, float y, float z) -{ - float tx = x; - float ty = y; - float tz = z; - - // Transform provided vector if required - if (RLGL.State.transformRequired) - { - tx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z + RLGL.State.transform.m12; - ty = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z + RLGL.State.transform.m13; - tz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z + RLGL.State.transform.m14; - } - - // WARNING: We can't break primitives when launching a new batch - // RL_LINES comes in pairs, RL_TRIANGLES come in groups of 3 vertices and RL_QUADS come in groups of 4 vertices - // We must check current draw.mode when a new vertex is required and finish the batch only if the draw.mode draw.vertexCount is %2, %3 or %4 - if (RLGL.State.vertexCounter > (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4 - 4)) - { - if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) && - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%2 == 0)) - { - // Reached the maximum number of vertices for RL_LINES drawing - // Launch a draw call but keep current state for next vertices comming - // NOTE: We add +1 vertex to the check for security - rlCheckRenderBatchLimit(2 + 1); - } - else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) && - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%3 == 0)) - { - rlCheckRenderBatchLimit(3 + 1); - } - else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_QUADS) && - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4 == 0)) - { - rlCheckRenderBatchLimit(4 + 1); - } - } - - // Add vertices - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter] = tx; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 1] = ty; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 2] = tz; - - // Add current texcoord - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter] = RLGL.State.texcoordx; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter + 1] = RLGL.State.texcoordy; - - // Add current normal - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter] = RLGL.State.normalx; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 1] = RLGL.State.normaly; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 2] = RLGL.State.normalz; - - // Add current color - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter] = RLGL.State.colorr; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 1] = RLGL.State.colorg; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 2] = RLGL.State.colorb; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 3] = RLGL.State.colora; - - RLGL.State.vertexCounter++; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount++; -} - -// Define one vertex (position) -void rlVertex2f(float x, float y) -{ - rlVertex3f(x, y, RLGL.currentBatch->currentDepth); -} - -// Define one vertex (position) -void rlVertex2i(int x, int y) -{ - rlVertex3f((float)x, (float)y, RLGL.currentBatch->currentDepth); -} - -// Define one vertex (texture coordinate) -// NOTE: Texture coordinates are limited to QUADS only -void rlTexCoord2f(float x, float y) -{ - RLGL.State.texcoordx = x; - RLGL.State.texcoordy = y; -} - -// Define one vertex (normal) -// NOTE: Normals limited to TRIANGLES only? -void rlNormal3f(float x, float y, float z) -{ - float normalx = x; - float normaly = y; - float normalz = z; - if (RLGL.State.transformRequired) - { - normalx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z; - normaly = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z; - normalz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z; - } - float length = sqrtf(normalx*normalx + normaly*normaly + normalz*normalz); - if (length != 0.0f) - { - float ilength = 1.0f/length; - normalx *= ilength; - normaly *= ilength; - normalz *= ilength; - } - RLGL.State.normalx = normalx; - RLGL.State.normaly = normaly; - RLGL.State.normalz = normalz; -} - -// Define one vertex (color) -void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) -{ - RLGL.State.colorr = x; - RLGL.State.colorg = y; - RLGL.State.colorb = z; - RLGL.State.colora = w; -} - -// Define one vertex (color) -void rlColor4f(float r, float g, float b, float a) -{ - rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); -} - -// Define one vertex (color) -void rlColor3f(float x, float y, float z) -{ - rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); -} - -#endif - -//-------------------------------------------------------------------------------------- -// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) -//-------------------------------------------------------------------------------------- - -// Set current texture to use -void rlSetTexture(unsigned int id) -{ - if (id == 0) - { -#if defined(GRAPHICS_API_OPENGL_11) - rlDisableTexture(); -#else - // NOTE: If quads batch limit is reached, we force a draw call and next batch starts - if (RLGL.State.vertexCounter >= - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4) - { - rlDrawRenderBatch(RLGL.currentBatch); - } -#endif - } - else - { -#if defined(GRAPHICS_API_OPENGL_11) - rlEnableTexture(id); -#else - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId != id) - { - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) - { - // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, - // that way, following QUADS drawing will keep aligned with index processing - // It implies adding some extra alignment vertex at the end of the draw, - // those vertex are not processed but they are considered as an additional offset - // for the next set of vertex to be drawn - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); - else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); - else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; - - if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) - { - RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; - - RLGL.currentBatch->drawCounter++; - } - } - - if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); - - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = id; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; - } -#endif - } -} - -// Select and active a texture slot -void rlActiveTextureSlot(int slot) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glActiveTexture(GL_TEXTURE0 + slot); -#endif -} - -// Enable texture -void rlEnableTexture(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_11) - glEnable(GL_TEXTURE_2D); -#endif - glBindTexture(GL_TEXTURE_2D, id); -} - -// Disable texture -void rlDisableTexture(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) - glDisable(GL_TEXTURE_2D); -#endif - glBindTexture(GL_TEXTURE_2D, 0); -} - -// Enable texture cubemap -void rlEnableTextureCubemap(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindTexture(GL_TEXTURE_CUBE_MAP, id); -#endif -} - -// Disable texture cubemap -void rlDisableTextureCubemap(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); -#endif -} - -// Set texture parameters (wrap mode/filter mode) -void rlTextureParameters(unsigned int id, int param, int value) -{ - glBindTexture(GL_TEXTURE_2D, id); - -#if !defined(GRAPHICS_API_OPENGL_11) - // Reset anisotropy filter, in case it was set - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); -#endif - - switch (param) - { - case RL_TEXTURE_WRAP_S: - case RL_TEXTURE_WRAP_T: - { - if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) - { -#if !defined(GRAPHICS_API_OPENGL_11) - if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); - else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); -#endif - } - else glTexParameteri(GL_TEXTURE_2D, param, value); - - } break; - case RL_TEXTURE_MAG_FILTER: - case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; - case RL_TEXTURE_FILTER_ANISOTROPIC: - { -#if !defined(GRAPHICS_API_OPENGL_11) - if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) - { - TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - } - else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); -#endif - } break; -#if defined(GRAPHICS_API_OPENGL_33) - case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, value/100.0f); -#endif - default: break; - } - - glBindTexture(GL_TEXTURE_2D, 0); -} - -// Set cubemap parameters (wrap mode/filter mode) -void rlCubemapParameters(unsigned int id, int param, int value) -{ -#if !defined(GRAPHICS_API_OPENGL_11) - glBindTexture(GL_TEXTURE_CUBE_MAP, id); - - // Reset anisotropy filter, in case it was set - glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); - - switch (param) - { - case RL_TEXTURE_WRAP_S: - case RL_TEXTURE_WRAP_T: - { - if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) - { - if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); - else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); - } - else glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); - - } break; - case RL_TEXTURE_MAG_FILTER: - case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); break; - case RL_TEXTURE_FILTER_ANISOTROPIC: - { - if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) - { - TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); - glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - } - else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); - } break; -#if defined(GRAPHICS_API_OPENGL_33) - case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_LOD_BIAS, value/100.0f); -#endif - default: break; - } - - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); -#endif -} - -// Enable shader program -void rlEnableShader(unsigned int id) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - glUseProgram(id); -#endif -} - -// Disable shader program -void rlDisableShader(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - glUseProgram(0); -#endif -} - -// Enable rendering to texture (fbo) -void rlEnableFramebuffer(unsigned int id) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, id); -#endif -} - -// return the active render texture (fbo) -unsigned int rlGetActiveFramebuffer(void) -{ - GLint fboId = 0; -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboId); -#endif - return fboId; -} - -// Disable rendering to texture -void rlDisableFramebuffer(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, 0); -#endif -} - -// Blit active framebuffer to main framebuffer -void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBlitFramebuffer(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask, GL_NEAREST); -#endif -} - -// Bind framebuffer object (fbo) -void rlBindFramebuffer(unsigned int target, unsigned int framebuffer) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(target, framebuffer); -#endif -} - -// Activate multiple draw color buffers -// NOTE: One color buffer is always active by default -void rlActiveDrawBuffers(int count) -{ -#if ((defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT)) - // NOTE: Maximum number of draw buffers supported is implementation dependant, - // it can be queried with glGet*() but it must be at least 8 - //GLint maxDrawBuffers = 0; - //glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); - - if (count > 0) - { - if (count > 8) TRACELOG(LOG_WARNING, "GL: Max color buffers limited to 8"); - else - { - unsigned int buffers[8] = { -#if defined(GRAPHICS_API_OPENGL_ES3) - GL_COLOR_ATTACHMENT0_EXT, - GL_COLOR_ATTACHMENT1_EXT, - GL_COLOR_ATTACHMENT2_EXT, - GL_COLOR_ATTACHMENT3_EXT, - GL_COLOR_ATTACHMENT4_EXT, - GL_COLOR_ATTACHMENT5_EXT, - GL_COLOR_ATTACHMENT6_EXT, - GL_COLOR_ATTACHMENT7_EXT, -#else - GL_COLOR_ATTACHMENT0, - GL_COLOR_ATTACHMENT1, - GL_COLOR_ATTACHMENT2, - GL_COLOR_ATTACHMENT3, - GL_COLOR_ATTACHMENT4, - GL_COLOR_ATTACHMENT5, - GL_COLOR_ATTACHMENT6, - GL_COLOR_ATTACHMENT7, -#endif - }; - -#if defined(GRAPHICS_API_OPENGL_ES3) - glDrawBuffersEXT(count, buffers); -#else - glDrawBuffers(count, buffers); -#endif - } - } - else TRACELOG(LOG_WARNING, "GL: One color buffer active by default"); -#endif -} - -//---------------------------------------------------------------------------------- -// General render state configuration -//---------------------------------------------------------------------------------- - -// Enable color blending -void rlEnableColorBlend(void) { glEnable(GL_BLEND); } - -// Disable color blending -void rlDisableColorBlend(void) { glDisable(GL_BLEND); } - -// Enable depth test -void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } - -// Disable depth test -void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } - -// Enable depth write -void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } - -// Disable depth write -void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } - -// Enable backface culling -void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } - -// Disable backface culling -void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } - -// Set color mask active for screen read/draw -void rlColorMask(bool r, bool g, bool b, bool a) { glColorMask(r, g, b, a); } - -// Set face culling mode -void rlSetCullFace(int mode) -{ - switch (mode) - { - case RL_CULL_FACE_BACK: glCullFace(GL_BACK); break; - case RL_CULL_FACE_FRONT: glCullFace(GL_FRONT); break; - default: break; - } -} - -// Enable scissor test -void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } - -// Disable scissor test -void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } - -// Scissor test -void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } - -// Enable wire mode -void rlEnableWireMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // NOTE: glPolygonMode() not available on OpenGL ES - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); -#endif -} - -// Enable point mode -void rlEnablePointMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // NOTE: glPolygonMode() not available on OpenGL ES - glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); - glEnable(GL_PROGRAM_POINT_SIZE); -#endif -} - -// Disable wire mode -void rlDisableWireMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // NOTE: glPolygonMode() not available on OpenGL ES - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); -#endif -} - -// Set the line drawing width -void rlSetLineWidth(float width) { glLineWidth(width); } - -// Get the line drawing width -float rlGetLineWidth(void) -{ - float width = 0; - glGetFloatv(GL_LINE_WIDTH, &width); - return width; -} - -// Enable line aliasing -void rlEnableSmoothLines(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) - glEnable(GL_LINE_SMOOTH); -#endif -} - -// Disable line aliasing -void rlDisableSmoothLines(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) - glDisable(GL_LINE_SMOOTH); -#endif -} - -// Enable stereo rendering -void rlEnableStereoRender(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - RLGL.State.stereoRender = true; -#endif -} - -// Disable stereo rendering -void rlDisableStereoRender(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - RLGL.State.stereoRender = false; -#endif -} - -// Check if stereo render is enabled -bool rlIsStereoRenderEnabled(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - return RLGL.State.stereoRender; -#else - return false; -#endif -} - -// Clear color buffer with color -void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - // Color values clamp to 0.0f(0) and 1.0f(255) - float cr = (float)r/255; - float cg = (float)g/255; - float cb = (float)b/255; - float ca = (float)a/255; - - glClearColor(cr, cg, cb, ca); -} - -// Clear used screen buffers (color and depth) -void rlClearScreenBuffers(void) -{ - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D) - //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... -} - -// Check and log OpenGL error codes -void rlCheckErrors(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - int check = 1; - while (check) - { - const GLenum err = glGetError(); - switch (err) - { - case GL_NO_ERROR: check = 0; break; - case 0x0500: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; - case 0x0501: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; - case 0x0502: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; - case 0x0503: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; - case 0x0504: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; - case 0x0505: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; - case 0x0506: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; - default: TRACELOG(RL_LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; - } - } -#endif -} - -// Set blend mode -void rlSetBlendMode(int mode) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.currentBlendMode != mode) || ((mode == RL_BLEND_CUSTOM || mode == RL_BLEND_CUSTOM_SEPARATE) && RLGL.State.glCustomBlendModeModified)) - { - rlDrawRenderBatch(RLGL.currentBatch); - - switch (mode) - { - case RL_BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; - case RL_BLEND_ALPHA_PREMULTIPLY: glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_CUSTOM: - { - // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactors() - glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); - - } break; - case RL_BLEND_CUSTOM_SEPARATE: - { - // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactorsSeparate() - glBlendFuncSeparate(RLGL.State.glBlendSrcFactorRGB, RLGL.State.glBlendDestFactorRGB, RLGL.State.glBlendSrcFactorAlpha, RLGL.State.glBlendDestFactorAlpha); - glBlendEquationSeparate(RLGL.State.glBlendEquationRGB, RLGL.State.glBlendEquationAlpha); - - } break; - default: break; - } - - RLGL.State.currentBlendMode = mode; - RLGL.State.glCustomBlendModeModified = false; - } -#endif -} - -// Set blending mode factor and equation -void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.glBlendSrcFactor != glSrcFactor) || - (RLGL.State.glBlendDstFactor != glDstFactor) || - (RLGL.State.glBlendEquation != glEquation)) - { - RLGL.State.glBlendSrcFactor = glSrcFactor; - RLGL.State.glBlendDstFactor = glDstFactor; - RLGL.State.glBlendEquation = glEquation; - - RLGL.State.glCustomBlendModeModified = true; - } -#endif -} - -// Set blending mode factor and equation separately for RGB and alpha -void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.glBlendSrcFactorRGB != glSrcRGB) || - (RLGL.State.glBlendDestFactorRGB != glDstRGB) || - (RLGL.State.glBlendSrcFactorAlpha != glSrcAlpha) || - (RLGL.State.glBlendDestFactorAlpha != glDstAlpha) || - (RLGL.State.glBlendEquationRGB != glEqRGB) || - (RLGL.State.glBlendEquationAlpha != glEqAlpha)) - { - RLGL.State.glBlendSrcFactorRGB = glSrcRGB; - RLGL.State.glBlendDestFactorRGB = glDstRGB; - RLGL.State.glBlendSrcFactorAlpha = glSrcAlpha; - RLGL.State.glBlendDestFactorAlpha = glDstAlpha; - RLGL.State.glBlendEquationRGB = glEqRGB; - RLGL.State.glBlendEquationAlpha = glEqAlpha; - - RLGL.State.glCustomBlendModeModified = true; - } -#endif -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - OpenGL Debug -//---------------------------------------------------------------------------------- -#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) -static void GLAPIENTRY rlDebugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) -{ - // Ignore non-significant error/warning codes (NVidia drivers) - // NOTE: Here there are the details with a sample output: - // - #131169 - Framebuffer detailed info: The driver allocated storage for renderbuffer 2. (severity: low) - // - #131185 - Buffer detailed info: Buffer object 1 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_ENUM_88e4) - // will use VIDEO memory as the source for buffer object operations. (severity: low) - // - #131218 - Program/shader state performance warning: Vertex shader in program 7 is being recompiled based on GL state. (severity: medium) - // - #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 does not have - // a defined base level and cannot be used for texture mapping. (severity: low) - if ((id == 131169) || (id == 131185) || (id == 131218) || (id == 131204)) return; - - const char *msgSource = NULL; - switch (source) - { - case GL_DEBUG_SOURCE_API: msgSource = "API"; break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM: msgSource = "WINDOW_SYSTEM"; break; - case GL_DEBUG_SOURCE_SHADER_COMPILER: msgSource = "SHADER_COMPILER"; break; - case GL_DEBUG_SOURCE_THIRD_PARTY: msgSource = "THIRD_PARTY"; break; - case GL_DEBUG_SOURCE_APPLICATION: msgSource = "APPLICATION"; break; - case GL_DEBUG_SOURCE_OTHER: msgSource = "OTHER"; break; - default: break; - } - - const char *msgType = NULL; - switch (type) - { - case GL_DEBUG_TYPE_ERROR: msgType = "ERROR"; break; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: msgType = "DEPRECATED_BEHAVIOR"; break; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: msgType = "UNDEFINED_BEHAVIOR"; break; - case GL_DEBUG_TYPE_PORTABILITY: msgType = "PORTABILITY"; break; - case GL_DEBUG_TYPE_PERFORMANCE: msgType = "PERFORMANCE"; break; - case GL_DEBUG_TYPE_MARKER: msgType = "MARKER"; break; - case GL_DEBUG_TYPE_PUSH_GROUP: msgType = "PUSH_GROUP"; break; - case GL_DEBUG_TYPE_POP_GROUP: msgType = "POP_GROUP"; break; - case GL_DEBUG_TYPE_OTHER: msgType = "OTHER"; break; - default: break; - } - - const char *msgSeverity = "DEFAULT"; - switch (severity) - { - case GL_DEBUG_SEVERITY_LOW: msgSeverity = "LOW"; break; - case GL_DEBUG_SEVERITY_MEDIUM: msgSeverity = "MEDIUM"; break; - case GL_DEBUG_SEVERITY_HIGH: msgSeverity = "HIGH"; break; - case GL_DEBUG_SEVERITY_NOTIFICATION: msgSeverity = "NOTIFICATION"; break; - default: break; - } - - TRACELOG(LOG_WARNING, "GL: OpenGL debug message: %s", message); - TRACELOG(LOG_WARNING, " > Type: %s", msgType); - TRACELOG(LOG_WARNING, " > Source = %s", msgSource); - TRACELOG(LOG_WARNING, " > Severity = %s", msgSeverity); -} -#endif - -//---------------------------------------------------------------------------------- -// Module Functions Definition - rlgl functionality -//---------------------------------------------------------------------------------- - -// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states -void rlglInit(int width, int height) -{ - // Enable OpenGL debug context if required -#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) - if ((glDebugMessageCallback != NULL) && (glDebugMessageControl != NULL)) - { - glDebugMessageCallback(rlDebugMessageCallback, 0); - // glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_HIGH, 0, 0, GL_TRUE); - - // Debug context options: - // - GL_DEBUG_OUTPUT - Faster version but not useful for breakpoints - // - GL_DEBUG_OUTPUT_SYNCHRONUS - Callback is in sync with errors, so a breakpoint can be placed on the callback in order to get a stacktrace for the GL error - glEnable(GL_DEBUG_OUTPUT); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - } -#endif - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Init default white texture - unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) - RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); - - if (RLGL.State.defaultTextureId != 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); - else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load default texture"); - - // Init default Shader (customized for GL 3.3 and ES2) - // Loaded: RLGL.State.defaultShaderId + RLGL.State.defaultShaderLocs - rlLoadShaderDefault(); - RLGL.State.currentShaderId = RLGL.State.defaultShaderId; - RLGL.State.currentShaderLocs = RLGL.State.defaultShaderLocs; - - // Init default vertex arrays buffers - // Simulate that the default shader has the location RL_SHADER_LOC_VERTEX_NORMAL to bind the normal buffer for the default render batch - RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL; - RLGL.defaultBatch = rlLoadRenderBatch(RL_DEFAULT_BATCH_BUFFERS, RL_DEFAULT_BATCH_BUFFER_ELEMENTS); - RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = -1; - RLGL.currentBatch = &RLGL.defaultBatch; - - // Init stack matrices (emulating OpenGL 1.1) - for (int i = 0; i < RL_MAX_MATRIX_STACK_SIZE; i++) RLGL.State.stack[i] = rlMatrixIdentity(); - - // Init internal matrices - RLGL.State.transform = rlMatrixIdentity(); - RLGL.State.projection = rlMatrixIdentity(); - RLGL.State.modelview = rlMatrixIdentity(); - RLGL.State.currentMatrix = &RLGL.State.modelview; -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - - // Initialize OpenGL default states - //---------------------------------------------------------- - // Init state: Depth test - glDepthFunc(GL_LEQUAL); // Type of depth testing to apply - glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) - - // Init state: Blending mode - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed) - glEnable(GL_BLEND); // Enable color blending (required to work with transparencies) - - // Init state: Culling - // NOTE: All shapes/models triangles are drawn CCW - glCullFace(GL_BACK); // Cull the back face (default) - glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) - glEnable(GL_CULL_FACE); // Enable backface culling - - // Init state: Cubemap seamless -#if defined(GRAPHICS_API_OPENGL_33) - glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - // Init state: Color hints (deprecated in OpenGL 3.0+) - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation - glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) -#endif - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Store screen size into global variables - RLGL.State.framebufferWidth = width; - RLGL.State.framebufferHeight = height; - - TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); - //---------------------------------------------------------- -#endif - - // Init state: Color/Depth buffers clear - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) - glClearDepth(1.0f); // Set clear depth value (default) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) -} - -// Vertex Buffer Object deinitialization (memory free) -void rlglClose(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - rlUnloadRenderBatch(RLGL.defaultBatch); - - rlUnloadShaderDefault(); // Unload default shader - - glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture - TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); -#endif -} - -// Load OpenGL extensions -// NOTE: External loader function must be provided -void rlLoadExtensions(void *loader) -{ -#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 - // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) - if (gladLoadGL((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); - else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); - - // Get number of supported extensions - GLint numExt = 0; - glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); - TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) - // Get supported extensions list - // WARNING: glGetStringi() not available on OpenGL 2.1 - TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); - for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", glGetStringi(GL_EXTENSIONS, i)); -#endif - -#if defined(GRAPHICS_API_OPENGL_21) - // Register supported extensions flags - // Optional OpenGL 2.1 extensions - RLGL.ExtSupported.vao = GLAD_GL_ARB_vertex_array_object; - RLGL.ExtSupported.instancing = (GLAD_GL_EXT_draw_instanced && GLAD_GL_ARB_instanced_arrays); - RLGL.ExtSupported.texNPOT = GLAD_GL_ARB_texture_non_power_of_two; - RLGL.ExtSupported.texFloat32 = GLAD_GL_ARB_texture_float; - RLGL.ExtSupported.texFloat16 = GLAD_GL_ARB_texture_float; - RLGL.ExtSupported.texDepth = GLAD_GL_ARB_depth_texture; - RLGL.ExtSupported.maxDepthBits = 32; - RLGL.ExtSupported.texAnisoFilter = GLAD_GL_EXT_texture_filter_anisotropic; - RLGL.ExtSupported.texMirrorClamp = GLAD_GL_EXT_texture_mirror_clamp; -#else - // Register supported extensions flags - // OpenGL 3.3 extensions supported by default (core) - RLGL.ExtSupported.vao = true; - RLGL.ExtSupported.instancing = true; - RLGL.ExtSupported.texNPOT = true; - RLGL.ExtSupported.texFloat32 = true; - RLGL.ExtSupported.texFloat16 = true; - RLGL.ExtSupported.texDepth = true; - RLGL.ExtSupported.maxDepthBits = 32; - RLGL.ExtSupported.texAnisoFilter = true; - RLGL.ExtSupported.texMirrorClamp = true; -#endif - - // Optional OpenGL 3.3 extensions - RLGL.ExtSupported.texCompASTC = GLAD_GL_KHR_texture_compression_astc_hdr && GLAD_GL_KHR_texture_compression_astc_ldr; - RLGL.ExtSupported.texCompDXT = GLAD_GL_EXT_texture_compression_s3tc; // Texture compression: DXT - RLGL.ExtSupported.texCompETC2 = GLAD_GL_ARB_ES3_compatibility; // Texture compression: ETC2/EAC - #if defined(GRAPHICS_API_OPENGL_43) - RLGL.ExtSupported.computeShader = GLAD_GL_ARB_compute_shader; - RLGL.ExtSupported.ssbo = GLAD_GL_ARB_shader_storage_buffer_object; - #endif - -#endif // GRAPHICS_API_OPENGL_33 - -#if defined(GRAPHICS_API_OPENGL_ES3) - // Register supported extensions flags - // OpenGL ES 3.0 extensions supported by default (or it should be) - RLGL.ExtSupported.vao = true; - RLGL.ExtSupported.instancing = true; - RLGL.ExtSupported.texNPOT = true; - RLGL.ExtSupported.texFloat32 = true; - RLGL.ExtSupported.texFloat16 = true; - RLGL.ExtSupported.texDepth = true; - RLGL.ExtSupported.texDepthWebGL = true; - RLGL.ExtSupported.maxDepthBits = 24; - RLGL.ExtSupported.texAnisoFilter = true; - RLGL.ExtSupported.texMirrorClamp = true; - // TODO: Check for additional OpenGL ES 3.0 supported extensions: - //RLGL.ExtSupported.texCompDXT = true; - //RLGL.ExtSupported.texCompETC1 = true; - //RLGL.ExtSupported.texCompETC2 = true; - //RLGL.ExtSupported.texCompPVRT = true; - //RLGL.ExtSupported.texCompASTC = true; - //RLGL.ExtSupported.maxAnisotropyLevel = true; - //RLGL.ExtSupported.computeShader = true; - //RLGL.ExtSupported.ssbo = true; - -#elif defined(GRAPHICS_API_OPENGL_ES2) - - #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) - // TODO: Support GLAD loader for OpenGL ES 3.0 - if (gladLoadGLES2((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL ES2.0 functions"); - else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL ES 2.0 loaded successfully"); - #endif - - // Get supported extensions list - GLint numExt = 0; - const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) - const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string - - // NOTE: We have to duplicate string because glGetString() returns a const string - int size = strlen(extensions) + 1; // Get extensions string size in bytes - char *extensionsDup = (char *)RL_CALLOC(size, sizeof(char)); - strcpy(extensionsDup, extensions); - extList[numExt] = extensionsDup; - - for (int i = 0; i < size; i++) - { - if (extensionsDup[i] == ' ') - { - extensionsDup[i] = '\0'; - numExt++; - extList[numExt] = &extensionsDup[i + 1]; - } - } - - TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) - TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); - for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", extList[i]); -#endif - - // Check required extensions - for (int i = 0; i < numExt; i++) - { - // Check VAO support - // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature - if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) - { - // The extension is supported by our hardware and driver, try to get related functions pointers - // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... - glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); - glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); - glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); - //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted - - if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; - } - - // Check instanced rendering support - if (strstr(extList[i], (const char*)"instanced_arrays") != NULL) // Broad check for instanced_arrays - { - // Specific check - if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // ANGLE - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); - glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); - } - else if (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0) // EXT - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); - glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); - } - else if (strcmp(extList[i], (const char *)"GL_NV_instanced_arrays") == 0) // NVIDIA GLES - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); - glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorNV"); - } - - // The feature will only be marked as supported if the elements from GL_XXX_instanced_arrays are present - if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; - } - else if (strstr(extList[i], (const char *)"draw_instanced") != NULL) - { - // GL_ANGLE_draw_instanced doesn't exist - if (strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); - } - else if (strcmp(extList[i], (const char*)"GL_NV_draw_instanced") == 0) - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); - } - - // But the functions will at least be loaded if only GL_XX_EXT_draw_instanced exist - if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; - } - - // Check NPOT textures support - // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature - if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; - - // Check texture float support - if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; - if (strcmp(extList[i], (const char *)"GL_OES_texture_half_float") == 0) RLGL.ExtSupported.texFloat16 = true; - - // Check depth texture support - if (strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) RLGL.ExtSupported.texDepth = true; - if (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0) RLGL.ExtSupported.texDepthWebGL = true; // WebGL requires unsized internal format - if (RLGL.ExtSupported.texDepthWebGL) RLGL.ExtSupported.texDepth = true; - - if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; // Not available on WebGL - if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; // Not available on WebGL - - // Check texture compression support: DXT - if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; - - // Check texture compression support: ETC1 - if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; - - // Check texture compression support: ETC2/EAC - if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; - - // Check texture compression support: PVR - if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; - - // Check texture compression support: ASTC - if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; - - // Check anisotropic texture filter support - if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; - - // Check clamp mirror wrap mode support - if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; - } - - // Free extensions pointers - RL_FREE(extList); - RL_FREE(extensionsDup); // Duplicated string must be deallocated -#endif // GRAPHICS_API_OPENGL_ES2 - - // Check OpenGL information and capabilities - //------------------------------------------------------------------------------ - // Show current OpenGL and GLSL version - TRACELOG(RL_LOG_INFO, "GL: OpenGL device information:"); - TRACELOG(RL_LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); - TRACELOG(RL_LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); - TRACELOG(RL_LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); - TRACELOG(RL_LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Anisotropy levels capability is an extension - #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF - #endif - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) - // Show some OpenGL GPU capabilities - TRACELOG(RL_LOG_INFO, "GL: OpenGL capabilities:"); - GLint capability = 0; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); - glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); - glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); - glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); - #if !defined(GRAPHICS_API_OPENGL_ES2) - glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); - glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); - if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); - #endif - glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); - TRACELOG(RL_LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); - GLint *compFormats = (GLint *)RL_CALLOC(capability, sizeof(GLint)); - glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, compFormats); - for (int i = 0; i < capability; i++) TRACELOG(RL_LOG_INFO, " %s", rlGetCompressedFormatName(compFormats[i])); - RL_FREE(compFormats); - -#if defined(GRAPHICS_API_OPENGL_43) - glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); - glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); -#endif // GRAPHICS_API_OPENGL_43 -#else // RLGL_SHOW_GL_DETAILS_INFO - - // Show some basic info about GL supported features - if (RLGL.ExtSupported.vao) TRACELOG(RL_LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); - else TRACELOG(RL_LOG_WARNING, "GL: VAO extension not found, VAO not supported"); - if (RLGL.ExtSupported.texNPOT) TRACELOG(RL_LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); - else TRACELOG(RL_LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); - if (RLGL.ExtSupported.texCompDXT) TRACELOG(RL_LOG_INFO, "GL: DXT compressed textures supported"); - if (RLGL.ExtSupported.texCompETC1) TRACELOG(RL_LOG_INFO, "GL: ETC1 compressed textures supported"); - if (RLGL.ExtSupported.texCompETC2) TRACELOG(RL_LOG_INFO, "GL: ETC2/EAC compressed textures supported"); - if (RLGL.ExtSupported.texCompPVRT) TRACELOG(RL_LOG_INFO, "GL: PVRT compressed textures supported"); - if (RLGL.ExtSupported.texCompASTC) TRACELOG(RL_LOG_INFO, "GL: ASTC compressed textures supported"); - if (RLGL.ExtSupported.computeShader) TRACELOG(RL_LOG_INFO, "GL: Compute shaders supported"); - if (RLGL.ExtSupported.ssbo) TRACELOG(RL_LOG_INFO, "GL: Shader storage buffer objects supported"); -#endif // RLGL_SHOW_GL_DETAILS_INFO - -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 -} - -// Get current OpenGL version -int rlGetVersion(void) -{ - int glVersion = 0; -#if defined(GRAPHICS_API_OPENGL_11) - glVersion = RL_OPENGL_11; -#endif -#if defined(GRAPHICS_API_OPENGL_21) - glVersion = RL_OPENGL_21; -#elif defined(GRAPHICS_API_OPENGL_43) - glVersion = RL_OPENGL_43; -#elif defined(GRAPHICS_API_OPENGL_33) - glVersion = RL_OPENGL_33; -#endif -#if defined(GRAPHICS_API_OPENGL_ES3) - glVersion = RL_OPENGL_ES_30; -#elif defined(GRAPHICS_API_OPENGL_ES2) - glVersion = RL_OPENGL_ES_20; -#endif - - return glVersion; -} - -// Set current framebuffer width -void rlSetFramebufferWidth(int width) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.framebufferWidth = width; -#endif -} - -// Set current framebuffer height -void rlSetFramebufferHeight(int height) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.framebufferHeight = height; -#endif -} - -// Get default framebuffer width -int rlGetFramebufferWidth(void) -{ - int width = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - width = RLGL.State.framebufferWidth; -#endif - return width; -} - -// Get default framebuffer height -int rlGetFramebufferHeight(void) -{ - int height = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - height = RLGL.State.framebufferHeight; -#endif - return height; -} - -// Get default internal texture (white texture) -// NOTE: Default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 -unsigned int rlGetTextureIdDefault(void) -{ - unsigned int id = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - id = RLGL.State.defaultTextureId; -#endif - return id; -} - -// Get default shader id -unsigned int rlGetShaderIdDefault(void) -{ - unsigned int id = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - id = RLGL.State.defaultShaderId; -#endif - return id; -} - -// Get default shader locs -int *rlGetShaderLocsDefault(void) -{ - int *locs = NULL; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - locs = RLGL.State.defaultShaderLocs; -#endif - return locs; -} - -// Render batch management -//------------------------------------------------------------------------------------------------ -// Load render batch -rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) -{ - rlRenderBatch batch = { 0 }; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) - //-------------------------------------------------------------------------------------------- - batch.vertexBuffer = (rlVertexBuffer *)RL_MALLOC(numBuffers*sizeof(rlVertexBuffer)); - - for (int i = 0; i < numBuffers; i++) - { - batch.vertexBuffer[i].elementCount = bufferElements; - - batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad - batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad - batch.vertexBuffer[i].normals = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad - batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad -#if defined(GRAPHICS_API_OPENGL_33) - batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) - batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) -#endif - - for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; - for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; - for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].normals[j] = 0.0f; - for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; - - int k = 0; - - // Indices can be initialized right now - for (int j = 0; j < (6*bufferElements); j += 6) - { - batch.vertexBuffer[i].indices[j] = 4*k; - batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; - batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; - batch.vertexBuffer[i].indices[j + 3] = 4*k; - batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; - batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; - - k++; - } - - RLGL.State.vertexCounter = 0; - } - - TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); - //-------------------------------------------------------------------------------------------- - - // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs - //-------------------------------------------------------------------------------------------- - for (int i = 0; i < numBuffers; i++) - { - if (RLGL.ExtSupported.vao) - { - // Initialize Quads VAO - glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); - glBindVertexArray(batch.vertexBuffer[i].vaoId); - } - - // Quads - Vertex buffers binding and attributes enable - // Vertex position buffer (shader-location = 0) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - - // Vertex texcoord buffer (shader-location = 1) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - - // Vertex normal buffer (shader-location = 2) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].normals, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); - - // Vertex color buffer (shader-location = 3) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - - // Fill index buffer - glGenBuffers(1, &batch.vertexBuffer[i].vboId[4]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[4]); -#if defined(GRAPHICS_API_OPENGL_33) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); -#endif - } - - TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); - - // Unbind the current VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - //-------------------------------------------------------------------------------------------- - - // Init draw calls tracking system - //-------------------------------------------------------------------------------------------- - batch.draws = (rlDrawCall *)RL_MALLOC(RL_DEFAULT_BATCH_DRAWCALLS*sizeof(rlDrawCall)); - - for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) - { - batch.draws[i].mode = RL_QUADS; - batch.draws[i].vertexCount = 0; - batch.draws[i].vertexAlignment = 0; - //batch.draws[i].vaoId = 0; - //batch.draws[i].shaderId = 0; - batch.draws[i].textureId = RLGL.State.defaultTextureId; - //batch.draws[i].RLGL.State.projection = rlMatrixIdentity(); - //batch.draws[i].RLGL.State.modelview = rlMatrixIdentity(); - } - - batch.bufferCount = numBuffers; // Record buffer count - batch.drawCounter = 1; // Reset draws counter - batch.currentDepth = -1.0f; // Reset depth value - //-------------------------------------------------------------------------------------------- -#endif - - return batch; -} - -// Unload default internal buffers vertex data from CPU and GPU -void rlUnloadRenderBatch(rlRenderBatch batch) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Unbind everything - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - // Unload all vertex buffers data - for (int i = 0; i < batch.bufferCount; i++) - { - // Unbind VAO attribs data - if (RLGL.ExtSupported.vao) - { - glBindVertexArray(batch.vertexBuffer[i].vaoId); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); - glBindVertexArray(0); - } - - // Delete VBOs from GPU (VRAM) - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[4]); - - // Delete VAOs from GPU (VRAM) - if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); - - // Free vertex arrays memory from CPU (RAM) - RL_FREE(batch.vertexBuffer[i].vertices); - RL_FREE(batch.vertexBuffer[i].texcoords); - RL_FREE(batch.vertexBuffer[i].normals); - RL_FREE(batch.vertexBuffer[i].colors); - RL_FREE(batch.vertexBuffer[i].indices); - } - - // Unload arrays - RL_FREE(batch.vertexBuffer); - RL_FREE(batch.draws); -#endif -} - -// Draw render batch -// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) -void rlDrawRenderBatch(rlRenderBatch *batch) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Update batch vertex buffers - //------------------------------------------------------------------------------------------------------------ - // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) - // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (use a change detector flag?) - if (RLGL.State.vertexCounter > 0) - { - // Activate elements VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); - - // Vertex positions buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer - - // Texture coordinates buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer - - // Normals buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].normals); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].normals, GL_DYNAMIC_DRAW); // Update all buffer - - // Colors buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer - - // NOTE: glMapBuffer() causes sync issue - // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job - // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer() - // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new - // allocated pointer immediately even if GPU is still working with the previous data - - // Another option: map the buffer object into client's memory - // Probably this code could be moved somewhere else... - // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); - // if (batch->vertexBuffer[batch->currentBuffer].vertices) - // { - // Update vertex data - // } - // glUnmapBuffer(GL_ARRAY_BUFFER); - - // Unbind the current VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - } - //------------------------------------------------------------------------------------------------------------ - - // Draw batch vertex buffers (considering VR stereo if required) - //------------------------------------------------------------------------------------------------------------ - Matrix matProjection = RLGL.State.projection; - Matrix matModelView = RLGL.State.modelview; - - int eyeCount = 1; - if (RLGL.State.stereoRender) eyeCount = 2; - - for (int eye = 0; eye < eyeCount; eye++) - { - if (eyeCount == 2) - { - // Setup current eye viewport (half screen width) - rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); - - // Set current eye view offset to modelview matrix - rlSetMatrixModelview(rlMatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); - // Set current eye projection matrix - rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); - } - - // Draw buffers - if (RLGL.State.vertexCounter > 0) - { - // Set current shader and upload current MVP matrix - glUseProgram(RLGL.State.currentShaderId); - - // Create modelview-projection matrix and upload to shader - Matrix matMVP = rlMatrixMultiply(RLGL.State.modelview, RLGL.State.projection); - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MVP], 1, false, rlMatrixToFloat(matMVP)); - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION], 1, false, rlMatrixToFloat(RLGL.State.projection)); - } - - // WARNING: For the following setup of the view, model, and normal matrices, it is expected that - // transformations and rendering occur between rlPushMatrix() and rlPopMatrix() - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW], 1, false, rlMatrixToFloat(RLGL.State.modelview)); - } - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL], 1, false, rlMatrixToFloat(RLGL.State.transform)); - } - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL], 1, false, rlMatrixToFloat(rlMatrixTranspose(rlMatrixInvert(RLGL.State.transform)))); - } - - if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); - else - { - // Bind vertex attrib: position (shader-location = 0) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); - - // Bind vertex attrib: texcoord (shader-location = 1) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); - - // Bind vertex attrib: normal (shader-location = 2) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); - - // Bind vertex attrib: color (shader-location = 3) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[4]); - } - - // Setup some default shader values - glUniform4f(RLGL.State.currentShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); - glUniform1i(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 - - // Activate additional sampler textures - // Those additional textures will be common for all draw calls of the batch - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) - { - if (RLGL.State.activeTextureId[i] > 0) - { - glActiveTexture(GL_TEXTURE0 + 1 + i); - glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); - } - } - - // Activate default sampler2D texture0 (one texture is always active for default batch shader) - // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls - glActiveTexture(GL_TEXTURE0); - - for (int i = 0, vertexOffset = 0; i < batch->drawCounter; i++) - { - // Bind current draw call texture, activated as GL_TEXTURE0 and Bound to sampler2D texture0 by default - glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); - - if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); - else - { - #if defined(GRAPHICS_API_OPENGL_33) - // We need to define the number of indices to be processed: elementCount*6 - // NOTE: The final parameter tells the GPU the offset in bytes from the - // start of the index buffer to the location of the first index to process - glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); - #endif - #if defined(GRAPHICS_API_OPENGL_ES2) - glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); - #endif - } - - vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); - } - - if (!RLGL.ExtSupported.vao) - { - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - - glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures - } - - if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO - - glUseProgram(0); // Unbind shader program - } - - // Restore viewport to default measures - if (eyeCount == 2) rlViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); - //------------------------------------------------------------------------------------------------------------ - - // Reset batch buffers - //------------------------------------------------------------------------------------------------------------ - // Reset vertex counter for next frame - RLGL.State.vertexCounter = 0; - - // Reset depth for next draw - batch->currentDepth = -1.0f; - - // Restore projection/modelview matrices - RLGL.State.projection = matProjection; - RLGL.State.modelview = matModelView; - - // Reset RLGL.currentBatch->draws array - for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) - { - batch->draws[i].mode = RL_QUADS; - batch->draws[i].vertexCount = 0; - batch->draws[i].textureId = RLGL.State.defaultTextureId; - } - - // Reset active texture units for next batch - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) RLGL.State.activeTextureId[i] = 0; - - // Reset draws counter to one draw for the batch - batch->drawCounter = 1; - //------------------------------------------------------------------------------------------------------------ - - // Change to next buffer in the list (in case of multi-buffering) - batch->currentBuffer++; - if (batch->currentBuffer >= batch->bufferCount) batch->currentBuffer = 0; -#endif -} - -// Set the active render batch for rlgl -void rlSetRenderBatchActive(rlRenderBatch *batch) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - rlDrawRenderBatch(RLGL.currentBatch); - - if (batch != NULL) RLGL.currentBatch = batch; - else RLGL.currentBatch = &RLGL.defaultBatch; -#endif -} - -// Update and draw internal render batch -void rlDrawRenderBatchActive(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside -#endif -} - -// Check internal buffer overflow for a given number of vertex -// and force a rlRenderBatch draw call if required -bool rlCheckRenderBatchLimit(int vCount) -{ - bool overflow = false; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.vertexCounter + vCount) >= - (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4)) - { - overflow = true; - - // Store current primitive drawing mode and texture id - int currentMode = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode; - int currentTexture = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId; - - rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside - - // Restore state of last batch so we can continue adding vertices - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = currentMode; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = currentTexture; - } -#endif - - return overflow; -} - -// Textures data management -//----------------------------------------------------------------------------------------- -// Convert image data to OpenGL texture (returns OpenGL valid Id) -unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount) -{ - unsigned int id = 0; - - glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding - - // Check texture format support by OpenGL 1.1 (compressed textures not supported) -#if defined(GRAPHICS_API_OPENGL_11) - if (format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) - { - TRACELOG(RL_LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); - return id; - } -#else - if ((!RLGL.ExtSupported.texCompDXT) && ((format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA) || - (format == RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: DXT compressed texture format not supported"); - return id; - } -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((!RLGL.ExtSupported.texCompETC1) && (format == RL_PIXELFORMAT_COMPRESSED_ETC1_RGB)) - { - TRACELOG(RL_LOG_WARNING, "GL: ETC1 compressed texture format not supported"); - return id; - } - - if ((!RLGL.ExtSupported.texCompETC2) && ((format == RL_PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: ETC2 compressed texture format not supported"); - return id; - } - - if ((!RLGL.ExtSupported.texCompPVRT) && ((format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: PVRT compressed texture format not supported"); - return id; - } - - if ((!RLGL.ExtSupported.texCompASTC) && ((format == RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: ASTC compressed texture format not supported"); - return id; - } -#endif -#endif // GRAPHICS_API_OPENGL_11 - - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - glGenTextures(1, &id); // Generate texture id - - glBindTexture(GL_TEXTURE_2D, id); - - int mipWidth = width; - int mipHeight = height; - int mipOffset = 0; // Mipmap data offset, only used for tracelog - - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned char *dataPtr = NULL; - if (data != NULL) dataPtr = (unsigned char *)data; - - // Load the different mipmap levels - for (int i = 0; i < mipmapCount; i++) - { - unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - - TRACELOGD("TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); - - if (glInternalFormat != 0) - { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, dataPtr); -#if !defined(GRAPHICS_API_OPENGL_11) - else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, dataPtr); -#endif - -#if defined(GRAPHICS_API_OPENGL_33) - if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) - { - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } - else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) - { -#if defined(GRAPHICS_API_OPENGL_21) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; -#elif defined(GRAPHICS_API_OPENGL_33) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; -#endif - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } -#endif - } - - mipWidth /= 2; - mipHeight /= 2; - mipOffset += mipSize; // Increment offset position to next mipmap - if (data != NULL) dataPtr += mipSize; // Increment data pointer to next mipmap - - // Security check for NPOT textures - if (mipWidth < 1) mipWidth = 1; - if (mipHeight < 1) mipHeight = 1; - } - - // Texture parameters configuration - // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used -#if defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used - if (RLGL.ExtSupported.texNPOT) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis - } - else - { - // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis - } -#else - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis -#endif - - // Magnification and minification filters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR - -#if defined(GRAPHICS_API_OPENGL_33) - if (mipmapCount > 1) - { - // Activate Trilinear filtering if mipmaps are available - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } -#endif - - // At this point we have the texture loaded in GPU and texture parameters configured - - // NOTE: If mipmaps were not in data, they are not generated automatically - - // Unbind current texture - glBindTexture(GL_TEXTURE_2D, 0); - - if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i | %s | %i mipmaps)", id, width, height, rlGetPixelFormatName(format), mipmapCount); - else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load texture"); - - return id; -} - -// Load depth texture/renderbuffer (to be attached to fbo) -// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture and WebGL requires WEBGL_depth_texture extensions -unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // In case depth textures not supported, we force renderbuffer usage - if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; - - // NOTE: We let the implementation to choose the best bit-depth - // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F - unsigned int glInternalFormat = GL_DEPTH_COMPONENT; - -#if (defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_ES3)) - // WARNING: WebGL platform requires unsized internal format definition (GL_DEPTH_COMPONENT) - // while other platforms using OpenGL ES 2.0 require/support sized internal formats depending on the GPU capabilities - if (!RLGL.ExtSupported.texDepthWebGL || useRenderBuffer) - { - if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; - else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; - else glInternalFormat = GL_DEPTH_COMPONENT16; - } -#endif - - if (!useRenderBuffer && RLGL.ExtSupported.texDepth) - { - glGenTextures(1, &id); - glBindTexture(GL_TEXTURE_2D, id); - glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glBindTexture(GL_TEXTURE_2D, 0); - - TRACELOG(RL_LOG_INFO, "TEXTURE: Depth texture loaded successfully"); - } - else - { - // Create the renderbuffer that will serve as the depth attachment for the framebuffer - // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices - glGenRenderbuffers(1, &id); - glBindRenderbuffer(GL_RENDERBUFFER, id); - glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); - - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); - } -#endif - - return id; -} - -// Load texture cubemap -// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), -// expected the following convention: +X, -X, +Y, -Y, +Z, -Z -unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - int mipSize = size; - - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned char *dataPtr = NULL; - if (data != NULL) dataPtr = (unsigned char *)data; - - unsigned int dataSize = rlGetPixelDataSize(size, size, format); - - glGenTextures(1, &id); - glBindTexture(GL_TEXTURE_CUBE_MAP, id); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - - if (glInternalFormat != 0) - { - // Load cubemap faces/mipmaps - for (int i = 0; i < 6*mipmapCount; i++) - { - int mipmapLevel = i/6; - int face = i%6; - - if (data == NULL) - { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) - { - if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || - (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32) || - (format == RL_PIXELFORMAT_UNCOMPRESSED_R16) || - (format == RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); - else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, NULL); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); - } - else - { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, (unsigned char *)dataPtr + face*dataSize); - else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, (unsigned char *)dataPtr + face*dataSize); - } - -#if defined(GRAPHICS_API_OPENGL_33) - if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) - { - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; - glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } - else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) - { -#if defined(GRAPHICS_API_OPENGL_21) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; -#elif defined(GRAPHICS_API_OPENGL_33) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; -#endif - glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } -#endif - if (face == 5) - { - mipSize /= 2; - if (data != NULL) dataPtr += dataSize*6; // Increment data pointer to next mipmap - - // Security check for NPOT textures - if (mipSize < 1) mipSize = 1; - - dataSize = rlGetPixelDataSize(mipSize, mipSize, format); - } - } - } - - // Set cubemap texture sampling parameters - if (mipmapCount > 1) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - else glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#if defined(GRAPHICS_API_OPENGL_33) - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 -#endif - - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); -#endif - - if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); - else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); - - return id; -} - -// Update already loaded texture in GPU with new data -// NOTE: We don't know safely if internal texture format is the expected one... -void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) -{ - glBindTexture(GL_TEXTURE_2D, id); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - - if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) - { - glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, data); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); -} - -// Get OpenGL internal formats and data type from raylib PixelFormat -void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) -{ - *glInternalFormat = 0; - *glFormat = 0; - *glType = 0; - - switch (format) - { - #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; - #if !defined(GRAPHICS_API_OPENGL_11) - #if defined(GRAPHICS_API_OPENGL_ES3) - case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F_EXT; *glFormat = GL_RED_EXT; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F_EXT; *glFormat = GL_RGB; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F_EXT; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F_EXT; *glFormat = GL_RED_EXT; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F_EXT; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F_EXT; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; - #else - case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - #if defined(GRAPHICS_API_OPENGL_21) - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_ARB; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_ARB; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_ARB; break; - #else // defined(GRAPHICS_API_OPENGL_ES2) - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float - #endif - #endif - #endif - #elif defined(GRAPHICS_API_OPENGL_33) - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F; *glFormat = GL_RED; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; - #endif - #if !defined(GRAPHICS_API_OPENGL_11) - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU - case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - #endif - default: TRACELOG(RL_LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; - } -} - -// Unload texture from GPU memory -void rlUnloadTexture(unsigned int id) -{ - glDeleteTextures(1, &id); -} - -// Generate mipmap data for selected texture -// NOTE: Only supports GPU mipmap generation -void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindTexture(GL_TEXTURE_2D, id); - - // Check if texture is power-of-two (POT) - bool texIsPOT = false; - - if (((width > 0) && ((width & (width - 1)) == 0)) && - ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; - - if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) - { - //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorithm: GL_FASTEST, GL_NICEST, GL_DONT_CARE - glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically - - #define MIN(a,b) (((a)<(b))? (a):(b)) - #define MAX(a,b) (((a)>(b))? (a):(b)) - - *mipmaps = 1 + (int)floor(log(MAX(width, height))/log(2)); - TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Mipmaps generated automatically, total: %i", id, *mipmaps); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps", id); - - glBindTexture(GL_TEXTURE_2D, 0); -#else - TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] GPU mipmap generation not supported", id); -#endif -} - -// Read texture pixel data -void *rlReadTexturePixels(unsigned int id, int width, int height, int format) -{ - void *pixels = NULL; - -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - glBindTexture(GL_TEXTURE_2D, id); - - // NOTE: Using texture id, we can retrieve some texture info (but not on OpenGL ES 2.0) - // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE - //int width, height, format; - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); - - // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding - // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting - // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) - // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - unsigned int size = rlGetPixelDataSize(width, height, format); - - if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) - { - pixels = RL_MALLOC(size); - glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", id, format); - - glBindTexture(GL_TEXTURE_2D, 0); -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - // glGetTexImage() is not available on OpenGL ES 2.0 - // Texture width and height are required on OpenGL ES 2.0, there is no way to get it from texture id - // Two possible Options: - // 1 - Bind texture to color fbo attachment and glReadPixels() - // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() - // We are using Option 1, just need to care for texture format on retrieval - // NOTE: This behaviour could be conditioned by graphic driver... - unsigned int fboId = rlLoadFramebuffer(); - - glBindFramebuffer(GL_FRAMEBUFFER, fboId); - glBindTexture(GL_TEXTURE_2D, 0); - - // Attach our texture to FBO - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); - - // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format - pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(width, height, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)); - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Clean up temporal fbo - rlUnloadFramebuffer(fboId); -#endif - - return pixels; -} - -// Read screen pixel data (color buffer) -unsigned char *rlReadScreenPixels(int width, int height) -{ - unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); - - // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer - // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); - - // Flip image vertically! - unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*4*sizeof(unsigned char)); - - for (int y = height - 1; y >= 0; y--) - { - for (int x = 0; x < (width*4); x++) - { - imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line - - // Set alpha component value to 255 (no trasparent image retrieval) - // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! - if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255; - } - } - - RL_FREE(screenData); - - return imgData; // NOTE: image data should be freed -} - -// Framebuffer management (fbo) -//----------------------------------------------------------------------------------------- -// Load a framebuffer to be used for rendering -// NOTE: No textures attached -unsigned int rlLoadFramebuffer(void) -{ - unsigned int fboId = 0; - -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glGenFramebuffers(1, &fboId); // Create the framebuffer object - glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer -#endif - - return fboId; -} - -// Attach color buffer texture to an fbo (unloads previous attachment) -// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture -void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, fboId); - - switch (attachType) - { - case RL_ATTACHMENT_COLOR_CHANNEL0: - case RL_ATTACHMENT_COLOR_CHANNEL1: - case RL_ATTACHMENT_COLOR_CHANNEL2: - case RL_ATTACHMENT_COLOR_CHANNEL3: - case RL_ATTACHMENT_COLOR_CHANNEL4: - case RL_ATTACHMENT_COLOR_CHANNEL5: - case RL_ATTACHMENT_COLOR_CHANNEL6: - case RL_ATTACHMENT_COLOR_CHANNEL7: - { - if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); - else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); - else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); - - } break; - case RL_ATTACHMENT_DEPTH: - { - if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); - else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); - - } break; - case RL_ATTACHMENT_STENCIL: - { - if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); - else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); - - } break; - default: break; - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -#endif -} - -// Verify render texture is complete -bool rlFramebufferComplete(unsigned int id) -{ - bool result = false; - -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, id); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - if (status != GL_FRAMEBUFFER_COMPLETE) - { - switch (status) - { - case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; -#if defined(GRAPHICS_API_OPENGL_ES2) - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; -#endif - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; - default: break; - } - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - result = (status == GL_FRAMEBUFFER_COMPLETE); -#endif - - return result; -} - -// Unload framebuffer from GPU memory -// NOTE: All attached textures/cubemaps/renderbuffers are also deleted -void rlUnloadFramebuffer(unsigned int id) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - // Query depth attachment to automatically delete texture/renderbuffer - int depthType = 0, depthId = 0; - glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type - glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); - - // TODO: Review warning retrieving object name in WebGL - // WARNING: WebGL: INVALID_ENUM: getFramebufferAttachmentParameter: invalid parameter name - // https://registry.khronos.org/webgl/specs/latest/1.0/ - glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); - - unsigned int depthIdU = (unsigned int)depthId; - if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); - else if (depthType == GL_TEXTURE) glDeleteTextures(1, &depthIdU); - - // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, - // the texture image is automatically detached from the currently bound framebuffer - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &id); - - TRACELOG(RL_LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); -#endif -} - -// Vertex data management -//----------------------------------------------------------------------------------------- -// Load a new attributes buffer -unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glGenBuffers(1, &id); - glBindBuffer(GL_ARRAY_BUFFER, id); - glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); -#endif - - return id; -} - -// Load a new attributes element buffer -unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glGenBuffers(1, &id); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); -#endif - - return id; -} - -// Enable vertex buffer (VBO) -void rlEnableVertexBuffer(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ARRAY_BUFFER, id); -#endif -} - -// Disable vertex buffer (VBO) -void rlDisableVertexBuffer(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ARRAY_BUFFER, 0); -#endif -} - -// Enable vertex buffer element (VBO element) -void rlEnableVertexBufferElement(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); -#endif -} - -// Disable vertex buffer element (VBO element) -void rlDisableVertexBufferElement(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); -#endif -} - -// Update vertex buffer with new data -// NOTE: dataSize and offset must be provided in bytes -void rlUpdateVertexBuffer(unsigned int id, const void *data, int dataSize, int offset) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ARRAY_BUFFER, id); - glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); -#endif -} - -// Update vertex buffer elements with new data -// NOTE: dataSize and offset must be provided in bytes -void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, dataSize, data); -#endif -} - -// Enable vertex array object (VAO) -bool rlEnableVertexArray(unsigned int vaoId) -{ - bool result = false; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - glBindVertexArray(vaoId); - result = true; - } -#endif - return result; -} - -// Disable vertex array object (VAO) -void rlDisableVertexArray(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) glBindVertexArray(0); -#endif -} - -// Enable vertex attribute index -void rlEnableVertexAttribute(unsigned int index) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glEnableVertexAttribArray(index); -#endif -} - -// Disable vertex attribute index -void rlDisableVertexAttribute(unsigned int index) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDisableVertexAttribArray(index); -#endif -} - -// Draw vertex array -void rlDrawVertexArray(int offset, int count) -{ - glDrawArrays(GL_TRIANGLES, offset, count); -} - -// Draw vertex array elements -void rlDrawVertexArrayElements(int offset, int count, const void *buffer) -{ - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned short *bufferPtr = (unsigned short *)buffer; - if (offset > 0) bufferPtr += offset; - - glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr); -} - -// Draw vertex array instanced -void rlDrawVertexArrayInstanced(int offset, int count, int instances) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); -#endif -} - -// Draw vertex array elements instanced -void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned short *bufferPtr = (unsigned short *)buffer; - if (offset > 0) bufferPtr += offset; - - glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr, instances); -#endif -} - -#if defined(GRAPHICS_API_OPENGL_11) -// Enable vertex state pointer -void rlEnableStatePointer(int vertexAttribType, void *buffer) -{ - if (buffer != NULL) glEnableClientState(vertexAttribType); - switch (vertexAttribType) - { - case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; - case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; - case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; - case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; - //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors - default: break; - } -} - -// Disable vertex state pointer -void rlDisableStatePointer(int vertexAttribType) -{ - glDisableClientState(vertexAttribType); -} -#endif - -// Load vertex array object (VAO) -unsigned int rlLoadVertexArray(void) -{ - unsigned int vaoId = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - glGenVertexArrays(1, &vaoId); - } -#endif - return vaoId; -} - -// Set vertex attribute -void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Data type could be: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT - // Additional types (depends on OpenGL version or extensions): - // - GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, - // - GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV - - size_t offsetNative = offset; - glVertexAttribPointer(index, compSize, type, normalized, stride, (void *)offsetNative); -#endif -} - -// Set vertex attribute divisor -void rlSetVertexAttributeDivisor(unsigned int index, int divisor) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glVertexAttribDivisor(index, divisor); -#endif -} - -// Unload vertex array object (VAO) -void rlUnloadVertexArray(unsigned int vaoId) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - glBindVertexArray(0); - glDeleteVertexArrays(1, &vaoId); - TRACELOG(RL_LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); - } -#endif -} - -// Unload vertex buffer (VBO) -void rlUnloadVertexBuffer(unsigned int vboId) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDeleteBuffers(1, &vboId); - //TRACELOG(RL_LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); -#endif -} - -// Shaders management -//----------------------------------------------------------------------------------------------- -// Load shader from code strings -// NOTE: If shader string is NULL, using default vertex/fragment shaders -unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int vertexShaderId = 0; - unsigned int fragmentShaderId = 0; - - // Compile vertex shader (if provided) - // NOTE: If not vertex shader is provided, use default one - if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); - else vertexShaderId = RLGL.State.defaultVShaderId; - - // Compile fragment shader (if provided) - // NOTE: If not vertex shader is provided, use default one - if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); - else fragmentShaderId = RLGL.State.defaultFShaderId; - - // In case vertex and fragment shader are the default ones, no need to recompile, we can just assign the default shader program id - if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; - else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) - { - // One of or both shader are new, we need to compile a new shader program - id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); - - // We can detach and delete vertex/fragment shaders (if not default ones) - // NOTE: We detach shader before deletion to make sure memory is freed - if (vertexShaderId != RLGL.State.defaultVShaderId) - { - // WARNING: Shader program linkage could fail and returned id is 0 - if (id > 0) glDetachShader(id, vertexShaderId); - glDeleteShader(vertexShaderId); - } - if (fragmentShaderId != RLGL.State.defaultFShaderId) - { - // WARNING: Shader program linkage could fail and returned id is 0 - if (id > 0) glDetachShader(id, fragmentShaderId); - glDeleteShader(fragmentShaderId); - } - - // In case shader program loading failed, we assign default shader - if (id == 0) - { - // In case shader loading fails, we return the default shader - TRACELOG(RL_LOG_WARNING, "SHADER: Failed to load custom shader code, using default shader"); - id = RLGL.State.defaultShaderId; - } - /* - else - { - // Get available shader uniforms - // NOTE: This information is useful for debug... - int uniformCount = -1; - glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); - - for (int i = 0; i < uniformCount; i++) - { - int namelen = -1; - int num = -1; - char name[256] = { 0 }; // Assume no variable names longer than 256 - GLenum type = GL_ZERO; - - // Get the name of the uniforms - glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); - - name[namelen] = 0; - TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); - } - } - */ - } -#endif - - return id; -} - -// Compile custom shader and return shader id -unsigned int rlCompileShader(const char *shaderCode, int type) -{ - unsigned int shader = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - shader = glCreateShader(type); - glShaderSource(shader, 1, &shaderCode, NULL); - - GLint success = 0; - glCompileShader(shader); - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - - if (success == GL_FALSE) - { - switch (type) - { - case GL_VERTEX_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shader); break; - case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shader); break; - //case GL_GEOMETRY_SHADER: - #if defined(GRAPHICS_API_OPENGL_43) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; - #elif defined(GRAPHICS_API_OPENGL_33) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; - #endif - default: break; - } - - int maxLength = 0; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); - - if (maxLength > 0) - { - int length = 0; - char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); - glGetShaderInfoLog(shader, maxLength, &length, log); - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); - RL_FREE(log); - } - - shader = 0; - } - else - { - switch (type) - { - case GL_VERTEX_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shader); break; - case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shader); break; - //case GL_GEOMETRY_SHADER: - #if defined(GRAPHICS_API_OPENGL_43) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; - #elif defined(GRAPHICS_API_OPENGL_33) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; - #endif - default: break; - } - } -#endif - - return shader; -} - -// Load custom shader strings and return program id -unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) -{ - unsigned int program = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - GLint success = 0; - program = glCreateProgram(); - - glAttachShader(program, vShaderId); - glAttachShader(program, fShaderId); - - // NOTE: Default attribute shader locations must be Bound before linking - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); - -#ifdef RL_SUPPORT_MESH_GPU_SKINNING - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); -#endif - - // NOTE: If some attrib name is no found on the shader, it locations becomes -1 - - glLinkProgram(program); - - // NOTE: All uniform variables are intitialised to 0 when a program links - - glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (success == GL_FALSE) - { - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", program); - - int maxLength = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - - if (maxLength > 0) - { - int length = 0; - char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); - glGetProgramInfoLog(program, maxLength, &length, log); - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); - RL_FREE(log); - } - - glDeleteProgram(program); - - program = 0; - } - else - { - // Get the size of compiled shader program (not available on OpenGL ES 2.0) - // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero - //GLint binarySize = 0; - //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", program); - } -#endif - return program; -} - -// Unload shader program -void rlUnloadShaderProgram(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDeleteProgram(id); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); -#endif -} - -// Get shader location uniform -int rlGetLocationUniform(unsigned int shaderId, const char *uniformName) -{ - int location = -1; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - location = glGetUniformLocation(shaderId, uniformName); - - //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); - //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); -#endif - return location; -} - -// Get shader location attribute -int rlGetLocationAttrib(unsigned int shaderId, const char *attribName) -{ - int location = -1; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - location = glGetAttribLocation(shaderId, attribName); - - //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); - //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); -#endif - return location; -} - -// Set shader value uniform -void rlSetUniform(int locIndex, const void *value, int uniformType, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - switch (uniformType) - { - case RL_SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; - case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; - case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; - case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; - #if !defined(GRAPHICS_API_OPENGL_ES2) - case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; - case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; - case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; - case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; - #endif - case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; - default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); - - // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() - } -#endif -} - -// Set shader value attribute -void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - switch (attribType) - { - case RL_SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; - case RL_SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; - case RL_SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; - case RL_SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; - default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); - } -#endif -} - -// Set shader value uniform matrix -void rlSetUniformMatrix(int locIndex, Matrix mat) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - float matfloat[16] = { - mat.m0, mat.m1, mat.m2, mat.m3, - mat.m4, mat.m5, mat.m6, mat.m7, - mat.m8, mat.m9, mat.m10, mat.m11, - mat.m12, mat.m13, mat.m14, mat.m15 - }; - glUniformMatrix4fv(locIndex, 1, false, matfloat); -#endif -} - -// Set shader value uniform matrix -void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) - glUniformMatrix4fv(locIndex, count, true, (const float *)matrices); -#elif defined(GRAPHICS_API_OPENGL_ES2) - // WARNING: WebGL does not support Matrix transpose ("true" parameter) - // REF: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix - glUniformMatrix4fv(locIndex, count, false, (const float *)matrices); -#endif -} - -// Set shader value uniform sampler -void rlSetUniformSampler(int locIndex, unsigned int textureId) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Check if texture is already active - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) - { - if (RLGL.State.activeTextureId[i] == textureId) - { - glUniform1i(locIndex, 1 + i); - return; - } - } - - // Register a new active texture for the internal batch system - // NOTE: Default texture is always activated as GL_TEXTURE0 - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) - { - if (RLGL.State.activeTextureId[i] == 0) - { - glUniform1i(locIndex, 1 + i); // Activate new texture unit - RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing - break; - } - } -#endif -} - -// Set shader currently active (id and locations) -void rlSetShader(unsigned int id, int *locs) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.State.currentShaderId != id) - { - rlDrawRenderBatch(RLGL.currentBatch); - RLGL.State.currentShaderId = id; - RLGL.State.currentShaderLocs = locs; - } -#endif -} - -// Load compute shader program -unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) -{ - unsigned int program = 0; - -#if defined(GRAPHICS_API_OPENGL_43) - GLint success = 0; - program = glCreateProgram(); - glAttachShader(program, shaderId); - glLinkProgram(program); - - // NOTE: All uniform variables are intitialised to 0 when a program links - - glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (success == GL_FALSE) - { - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link compute shader program", program); - - int maxLength = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - - if (maxLength > 0) - { - int length = 0; - char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); - glGetProgramInfoLog(program, maxLength, &length, log); - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); - RL_FREE(log); - } - - glDeleteProgram(program); - - program = 0; - } - else - { - // Get the size of compiled shader program (not available on OpenGL ES 2.0) - // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero - //GLint binarySize = 0; - //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", program); - } -#else - TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif - - return program; -} - -// Dispatch compute shader (equivalent to *draw* for graphics pilepine) -void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glDispatchCompute(groupX, groupY, groupZ); -#endif -} - -// Load shader storage buffer object (SSBO) -unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint) -{ - unsigned int ssbo = 0; - -#if defined(GRAPHICS_API_OPENGL_43) - glGenBuffers(1, &ssbo); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); - glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); - if (data == NULL) glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL); // Clear buffer data to 0 - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); -#else - TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif - - return ssbo; -} - -// Unload shader storage buffer object (SSBO) -void rlUnloadShaderBuffer(unsigned int ssboId) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glDeleteBuffers(1, &ssboId); -#else - TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif - -} - -// Update SSBO buffer data -void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, dataSize, data); -#endif -} - -// Get SSBO buffer size -unsigned int rlGetShaderBufferSize(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_43) - GLint64 size = 0; - glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); - return (size > 0)? (unsigned int)size : 0; -#else - return 0; -#endif -} - -// Read SSBO buffer data (GPU->CPU) -void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, count, dest); -#endif -} - -// Bind SSBO buffer -void rlBindShaderBuffer(unsigned int id, unsigned int index) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index, id); -#endif -} - -// Copy SSBO buffer data -void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBuffer(GL_COPY_READ_BUFFER, srcId); - glBindBuffer(GL_COPY_WRITE_BUFFER, destId); - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, srcOffset, destOffset, count); -#endif -} - -// Bind image texture -void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly) -{ -#if defined(GRAPHICS_API_OPENGL_43) - unsigned int glInternalFormat = 0, glFormat = 0, glType = 0; - - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - glBindImageTexture(index, id, 0, 0, 0, readonly? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); -#else - TRACELOG(RL_LOG_WARNING, "TEXTURE: Image texture binding not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif -} - -// Matrix state management -//----------------------------------------------------------------------------------------- -// Get internal modelview matrix -Matrix rlGetMatrixModelview(void) -{ - Matrix matrix = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_11) - float mat[16]; - glGetFloatv(GL_MODELVIEW_MATRIX, mat); - matrix.m0 = mat[0]; - matrix.m1 = mat[1]; - matrix.m2 = mat[2]; - matrix.m3 = mat[3]; - matrix.m4 = mat[4]; - matrix.m5 = mat[5]; - matrix.m6 = mat[6]; - matrix.m7 = mat[7]; - matrix.m8 = mat[8]; - matrix.m9 = mat[9]; - matrix.m10 = mat[10]; - matrix.m11 = mat[11]; - matrix.m12 = mat[12]; - matrix.m13 = mat[13]; - matrix.m14 = mat[14]; - matrix.m15 = mat[15]; -#else - matrix = RLGL.State.modelview; -#endif - return matrix; -} - -// Get internal projection matrix -Matrix rlGetMatrixProjection(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) - float mat[16]; - glGetFloatv(GL_PROJECTION_MATRIX,mat); - Matrix m; - m.m0 = mat[0]; - m.m1 = mat[1]; - m.m2 = mat[2]; - m.m3 = mat[3]; - m.m4 = mat[4]; - m.m5 = mat[5]; - m.m6 = mat[6]; - m.m7 = mat[7]; - m.m8 = mat[8]; - m.m9 = mat[9]; - m.m10 = mat[10]; - m.m11 = mat[11]; - m.m12 = mat[12]; - m.m13 = mat[13]; - m.m14 = mat[14]; - m.m15 = mat[15]; - return m; -#else - return RLGL.State.projection; -#endif -} - -// Get internal accumulated transform matrix -Matrix rlGetMatrixTransform(void) -{ - Matrix mat = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // TODO: Consider possible transform matrices in the RLGL.State.stack - // Is this the right order? or should we start with the first stored matrix instead of the last one? - //Matrix matStackTransform = rlMatrixIdentity(); - //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = rlMatrixMultiply(RLGL.State.stack[i], matStackTransform); - mat = RLGL.State.transform; -#endif - return mat; -} - -// Get internal projection matrix for stereo render (selected eye) -Matrix rlGetMatrixProjectionStereo(int eye) -{ - Matrix mat = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - mat = RLGL.State.projectionStereo[eye]; -#endif - return mat; -} - -// Get internal view offset matrix for stereo render (selected eye) -Matrix rlGetMatrixViewOffsetStereo(int eye) -{ - Matrix mat = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - mat = RLGL.State.viewOffsetStereo[eye]; -#endif - return mat; -} - -// Set a custom modelview matrix (replaces internal modelview matrix) -void rlSetMatrixModelview(Matrix view) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.modelview = view; -#endif -} - -// Set a custom projection matrix (replaces internal projection matrix) -void rlSetMatrixProjection(Matrix projection) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.projection = projection; -#endif -} - -// Set eyes projection matrices for stereo rendering -void rlSetMatrixProjectionStereo(Matrix right, Matrix left) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.projectionStereo[0] = right; - RLGL.State.projectionStereo[1] = left; -#endif -} - -// Set eyes view offsets matrices for stereo rendering -void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.viewOffsetStereo[0] = right; - RLGL.State.viewOffsetStereo[1] = left; -#endif -} - -// Load and draw a quad in NDC -void rlLoadDrawQuad(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int quadVAO = 0; - unsigned int quadVBO = 0; - - float vertices[] = { - // Positions Texcoords - -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, - }; - - // Gen VAO to contain VBO - glGenVertexArrays(1, &quadVAO); - glBindVertexArray(quadVAO); - - // Gen and fill vertex buffer (VBO) - glGenBuffers(1, &quadVBO); - glBindBuffer(GL_ARRAY_BUFFER, quadVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); - - // Bind vertex attributes (position, texcoords) - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords - - // Draw quad - glBindVertexArray(quadVAO); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glBindVertexArray(0); - - // Delete buffers (VBO and VAO) - glDeleteBuffers(1, &quadVBO); - glDeleteVertexArrays(1, &quadVAO); -#endif -} - -// Load and draw a cube in NDC -void rlLoadDrawCube(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int cubeVAO = 0; - unsigned int cubeVBO = 0; - - float vertices[] = { - // Positions Normals Texcoords - -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, - -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, - -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, - -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, - -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, - -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, - -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f - }; - - // Gen VAO to contain VBO - glGenVertexArrays(1, &cubeVAO); - glBindVertexArray(cubeVAO); - - // Gen and fill vertex buffer (VBO) - glGenBuffers(1, &cubeVBO); - glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - // Bind vertex attributes (position, normals, texcoords) - glBindVertexArray(cubeVAO); - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - - // Draw cube - glBindVertexArray(cubeVAO); - glDrawArrays(GL_TRIANGLES, 0, 36); - glBindVertexArray(0); - - // Delete VBO and VAO - glDeleteBuffers(1, &cubeVBO); - glDeleteVertexArrays(1, &cubeVAO); -#endif -} - -// Get name string for pixel format -const char *rlGetPixelFormatName(unsigned int format) -{ - switch (format) - { - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: return "GRAYSCALE"; break; // 8 bit per pixel (no alpha) - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: return "GRAY_ALPHA"; break; // 8*2 bpp (2 channels) - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: return "R5G6B5"; break; // 16 bpp - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: return "R8G8B8"; break; // 24 bpp - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: return "R5G5B5A1"; break; // 16 bpp (1 bit alpha) - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: return "R4G4B4A4"; break; // 16 bpp (4 bit alpha) - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: return "R8G8B8A8"; break; // 32 bpp - case RL_PIXELFORMAT_UNCOMPRESSED_R32: return "R32"; break; // 32 bpp (1 channel - float) - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: return "R32G32B32"; break; // 32*3 bpp (3 channels - float) - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: return "R32G32B32A32"; break; // 32*4 bpp (4 channels - float) - case RL_PIXELFORMAT_UNCOMPRESSED_R16: return "R16"; break; // 16 bpp (1 channel - half float) - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: return "R16G16B16"; break; // 16*3 bpp (3 channels - half float) - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: return "R16G16B16A16"; break; // 16*4 bpp (4 channels - half float) - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: return "DXT1_RGB"; break; // 4 bpp (no alpha) - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: return "DXT1_RGBA"; break; // 4 bpp (1 bit alpha) - case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: return "DXT3_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: return "DXT5_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: return "ETC1_RGB"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: return "ETC2_RGB"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: return "ETC2_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: return "PVRT_RGB"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: return "PVRT_RGBA"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: return "ASTC_4x4_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: return "ASTC_8x8_RGBA"; break; // 2 bpp - default: return "UNKNOWN"; break; - } -} - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Load default shader (just vertex positioning and texture coloring) -// NOTE: This shader program is used for internal buffers -// NOTE: Loaded: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs -static void rlLoadShaderDefault(void) -{ - RLGL.State.defaultShaderLocs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); - - // NOTE: All locations must be reseted to -1 (no location) - for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShaderLocs[i] = -1; - - // Vertex shader directly defined, no external file required - const char *defaultVShaderCode = -#if defined(GRAPHICS_API_OPENGL_21) - "#version 120 \n" - "attribute vec3 vertexPosition; \n" - "attribute vec2 vertexTexCoord; \n" - "attribute vec4 vertexColor; \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" -#elif defined(GRAPHICS_API_OPENGL_33) - "#version 330 \n" - "in vec3 vertexPosition; \n" - "in vec2 vertexTexCoord; \n" - "in vec4 vertexColor; \n" - "out vec2 fragTexCoord; \n" - "out vec4 fragColor; \n" -#endif - -#if defined(GRAPHICS_API_OPENGL_ES3) - "#version 300 es \n" - "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) (on some browsers) - "in vec3 vertexPosition; \n" - "in vec2 vertexTexCoord; \n" - "in vec4 vertexColor; \n" - "out vec2 fragTexCoord; \n" - "out vec4 fragColor; \n" -#elif defined(GRAPHICS_API_OPENGL_ES2) - "#version 100 \n" - "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) (on some browsers) - "attribute vec3 vertexPosition; \n" - "attribute vec2 vertexTexCoord; \n" - "attribute vec4 vertexColor; \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" -#endif - - "uniform mat4 mvp; \n" - "void main() \n" - "{ \n" - " fragTexCoord = vertexTexCoord; \n" - " fragColor = vertexColor; \n" - " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" - "} \n"; - - // Fragment shader directly defined, no external file required - const char *defaultFShaderCode = -#if defined(GRAPHICS_API_OPENGL_21) - "#version 120 \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" - " gl_FragColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#elif defined(GRAPHICS_API_OPENGL_33) - "#version 330 \n" - "in vec2 fragTexCoord; \n" - "in vec4 fragColor; \n" - "out vec4 finalColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture(texture0, fragTexCoord); \n" - " finalColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#endif - -#if defined(GRAPHICS_API_OPENGL_ES3) - "#version 300 es \n" - "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) - "in vec2 fragTexCoord; \n" - "in vec4 fragColor; \n" - "out vec4 finalColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture(texture0, fragTexCoord); \n" - " finalColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#elif defined(GRAPHICS_API_OPENGL_ES2) - "#version 100 \n" - "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" - " gl_FragColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#endif - - // NOTE: Compiled vertex/fragment shaders are not deleted, - // they are kept for re-use as default shaders in case some shader loading fails - RLGL.State.defaultVShaderId = rlCompileShader(defaultVShaderCode, GL_VERTEX_SHADER); // Compile default vertex shader - RLGL.State.defaultFShaderId = rlCompileShader(defaultFShaderCode, GL_FRAGMENT_SHADER); // Compile default fragment shader - - RLGL.State.defaultShaderId = rlLoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); - - if (RLGL.State.defaultShaderId > 0) - { - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShaderId); - - // Set default shader locations: attributes locations - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); - - // Set default shader locations: uniform locations - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); - } - else TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShaderId); -} - -// Unload default shader -// NOTE: Unloads: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs -static void rlUnloadShaderDefault(void) -{ - glUseProgram(0); - - glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultVShaderId); - glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultFShaderId); - glDeleteShader(RLGL.State.defaultVShaderId); - glDeleteShader(RLGL.State.defaultFShaderId); - - glDeleteProgram(RLGL.State.defaultShaderId); - - RL_FREE(RLGL.State.defaultShaderLocs); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShaderId); -} - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) -// Get compressed format official GL identifier name -static const char *rlGetCompressedFormatName(int format) -{ - switch (format) - { - // GL_EXT_texture_compression_s3tc - case 0x83F0: return "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"; break; - case 0x83F1: return "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"; break; - case 0x83F2: return "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"; break; - case 0x83F3: return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"; break; - // GL_3DFX_texture_compression_FXT1 - case 0x86B0: return "GL_COMPRESSED_RGB_FXT1_3DFX"; break; - case 0x86B1: return "GL_COMPRESSED_RGBA_FXT1_3DFX"; break; - // GL_IMG_texture_compression_pvrtc - case 0x8C00: return "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"; break; - case 0x8C01: return "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"; break; - case 0x8C02: return "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"; break; - case 0x8C03: return "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"; break; - // GL_OES_compressed_ETC1_RGB8_texture - case 0x8D64: return "GL_ETC1_RGB8_OES"; break; - // GL_ARB_texture_compression_rgtc - case 0x8DBB: return "GL_COMPRESSED_RED_RGTC1"; break; - case 0x8DBC: return "GL_COMPRESSED_SIGNED_RED_RGTC1"; break; - case 0x8DBD: return "GL_COMPRESSED_RG_RGTC2"; break; - case 0x8DBE: return "GL_COMPRESSED_SIGNED_RG_RGTC2"; break; - // GL_ARB_texture_compression_bptc - case 0x8E8C: return "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"; break; - case 0x8E8D: return "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"; break; - case 0x8E8E: return "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"; break; - case 0x8E8F: return "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"; break; - // GL_ARB_ES3_compatibility - case 0x9274: return "GL_COMPRESSED_RGB8_ETC2"; break; - case 0x9275: return "GL_COMPRESSED_SRGB8_ETC2"; break; - case 0x9276: return "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; - case 0x9277: return "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; - case 0x9278: return "GL_COMPRESSED_RGBA8_ETC2_EAC"; break; - case 0x9279: return "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"; break; - case 0x9270: return "GL_COMPRESSED_R11_EAC"; break; - case 0x9271: return "GL_COMPRESSED_SIGNED_R11_EAC"; break; - case 0x9272: return "GL_COMPRESSED_RG11_EAC"; break; - case 0x9273: return "GL_COMPRESSED_SIGNED_RG11_EAC"; break; - // GL_KHR_texture_compression_astc_hdr - case 0x93B0: return "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"; break; - case 0x93B1: return "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"; break; - case 0x93B2: return "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"; break; - case 0x93B3: return "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"; break; - case 0x93B4: return "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"; break; - case 0x93B5: return "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"; break; - case 0x93B6: return "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"; break; - case 0x93B7: return "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"; break; - case 0x93B8: return "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"; break; - case 0x93B9: return "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"; break; - case 0x93BA: return "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"; break; - case 0x93BB: return "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"; break; - case 0x93BC: return "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"; break; - case 0x93BD: return "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"; break; - case 0x93D0: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"; break; - case 0x93D1: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"; break; - case 0x93D2: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"; break; - case 0x93D3: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"; break; - case 0x93D4: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"; break; - case 0x93D5: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"; break; - case 0x93D6: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"; break; - case 0x93D7: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"; break; - case 0x93D8: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"; break; - case 0x93D9: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"; break; - case 0x93DA: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"; break; - case 0x93DB: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"; break; - case 0x93DC: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"; break; - case 0x93DD: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"; break; - default: return "GL_COMPRESSED_UNKNOWN"; break; - } -} -#endif // RLGL_SHOW_GL_DETAILS_INFO - -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -// Get pixel data size in bytes (image or texture) -// NOTE: Size depends on pixel format -static int rlGetPixelDataSize(int width, int height, int format) -{ - int dataSize = 0; // Size in bytes - int bpp = 0; // Bits per pixel - - switch (format) - { - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16: bpp = 16; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 16*3; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 16*4; break; - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: - case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: - case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; - case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: - case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: - case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: - case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; - case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; - default: break; - } - - double bytesPerPixel = (double)bpp/8.0; - dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes - - // Most compressed formats works on 4x4 blocks, - // if texture is smaller, minimum dataSize is 8 or 16 - if ((width < 4) && (height < 4)) - { - if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; - else if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; - } - - return dataSize; -} - -// Auxiliar math functions - -// Get float array of matrix data -static rl_float16 rlMatrixToFloatV(Matrix mat) -{ - rl_float16 result = { 0 }; - - result.v[0] = mat.m0; - result.v[1] = mat.m1; - result.v[2] = mat.m2; - result.v[3] = mat.m3; - result.v[4] = mat.m4; - result.v[5] = mat.m5; - result.v[6] = mat.m6; - result.v[7] = mat.m7; - result.v[8] = mat.m8; - result.v[9] = mat.m9; - result.v[10] = mat.m10; - result.v[11] = mat.m11; - result.v[12] = mat.m12; - result.v[13] = mat.m13; - result.v[14] = mat.m14; - result.v[15] = mat.m15; - - return result; -} - -// Get identity matrix -static Matrix rlMatrixIdentity(void) -{ - Matrix result = { - 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 - }; - - return result; -} - -// Get two matrix multiplication -// NOTE: When multiplying matrices... the order matters! -static Matrix rlMatrixMultiply(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; - result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; - result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; - result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; - result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; - result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; - result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; - result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; - result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; - result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; - result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; - result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; - result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; - result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; - result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; - result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; - - return result; -} - -// Transposes provided matrix -static Matrix rlMatrixTranspose(Matrix mat) -{ - Matrix result = { 0 }; - - result.m0 = mat.m0; - result.m1 = mat.m4; - result.m2 = mat.m8; - result.m3 = mat.m12; - result.m4 = mat.m1; - result.m5 = mat.m5; - result.m6 = mat.m9; - result.m7 = mat.m13; - result.m8 = mat.m2; - result.m9 = mat.m6; - result.m10 = mat.m10; - result.m11 = mat.m14; - result.m12 = mat.m3; - result.m13 = mat.m7; - result.m14 = mat.m11; - result.m15 = mat.m15; - - return result; -} - -// Invert provided matrix -static Matrix rlMatrixInvert(Matrix mat) -{ - Matrix result = { 0 }; - - // Cache the matrix values (speed optimization) - float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; - float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; - float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; - float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; - - float b00 = a00*a11 - a01*a10; - float b01 = a00*a12 - a02*a10; - float b02 = a00*a13 - a03*a10; - float b03 = a01*a12 - a02*a11; - float b04 = a01*a13 - a03*a11; - float b05 = a02*a13 - a03*a12; - float b06 = a20*a31 - a21*a30; - float b07 = a20*a32 - a22*a30; - float b08 = a20*a33 - a23*a30; - float b09 = a21*a32 - a22*a31; - float b10 = a21*a33 - a23*a31; - float b11 = a22*a33 - a23*a32; - - // Calculate the invert determinant (inlined to avoid double-caching) - float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); - - result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; - result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; - result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; - result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; - result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; - result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; - result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; - result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; - result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; - result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; - result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; - result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; - result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; - result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; - result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; - result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; - - return result; -} - -#endif // RLGL_IMPLEMENTATION diff --git a/project.json b/project.json deleted file mode 100644 index 96825c9..0000000 --- a/project.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "id": "colysis", - "type": "application", - "value": { - "author": "Sander Mertens and Raysan5", - "description": "A template including both Raylib and Flecs!", - "use": [ - "flecs" - ], - "standalone": true - }, - "lang.c": { - "${os linux}": { - "lib": [ - "rt", - "pthread", - "m", - "GL", - "X11", - "Xi", - "Xcursor", - "dl", - "raylib" - ] - }, - "${os windows}": { - "lib": [ - "raylibdll.lib", - "ws2_32", - "gdi32", - "opengl32", - "winmm", - "kernel32" - ], - "defines": [ - "_WINDOWS", - "_USRDLL", - "CGLM_EXPORTS", - "CGLM_DLL" - ] - }, - "${cfg sanitize}": { - "defines": [ - "FLECS_SANITIZE" - ] - }, - "${os darwin}": { - "lib": [ - "raylib" - ], - "ldflags": [ - "-framework Cocoa", - "-framework CoreVideo", - "-framework IOKit", - "-framework GLUT", - "-framework OpenGL" - ] - }, - "${target em}": { - "libpath": [ - "~\/raylib\/src" - ], - "embed": [ - "assets" - ], - "ldflags": [ - "-I ~\/raylib\/src", - "-I ~\/raylib\/src\/external", - "-s USE_GLFW=3", - "-s ASYNCIFY", - "-s TOTAL_MEMORY=67108864", - "-s FORCE_FILESYSTEM=1", - "-s EXPORTED_FUNCTIONS=['_free','_malloc','_main']", - "-s EXPORTED_RUNTIME_METHODS=ccall", - "--shell-file ~\/raylib\/src\/minshell.html ~\/raylib\/src\/web\/libraylib.a", - "-D PLATFORM_WEB" - ] - } - } -} \ No newline at end of file diff --git a/src/main.c b/src/main.c deleted file mode 100644 index b631dd9..0000000 --- a/src/main.c +++ /dev/null @@ -1,27 +0,0 @@ -#include - -int main(int argc, char *argv[]) { - - SetTargetFPS(60); - - InitWindow(900, 500, "Colysis"); - - ecs_world_t *world = ecs_init(); - ecs_entity_t ray = ecs_entity(world, { .name = "raysan5" }); - - puts(ecs_get_name(world, ray)); - Texture2D logo = LoadTexture("assets/Raylib_logo.png"); - - while (!WindowShouldClose()) { - BeginDrawing(); - DrawTexture(logo, GetScreenWidth() / 2 - logo.width / 2, GetScreenHeight() / 2 - logo.height / 2, WHITE); - DrawText(ecs_get_name(world, ray), GetScreenWidth() / 2 - 25 * 3.5f, GetScreenHeight() - 50, 50, BLACK); - - ClearBackground(RAYWHITE); - EndDrawing(); - } - - UnloadTexture(logo); - CloseWindow(); - return ecs_fini(world); -} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..766a3f5 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,16 @@ +#include +#include +#include + +int init() { + std::cout << "colysis: init" << std::endl; + return 0; +} + +void update(float dt) { + +} + +void render(float dt) { + +} \ No newline at end of file