diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 6adb461..515e262 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -7,148 +7,236 @@ on: pull_request: branches: [ "dev" ] workflow_dispatch: - + jobs: + data: + runs-on: ubuntu-latest + outputs: + lib_debug_linux_x86_64: libglecs.linux.template_debug.x86_64.so + lib_release_linux_x86_64: libglecs.linux.template_release.x86_64.so + + lib_debug_macos_x86_64: libglecs.macos.template_debug.x86_64.dylib + lib_release_macos_x86_64: libglecs.macos.template_release.x86_64.dylib + + lib_debug_windows_x86_64: libglecs.windows.template_debug.x86_64.dll + lib_release_windows_x86_64: libglecs.windows.template_release.x86_64.dll + steps: + - name: Pass + run: echo Pass + compile_glecs: + needs: [data] strategy: matrix: include: # Linux - - target: x86_64-unknown-linux-gnu - os: ubuntu-latest - glecs_lib: libglecs.so - - target: i686-unknown-linux-gnu + - platform: linux + arch: x86_64 + glecs_lib: ${{ needs.data.outputs.lib_debug_linux_x86_64 }} os: ubuntu-latest - glecs_lib: libglecs.so + # - platform: linux + # arc: arm64 + # target: i686-unknown-linux-gnu + # glecs_lib: libglecs.linux.template_debug.arm64.so + # os: ubuntu-latest # Mac - - target: aarch64-apple-darwin - os: macos-latest - glecs_lib: libglecs.dylib - - target: x86_64-apple-darwin - os: macos-latest - glecs_lib: libglecs.dylib + - platform: macos + arch: x86_64 + glecs_lib: ${{ needs.data.outputs.lib_debug_macos_x86_64 }} + os: macos-13 + # - target: aarch64-apple-darwin + # os: macos-latest + # glecs_lib: libglecs.dylib # Windows - - target: x86_64-pc-windows-gnu - os: ubuntu-latest - glecs_lib: glecs.dll + - platform: windows + release plreleasem: windows + arch: x86_64 + glecs_lib: ${{ needs.data.outputs.lib_debug_windows_x86_64 }} + os: windows-latest runs-on: ${{ matrix.os }} outputs: # Linux - x86_64-linux_lib_cache_key: ${{ steps.set_cache_key.outputs.x86_64-unknown-linux-gnu_lib_cache_key }} - i686-linux_lib_cache_key: ${{ steps.set_cache_key.outputs.i686-unknown-linux-gnu_lib_cache_key }} + bin_key_linux_x86_64: ${{ steps.set_cache_key.outputs.bin_key_linux_x86_64 }} + # i686-linux_bin_key: ${{ steps.set_cache_key.outputs.i686-unknown-linux-gnu_bin_key }} # Mac - aarch64-mac_lib_cache_key: ${{ steps.set_cache_key.outputs.x86_64-apple-darwin_lib_cache_key }} - x86_64-mac_lib_cache_key: ${{ steps.set_cache_key.outputs.aarch64-apple-darwin_lib_cache_key }} + bin_key_macos_x86_64: ${{ steps.set_cache_key.outputs.bin_key_macos_x86_64 }} + # bin_key_macos_i686: ${{ steps.set_cache_key.outputs.bin_key_macos_i686 }} # Windows - x86_64-windows_lib_cache_key: ${{ steps.set_cache_key.outputs.x86_64-pc-windows-gnu_lib_cache_key }} + bin_key_windows_x86_64: ${{ steps.set_cache_key.outputs.bin_key_windows_x86_64 }} steps: - - name: 🐙 Settup git environment + - name: 🐙 Setup git environment uses: actions/checkout@v4 - name: 🗳️ Initialize git submodules run: git submodule update --init --recursive - - name: 💾 Rust cache - uses: Swatinem/rust-cache@v2 + - name: 🐍 Python install + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + # --- Define keys --- + + - name: 🏔️ Define library cache key in env Linux + shell: bash + run: echo "bin_key=lib.${{ matrix.platform }}_${{ matrix.arch }}.${{ hashFiles('addons/glecs/bin') }}" >> $GITHUB_ENV + + - name: 🏔️ Define cpp cache key Linux + shell: bash + run: echo "cpp_key=cpp.${{ matrix.platform }}_${{ matrix.arch }}.${{ hashFiles('addons/glecs/cpp/') }}" >> $GITHUB_ENV + + - name: 🏔️ Define includes cache key Linux + shell: bash + run: echo "include_key=include.${{ matrix.platform }}_${{ matrix.arch }}.${{ hashFiles('addons/glecs/cpp/include') }}.${{ hashFiles('addons/glecs/cpp/SConstruct') }}" >> $GITHUB_ENV + + # --- Restore caches --- + + - name: 💾 Restore cached cpp files + uses: actions/cache/restore@v4.0.2 with: - workspaces: "./addons/glecs/rust/glecs" - cache-on-failure: true + path: addons/glecs/cpp + key: ${{ env.cpp_key }} - - name: 🏗️ Update Rust + - name: 💾 Restore cached include files + uses: actions/cache/restore@v4.0.2 + with: + path: addons/glecs/cpp/include + key: ${{ env.include_key }} + + # --- Platform specific setup --- + + # - name: 🐧 Install GCC multilib for Linux i686 build + # if: ${{ matrix.platform == 'linux' && matrix.arch == 'i686' && runner.os == 'Linux'}} + # run: sudo apt-get install gcc-multilib + + - name: 🪟 Install mingw for cross compiling from Linux to Windows + if: ${{ matrix.platform == 'windows' && runner.os == 'Linux' }} run: | - rustup update - rustup target add ${{ matrix.target }} - - - name: 🐧 Install GCC multilib for Linux i686 build - if: ${{ matrix.target == 'i686-unknown-linux-gnu' && runner.os == 'Linux'}} - run: sudo apt-get install gcc-multilib - - - name: 🪟 Install mingw for Windows build - if: ${{ matrix.target == 'x86_64-pc-windows-gnu' && runner.os == 'Linux' }} - run: sudo apt install gcc-mingw-w64 - - - name: 🦀 Build Rust code + sudo apt install g++-mingw-w64-x86-64 + sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix + + # --- Compile Glecs --- + + - name: 🔨 Install scons + run: | + pip install scons + + - name: 🛠️ Build C++ code run: | - cargo build \ - --release \ - --manifest-path ./addons/glecs/rust/glecs/Cargo.toml \ - --features compile_bindings \ - --target-dir ./addons/glecs/bin \ - --target ${{ matrix.target }} - - - name: 🏔️ Set Glecs library cache key ENV variable + scons --directory=addons/glecs/cpp platform=${{ matrix.platform }} arch=${{ matrix.arch }} + printf *** ls addons/glecs/bin/ *** + ls addons/glecs/bin + #target=template_release + + # --- Define bin key --- + + - name: 🏔️ Define library cache key in env + shell: bash run: | - echo "lib_cache_key=glecslib.${{ matrix.target }}.${{ hashFiles('addons/glecs/bin') }}" >> $GITHUB_ENV - - - name: 🔑 Set Glecs library cache key + printf "*** Setting key in env***" + echo "bin_key=bin.${{ matrix.platform }}_${{ matrix.arch }}.${{ hashFiles('addons/glecs/bin') }}" >> $GITHUB_ENV + - name: 🏔️ Define library cache key in output id: set_cache_key + shell: bash run: | - echo "${{ matrix.TARGET }}_lib_cache_key=$lib_cache_key" >> $GITHUB_OUTPUT + printf "*** Setting key ***" + echo "bin_key_${{ matrix.platform }}_${{ matrix.arch }}=${{env.bin_key}}" >> $GITHUB_OUTPUT + + # --- Save caches --- + + - name: 💾 Cache cpp source build files + uses: actions/cache/save@v4 + with: + path: addons/glecs/cpp + key: ${{ env.cpp_key }} + + - name: 💾 Cache cpp include build files + uses: actions/cache/save@v4 + with: + path: addons/glecs/cpp/include + key: ${{ env.include_key }} - - name: 💾 Cache compiled library + - name: 💾 Cache bin uses: actions/cache/save@v4.0.2 with: - # A list of files, directories, and wildcard patterns to cache and restore - path: ./addons/glecs/bin/${{ matrix.target }}/release/${{ matrix.glecs_lib }} - # An explicit key for restoring and saving the cache - key: ${{ env.lib_cache_key }} + path: ./addons/glecs/bin/${{ matrix.glecs_lib }} + key: ${{ env.bin_key }} enableCrossOsArchive: true - run_test_suite: - needs: compile_glecs - runs-on: ubuntu-latest + needs: [data, compile_glecs] + strategy: + matrix: + include: + - os: ubuntu-latest + bin_key: ${{ needs.compile_glecs.outputs.bin_key_linux_x86_64 }} + lib_debug: ${{ needs.data.outputs.lib_debug_linux_x86_64 }} + lib_release: ${{ needs.data.outputs.lib_release_linux_x86_64 }} + - os: macos-13 + bin_key: ${{ needs.compile_glecs.outputs.bin_key_macos_x86_64 }} + lib_debug: ${{ needs.data.outputs.lib_debug_macos_x86_64 }} + lib_release: ${{ needs.data.outputs.lib_release_macos_x86_64 }} + - os: windows-latest + bin_key: ${{ needs.compile_glecs.outputs.bin_key_windows_x86_64 }} + lib_debug: ${{ needs.data.outputs.lib_debug_windows_x86_64 }} + lib_release: ${{ needs.data.outputs.lib_release_windows_x86_64 }} + runs-on: ${{ matrix.os }} steps: - - name: 🐙 Settup git environment + - name: 🐙 Setup git environment uses: actions/checkout@v4 - - name: 💾 Load cached library + - name: 💾 Load cached library as debug uses: actions/cache/restore@v4.0.2 with: - path: ./addons/glecs/bin/x86_64-unknown-linux-gnu/release/libglecs.so - key: ${{ needs.compile_glecs.outputs.x86_64-linux_lib_cache_key }} + path: ./addons/glecs/bin/${{ matrix.lib_debug }} + key: ${{ matrix.bin_key }} + enableCrossOsArchive: true + - name: 💾 Load cached library as release + uses: actions/cache/restore@v4.0.2 + with: + path: ./addons/glecs/bin/${{ matrix.lib_release }} + key: ${{ matrix.bin_key }} enableCrossOsArchive: true - - name: ↪️ Move lib - if: ${{ matrix.os }} == "ubuntu-latest" - run: | - mkdir ./addons/glecs/bin/release/ - mkdir ./addons/glecs/bin/debug/ - cp ./addons/glecs/bin/x86_64-unknown-linux-gnu/release/libglecs.so ./addons/glecs/bin/release/libglecs.so - cp ./addons/glecs/bin/x86_64-unknown-linux-gnu/release/libglecs.so ./addons/glecs/bin/debug/libglecs.so + - name: 🤖 Setup Godot binary + uses: lihop/setup-godot@v2 + with: + version: 4.3-stable + bits: 64 + mono: false + + - name: 🤖 Run Godot import + shell: bash + run: godot --headless --import - - name: 🤖 Run Godot unit tests + - name: 📝 Run Glecs tests id: run-godot-tests - uses: croconut/godot-tester@v5 - with: - # required - version: "4.2.1" - is-mono: "false" - # the folder with your project.godot file in it - path: "./" - # the ratio of tests that must pass for this action to pass - # e.g. 0.6 means 60% of your tests must pass - minimum-pass: "1.0" - # the directory containing Gut tests - test-dir: "res://unittests" - # default is GUTs default: 'res://.gutconfig.json'; set this to load a different config file - config-file: "res://.gut_editor_config.json" - # relative path to the xml file to read / write GUT's results from, recommended - # for direct-scene users to check this file if you have issues - result-output-file: "test_results.xml" - - - name: 📄 Print Godot logs + shell: bash + run: godot --script unittests.gd --headless --verbose + + - name: 📄 Debug print when tests error + shell: bash if: failure() && steps.run-godot-tests.outcome != 'success' - run: echo "$(cat logs/godot.log)" + run: | + echo "" + echo "***" + echo "LS" + echo "***" + ls -g -A + echo "" + echo "***" + echo "LS glecs/bin/" + echo "***" + ls addons/glecs/bin update_nightly: if: ${{ github.event_name == 'push' || github.event_name == 'closed' }} needs: ["compile_glecs", "run_test_suite"] runs-on: windows-latest steps: - - name: 🐙 Settup git environment + - name: 🐙 Setup git environment uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -160,27 +248,7 @@ jobs: # A list of files, directories, and wildcard patterns to cache and restore path: ./addons/glecs/bin/x86_64-unknown-linux-gnu/release/libglecs.so # An explicit key for restoring and saving the cache - key: ${{ needs.compile_glecs.outputs.x86_64-linux_lib_cache_key }} - # An optional boolean when enabled, allows windows runners to save or restore caches that can be restored or saved respectively on other platforms - enableCrossOsArchive: true - - - name: 🐧 Load compiled library, Linux i686 - uses: actions/cache/restore@v4.0.2 - with: - # A list of files, directories, and wildcard patterns to cache and restore - path: ./addons/glecs/bin/i686-unknown-linux-gnu/release/libglecs.so - # An explicit key for restoring and saving the cache - key: ${{ needs.compile_glecs.outputs.i686-linux_lib_cache_key }} - # An optional boolean when enabled, allows windows runners to save or restore caches that can be restored or saved respectively on other platforms - enableCrossOsArchive: true - - - name: 🍎 Load compiled library, Mac aarch64 - uses: actions/cache/restore@v4.0.2 - with: - # A list of files, directories, and wildcard patterns to cache and restore - path: ./addons/glecs/bin/aarch64-apple-darwin/release/libglecs.dylib - # An explicit key for restoring and saving the cache - key: ${{ needs.compile_glecs.outputs.aarch64-mac_lib_cache_key }} + key: ${{ needs.compile_glecs.outputs.bin_key_linux_x86_64 }} # An optional boolean when enabled, allows windows runners to save or restore caches that can be restored or saved respectively on other platforms enableCrossOsArchive: true @@ -190,7 +258,7 @@ jobs: # A list of files, directories, and wildcard patterns to cache and restore path: ./addons/glecs/bin/x86_64-apple-darwin/release/libglecs.dylib # An explicit key for restoring and saving the cache - key: ${{ needs.compile_glecs.outputs.x86_64-mac_lib_cache_key }} + key: ${{ needs.compile_glecs.outputs.bin_key_macos_x86_64 }} # An optional boolean when enabled, allows windows runners to save or restore caches that can be restored or saved respectively on other platforms enableCrossOsArchive: true @@ -200,7 +268,7 @@ jobs: # A list of files, directories, and wildcard patterns to cache and restore path: ./addons/glecs/bin/x86_64-pc-windows-gnu/release/glecs.dll # An explicit key for restoring and saving the cache - key: ${{ needs.compile_glecs.outputs.x86_64-windows_lib_cache_key }} + key: ${{ needs.compile_glecs.outputs.bin_key_windows_x86_64 }} # An optional boolean when enabled, allows windows runners to save or restore caches that can be restored or saved respectively on other platforms enableCrossOsArchive: true @@ -210,11 +278,11 @@ jobs: python-version: 3.11.2 cache: pip - - name: 🚣‍♂️ Convert dev plugin to nightly format - run: | - cd addons/glecs - pip install regex - python _build_for_nightly.py + # - name: 🚣‍♂️ Convert dev plugin to nightly format + # run: | + # cd addons/glecs + # pip install regex + # python _build_for_nightly.py - name: 🌙 Clone nightly branch run: | @@ -222,11 +290,11 @@ jobs: mkdir ../nightly mkdir ../nightly/.git cp -r ../nightly_tmp/.git/* ../nightly/.git/ - + - name: 🖨️ Copy dev branch plugin to nightly run: | cp -r -force ./addons/glecs/* ../nightly/ - + - name: ➕ Add and commit to nightly id: add-and-commit continue-on-error: true @@ -244,6 +312,6 @@ jobs: repository: ${{ github.repository }} branch: nightly directory: ../nightly - + - name: 🏁 Finish run: exit 0 diff --git a/.gitignore b/.gitignore index f1f10ef..59a7f28 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,12 @@ addons/gut/gui/GutSceneTheme.tres unittest_log.xml test_results.xml -logs/* \ No newline at end of file +logs/*.log + +# Ignore C++ build files +addons/glecs/cpp/.sconsign.dblite +*.os +addons/glecs/cpp/.cache +addons/glecs/cpp/compile_commands.json +.godot/scene_groups_cache.cfg +addons/glecs/cpp/src/doc_data.gen.h diff --git a/.gitmodules b/.gitmodules index a0e2868..8146f4d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,8 @@ -[submodule "addons/glecs/rust/flecs-rs"] - path = addons/glecs/rust/flecs-rs - url = https://github.com/GsLogiMaker/flecs-rs +[submodule "addons/glecs/cpp/include/--force"] + path = addons/glecs/cpp/include/--force + url = https://github.com/godotengine/godot-cpp + branch = 4.2 +[submodule "addons/glecs/cpp/include/godot-cpp"] + path = addons/glecs/cpp/include/godot-cpp + url = https://github.com/godotengine/godot-cpp + branch = 4.2 diff --git a/.godot/global_script_class_cache.cfg b/.godot/global_script_class_cache.cfg index 9b25793..c2e1e77 100644 --- a/.godot/global_script_class_cache.cfg +++ b/.godot/global_script_class_cache.cfg @@ -1,47 +1,5 @@ list=Array[Dictionary]([{ "base": &"RefCounted", -"class": &"Glecs", -"icon": "", -"language": &"GDScript", -"path": "res://addons/glecs/gd/glecs.gd" -}, { -"base": &"_GlecsBaseComponent", -"class": &"GlecsComponent", -"icon": "", -"language": &"GDScript", -"path": "res://addons/glecs/gd/component.gd" -}, { -"base": &"_GlecsBaseEntity", -"class": &"GlecsEntity", -"icon": "", -"language": &"GDScript", -"path": "res://addons/glecs/gd/entity.gd" -}, { -"base": &"GlecsEntity", -"class": &"GlecsModule", -"icon": "", -"language": &"GDScript", -"path": "res://addons/glecs/gd/module.gd" -}, { -"base": &"_GlecsBaseSystemBuilder", -"class": &"GlecsSystemBuilder", -"icon": "", -"language": &"GDScript", -"path": "res://addons/glecs/gd/system_builder.gd" -}, { -"base": &"_GlecsBaseWorldNode", -"class": &"GlecsWorldNode", -"icon": "", -"language": &"GDScript", -"path": "res://addons/glecs/gd/world_node.gd" -}, { -"base": &"_GlecsBaseWorld", -"class": &"GlecsWorldObject", -"icon": "", -"language": &"GDScript", -"path": "res://addons/glecs/gd/world_object.gd" -}, { -"base": &"RefCounted", "class": &"GutHookScript", "icon": "", "language": &"GDScript", diff --git a/.gut_editor_config.json b/.gut_editor_config.json index fab8818..7e6e6fc 100644 --- a/.gut_editor_config.json +++ b/.gut_editor_config.json @@ -16,7 +16,7 @@ "ignore_pause": false, "include_subdirs": true, "inner_class": null, - "junit_xml_file": "./test_results.xml", + "junit_xml_file": "res://test_results.xml", "junit_xml_timestamp": false, "log_level": 1, "opacity": 100, @@ -42,4 +42,4 @@ "suffix": ".gd", "tests": [], "unit_test_name": "" -} \ No newline at end of file +} diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..5819b5f --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "linux-gcc-x64", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "cStandard": "${default}", + "cppStandard": "${default}", + "intelliSenseMode": "linux-gcc-x64", + "compilerPath": "/usr/bin/gcc" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 2c22688..58dddfc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,40 +2,17 @@ "version": "0.2.0", "configurations": [ { - "name": "Debug Game", - "preLaunchTask": "godot-rust: Build Debug", - "type": "lldb", + "name": "Debug Extension", "request": "launch", - "program": "${config:godot-rust.environment.godotEditorPath}", - "cwd": "${config:godot-rust.environment.godotProjectPath}", - "presentation": { - "group": "Debug", - "order": 1 - } - }, - { - "name": "Release Game", - "preLaunchTask": "godot-rust: Build Release", - "type": "lldb", - "request": "launch", - "program": "${config:godot-rust.environment.godotEditorPath}", - "cwd": "${config:godot-rust.environment.godotProjectPath}", - "presentation": { - "group": "Debug", - "order": 2 - } - }, - { - "name": "Attach to Game", - "pid": "${command:pickMyProcess}", "type": "lldb", - "request": "attach", - "program": "${config:godot-rust.environment.godotEditorPath}", - "port": 6007, - "presentation": { - "group": "Debug", - "order": 3 - } - } + "preLaunchTask": "godot-extension: Build Debug", + "program": "${config:godotTools.editorPath.godot4}", + "args": [ + "--path", + "${workspaceFolder}", + "--headless" + ], + "cwd": "${workspaceFolder}" + } ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 80ee24e..8d3a436 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,218 @@ { - "rust-analyzer.linkedProjects": [ - "./addons/glecs/rust/glecs/Cargo.toml", - "./addons/glecs/rust/flecs-rs/Cargo.toml", - ], - "godot-rust.environment.godotProjectPath": "${workspaceFolder}" + "editor.tabSize": 4, + "editor.rulers": [ + 120 + ], + "editor.renderWhitespace": "trailing", + "editor.suggestSelection": "first", + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.stickyScroll.enabled": false, + "editor.bracketPairColorization.enabled": false, + "editor.cursorSmoothCaretAnimation": "on", + "editor.suggest.preview": true, + "terminal.integrated.defaultProfile.windows": "Command Prompt", + "debug.onTaskErrors": "debugAnyway", + "explorer.compactFolders": false, + "explorer.confirmDragAndDrop": false, + "explorer.confirmDelete": false, + "explorer.copyRelativePathSeparator": "/", + "files.autoSave": "onFocusChange", + "files.exclude": { + "node_modules/**/*": true, + "**/.classpath": true, + "**/.project": true, + "**/.settings": true, + "**/.factorypath": true + }, + "files.associations": { + ".clang*": "yaml", + "*.hpp.in": "cpp", + "*.in": "cpp" + }, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.trimTrailingWhitespace": true, + "workbench.startupEditor": "none", + "workbench.editorAssociations": { + "*.md": "vscode.markdown.preview.editor", + "*.svg": "svgPreviewer.customEditor" + }, + "workbench.colorTheme": "Default Dark+", + "git.enableSmartCommit": true, + "git.autofetch": true, + "git.confirmSync": false, + "git.openRepositoryInParentFolders": "always", + "prettier.tabWidth": 4, + "prettier.singleQuote": true, + "prettier.jsxSingleQuote": true, + "prettier.trailingComma": "all", + "prettier.useEditorConfig": true, + "prettier.bracketSpacing": false, + "markdown.validate.enabled": true, + "[markdown]": { + "files.trimTrailingWhitespace": false, + "editor.formatOnSave": false, + "editor.defaultFormatter": "yzhang.markdown-all-in-one", + "editor.wordWrap": "wordWrapColumn", + "editor.wordWrapColumn": 120 + }, + "[yaml]": { + "editor.formatOnSave": false, + "editor.defaultFormatter": "redhat.vscode-yaml", + "editor.wordWrap": "wordWrapColumn", + "editor.wordWrapColumn": 120 + }, + "[json]": { + "editor.formatOnSave": false, + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[jsonc]": { + "editor.formatOnSave": false + }, + "[plaintext]": { + "editor.wordWrap": "wordWrapColumn", + "editor.wordWrapColumn": 120 + }, + "[toml]": { + "editor.wordWrap": "wordWrapColumn", + "editor.wordWrapColumn": 120 + }, + "better-comments.tags": [ + { + "tag": "XXX", + "color": "#F8C471" + }, + { + "tag": "WARN", + "color": "#FF6961" + }, + { + "tag": "NOTE", + "color": "#3498DB" + }, + { + "tag": "TODO", + "color": "#77C3EC" + } + ], + "vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue", + "codesnap.showWindowControls": false, + "codesnap.shutterAction": "copy", + "Workspace_Formatter.excludePattern": [ + "**/build", + "**/.*", + "**/.vscode", + "**/html" + ], + "Workspace_Formatter.includePattern": [ + "*.c", + "*.h", + "*.cc", + "*.hh", + "*.cpp", + "*.hpp" + ], + "svg.preview.autoOpen": true, + "remote.WSL.fileWatcher.polling": true, + "errorLens.delay": 1000, + "errorLens.enabledDiagnosticLevels": [ + "error", + "warning" + ], + "errorLens.enabled": false, + "C_Cpp.clang_format_sortIncludes": true, + "C_Cpp.vcFormat.indent.preserveComments": true, + "C_Cpp.vcFormat.indent.namespaceContents": false, + "C_Cpp.vcFormat.indent.caseContentsWhenBlock": true, + "C_Cpp.vcFormat.space.pointerReferenceAlignment": "right", + "C_Cpp.default.browse.limitSymbolsToIncludedHeaders": false, + "C_Cpp.default.cppStandard": "c++20", + "C_Cpp.default.cStandard": "c11", + "C_Cpp.formatting": "clangFormat", + "[c]": { + "editor.formatOnSave": false, + "editor.defaultFormatter": "ms-vscode.cpptools" + }, + "[cpp]": { + "editor.formatOnSave": false, + "editor.defaultFormatter": "ms-vscode.cpptools" + }, + "[cuda-cpp]": { + "editor.defaultFormatter": "ms-vscode.cpptools" + }, + "cmake.configureOnOpen": false, + "cmake.autoSelectActiveFolder": false, + "cmake.configureOnEdit": false, + "[cmake]": { + "editor.formatOnSave": false, + "editor.defaultFormatter": "cheshirekow.cmake-format" + }, + "cmake.options.statusBarVisibility": "visible", + "doxdocgen.file.fileTemplate": "@file {name}", + "doxdocgen.cpp.tparamTemplate": "@tparam {param} ", + "doxdocgen.generic.briefTemplate": "@brief {text}", + "doxdocgen.generic.boolReturnsTrueFalse": false, + "doxdocgen.generic.paramTemplate": "@param {param} ", + "doxdocgen.generic.returnTemplate": "@return", + "doxdocgen.generic.includeTypeAtReturn": false, + "C_Cpp.codeAnalysis.clangTidy.enabled": false, + "C_Cpp.configurationWarnings": "disabled", + "C_Cpp_Runner.cppStandard": "c++20", + "C_Cpp_Runner.cStandard": "c11", + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.cCompilerPath": "gcc", + "C_Cpp_Runner.cppCompilerPath": "g++", + "C_Cpp_Runner.debuggerPath": "gdb", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.compilerArgs": [], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": [ + "*", + "**/*" + ], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": false, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false, + "C_Cpp_Runner.msvcBatchPath": "" } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ba19742..f54ba81 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,52 +2,33 @@ "version": "2.0.0", "tasks": [ { - "label": "godot-rust: Launch Debug Editor", - "detail": "Builds debug rust game library, then opens the project with Godot Editor", - "dependsOn": "godot-rust: Build Debug", + "label": "godot-extension: Build Debug", + "detail": "Builds debug godot extension library", "type": "shell", - "options": { - "cwd": "${config:godot-rust.environment.godotProjectPath}" - }, - "command": "${config:godot-rust.environment.godotEditorPath} --editor", - "group": "build", - "problemMatcher": [] - }, - { - "label": "godot-rust: Launch Release Editor", - "detail": "Builds release rust game library, then opens the project with Godot Editor", - "dependsOn": "godot-rust: Build Release", - "type": "shell", - "options": { - "cwd": "${config:godot-rust.environment.godotProjectPath}" - }, - "command": "${config:godot-rust.environment.godotEditorPath} --editor", - "group": "build" - }, - { - "label": "godot-rust: Build Debug", - "detail": "Builds debug rust game library", - "type": "shell", - "command": "cargo build --manifest-path ./addons/glecs/rust/glecs/Cargo.toml --target-dir ./addons/glecs/bin", + "command": "scons --directory addons/glecs/cpp target=template_debug debug_symbols=yes optimize=debug", "group": { "kind": "build", "isDefault": true }, "problemMatcher": [] - }, - { - "label": "godot-rust: Build Release", - "detail": "Builds release rust game library", + }, { + "label": "godot-extension: Clean Build Files", + "detail": "Removes artifacts from the build process", "type": "shell", - "command": "cargo build --release --manifest-path ./addons/glecs/rust/glecs/Cargo.toml --target-dir ./addons/glecs/bin", - "group": "build" - }, - { - "label": "godot-rust: Clean", - "detail": "Removes all artifacts of rust game library and its dependencies", + "command": "scons --clean --directory addons/glecs/cpp", + "problemMatcher": [] + }, { + "label": "godot-extension: Build Compilation Commands", + "detail": "Removes artifacts from the build process", "type": "shell", - "command": "cargo clean --manifest-path ./addons/glecs/rust/glecs/Cargo.toml --target-dir ./addons/glecs/bin", - "group": "build" + "command": "scons cdb --directory addons/glecs/cpp target=template_debug debug_symbols=yes optimize=debug", + "problemMatcher": [] + }, { + "label": "godot-extension: Doctool Dump Documentation Files", + "detail": "Dumps GDExtension documentation xml files", + "type": "shell", + "command": "${config:godotTools.editorPath.godot4} --doctool ./addons/glecs/cpp/src/ --gdextension-docs --editor --headless --quit", + "problemMatcher": [] } ] -} \ No newline at end of file +} diff --git a/addons/glecs/_build_for_nightly.py b/addons/glecs/_build_for_nightly.py index 31df958..afc173b 100644 --- a/addons/glecs/_build_for_nightly.py +++ b/addons/glecs/_build_for_nightly.py @@ -2,23 +2,22 @@ import os import regex -os.remove("glecs.gdextension") -os.rename("glecs.gdextension.release", "glecs.gdextension") - +# Regex patterns name_ptrn = regex.compile(r"""name\s*=\s*\"(.*)\"""") version_ptrn = regex.compile(r"""version\s*=\s*\"(.*)\"""") +# Edit plugin config with open("plugin.cfg", "r+") as f: txt = f.read() - + start, end = name_ptrn.search(txt).span(1) txt = txt[:start] + "GlecsNightly" + txt[end:] start, end = version_ptrn.search(txt).span(1) txt = txt[:end] + "-nightly" + txt[end:] - + f.seek(0) f.write(txt) f.truncate() -exit(0) \ No newline at end of file +exit(0) diff --git a/addons/glecs/cpp/SConstruct b/addons/glecs/cpp/SConstruct new file mode 100644 index 0000000..d42d977 --- /dev/null +++ b/addons/glecs/cpp/SConstruct @@ -0,0 +1,109 @@ +#!/usr/bin/env python +import os +import sys + +from SCons.Script import * +from SCons.Script.SConscript import SConsEnvironment + +LIB_NAME = "libglecs" + +def main(): + env:SConsEnvironment = SConscript("include/godot-cpp/SConstruct") + + # Settings for speeding up compilation + env.Decider('MD5-timestamp') + env.SetOption('max_drift', 1) + env.SetOption('implicit_cache', 1) + + # Compilation commands + env.Tool('compilation_db') + cdb = env.CompilationDatabase() + x = env.Alias('cdb', cdb) + if "cdb" in COMMAND_LINE_TARGETS: + env._dict["CXXFLAGS"].remove("-fno-gnu-unique") + + platform:str = env["platform"] + target:str = env["target"] + suffix:str = env["suffix"] + shlib_suffix:str = env["SHLIBSUFFIX"] + + # Set cpp version + for item in env['CXXFLAGS']: + if "std" in item: + env['CXXFLAGS'].remove(item) + if "windows" in platform : + env.Append(CXXFLAGS=['/std:c++20']) + else: + env.Append(CXXFLAGS=['-std=c++20']) + + # For reference: + # - CCFLAGS are compilation flags shared between C and C++ + # - CFLAGS are for C-specific compilation flags + # - CXXFLAGS are for C++-specific compilation flags + # - CPPFLAGS are for pre-processor flags + # - CPPDEFINES are for pre-processor defines + # - LINKFLAGS are for linking flags + + # tweak this if you want to use different folders, or more folders, to store your source code in. + env.Append(CPPPATH=["src/"]) + env.Append(CPPPATH=["include/flecs/"]) + sources = env.Glob("src/*.cpp") + env.Glob("include/flecs/*.c") + + # Allow throwing exceptions + env.Append(CXXFLAGS=["-fexceptions"]) + + # Compile documentation + docs_xml = [] + docs_xml += env.Glob("src/doc_classes/*.xml") + docs_xml = sorted(docs_xml) + docs_header = [f"src/doc_data.gen.h"] + env.Command(docs_header, docs_xml, env.Action(make_doc_header, "Generating documentation header")) + + library = env.SharedLibrary( + f"../bin/{LIB_NAME}{suffix}{shlib_suffix}", + source=sources, + ) + + env.VariantDir(f"/build/{platform}.{target}", 'src') + env.Default(library) + +def make_doc_header(target, source, env): + #import zlib + + dst = str(target[0]) + g = open(dst, "w", encoding="utf-8") + buf = "" + docbegin = "" + docend = "" + for src in source: + src = str(src) + if not src.endswith(".xml"): + continue + with open(src, "r", encoding="utf-8") as f: + content = f.read() + buf += content + + buf = (docbegin + buf + docend).encode("utf-8") + #decomp_size = len(buf) + + # Use maximum zlib compression level to further reduce file size + # (at the cost of initial build times). + #buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION) + + g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") + g.write("#ifndef _DOC_DATA_RAW_H\n") + g.write("#define _DOC_DATA_RAW_H\n") + #g.write("static const int _doc_data_compressed_size = " + str(len(buf)) + ";\n") + #g.write("static const int _doc_data_uncompressed_size = " + str(decomp_size) + ";\n") + #g.write("static const unsigned char _doc_data_compressed[] = {\n") + g.write("static const int _doc_data_size = " + str(len(buf)) + ";\n") + g.write("static const char _doc_data[] = {\n") + for i in range(len(buf)): + g.write("\t" + str(buf[i]) + ",\n") + g.write("};\n") + + g.write("#endif") + + g.close() + +main() diff --git a/addons/glecs/cpp/include/flecs/flecs.c b/addons/glecs/cpp/include/flecs/flecs.c new file mode 100644 index 0000000..25a4698 --- /dev/null +++ b/addons/glecs/cpp/include/flecs/flecs.c @@ -0,0 +1,73769 @@ +/** + * @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_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_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; /* Index into diff vector, if non trivial 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); + +#endif + + +/* 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 { + ecs_vec_t data; /* Vector with component data */ + ecs_id_t id; /* Component id */ + ecs_type_info_t *ti; /* Component type info */ + ecs_size_t size; /* Component size */ +} ecs_column_t; + +/** Table data */ +struct ecs_data_t { + ecs_vec_t entities; /* Entity ids */ + ecs_column_t *columns; /* Component data */ +}; + +/** 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 */ + int32_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); + +/* Return number of entities in data */ +int32_t flecs_table_data_count( + const ecs_data_t *data); + +/* 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); + +/* Make sure table records are in correct table cache list */ +bool flecs_table_records_update_empty( + ecs_table_t *table); + +/* 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, + ecs_data_t *data, + int32_t count, + const ecs_entity_t *ids); + +/* Set table to a fixed size. Useful for preallocating memory in advance. */ +void flecs_table_set_size( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t count); + +/* 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_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); + +const ecs_vec_t* flecs_table_entities( + const ecs_table_t *table); + +ecs_entity_t* flecs_table_entities_array( + const ecs_table_t *table); + +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); + +#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; + +/** Must appear as first member in payload of table cache */ +typedef struct ecs_table_cache_hdr_t { + struct ecs_table_cache_t *cache; + ecs_table_t *table; + struct ecs_table_cache_hdr_t *prev, *next; + bool empty; +} ecs_table_cache_hdr_t; + +/** Linked list of tables in table cache */ +typedef struct ecs_table_cache_list_t { + ecs_table_cache_hdr_t *first; + ecs_table_cache_hdr_t *last; + int32_t count; +} ecs_table_cache_list_t; + +/** Table cache */ +typedef struct ecs_table_cache_t { + ecs_map_t index; /* */ + ecs_table_cache_list_t tables; + ecs_table_cache_list_t empty_tables; +} ecs_table_cache_t; + +/* 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 stack, for nested execution */ + ecs_commands_t *cmd; + ecs_commands_t cmd_stack[ECS_MAX_DEFER_STACK]; + int32_t cmd_sp; + + /* 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; + + /* Temporary token storage for DSL parser. This allows for parsing and + * interpreting a term without having to do allocations. */ + char parser_tokens[1024]; + char *parser_token; /* Pointer to next token */ +}; + +/* 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; + + /* Observers */ + ecs_sparse_t observers; /* sparse */ + + /* Records cache */ + ecs_vec_t records; + + /* Stack of ids being deleted. */ + ecs_vec_t marked_ids; /* vector */ + + /* Entity ids associated with depth (for flat hierarchies) */ + ecs_vec_t depth_ids; + ecs_map_t entity_to_depth; /* What it says */ +} 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_sparse_t type_info; /* sparse */ + + /* -- 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; + + /* -- Pending table event buffers -- */ + ecs_sparse_t *pending_buffer; /* sparse */ + ecs_sparse_t *pending_tables; /* sparse */ + + /* 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 */ + + /* 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_insert_w_empty( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *result, + bool is_empty); + +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); + +bool ecs_table_cache_set_empty( + ecs_table_cache_t *cache, + const ecs_table_t *table, + bool empty); + +bool ecs_table_cache_is_empty( + const ecs_table_cache_t *cache); + +#define flecs_table_cache_count(cache) (cache)->tables.count +#define flecs_table_cache_empty_count(cache) (cache)->empty_tables.count +#define flecs_table_cache_all_count(cache) ((cache)->tables.count + (cache)->empty_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 + +/* Payload for id cache */ +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 */ +}; + +/* 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); + +#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; + +/* -- Instruction kinds -- */ +typedef enum { + EcsQueryAnd, /* And operator: find or match id against variable source */ + EcsQueryAndId, /* And operator for fixed id (no wildcards/variables) */ + 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) */ + EcsQueryTrivData, /* Trivial search with setting data fields */ + EcsQueryTrivWildcard, /* Trivial search with (exclusive) wildcard ids */ + EcsQueryCache, /* Cached search */ + EcsQueryCacheData, /* Cached search with setting data fields */ + EcsQueryIsCache, /* Cached search for queries that are entirely cached */ + EcsQueryIsCacheData, /* Same as EcsQueryIsCache with data fields */ + EcsQueryUp, /* Up traversal */ + EcsQueryUpId, /* Up traversal for fixed id (like AndId) */ + EcsQuerySelfUp, /* Self|up traversal */ + EcsQuerySelfUpId, /* Self|up traversal for fixed id (like AndId) */ + 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 */ + EcsQueryPopulate, /* Populate any data fields */ + EcsQueryPopulateSelf, /* Populate only self (owned) data fields */ + EcsQueryPopulateSparse, /* Populate sparse fields */ + 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; + int32_t column; + 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; + int32_t column; +} 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 first_to_eval; +} ecs_query_trivial_ctx_t; + +/* *From operator iterator context */ +typedef struct { + ecs_query_and_ctx_t and; + 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; + +typedef struct { + ecs_query_var_t var; + const char *name; +} ecs_query_var_cache_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_query_var_cache_t vars_cache; /* For trivial queries with only This 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 */ + + /* Query cache */ + struct ecs_query_cache_t *cache; /* Cache, if query contains cached terms */ + int8_t *field_map; /* Map field indices from cache to query */ + + /* Change detection */ + int32_t *monitor; /* Change monitor for fields with fixed src */ + + /* Misc */ + int16_t tokens_len; /* Length of tokens buffer */ + char *tokens; /* Buffer with string tokens used by 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 */ + int32_t *columns; /* Mapping from query fields to table columns */ + int32_t *storage_columns; /* Mapping from query fields to storage columns */ + ecs_id_t *ids; /* Resolved (component) ids for current table */ + ecs_entity_t *sources; /* Subjects (sources) of ids */ + ecs_vec_t refs; /* Cached components for non-this terms */ + 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 columns; + 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; + + /* 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_flags64_t *populated, + 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 empty tables in cache */ +int32_t flecs_query_cache_empty_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 without data */ +bool flecs_query_cache_search( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx); + +/* Cache search with data */ +bool flecs_query_cache_data_search( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx); + +/* Cache search without data where entire query is cached */ +bool flecs_query_is_cache_search( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx); + +/* Cache search with data where entire query is cached */ +bool flecs_query_is_cache_data_search( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx); + +/* Cache test without data */ +bool flecs_query_cache_test( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + bool first); + +/* Cache test without data where entire query is cached */ +bool flecs_query_is_cache_test( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + bool first); + +/* Cache test with data */ +bool flecs_query_cache_data_test( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + bool first); + +/* Cache test with data where entire query is cached */ +bool flecs_query_is_cache_data_test( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + bool first); + +/** + * @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 trivial queries. */ +bool flecs_query_trivial_search( + const ecs_query_impl_t *query, + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + bool first, + ecs_flags64_t field_set); + +/* Iterator for trivial queries. */ +bool flecs_query_trivial_search_nodata( + const ecs_query_impl_t *query, + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + bool first, + ecs_flags64_t field_set); + +/* Iterator for trivial queries with wildcard matching. */ +bool flecs_query_trivial_search_w_wildcards( + const ecs_query_impl_t *query, + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + bool first, + ecs_flags64_t field_set); + +/* Trivial test for constrained $this. */ +bool flecs_query_trivial_test( + const ecs_query_impl_t *query, + const ecs_query_run_ctx_t *ctx, + bool first, + ecs_flags64_t field_set); + +/* Trivial test for constrained $this with wildcard matching. */ +bool flecs_query_trivial_test_w_wildcards( + const ecs_query_impl_t *query, + 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_column( + ecs_iter_t *it, + int32_t field_index, + int32_t column); + +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, + int32_t column, + 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); + +void flecs_query_populate_field_from_range( + ecs_iter_t *it, + ecs_table_range_t *range, + int8_t field_index, + int32_t index); + +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_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); + +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); + + +/* Populate data fields */ + +bool flecs_query_populate( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + +bool flecs_query_populate_self( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + +bool flecs_query_populate_sparse( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx); + + +/* 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); + + +/* 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); + +/** + * @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) */ + int32_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_validate( + ecs_iter_t *it); + +bool flecs_iter_next_row( + ecs_iter_t *it); + +bool flecs_iter_next_instanced( + ecs_iter_t *it, + bool result); + +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); + +void flecs_commands_push( + ecs_stage_t *stage); + +void flecs_commands_pop( + ecs_stage_t *stage); + +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); + +#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_table_set_empty( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_delete_table( + ecs_world_t *world, + ecs_table_t *table); + +void flecs_process_pending_tables( + const ecs_world_t *world); + +/* 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_type_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); + +void flecs_instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_table_t *table, + int32_t row, + int32_t count); + +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, + int32_t count, + int32_t row, + ecs_entity_t *entities, + void *ptr, + ecs_id_t id, + const ecs_type_info_t *ti, + ecs_entity_t event, + ecs_iter_action_t hook); + +//////////////////////////////////////////////////////////////////////////////// +//// 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) { + flecs_type_info_free(world, 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 = ecs_vec_first(&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 = ecs_vec_first(&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 = ecs_vec_first(&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); + ecs_data_t *data = &result->data; + + /* Preallocate enough memory for initial components */ + ecs_allocator_t *a = &world->allocator; + ecs_vec_init_t(a, &data->entities, ecs_entity_t, EcsFirstUserComponentId); + ecs_vec_init_t(a, &data->columns[0].data, EcsComponent, EcsFirstUserComponentId); + ecs_vec_init_t(a, &data->columns[1].data, EcsIdentifier, EcsFirstUserComponentId); + ecs_vec_init_t(a, &data->columns[2].data, EcsIdentifier, EcsFirstUserComponentId); + + 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, 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, .src.id = EcsSelf }, + .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); + flecs_bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); + flecs_bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); + + /* Unqueryable entities */ + ecs_add_id(world, EcsThis, EcsNotQueryable); + ecs_add_id(world, EcsWildcard, EcsNotQueryable); + ecs_add_id(world, EcsAny, 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); + + /* 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); + + /* 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); + + /* 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, .src.id = EcsSelf }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_final + }); + + ecs_observer(world, { + .query.terms = { + { .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.id = EcsSelf } + }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_on_delete + }); + + ecs_observer(world, { + .query.terms = { + { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.id = EcsSelf } + }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_register_on_delete_object + }); + + ecs_observer(world, { + .query.terms = { + { .id = ecs_pair(EcsOnInstantiate, EcsWildcard), .src.id = EcsSelf } + }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_on_instantiate + }); + + ecs_observer(world, { + .query.terms = {{ .id = EcsSymmetric, .src.id = EcsSelf }}, + .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, .src.id = EcsSelf }}, + .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, .src.id = EcsSelf }}, + .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, .src.id = EcsSelf }}, + .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), .src.id = EcsSelf }, + }, + .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, .src.id = EcsSelf }}, + .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, .src.id = EcsSelf }}, + .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), .src.id = EcsSelf } + }, + .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, .src.id = EcsSelf } }, + .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, .src.id = EcsSelf, .inout = EcsInOutFilter }, + { .id = EcsDisabled, .src.id = EcsSelf }, + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_disable_observer + }); + + /* Observer that tracks whether modules are disabled */ + ecs_observer(world, { + .query.terms = { + { .id = EcsModule, .src.id = EcsSelf, .inout = EcsInOutFilter }, + { .id = EcsDisabled, .src.id = EcsSelf }, + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_disable_module + }); + + /* Set scope back to flecs core */ + ecs_set_scope(world, EcsFlecsCore); + + /* 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); + ecs_add_pair(world, EcsDisabled, 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); + + flecs_process_pending_tables(world); + + 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; + 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; + ecs_table_record_t *next = flecs_table_cache_next( + &each_iter->it, ecs_table_record_t); + it->flags |= EcsIterIsValid; + if (next) { + ecs_table_t *table = next->hdr.table; + it->table = table; + it->count = ecs_table_count(table); + it->entities = flecs_table_entities_array(table); + it->ids = &table->type.array[next->index]; + it->columns = &each_iter->columns; + it->sources = &each_iter->sources; + it->sizes = &each_iter->sizes; + it->ptrs = &each_iter->ptrs; + it->set_fields = 1; + each_iter->columns = next->index; + + if (next->column != -1) { + each_iter->ptrs = ecs_vec_first( + &it->table->data.columns[next->column].data); + } + 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; + char *token_buffer; + 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 = ']', + EcsTokMul = '*', + EcsTokComma = ',', + EcsTokSemiColon = ';', + EcsTokColon = ':', + EcsTokAssign = '=', + EcsTokBitwiseOr = '|', + EcsTokNot = '!', + EcsTokOptional = '?', + EcsTokAnnotation = '@', + EcsTokNewline = '\n', + EcsTokEq, + EcsTokNeq, + EcsTokMatch, + EcsTokOr, + EcsTokIdentifier, + EcsTokString, + EcsTokNumber, + EcsTokKeywordModule, + EcsTokKeywordUsing, + EcsTokKeywordWith, + EcsTokKeywordIf, + EcsTokKeywordElse, + EcsTokKeywordTemplate, + EcsTokKeywordProp, + EcsTokKeywordConst, +} 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; + +const char* flecs_script_expr( + ecs_script_parser_t *parser, + const char *ptr, + ecs_script_token_t *out, + char until); + +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( + 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); + +#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; + + /* For term parser */ + ecs_term_t *term; + ecs_oper_kind_t extra_oper; + ecs_term_ref_t *extra_args; +}; + +/** + * @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, +} 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; +}; + +typedef struct ecs_script_id_t { + const char *first; + const char *second; + ecs_id_t flag; + ecs_id_t eval; +} 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; + const char *expr; + ecs_value_t eval; + bool is_collection; +} ecs_script_component_t; + +typedef struct ecs_script_default_component_t { + ecs_script_node_t node; + const char *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; +} 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; + + // 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; + const char *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; + const char *expr; +} ecs_script_if_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); + +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); + +#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_allocator_t *allocator; + ecs_script_template_t *template; + ecs_entity_t module; + ecs_entity_t parent; + ecs_script_entity_t *entity; + ecs_vec_t using; + ecs_vec_t with; + ecs_vec_t annot; + ecs_entity_t with_relationship; + int32_t with_relationship_sp; + ecs_script_vars_t *vars; + ecs_stack_t stack; +} 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__) + +ecs_entity_t flecs_script_find_entity( + ecs_script_eval_visitor_t *v, + ecs_entity_t from, + const char *path); + +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, + const char *expr, + ecs_value_t *value); + +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_eval_visit_init( + ecs_script_impl_t *script, + ecs_script_eval_visitor_t *v); + +void flecs_script_eval_visit_fini( + ecs_script_eval_visitor_t *v); + +int flecs_script_eval_node( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node); + +#endif + + +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; +}; + +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); + +#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_vec_get(&column->data, column->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 = flecs_table_entities_array(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) +{ + 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; + } + + int32_t storage_index = ecs_table_type_to_column_index(child_table, i); + if (storage_index != -1) { + ecs_vec_t *column = &child_data->columns[storage_index].data; + component_data[diff.added.count] = ecs_vec_first(column); + } else { + component_data[diff.added.count] = NULL; + } + + diff.added.array[diff.added.count] = id; + diff.added.count ++; + } + + /* 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 */ + ecs_entity_t *instances = ecs_vec_first(&table->data.entities); + int32_t child_count = ecs_vec_count(&child_data->entities); + + 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. */ + ecs_entity_t *children = ecs_vec_first(&child_data->entities); + +#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 + + /* Create children */ + int32_t child_row; + const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL, + &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); + } + } +error: + return; +} + +void flecs_instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_table_t *table, + int32_t row, + int32_t count) +{ + ecs_record_t *record = flecs_entities_get_any(world, base); + ecs_table_t *base_table = record->table; + if (!base_table || !(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)) { + 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); + } + } +} + +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; + ecs_entity_t *entities = flecs_table_entities_array(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) { + flecs_invoke_hook(world, table, count, row, &entities[row], + ptr, 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; + ecs_xtor_t dtor = ti->hooks.dtor; + ecs_iter_action_t on_remove = ti->hooks.on_remove; + ecs_entity_t *entities = flecs_table_entities_array(table); + for (j = 0; j < count; j ++) { + ecs_entity_t e = entities[row + j]; + void *ptr = flecs_sparse_remove_fast(idr->sparse, 0, e); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (on_remove) { + flecs_invoke_hook(world, table, count, row, &entities[row], + ptr, id, ti, EcsOnRemove, on_remove); + } + 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) { + ecs_entity_t *entities = flecs_table_entities_array(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) { + ecs_entity_t *entities = flecs_table_entities_array(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_type_t *added, + ecs_flags32_t flags, + ecs_flags64_t set_mask, + bool construct) +{ + ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); + + if (added->count) { + ecs_flags32_t table_flags = table->flags; + + if (table_flags & EcsTableHasSparse) { + flecs_sparse_on_add(world, table, row, count, added, construct); + } + + if (table_flags & EcsTableHasUnion) { + flecs_union_on_add(world, table, row, count, added); + } + + if (table_flags & (EcsTableHasOnAdd|EcsTableHasIsA|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_type_t *removed) +{ + ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + + if (removed->count) { + ecs_flags32_t table_flags = table->flags; + + if (table_flags & EcsTableHasSparse) { + flecs_sparse_on_remove(world, table, row, count, removed); + } + + if (table_flags & EcsTableHasUnion) { + flecs_union_on_remove(world, table, row, count, removed); + } + + if (table_flags & (EcsTableHasOnRemove|EcsTableHasIsA| + 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 + }); + } + } +} + +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; + ecs_entity_t *entities = ecs_vec_get_t( + &dst->data.entities, ecs_entity_t, 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_vec_count(&table->data.entities) > row, + ECS_INTERNAL_ERROR, NULL); + flecs_notify_on_add( + world, table, NULL, row, 1, &diff->added, evt_flags, 0, ctor); + + return record; +} + +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_vec_count(&src_table->data.entities) > 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); + + /* 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->removed); + + /* Copy entity & components from src_table to dst_table */ + flecs_table_move(world, entity, entity, dst_table, dst_row, + src_table, src_row, ctor); + + /* 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->added, evt_flags, 0, ctor); + + flecs_update_name_index(world, src_table, dst_table, dst_row, 1); + +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->removed); + 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) { + flecs_notify_on_add(world, src_table, src_table, + ECS_RECORD_TO_ROW(record->row), 1, &diff->added, evt_flags, 0, + construct); + } + flecs_journal_end(); + return; + } + + 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); + } + +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; + } + + ecs_data_t *data = &table->data; + int32_t row = flecs_table_appendn(world, table, data, 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->added, + (component_data == NULL) ? 0 : EcsEventNoOnSet, 0, 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 = column->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + void *ptr = ecs_vec_get(&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_type_t set_type = { + .array = &table->data.columns[j].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); +} + +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 = flecs_id_record_get(world, id); + if (r->table) { + dst = flecs_get_component_ptr(r->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, + int32_t count, + int32_t row, + ecs_entity_t *entities, + void *ptr, + 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.ptrs[0] = ptr; + 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; + flecs_iter_validate(&it); + 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); + ecs_data_t *data = &table->data; + + ecs_entity_t *entities = ecs_vec_get_t( + &data->entities, ecs_entity_t, row); + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_vec_count(&data->entities), + 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; + } + + if (idr->flags & EcsIdIsSparse) { + int32_t j; + for (j = 0; j < count; j ++) { + void *ptr = flecs_sparse_get_any( + idr->sparse, 0, entities[j]); + flecs_invoke_hook(world, table, 1, row, &entities[j], ptr, + id, ti, EcsOnSet, on_set); + } + } else { + const ecs_table_record_t *tr = + flecs_id_record_get_table(idr, table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + + int32_t index = tr->column; + ecs_column_t *column = &table->data.columns[index]; + if (on_set) { + ecs_vec_t *c = &column->data; + void *ptr = ecs_vec_get(c, column->size, row); + flecs_invoke_hook(world, table, count, row, entities, ptr, + 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; + } + if (removed) { + diff.removed = *removed; + } + + 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); + + flecs_stage_from_world(&world); + ecs_entity_t entity = ecs_new(world); + ecs_record_t *r = flecs_entities_get(world, entity); + + ecs_table_diff_t table_diff = { .added = table->type }; + 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; + } + 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, desc->symbol); + } 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) { + ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); + } 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: + 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; + } + + /* 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 (stage->defer) { + 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 + }; + + 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; + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + int32_t i, count = ecs_vec_count(&table->data.entities); + 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.removed); + 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 count = ecs_vec_count(&world->store.marked_ids); + + /* Make sure we're evaluating a consistent list of non-empty tables */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + /* 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); + + /* All marked tables are empty, ensure they're in the right list */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + /* 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); + int32_t i; 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); + + 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_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 + }; + + 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: + 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 }; + ecs_record_t *dst_r = flecs_entities_get(world, dst); + flecs_new_entity(world, dst, dst_r, dst_table, &diff, true, 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_type_t type = { + .array = &dst_table->data.columns[i].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; +} + +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; + } + + 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; + } + + 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 ecs_vec_get_t(&table->data.entities, ecs_entity_t, + ECS_RECORD_TO_ROW(record->row))[0]; +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_check(is_new || !ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, + "cannot emplace a component the entity already has"); + + 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_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 (!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_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_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_enable(stage, entity, id, enable)) { + return; + } + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_entity_t bs_id = id | ECS_TOGGLE; + + ecs_table_t *table = r->table; + int32_t index = -1; + if (table) { + index = ecs_table_get_type_index(world, table, bs_id); + } + + if (index == -1) { + ecs_add_id(world, entity, bs_id); + flecs_defer_end(world, stage); + ecs_enable_id(world, entity, id, enable); + return; + } + + 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; + } + + ecs_id_record_t *idr = flecs_id_record_get(world, id); + int32_t column; + 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; + 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) +{ + return (ecs_search(world, ecs_get_table(world, entity), id, 0) != -1); +} + +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); + + 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.array; + 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); + ecs_strbuf_appendch(buf, ','); + ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); + ecs_strbuf_appendch(buf, ')'); + } else { + ecs_entity_t e = id & ECS_COMPONENT_MASK; + ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); + } + +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); + + 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); + flecs_component_ptr_t ptr = flecs_get_component_ptr( + start_table, row, idr); + if (ptr.ptr) { + const ecs_type_info_t *ti = ptr.ti; + 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, 1, row, &entity, + ptr.ptr, cmd->id, ptr.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); + + table = flecs_find_table_add(world, table, id, diff); + + 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); + } + 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 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); + cmd->is._1.value = NULL; + } + } else { + ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); + } + + if (cmd->kind == EcsCmdSet) { + /* A set operation is add + copy + modified. We just did + * the add the 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) { + flecs_defer_begin(world, world->stages[0]); + flecs_notify_on_add(world, table, start_table, + ECS_RECORD_TO_ROW(r->row), 1, &added, 0, set_mask, true); + flecs_defer_end(world, world->stages[0]); + } + + 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) { + /* Test whether we're flushing to another queue or whether we're + * flushing to the storage */ + bool merge_to_world = false; + if (flecs_poly_is(world, ecs_world_t)) { + merge_to_world = world->stages[0]->defer == 0; + } + + ecs_stage_t *dst_stage = flecs_stage_from_world(&world); + ecs_commands_t *commands = stage->cmd; + ecs_vec_t *queue = &commands->queue; + + if (ecs_vec_count(queue)) { + /* 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); + flecs_commands_push(stage); + + 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); + } + } + + flecs_stack_reset(&commands->stack); + ecs_vec_clear(queue); + flecs_commands_pop(stage); + + 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); + } + } + + 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) +{ + 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)) { + 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); + 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) { + 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); + return flecs_ito(uint64_t, atoll(name + 1)); +} + +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) { + 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 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); + + 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), + .src.id = EcsSelf + }, + .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) +{ + 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); + } 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); + 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); + 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 = "."; + } + + parent = flecs_get_parent_from_path( + stage, parent, &path, sep, prefix, true); + + 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_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); + + 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); + parent = flecs_get_parent_from_path( + world, parent, &path, sep, prefix, !entity); + + 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) && + (ecs_get_stage_count(world) <= 1); + + 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. + */ + + +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; + } +} + +/** + * @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, columns, int32_t, it->field_count); + INIT_CACHE(it, stack, fields, variables, ecs_var_t, it->variable_count); + INIT_CACHE(it, stack, fields, ptrs, void*, it->field_count); +} + +void flecs_iter_validate( + ecs_iter_t *it) +{ + ECS_BIT_SET(it->flags, EcsIterIsValid); + + /* Make sure multithreaded iterator isn't created for real world */ + ecs_world_t *world = it->real_world; + flecs_poly_assert(world, ecs_world_t); + ecs_check(!(world->flags & EcsWorldMultiThreaded) || it->world != it->real_world, + ECS_INVALID_PARAMETER, + "create iterator for stage when world is in multithreaded mode"); + (void)world; +error: + return; +} + +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, columns, int32_t, it->field_count); + FINI_CACHE(it, variables, ecs_var_t, it->variable_count); + FINI_CACHE(it, ptrs, void*, it->field_count); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_stack_restore_cursor(&stage->allocators.iter_stack, + it->priv_.cache.stack_cursor); +} + +bool flecs_iter_next_row( + ecs_iter_t *it) +{ + ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); + + bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + if (!is_instanced) { + int32_t instance_count = it->instance_count; + int32_t count = it->count; + int32_t offset = it->offset; + + if (instance_count > count && offset < (instance_count - 1)) { + ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); + int t, field_count = it->field_count; + + for (t = 0; t < field_count; t ++) { + ecs_entity_t src = it->sources[t]; + if (!src) { + void *ptr = it->ptrs[t]; + if (ptr) { + it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); + } + } + } + + if (it->entities) { + it->entities ++; + } + it->offset ++; + + return true; + } + } + + return false; +} + +bool flecs_iter_next_instanced( + ecs_iter_t *it, + bool result) +{ + it->instance_count = it->count; + bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + + if (result && !is_instanced && it->count && it->shared_fields) { + it->count = 1; + } + + return result; +} + +/* --- Public API --- */ + +void* ecs_field_w_size( + const ecs_iter_t *it, + size_t size, + int32_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) && (!it->ptrs[index])), + ECS_INVALID_PARAMETER, "mismatching size for field %d", index); + (void)size; + + return it->ptrs[index]; +error: + return NULL; +} + +bool ecs_field_is_readonly( + const ecs_iter_t *it, + int32_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, + int32_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, + int32_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, + int32_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, + int32_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, + int32_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->columns[index]; +error: + return 0; +} + +ecs_entity_t ecs_field_src( + const ecs_iter_t *it, + int32_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, + int32_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; + int 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); + ECS_BIT_SET(it->flags, EcsIterIsInstanced); + + 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_BIT_SET(it->flags, EcsIterIsInstanced); + + 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); + ECS_BIT_SET(it->flags, EcsIterIsInstanced); + + 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_vec_get_t(&table->data.entities, ecs_entity_t, + var->range.offset)[0]; + } + } + } 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); + +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_vec_get_t( + &table->data.entities, ecs_entity_t, range->offset)[0]; + } else { + var->entity = 0; + } + + it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); + +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 }; +} + +static +void flecs_offset_iter( + ecs_iter_t *it, + int32_t offset) +{ + it->entities = &it->entities[offset]; + + int32_t t, field_count = it->field_count; + void **it_ptrs = it->ptrs; + if (it_ptrs) { + for (t = 0; t < field_count; t ++) { + void *ptrs = it_ptrs[t]; + if (!ptrs) { + continue; + } + + if (it->sources[t]) { + continue; + } + + it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); + } + } +} + +static +bool ecs_page_next_instanced( + 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; + bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + + 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_)); + + /* Keep instancing setting from original iterator */ + ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); + + 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 { + it->offset += offset; + count = it->count -= offset; + iter->offset = 0; + flecs_offset_iter(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: + if (!ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { + it->offset = 0; + } + + return true; +done: + /* Cleanup iterator resources if it wasn't yet depleted */ + ecs_iter_fini(chain_it); +depleted: +error: + return false; +} + +bool ecs_page_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + + ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); + + if (flecs_iter_next_row(it)) { + return true; + } + + return flecs_iter_next_instanced(it, ecs_page_next_instanced(it)); +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 }; +} + +static +bool ecs_worker_next_instanced( + 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); + + bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); + + ecs_iter_t *chain_it = it->chain_it; + ecs_worker_iter_t *iter = &it->priv_.iter.worker; + int32_t res_count = iter->count, res_index = iter->index; + int32_t per_worker, instances_per_worker, first; + + do { + if (!ecs_iter_next(chain_it)) { + return false; + } + + /* Copy everything up to the private iterator data */ + ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv_)); + + /* Keep instancing setting from original iterator */ + ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); + + int32_t count = it->count; + int32_t instance_count = it->instance_count; + per_worker = count / res_count; + instances_per_worker = instance_count / res_count; + first = per_worker * res_index; + count -= per_worker * res_count; + + if (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->instance_count = instances_per_worker; + it->frame_offset += first; + + flecs_offset_iter(it, it->offset + first); + it->count = per_worker; + + if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { + it->offset += first; + } else { + it->offset = 0; + } + + return true; +error: + return false; +} + +bool ecs_worker_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + + ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); + + if (flecs_iter_next_row(it)) { + return true; + } + + return flecs_iter_next_instanced(it, ecs_worker_next_instanced(it)); +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: + 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); + iders[count] = flecs_event_id_record_get_if(er, id_fwc); + count += iders[count] != 0; + iders[count] = flecs_event_id_record_get_if(er, id_swc); + count += iders[count] != 0; + iders[count] = flecs_event_id_record_get_if(er, id_pwc); + count += iders[count] != 0; + } else { + 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; + if (entity_count) { + it->entities = ecs_vec_first(&table->data.entities); + } + + /* Treat as new event as this could invoke observers again for + * different tables. */ + it->event_cur = ++ world->event_id; + + 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; + } + + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + 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; +} + +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); + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + + 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) +{ + ecs_entity_t *entities = ecs_vec_get_t(&table->data.entities, + ecs_entity_t, 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, + 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; + 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_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) { + ecs_entity_t *entities = ecs_vec_get_t( + &table->data.entities, ecs_entity_t, offset); + flecs_invoke_hook(world, table, count, offset, entities, + dst, 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_vec_get(&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); + +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, + int32_t offset, + 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; + it->ptrs[0] = NULL; + ECS_CONST_CAST(int32_t*, it->sizes)[0] = 0; /* safe, owned by observer */ + + 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->ptrs[0] = ecs_vec_get(&c->data, c->ti->size, offset); + 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. */ + void *base_ptr = it->ptrs[0]; + void *ptr = flecs_override(it, emit_ids, id, table, idr); + if (ptr) { + override = true; + it->ptrs[0] = ptr; + } + + 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; + 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->ptrs[0] = base_ptr; + } + } +} + +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, + int32_t offset, + 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, offset, 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; + } + + int32_t rc_offset = ECS_RECORD_TO_ROW(rc_record->row); + 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, + rc_offset, 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) +{ + 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 offset = ECS_RECORD_TO_ROW(tgt_record->row); + 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; + } + + /* 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); + 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, + offset, 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) +{ + 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); +} + +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); + 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); + + int32_t offset = ECS_RECORD_TO_ROW(r->row); + flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, + rc_idr, elem->src, r->table, tr->index, offset, trav); + } + } + + /* Propagate events for new reachable ids downwards */ + if (table->_->traversable_count) { + int32_t i; + ecs_entity_t *entities = flecs_table_entities_array(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; + + 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_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; + void *ptrs_cache = NULL; + ecs_size_t sizes_cache = 0; + int32_t columns_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, + .ptrs = &ptrs_cache, + .sizes = &sizes_cache, + .columns = &columns_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_vec_get_t(&storage->entities, ecs_entity_t, 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]; + int32_t ider_i, ider_count = 0; + bool is_pair = ECS_IS_PAIR(id); + void *override_ptr = 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); + 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) { + ecs_table_record_t *base_tr = NULL; + 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); + ecs_vec_t *base_v = &base_table->data.columns[base_column].data; + override_ptr = ecs_vec_get(base_v, ti->size, base_row); + } + } + } + } + } + + 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 column = tr->index, storage_i; + it.columns[0] = column + 1; + it.ptrs[0] = NULL; + 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 (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 = flecs_table_entities_array(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_vec_get(&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, ti, ptr, override_ptr, offset, count); + } else if (er_onset) { + /* 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.ptrs[0] = ptr; + 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.ptrs[0] = ptr; + } + } + + /* 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; + + 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 == EcsOnTableFill) { + return EcsIdHasOnTableFill; + } + if (e == EcsOnTableEmpty) { + return EcsIdHasOnTableEmpty; + } + 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 +bool flecs_is_simple_result( + ecs_iter_t *it) +{ + return (it->count == 1) || (it->sizes[0] == 0) || (it->sources[0] == 0); +} + +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, + bool simple_result) +{ + 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); + } + + bool instanced = query->flags & EcsQueryIsInstanced; + bool match_this = query->flags & EcsQueryMatchThis; + bool table_only = it->flags & EcsIterTableOnly; + + if (match_this && (simple_result || instanced || table_only)) { + callback(it); + 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; + } + + 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); + query->eval_count ++; + } else if (observer_src == e) { + ecs_entity_t dummy = 0; + it->entities = &dummy; + if (!src) { + it->sources[0] = e; + } + + callback(it); + query->eval_count ++; + it->sources[0] = src; + break; + } + } + it->entities = entities; + it->count = count; + } + + 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, + flecs_is_simple_result(it)); + 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, + bool simple_result) +{ + 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 = o->query; + + 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, simple_result); + } + + 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); + + bool simple_result = flecs_is_simple_result(it); + 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, simple_result); + } + + 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; + } + + impl->last_event_id[0] = world->event_id; + + ecs_table_t *table = it->table; + ecs_table_t *prev_table = it->other_table; + int32_t pivot_term = it->term_index; + ecs_term_t *term = &o->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; + } + } + + ECS_BIT_COND(user_it.flags, EcsIterNoData, + ECS_BIT_IS_SET(o->query->flags, EcsQueryNoData)); + + /* 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. */ + int32_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.ptrs[pivot_field] = it->ptrs[0]; + user_it.columns[pivot_field] = it->columns[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); + + return; + } 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; +} + +/* 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) +{ + ecs_run_action_t run = o->run; + if (!run) { + run = flecs_multi_observer_invoke; + } + + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + 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_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(int16_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_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; + 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); + + 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 (query->flags & EcsQueryMatchPrefab) { + child_desc.query.flags |= EcsQueryMatchPrefab; + } + + if (query->flags & EcsQueryMatchDisabled) { + child_desc.query.flags |= EcsQueryMatchDisabled; + } + + 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; + + /* 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) { + 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_sparse_add_t( + &world->store.observers, ecs_observer_impl_t); + ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_poly_init(impl, ecs_observer_t); + ecs_observer_t *o = &impl->pub; + impl->id = flecs_sparse_last_id(&world->store.observers); + 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_; + + /* 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; + } else { + o->events[i] = event; + } + + 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 (desc->yield_existing) { + flecs_observer_yield_existing(world, o); + } + + 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; + 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_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 (desc->run) { + o->run = desc->run; + } + + if (desc->callback) { + o->callback = desc->callback; + } + + 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); + } + } + + if (o->run_ctx_free) { + if (o->run_ctx && + o->run_ctx != desc->run_ctx) + { + o->run_ctx_free(o->run_ctx); + } + } + + 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 & 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_sparse_remove_t( + &world->store.observers, ecs_observer_impl_t, impl->id); +} + +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); +} + +/* 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->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(world->flags, + 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) { + ecs_assert(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); + /* Entity has the component */ + ecs_vec_t *column = &table->data.columns[tr->column].data; + existing = ecs_vec_get(column, size, ECS_RECORD_TO_ROW(r->row)); + } + } + } + + /* 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); +} + +static +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); +} + +static +void flecs_commands_fini( + ecs_stage_t *stage, + ecs_commands_t *cmd) +{ + /* Make sure stage has no unmerged data */ + ecs_assert(ecs_vec_count(&stage->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); +} + +void flecs_commands_push( + ecs_stage_t *stage) +{ + int32_t sp = ++ stage->cmd_sp; + ecs_assert(sp < ECS_MAX_DEFER_STACK, ECS_INTERNAL_ERROR, NULL); + stage->cmd = &stage->cmd_stack[sp]; +} + +void flecs_commands_pop( + ecs_stage_t *stage) +{ + int32_t sp = -- stage->cmd_sp; + ecs_assert(sp >= 0, ECS_INTERNAL_ERROR, NULL); + stage->cmd = &stage->cmd_stack[sp]; +} + +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 < ECS_MAX_DEFER_STACK; 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 < ECS_MAX_DEFER_STACK; i ++) { + flecs_commands_fini(stage, &stage->cmd_stack[i]); + } + + 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); + + flecs_process_pending_tables(world); + + 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(&world->allocator, ti->size); + 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; +const ecs_entity_t EcsOnTableEmpty = FLECS_HI_COMPONENT_ID + 47; +const ecs_entity_t EcsOnTableFill = FLECS_HI_COMPONENT_ID + 48; + +/* 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; +#endif + +/* REST module components */ +#ifdef FLECS_REST +const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 118; +#endif + +/* 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_DECLARE(EcsUnitPrefixes); + +ECS_DECLARE(EcsYocto); +ECS_DECLARE(EcsZepto); +ECS_DECLARE(EcsAtto); +ECS_DECLARE(EcsFemto); +ECS_DECLARE(EcsPico); +ECS_DECLARE(EcsNano); +ECS_DECLARE(EcsMicro); +ECS_DECLARE(EcsMilli); +ECS_DECLARE(EcsCenti); +ECS_DECLARE(EcsDeci); +ECS_DECLARE(EcsDeca); +ECS_DECLARE(EcsHecto); +ECS_DECLARE(EcsKilo); +ECS_DECLARE(EcsMega); +ECS_DECLARE(EcsGiga); +ECS_DECLARE(EcsTera); +ECS_DECLARE(EcsPeta); +ECS_DECLARE(EcsExa); +ECS_DECLARE(EcsZetta); +ECS_DECLARE(EcsYotta); + +ECS_DECLARE(EcsKibi); +ECS_DECLARE(EcsMebi); +ECS_DECLARE(EcsGibi); +ECS_DECLARE(EcsTebi); +ECS_DECLARE(EcsPebi); +ECS_DECLARE(EcsExbi); +ECS_DECLARE(EcsZebi); +ECS_DECLARE(EcsYobi); + +ECS_DECLARE(EcsDuration); + ECS_DECLARE(EcsPicoSeconds); + ECS_DECLARE(EcsNanoSeconds); + ECS_DECLARE(EcsMicroSeconds); + ECS_DECLARE(EcsMilliSeconds); + ECS_DECLARE(EcsSeconds); + ECS_DECLARE(EcsMinutes); + ECS_DECLARE(EcsHours); + ECS_DECLARE(EcsDays); + +ECS_DECLARE(EcsTime); + ECS_DECLARE(EcsDate); + +ECS_DECLARE(EcsMass); + ECS_DECLARE(EcsGrams); + ECS_DECLARE(EcsKiloGrams); + +ECS_DECLARE(EcsElectricCurrent); + ECS_DECLARE(EcsAmpere); + +ECS_DECLARE(EcsAmount); + ECS_DECLARE(EcsMole); + +ECS_DECLARE(EcsLuminousIntensity); + ECS_DECLARE(EcsCandela); + +ECS_DECLARE(EcsForce); + ECS_DECLARE(EcsNewton); + +ECS_DECLARE(EcsLength); + ECS_DECLARE(EcsMeters); + ECS_DECLARE(EcsPicoMeters); + ECS_DECLARE(EcsNanoMeters); + ECS_DECLARE(EcsMicroMeters); + ECS_DECLARE(EcsMilliMeters); + ECS_DECLARE(EcsCentiMeters); + ECS_DECLARE(EcsKiloMeters); + ECS_DECLARE(EcsMiles); + ECS_DECLARE(EcsPixels); + +ECS_DECLARE(EcsPressure); + ECS_DECLARE(EcsPascal); + ECS_DECLARE(EcsBar); + +ECS_DECLARE(EcsSpeed); + ECS_DECLARE(EcsMetersPerSecond); + ECS_DECLARE(EcsKiloMetersPerSecond); + ECS_DECLARE(EcsKiloMetersPerHour); + ECS_DECLARE(EcsMilesPerHour); + +ECS_DECLARE(EcsAcceleration); + +ECS_DECLARE(EcsTemperature); + ECS_DECLARE(EcsKelvin); + ECS_DECLARE(EcsCelsius); + ECS_DECLARE(EcsFahrenheit); + +ECS_DECLARE(EcsData); + ECS_DECLARE(EcsBits); + ECS_DECLARE(EcsKiloBits); + ECS_DECLARE(EcsMegaBits); + ECS_DECLARE(EcsGigaBits); + ECS_DECLARE(EcsBytes); + ECS_DECLARE(EcsKiloBytes); + ECS_DECLARE(EcsMegaBytes); + ECS_DECLARE(EcsGigaBytes); + ECS_DECLARE(EcsKibiBytes); + ECS_DECLARE(EcsGibiBytes); + ECS_DECLARE(EcsMebiBytes); + +ECS_DECLARE(EcsDataRate); + ECS_DECLARE(EcsBitsPerSecond); + ECS_DECLARE(EcsKiloBitsPerSecond); + ECS_DECLARE(EcsMegaBitsPerSecond); + ECS_DECLARE(EcsGigaBitsPerSecond); + ECS_DECLARE(EcsBytesPerSecond); + ECS_DECLARE(EcsKiloBytesPerSecond); + ECS_DECLARE(EcsMegaBytesPerSecond); + ECS_DECLARE(EcsGigaBytesPerSecond); + +ECS_DECLARE(EcsPercentage); + +ECS_DECLARE(EcsAngle); + ECS_DECLARE(EcsRadians); + ECS_DECLARE(EcsDegrees); + +ECS_DECLARE(EcsColor); + ECS_DECLARE(EcsColorRgb); + ECS_DECLARE(EcsColorHsl); + ECS_DECLARE(EcsColorCss); + +ECS_DECLARE(EcsBel); +ECS_DECLARE(EcsDeciBel); + +ECS_DECLARE(EcsFrequency); + ECS_DECLARE(EcsHertz); + ECS_DECLARE(EcsKiloHertz); + ECS_DECLARE(EcsMegaHertz); + ECS_DECLARE(EcsGigaHertz); + +ECS_DECLARE(EcsUri); + ECS_DECLARE(EcsUriHyperlink); + ECS_DECLARE(EcsUriImage); + ECS_DECLARE(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) || + (ecs_get_stage_count(world) <= 1), ECS_INVALID_WHILE_READONLY, NULL); + + state->is_readonly = is_readonly; + state->is_deferred = stage->defer != 0; + + /* Silence readonly checks */ + world->flags &= ~EcsWorldReadonly; + + /* Hack around safety checks (this ought to look ugly) */ + state->defer_count = stage->defer; + state->commands = stage->cmd->queue; + state->defer_stack = stage->cmd->stack; + flecs_stack_init(&stage->cmd->stack); + state->scope = stage->scope; + state->with = stage->with; + stage->defer = 0; + ecs_vec_init_t(NULL, &stage->cmd->queue, ecs_cmd_t, 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; + ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); + stage->cmd->queue = state->commands; + flecs_stack_fini(&stage->cmd->stack); + stage->cmd->stack = state->defer_stack; + 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->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 + }); + } + } +} + +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.depth_ids, ecs_entity_t, 0); + ecs_map_init(&world->store.entity_to_depth, &world->allocator); + + /* 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); + + /* Initilaize observer sparse set */ + flecs_sparse_init_t(&world->store.observers, + a, &world->allocators.sparse_chunk, ecs_observer_impl_t); +} + +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_table_cache_iter_t it; + + 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 = table->data.entities.count; + ecs_entity_t *entities = table->data.entities.array; + + 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); + if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { + ecs_delete(world, entities[i]); + } + } + } 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); + if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) { + ecs_delete(world, entities[i]); + } + } + } + } +} + +static +void flecs_fini_roots( + ecs_world_t *world) +{ + ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); + + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + /* 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(flecs_sparse_count(&world->store.observers) == 0, + ECS_INTERNAL_ERROR, NULL); + flecs_sparse_fini(&world->store.observers); + + 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.depth_ids, ecs_entity_t); + ecs_map_fini(&world->store.entity_to_depth); +} + +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; + + world->self = world; + flecs_sparse_init_t(&world->type_info, a, + &world->allocators.sparse_chunk, ecs_type_info_t); + 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); + + world->pending_tables = ecs_os_calloc_t(ecs_sparse_t); + flecs_sparse_init_t(world->pending_tables, a, + &world->allocators.sparse_chunk, ecs_table_t*); + world->pending_buffer = ecs_os_calloc_t(ecs_sparse_t); + flecs_sparse_init_t(world->pending_buffer, a, + &world->allocators.sparse_chunk, ecs_table_t*); + + 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); + + 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, 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, 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); +} + +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); + + flecs_stage_from_world(&world); + + /* Ensure that no tables have yet been created for the component */ + ecs_assert( ecs_id_in_use(world, component) == false, + ECS_ALREADY_IN_USE, ecs_get_name(world, component)); + ecs_assert( ecs_id_in_use(world, ecs_pair(component, EcsWildcard)) == false, + ECS_ALREADY_IN_USE, ecs_get_name(world, component)); + + 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->ctx_free) ti->hooks.ctx_free = h->ctx_free; + if (h->binding_ctx_free) ti->hooks.binding_ctx_free = h->binding_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 && !h->copy_ctor) { + ti->hooks.copy_ctor = flecs_default_copy_ctor; + } + + if (h->move && !h->move_ctor) { + ti->hooks.move_ctor = flecs_default_move_ctor; + } + + if (!h->ctor_move_dtor) { + if (h->move) { + if (h->dtor) { + if (h->move_ctor) { + /* 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 { + /* 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 { + /* 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) { + if (h->dtor) { + ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; + } else { + ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; + } + } + } + } + + if (!h->move_dtor) { + if (h->move) { + if (h->dtor) { + ti->hooks.move_dtor = flecs_default_move_w_dtor; + } else { + ti->hooks.move_dtor = flecs_default_move; + } + } else { + if (h->dtor) { + ti->hooks.move_dtor = flecs_default_dtor; + } + } + } + +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_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) +{ + int32_t i, count = flecs_sparse_count(&world->type_info); + ecs_sparse_t *type_info = &world->type_info; + for (i = 0; i < count; i ++) { + ecs_type_info_t *ti = flecs_sparse_get_dense_t(type_info, + ecs_type_info_t, i); + flecs_type_info_fini(ti); + } + flecs_sparse_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_sparse_fini(world->pending_tables); + flecs_sparse_fini(world->pending_buffer); + ecs_os_free(world->pending_tables); + ecs_os_free(world->pending_buffer); + 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_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_process_pending_tables(world); + 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); + flecs_process_pending_tables(world); + 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); + ecs_check(!id_end || id_end > flecs_entities_max_id(world), + ECS_INVALID_PARAMETER, NULL); + + uint32_t start = (uint32_t)id_start; + uint32_t end = (uint32_t)id_end; + + if (flecs_entities_max_id(world) < start) { + 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 flecs_sparse_try_t(&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 = flecs_sparse_ensure_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); + flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, 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->name) { + /* Safe to cast away const, world has ownership over string */ + ecs_os_free(ECS_CONST_CAST(char*, ti->name)); + ti->name = NULL; + } +} + +void flecs_type_info_free( + ecs_world_t *world, + ecs_entity_t component) +{ + 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 = flecs_sparse_try_t(&world->type_info, + ecs_type_info_t, component); + if (ti) { + flecs_type_info_fini(ti); + flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, 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_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)); + + *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(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); + + 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"); + + 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; +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; + } + + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + /* 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; + ecs_entity_t *entities = table->data.entities.array; + if (!ecs_query_is_true(query)) { + ecs_add_id(world, entities[i], EcsEmpty); + } + } + } + } + + flecs_defer_end(world, world->stages[0]); +} + +/** Walk over tables that had a state change which requires bookkeeping */ +void flecs_process_pending_tables( + const ecs_world_t *world_r) +{ + flecs_poly_assert(world_r, ecs_world_t); + + /* We can't update the administration while in readonly mode, but we can + * ensure that when this function is called there are no pending events. */ + if (world_r->flags & EcsWorldReadonly) { + ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, + ECS_INTERNAL_ERROR, NULL); + return; + } + + /* Safe to cast, world is not readonly */ + ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, world_r); + + /* If pending buffer is NULL there already is a stackframe that's iterating + * the table list. This can happen when an observer for a table event results + * in a mutation that causes another table to change state. A typical + * example of this is a system that becomes active/inactive as the result of + * a query (and as a result, its matched tables) becoming empty/non empty */ + if (!world->pending_buffer) { + return; + } + + /* Swap buffer. The logic could in theory have been implemented with a + * single sparse set, but that would've complicated (and slowed down) the + * iteration. Additionally, by using a double buffer approach we can still + * keep most of the original ordering of events intact, which is desirable + * as it means that the ordering of tables in the internal data structures is + * more predictable. */ + int32_t i, count = flecs_sparse_count(world->pending_tables); + if (!count) { + return; + } + + flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); + + do { + ecs_sparse_t *pending_tables = world->pending_tables; + world->pending_tables = world->pending_buffer; + world->pending_buffer = NULL; + + /* Make sure that any ECS operations that occur while delivering the + * events does not cause inconsistencies, like sending an Empty + * notification for a table that just became non-empty. */ + flecs_defer_begin(world, world->stages[0]); + + for (i = 0; i < count; i ++) { + ecs_table_t *table = flecs_sparse_get_dense_t( + pending_tables, ecs_table_t*, i)[0]; + if (!table->id) { + /* Table is being deleted, ignore empty events */ + continue; + } + + /* For each id in the table, add it to the empty/non empty list + * based on its current state */ + if (flecs_table_records_update_empty(table)) { + int32_t table_count = ecs_table_count(table); + if (table->flags & (EcsTableHasOnTableFill|EcsTableHasOnTableEmpty)) { + /* Only emit an event when there was a change in the + * administration. It is possible that a table ended up in the + * pending_tables list by going from empty->non-empty, but then + * became empty again. By the time we run this code, no changes + * in the administration would actually be made. */ + ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; + if (ecs_should_log_3()) { + ecs_dbg_3("table %u state change (%s)", + (uint32_t)table->id, + table_count ? "non-empty" : "empty"); + } + + ecs_log_push_3(); + + flecs_table_emit(world, table, evt); + + ecs_log_pop_3(); + } + world->info.empty_table_count += (table_count == 0) * 2 - 1; + } + } + + flecs_sparse_clear(pending_tables); + ecs_defer_end(world); + + world->pending_buffer = pending_tables; + } while ((count = flecs_sparse_count(world->pending_tables))); + + flecs_journal_end(); +} + +void flecs_table_set_empty( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_table_count(table)) { + table->_->generation = 0; + } + + flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, + (uint32_t)table->id)[0] = table; +} + +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) || + (flecs_table_cache_empty_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 || (flags & EcsAperiodicEmptyTables)) { + flecs_process_pending_tables(world); + } + + 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, + ecs_id_t id, + uint16_t clear_generation, + uint16_t delete_generation, + int32_t min_id_count, + double time_budget_seconds) +{ + flecs_poly_assert(world, ecs_world_t); + + /* Make sure empty tables are in the empty table lists */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + + ecs_time_t start = {0}, cur = {0}; + int32_t delete_count = 0, clear_count = 0; + bool time_budget = false; + + 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)) { + if (flecs_table_shrink(world, table)) { + clear_count ++; + } + } + } + } + +done: + if (ecs_should_log_1() && ecs_os_has_time()) { + if (delete_count) { + ecs_dbg_1("#[red]deleted#[normal] %d empty tables in %.2fs", + delete_count, ecs_time_measure(&start)); + } + if (clear_count) { + ecs_dbg_1("#[red]cleared#[normal] %d empty tables in %.2fs", + clear_count, ecs_time_measure(&start)); + } + } + + return delete_count; +} + +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; +} + +/** + * @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: + 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; + rit.flags |= EcsIterIsInstanced; + + 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; + rit.flags |= EcsIterIsInstanced; + 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; + +EMSCRIPTEN_KEEPALIVE +char* flecs_explorer_request(const char *method, char *request) { + ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; + ecs_http_server_request(flecs_wasm_rest_server, method, request, &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; + + /* Don't set FPS & threads if using emscripten */ +#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); +#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_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_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, EcsOnTableFill, "Event emitted when table becomes non-empty"); + ecs_doc_set_brief(world, EcsOnTableEmpty, "Event emitted when table becomes empty"); + 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, 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) +{ + // Symbol is same as name, but with '::' replaced with '.' + ecs_os_strcpy(symbol_name, type_name); + + char *ptr; + size_t i; + for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) { + if (*ptr == ':') { + symbol_name[i] = '.'; + ptr ++; + } else { + symbol_name[i] = *ptr; + } + } + + 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; +} + +// Validate registered component +void ecs_cpp_component_validate( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + const char *symbol, + size_t size, + size_t alignment, + bool implicit_name) +{ + /* If entity has a name check if it matches */ + if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { + if (!implicit_name && id >= EcsFirstUserComponentId) { +#ifndef FLECS_NDEBUG + char *path = ecs_get_path_w_sep( + world, 0, id, "::", NULL); + if (ecs_os_strcmp(path, name)) { + ecs_abort(ECS_INCONSISTENT_NAME, + "component '%s' already registered with name '%s'", + name, path); + } + ecs_os_free(path); +#endif + } + + if (symbol) { + const char *existing_symbol = ecs_get_symbol(world, id); + if (existing_symbol) { + if (ecs_os_strcmp(symbol, existing_symbol)) { + ecs_abort(ECS_INCONSISTENT_NAME, + "component '%s' with symbol '%s' already registered with symbol '%s'", + name, symbol, existing_symbol); + } + } + } + } else { + /* Ensure that the entity id valid */ + if (!ecs_is_alive(world, id)) { + ecs_make_alive(world, id); + } + + /* Register name with entity, so that when the entity is created the + * correct id will be resolved from the name. Only do this when the + * entity is empty. */ + ecs_add_path_w_sep(world, id, 0, name, "::", "::"); + } + + /* If a component was already registered with this id but with a + * different size, the ecs_component_init function will fail. */ + + /* We need to explicitly call ecs_component_init here again. Even though + * the component was already registered, it may have been registered + * with a different world. This ensures that the component is registered + * with the same id for the current world. + * If the component was registered already, nothing will change. */ + ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t){ + .entity = id, + .type.size = flecs_uto(int32_t, size), + .type.alignment = flecs_uto(int32_t, alignment) + }); + (void)ent; + ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); +} + +ecs_entity_t ecs_cpp_component_register( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + const char *symbol, + ecs_size_t size, + ecs_size_t alignment, + bool implicit_name, + bool *existing_out) +{ + (void)size; + (void)alignment; + + /* If the component is not yet registered, ensure no other component + * or entity has been registered with this name. Ensure component is + * looked up from root. */ + bool existing = false; + 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 = 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) || + component->size != size || + component->alignment != alignment) + { + 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); + } + 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); + } + + if (existing_out) { + *existing_out = existing; + } + + return ent; +} + +ecs_entity_t ecs_cpp_component_register_explicit( + ecs_world_t *world, + ecs_entity_t s_id, + ecs_entity_t id, + const char *name, + const char *type_name, + const char *symbol, + size_t size, + size_t alignment, + bool is_component, + bool *existing_out) +{ + char *existing_name = NULL; + if (existing_out) *existing_out = false; + + // 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; + if (existing_out) *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) +{ + (void)world; + (void)id; +#ifdef FLECS_META + ecs_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); + ecs_set(world, id, EcsEnum, {0}); + 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, + int value) +{ + 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"); + ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED, + "enum component must have 32bit size"); + #endif + +#ifdef FLECS_META + ecs_set_id(world, id, ecs_pair(EcsConstant, ecs_id(ecs_i32_t)), + sizeof(ecs_i32_t), &value); +#endif + + flecs_resume_readonly(world, &readonly_state); + + ecs_trace("#[green]constant#[reset] %s.%s created with value %d", + ecs_get_name(world, parent), name, value); + + return id; +} + +static int32_t flecs_reset_count = 0; + +int32_t ecs_cpp_reset_count_get(void) { + return flecs_reset_count; +} + +int32_t ecs_cpp_reset_count_inc(void) { + return ++flecs_reset_count; +} + +#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 (16 * 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_HTTP_SEND_RECV_BUFFER_SIZE]; + ecs_http_fragment_t frag = {0}; + int32_t retries = 0; + + do { + if ((bytes_read = http_recv( + sock, recv_buf, ECS_SIZEOF(recv_buf), 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 (retries == ECS_HTTP_REQUEST_RECV_RETRY) { + http_close(&sock); + } + +done: + 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 +void 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; + } + } else { + http_close(&testsocket); + } +#endif + + /* Resolve name + port (used for logging) */ + char addr_host[256]; + char addr_port[20]; + + 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) { + 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); +} + +static +void* http_server_thread(void* arg) { + ecs_http_server_t *srv = arg; + struct sockaddr_in addr; + ecs_os_zeromem(&addr); + addr.sin_family = AF_INET; + addr.sin_port = htons(srv->port); + + if (!srv->ipaddr) { + addr.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); + } + + http_accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); + 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->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); + } + } +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_check(ecs_os_has_threading(), ECS_UNSUPPORTED, + "missing OS API implementation"); + + ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); + 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; +error: + return NULL; +} + +void ecs_http_server_fini( + ecs_http_server_t* srv) +{ + if (srv->should_run) { + ecs_http_server_stop(srv); + } + 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); + + 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"); + + /* 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, + ecs_http_reply_t *reply_out) +{ + const char *http_ver = " HTTP/1.1\r\n\r\n"; + int32_t method_len = ecs_os_strlen(method); + int32_t req_len = ecs_os_strlen(req); + int32_t http_ver_len = ecs_os_strlen(http_ver); + char reqbuf[1024], *reqstr = reqbuf; + + int32_t len = method_len + req_len + http_ver_len + 1; + if (method_len + req_len + http_ver_len >= 1024) { + reqstr = ecs_os_malloc(len + 1); + } + + 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; + ptr[0] = '\n'; + + int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); + if (reqbuf != reqstr) { + ecs_os_free(reqstr); + } + + return result; +} + +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); + } else if (kind == EcsJournalTableEvents) { + ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " + "EcsAperiodicEmptyTables);"); + } + 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("red]"))) { + 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); + } +} + +void ecs_parser_errorv_( + const char *name, + const char *expr, + int64_t column_arg, + const char *fmt, + va_list args) +{ + 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); + ecs_os_err(name, 0, msg); + ecs_os_free(msg); + } +} + +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_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_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 = 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, + ecs_table_type_to_column_index(table, it->columns[0]), 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, + .src.id = EcsSelf, + .inout = EcsInOutNone + }, + .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, + .src.id = EcsSelf, + .inout = EcsInOutNone + }, + .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 = ecs_childof(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, + .src.id = EcsSelf, + .inout = EcsInOutNone + }, + .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) +{ + 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 +void flecs_rest_capture_log( + int32_t level, + const char *file, + int32_t line, + const char *msg) +{ + (void)file; (void)line; + +#ifdef FLECS_DEBUG + 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 < 0) { + 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, "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, "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) { + flecs_reply_error(reply, "missing data parameter"); + return true; + } + + bool try = false; + flecs_rest_bool_param(req, "try", &try); + + bool prev_color = ecs_log_enable_colors(false); + ecs_os_api_log_t prev_log = ecs_os_api.log_; + rest_prev_log = try ? NULL : prev_log; + 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, 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, 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; + } + + 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); + rest_prev_log = ecs_os_api.log_; + 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_; + rest_prev_log = try ? NULL : prev_log; + 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); + 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; + + used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t); + allocated += table->data.entities.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 += columns[i].data.count * columns[i].ti->size; + allocated += columns[i].data.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 = it->ptrs[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); +} + +#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; + 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 + ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED, + "(size = %u)", (uint32_t)ba->data_size); +#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) +{ + 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"); + ba->alloc_count ++; + *(int64_t*)result = ba->chunk_size; + 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) +{ +#ifdef FLECS_USE_OS_ALLOC + return ecs_os_calloc(ba->data_size); +#else + if (!ba) return NULL; + void *result = flecs_balloc(ba); + 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); +} + +FLECS_API +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)); + if (*(int64_t*)memory != ba->chunk_size) { + if (type_name) { + ecs_err("chunk %p returned to wrong allocator " + "(chunk = %ub, allocator = %ub, type = %s)", + memory, *(int64_t*)memory, ba->chunk_size, type_name); + } else { + ecs_err("chunk %p returned to wrong allocator " + "(chunk = %ub, allocator = %ub)", + memory, *(int64_t*)memory, ba->chunk_size); + } + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + ba->alloc_count --; +#endif + + ecs_block_allocator_chunk_header_t *chunk = memory; + chunk->next = ba->head; + ba->head = chunk; + ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); +#endif +} + +void* flecs_brealloc( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory) +{ + 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(dst); + 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(src, memory); +#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); +#ifdef FLECS_SANITIZE + 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; +#ifdef FLECS_SANITIZE + 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; + 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; + if (page == &stack->first && !page->data) { + page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); + ecs_os_linc(&ecs_stack_allocator_alloc_count); + } + + 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: +#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->tail_page = &stack->first; + stack->first.data = NULL; +} + +void flecs_stack_fini( + ecs_stack_t *stack) +{ + ecs_stack_page_t *next, *cur = &stack->first; + ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + ecs_assert(stack->tail_page == &stack->first, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + + do { + next = cur->next; + if (cur == &stack->first) { + if (cur->data) { + ecs_os_linc(&ecs_stack_allocator_free_count); + } + ecs_os_free(cur->data); + } else { + 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; + } 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; + } + + 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_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + v->array = NULL; + v->count = 0; + if (elem_count) { + if (allocator) { + v->array = flecs_alloc(allocator, size * elem_count); + } else { + v->array = ecs_os_malloc(size * elem_count); + } + } + v->size = elem_count; +#ifdef FLECS_SANITIZE + v->elem_size = size; +#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) { + v->array = flecs_realloc( + allocator, size * elem_count, size * v->size, v->array); + } 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 + +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; + + /* If caching policy is default, try to pick a policy that does the right + * thing in most cases. */ + if (kind == EcsQueryCacheDefault) { + if (desc->entity || desc->group_by || desc->group_by_callback || + desc->order_by || desc->order_by_callback) + { + /* 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 */ + impl->pub.cache_kind = EcsQueryCacheAuto; + } + } + + 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->field_map = flecs_alloc_n(&impl->stage->allocator, + int8_t, FLECS_TERM_COUNT_MAX); + + ecs_os_memcpy_n(impl->field_map, field_map, int8_t, dst_count); + } + } + + 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 != &impl->vars_cache.var) { + flecs_free(a, (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * + impl->var_size, impl->vars); + } + + 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); + flecs_free_n(a, int8_t, FLECS_TERM_COUNT_MAX, impl->field_map); + flecs_name_index_fini(&impl->tvar_index); + flecs_name_index_fini(&impl->evar_index); + + 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 (!(q->world->flags & 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_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 +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); + } +} + +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); + + if (flecs_query_finalize_query(world, &result->pub, &desc)) { + goto error; + } + + /* If query terms have itself as source, add term ids to self */ + flecs_query_add_self_ref(&result->pub); + + /* Store remaining string tokens in terms (after entity lookups) in single + * token buffer which simplifies memory management & reduces allocations. */ + flecs_query_populate_tokens(result); + + /* 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; + } + + /* Compile query to operations */ + 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_run_aperiodic(q->world, EcsAperiodicEmptyTables); + + ecs_query_impl_t *impl = flecs_query_impl(q); + if (impl->cache && q->flags & EcsQueryIsCacheable) { + result.results = flecs_query_cache_table_count(impl->cache); + result.entities = flecs_query_cache_entity_count(impl->cache); + result.tables = flecs_query_cache_table_count(impl->cache); + result.empty_tables = flecs_query_cache_empty_table_count(impl->cache); + } else { + ecs_iter_t it = flecs_query_iter(q->world, q); + it.flags |= EcsIterIsInstanced; + it.flags |= EcsIterNoData; + + while (flecs_query_next_instanced(&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_run_aperiodic(q->world, EcsAperiodicEmptyTables); + + ecs_query_impl_t *impl = flecs_query_impl(q); + if (impl->cache && q->flags & EcsQueryIsCacheable) { + return flecs_query_cache_table_count(impl->cache) != 0; + } else { + 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; + } +} + + /** + * @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 EcsQueryAndId: return "andid "; + case EcsQueryAndAny: return "andany "; + case EcsQueryTriv: return "triv "; + case EcsQueryTrivData: return "trivpop "; + case EcsQueryTrivWildcard: return "trivwc "; + case EcsQueryCache: return "cache "; + case EcsQueryCacheData: return "cachepop "; + case EcsQueryIsCache: return "xcache "; + case EcsQueryIsCacheData: return "xcachepop "; + case EcsQueryOnlyAny: return "any "; + case EcsQueryUp: return "up "; + case EcsQueryUpId: return "upid "; + case EcsQuerySelfUp: return "selfup "; + case EcsQuerySelfUpId: return "selfupid "; + 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 EcsQueryPopulate: return "pop "; + case EcsQueryPopulateSelf: return "popself "; + case EcsQueryPopulateSparse: return "popsparse "; + 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; + 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 == EcsQueryPopulate || + op->kind == EcsQueryPopulateSelf || + op->kind == EcsQueryPopulateSparse || + op->kind == EcsQueryTriv || + op->kind == EcsQueryTrivData || + op->kind == EcsQueryTrivWildcard) + { + 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) { + ecs_strbuf_appendstr(buf, term->src.name); + } 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); + } + } 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, EcsIterIsInstanced, + ECS_BIT_IS_SET(query->flags, EcsQueryIsInstanced)); + ECS_BIT_COND(it->flags, EcsIterNoData, + ECS_BIT_IS_SET(query->flags, EcsQueryNoData)); + ECS_BIT_COND(it->flags, EcsIterHasCondSet, + ECS_BIT_IS_SET(query->flags, EcsQueryHasCondSet)); +} + +/** + * @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->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) { + /* Add traversal flags for transitive relationships */ + if (ecs_term_ref_is_set(second) && !((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) { + 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->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) +{ + int32_t i, term_count = q->term_count, field_count = 0; + ecs_term_t *terms = q->terms; + int32_t nodata_terms = 0, 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; + 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 (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(int16_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 (ECS_BIT_IS_SET(q->flags, EcsQueryNoData)) { + term->inout = EcsInOutNone; + } + + 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->inout == EcsInOutNone) { + nodata_term = true; + } else if (!ecs_get_type_info(world, term->id)) { + 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 (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) { + if (first == &term[-1]) { + if (!(term[-1].flags_ & EcsTermNoData)) { + nodata_terms ++; + } + } + nodata_term = true; + } + q->data_fields &= (ecs_termset_t)~(1llu << term->field_index); + } + } + + if (term->flags_ & EcsTermIsMember) { + nodata_term = false; + } + + if (nodata_term) { + nodata_terms ++; + term->flags_ |= EcsTermNoData; + } else if (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); + } + + 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) { + if (!(term->flags_ & EcsTermNoData)) { + term->flags_ |= EcsTermIsSparse; + ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); + if (term->flags_ & EcsTermIsCacheable) { + cacheable_terms --; + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); + } + } + } + } + + 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 '{'"); + } + } + + 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; + } + } + + if (term->flags_ & EcsTermIsMember) { + q->sizes[field] = ECS_SIZEOF(ecs_entity_t); +#ifdef FLECS_META + q->ids[field] = ecs_id(ecs_entity_t); +#endif + } + } + } + + ecs_assert(nodata_terms <= term_count, ECS_INTERNAL_ERROR, NULL); + if (nodata_terms == term_count) { + ECS_BIT_SET(q->flags, EcsQueryNoData); + } + + 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 (!(q->flags & EcsQueryNoData)) { + if (term->inout == EcsInOutNone) { + 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); + ECS_BIT_COND(q->flags, EcsQueryIsCacheable, + cacheable && (cacheable_terms == term_count)); + + 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( + &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; +} + +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; + + /* 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; + } + + 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_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; + 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_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); + if (hash >= FLECS_HI_ID_RECORD_ID) { + idr = flecs_bcalloc(&world->allocators.id_record); + ecs_map_insert_ptr(&world->id_index_hi, hash, idr); + } else { + idr = &world->id_index_lo[hash]; + ecs_os_zeromem(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; + } + } + + ecs_observable_t *o = &world->observable; + idr->flags |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; + idr->flags |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; + idr->flags |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableFill) * EcsIdHasOnTableFill; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableEmpty) * EcsIdHasOnTableEmpty; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; + idr->flags |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; + + 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); + ecs_assert(flecs_table_cache_empty_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); + flecs_bfree(&world->allocators.id_record, idr); + } else { + idr->id = 0; /* Tombstone */ + } + + 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]; + if (!idr->id) { + idr = NULL; + } + } + + 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->id) { + 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); +} + +/** + * @file storage/table.c + * @brief Table storage implementation. + * + * Tables are the data structure that store the component data. Tables have + * columns for each component in the table, and rows for each entity stored in + * the table. Once created, the component list for a table doesn't change, but + * entities can move from one table to another. + * + * Each table has a type, which is a vector with the (component) ids in the + * table. The vector is sorted by id, which ensures that there can be only one + * table for each unique combination of components. + * + * Not all ids in a table have to be components. Tags are ids that have no + * data type associated with them, and as a result don't need to be explicitly + * stored beyond an element in the table type. To save space and speed up table + * creation, each table has a reference to a "storage table", which is a table + * that only includes component ids (so excluding tags). + * + * Note that the actual data is not stored on the storage table. The storage + * table is only used for sharing administration. A column_map member maps + * between column indices of the table and its storage table. Tables are + * refcounted, which ensures that storage tables won't be deleted if other + * tables have references to it. + */ + + +/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as + * this can severely slow down many ECS operations. */ +#ifdef FLECS_SANITIZE +static +void flecs_table_check_sanity(ecs_table_t *table) { + int32_t size = ecs_vec_size(&table->data.entities); + int32_t count = ecs_vec_count(&table->data.entities); + + int32_t i; + 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 (table->column_count) { + int32_t column_count = table->column_count; + ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); + + int32_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 ++) { + ecs_vec_t *column = &table->data.columns[i].data; + ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); + int32_t column_map_id = column_map[i + type_count]; + ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL); + } + } else { + ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (bs_count) { + ecs_assert(table->_->bs_columns != NULL, + ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &table->_->bs_columns[i]; + ecs_assert(flecs_bitset_count(bs) == count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), + ECS_INTERNAL_ERROR, NULL); + } + } + + ecs_assert((table->_->traversable_count == 0) || + (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); +} +#else +#define flecs_table_check_sanity(table) +#endif + +/* Set flags for type hooks so table operations can quickly check whether a + * fast or complex operation that invokes hooks is required. */ +static +ecs_flags32_t flecs_type_info_flags( + const ecs_type_info_t *ti) +{ + ecs_flags32_t flags = 0; + + if (ti->hooks.ctor) { + flags |= EcsTableHasCtors; + } + if (ti->hooks.on_add) { + flags |= EcsTableHasCtors; + } + if (ti->hooks.dtor) { + flags |= EcsTableHasDtors; + } + if (ti->hooks.on_remove) { + flags |= EcsTableHasDtors; + } + if (ti->hooks.copy) { + flags |= EcsTableHasCopy; + } + if (ti->hooks.move) { + flags |= EcsTableHasMove; + } + + return flags; +} + +static +void flecs_table_init_columns( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_count) +{ + if (!column_count) { + return; + } + + int32_t i, cur = 0, ids_count = table->type.count; + 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; + int32_t *t2s = table->column_map; + int32_t *s2t = &table->column_map[ids_count]; + + for (i = 0; i < ids_count; 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); + columns[cur].id = ids[i]; + columns[cur].size = ti->size; + + if (ECS_IS_PAIR(ids[i])) { + ecs_table_record_t *wc_tr = flecs_id_record_get_table( + idr->parent, table); + if (wc_tr->index == tr->index) { + wc_tr->column = tr->column; + } + } + +#ifdef FLECS_DEBUG + ecs_vec_init(NULL, &columns[cur].data, ti->size, 0); +#endif + + table->flags |= flecs_type_info_flags(ti); + cur ++; + } +} + +/* Initialize table storage */ +void flecs_table_init_data( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_data_t *storage = &table->data; + ecs_vec_init_t(NULL, &storage->entities, ecs_entity_t, 0); + + 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) +{ + flecs_emit(world, world, 0, &(ecs_event_desc_t) { + .ids = &table->type, + .event = event, + .table = table, + .flags = EcsEventTableOnly, + .observable = 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 ++; + } + } + } + + if (column_count) { + table->column_map = flecs_walloc_n(world, int32_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_entity_t event) +{ + (void)world; + + if (event == EcsOnAdd) { + table->flags |= EcsTableHasOnAdd; + } else if (event == EcsOnRemove) { + table->flags |= EcsTableHasOnRemove; + } else if (event == EcsOnSet) { + table->flags |= EcsTableHasOnSet; + } else if (event == EcsOnTableFill) { + table->flags |= EcsTableHasOnTableFill; + } else if (event == EcsOnTableEmpty) { + table->flags |= EcsTableHasOnTableEmpty; + } else if (event == EcsWildcard) { + table->flags |= EcsTableHasOnAdd|EcsTableHasOnRemove|EcsTableHasOnSet| + EcsTableHasOnTableFill|EcsTableHasOnTableEmpty| + EcsTableHasOnTableCreate|EcsTableHasOnTableDelete; + } +} + +/* 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, + ecs_data_t *data) +{ + int32_t count = data->entities.count; + if (count) { + flecs_notify_on_remove(world, table, NULL, 0, count, &table->type); + } +} + +/* 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, + ecs_entity_t *entities, + int32_t row, + int32_t count) +{ + void *ptr = ecs_vec_get(&column->data, column->size, row); + flecs_invoke_hook(world, table, count, row, entities, ptr, column->id, + column->ti, event, callback); +} + +/* Construct components */ +static +void flecs_table_invoke_ctor( + ecs_column_t *column, + int32_t row, + int32_t count) +{ + 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_vec_get(&column->data, column->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_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_vec_get(&column->data, column->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_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_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, + ecs_data_t *data, + int32_t row, + int32_t count, + bool is_delete) +{ + int32_t ids_count = table->column_count; + ecs_entity_t *entities = data->entities.array; + int32_t i, c, end = row + count; + + 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 < ids_count; c++) { + ecs_column_t *column = &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 < ids_count; c++) { + flecs_table_invoke_dtor(&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, + ecs_data_t *data, + bool do_on_remove, + bool is_delete, + bool deactivate) +{ + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + + if (!data) { + return; + } + + if (do_on_remove) { + flecs_table_notify_on_remove(world, table, data); + } + + int32_t count = flecs_table_data_count(data); + if (count) { + flecs_table_dtor_all(world, table, data, 0, count, is_delete); + } + + ecs_column_t *columns = data->columns; + if (columns) { + int32_t c, column_count = table->column_count; + for (c = 0; c < column_count; c ++) { + /* Sanity check */ + ecs_assert(columns[c].data.count == data->entities.count, + ECS_INTERNAL_ERROR, NULL); + ecs_vec_fini(&world->allocator, + &columns[c].data, columns[c].size); + } + flecs_wfree_n(world, ecs_column_t, column_count, columns); + 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; + for (c = 0; c < column_count; c ++) { + flecs_bitset_fini(&bs_columns[c]); + } + flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); + meta->bs_columns = NULL; + } + + ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t); + + if (deactivate && count) { + flecs_table_set_empty(world, table); + } + + table->_->traversable_count = 0; + table->flags &= ~EcsTableHasTraversable; +} + +const ecs_vec_t* flecs_table_entities( + const ecs_table_t *table) +{ + return &table->data.entities; +} + +ecs_entity_t* flecs_table_entities_array( + const ecs_table_t *table) +{ + return ecs_vec_first(flecs_table_entities(table)); +} + +/* Cleanup, no OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities_silent( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_table_fini_data(world, table, &table->data, false, false, true); +} + +/* Cleanup, run OnRemove, clear entity index, deactivate table */ +void flecs_table_clear_entities( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_table_fini_data(world, table, &table->data, true, false, true); +} + +/* Cleanup, run OnRemove, delete from entity index, deactivate table */ +void flecs_table_delete_entities( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_table_fini_data(world, table, &table->data, 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, &table->data); +} + +/* Free table resources. */ +void flecs_table_fini( + ecs_world_t *world, + ecs_table_t *table) +{ + 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; + + 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(); + } + + world->info.empty_table_count -= (ecs_table_count(table) == 0); + + /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ + flecs_table_fini_data(world, table, &table->data, false, true, false); + 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, int32_t, table->column_count + table->type.count, + table->column_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(); +} + +/* 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) { + 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; + } + + table->dirty_state[tr->column + 1] ++; + } +} + +/* 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_column_t *column, + int32_t to_add, + int32_t dst_size, + bool construct) +{ + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_type_info_t *ti = column->ti; + int32_t size = column->size; + int32_t count = column->data.count; + int32_t src_size = column->data.size; + int32_t dst_count = count + to_add; + bool can_realloc = dst_size != src_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, size, dst_size); + dst.count = dst_count; + + void *src_buffer = column->data.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, size, count); + ctor(result, to_add, ti); + } + + /* Free old vector */ + ecs_vec_fini(&world->allocator, &column->data, size); + + column->data = 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->data, size, dst_size); + } + + result = ecs_vec_grow(&world->allocator, &column->data, 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->data.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, + ecs_data_t *data, + int32_t to_add, + int32_t size, + const ecs_entity_t *ids) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t cur_count = flecs_table_data_count(data); + int32_t column_count = table->column_count; + + /* Add entity to column with entity ids */ + ecs_vec_set_size_t(&world->allocator, &data->entities, ecs_entity_t, size); + ecs_entity_t *e = ecs_vec_last_t(&data->entities, ecs_entity_t) + 1; + data->entities.count += to_add; + if (data->entities.size > size) { + size = data->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 = data->columns; + for (i = 0; i < column_count; i ++) { + flecs_table_grow_column(world, &columns[i], to_add, size, true); + ecs_assert(columns[i].data.size == size, ECS_INTERNAL_ERROR, NULL); + flecs_table_invoke_add_hooks(world, table, &columns[i], e, + cur_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); + + if (!(world->flags & EcsWorldReadonly) && !cur_count) { + flecs_table_set_empty(world, table); + } + + /* Return index of first added entity */ + return cur_count; +} + +/* Append operation for tables that don't have any complex logic */ +static +void flecs_table_fast_append( + ecs_world_t *world, + ecs_column_t *columns, + int32_t count) +{ + /* Add elements to each column array */ + int32_t i; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_vec_append(&world->allocator, &column->data, column->size); + } +} + +/* 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(table); + + /* Get count & size before growing entities array. This tells us whether the + * arrays will realloc */ + ecs_data_t *data = &table->data; + int32_t count = data->entities.count; + 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_entity_t *e = ecs_vec_append_t(&world->allocator, + &data->entities, ecs_entity_t); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + *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, columns, column_count); + if (!count) { + flecs_table_set_empty(world, table); /* See below */ + } + return count; + } + + ecs_entity_t *entities = data->entities.array; + + /* 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 = data->entities.size; + + /* Grow component arrays with 1 element */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + flecs_table_grow_column(world, column, 1, size, construct); + + 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(columns[i].data.size == + data->entities.size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(columns[i].data.count == + data->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); + } + + /* If this is the first entity in this table, signal queries so that the + * table moves from an inactive table to an active table. */ + if (!count) { + flecs_table_set_empty(world, table); + } + + flecs_table_check_sanity(table); + + return count; +} + +/* Delete last operation for tables that don't have any complex logic */ +static +void flecs_table_fast_delete_last( + ecs_column_t *columns, + int32_t column_count) +{ + int i; + for (i = 0; i < column_count; i ++) { + ecs_vec_remove_last(&columns[i].data); + } +} + +/* Delete operation for tables that don't have any complex logic */ +static +void flecs_table_fast_delete( + ecs_column_t *columns, + int32_t column_count, + int32_t index) +{ + int i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_vec_remove(&column->data, column->size, index); + } +} + +/* Delete entity from table */ +void flecs_table_delete( + ecs_world_t *world, + ecs_table_t *table, + int32_t index, + 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(table); + + ecs_data_t *data = &table->data; + int32_t count = data->entities.count; + + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + count --; + ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); + + /* Move last entity id to index */ + ecs_entity_t *entities = data->entities.array; + ecs_entity_t entity_to_move = entities[count]; + ecs_entity_t entity_to_delete = entities[index]; + entities[index] = entity_to_move; + ecs_vec_remove_last(&data->entities); + + /* Update record of moved entity in entity index */ + if (index != 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(index, row_flags); + ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); + } + } + + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); + + /* If table is empty, deactivate it */ + if (!count) { + flecs_table_set_empty(world, table); + } + + /* Destruct component data */ + ecs_column_t *columns = 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 (index == count) { + flecs_table_fast_delete_last(columns, column_count); + } else { + flecs_table_fast_delete(columns, column_count, index); + } + + flecs_table_check_sanity(table); + return; + } + + /* Last element, destruct & remove */ + if (index == count) { + /* If table has component destructors, invoke */ + if (destruct && (table->flags & EcsTableHasDtors)) { + for (i = 0; i < column_count; i ++) { + flecs_table_invoke_remove_hooks(world, table, &columns[i], + &entity_to_delete, index, 1, true); + } + } + + flecs_table_fast_delete_last(columns, column_count); + + /* 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 = column->size; + void *dst = ecs_vec_get(&column->data, size, index); + void *src = ecs_vec_last(&column->data, size); + + 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, index, 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); + } + + ecs_vec_remove_last(&column->data); + } + } else { + flecs_table_fast_delete(columns, column_count, index); + } + } + + /* 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], index); + } + + flecs_table_check_sanity(table); +} + +/* Move operation for tables that don't have any complex logic */ +static +void flecs_table_fast_move( + ecs_table_t *dst_table, + int32_t dst_index, + ecs_table_t *src_table, + int32_t src_index) +{ + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; + + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; + + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_column_t *dst_column = &dst_columns[i_new]; + ecs_column_t *src_column = &src_columns[i_old]; + ecs_id_t dst_id = dst_column->id; + ecs_id_t src_id = src_column->id; + + if (dst_id == src_id) { + int32_t size = dst_column->size; + void *dst = ecs_vec_get(&dst_column->data, size, dst_index); + void *src = ecs_vec_get(&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(dst_table); + flecs_table_check_sanity(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(dst_table); + flecs_table_check_sanity(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 = dst_column->id; + ecs_id_t src_id = src_column->id; + + if (dst_id == src_id) { + int32_t size = dst_column->size; + + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + void *dst = ecs_vec_get(&dst_column->data, size, dst_index); + void *src = ecs_vec_get(&src_column->data, size, src_index); + ecs_type_info_t *ti = dst_column->ti; + + 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 { + 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); + } + + for (; (i_old < src_column_count); i_old ++) { + flecs_table_invoke_remove_hooks(world, src_table, &src_columns[i_old], + &src_entity, src_index, 1, use_move_dtor); + } + + flecs_table_check_sanity(dst_table); + flecs_table_check_sanity(src_table); +} + +/* Append n entities to table */ +int32_t flecs_table_appendn( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t to_add, + const ecs_entity_t *ids) +{ + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + + flecs_table_check_sanity(table); + int32_t cur_count = flecs_table_data_count(data); + int32_t result = flecs_table_grow_data( + world, table, data, to_add, cur_count + to_add, ids); + flecs_table_check_sanity(table); + + return result; +} + +/* Set allocated table size */ +void flecs_table_set_size( + ecs_world_t *world, + ecs_table_t *table, + ecs_data_t *data, + int32_t size) +{ + ecs_assert(table != NULL, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + + flecs_table_check_sanity(table); + + int32_t cur_count = flecs_table_data_count(data); + + if (cur_count < size) { + flecs_table_grow_data(world, table, data, 0, size, NULL); + flecs_table_check_sanity(table); + } +} + +/* 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(table); + + ecs_data_t *data = &table->data; + bool has_payload = data->entities.array != NULL; + ecs_vec_reclaim_t(&world->allocator, &data->entities, ecs_entity_t); + + int32_t i, count = table->column_count; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &data->columns[i]; + ecs_vec_reclaim(&world->allocator, &column->data, column->size); + } + + return has_payload; +} + +/* Return number of entities in table */ +int32_t flecs_table_data_count( + const ecs_data_t *data) +{ + return data ? data->entities.count : 0; +} + +/* 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(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.array; + ecs_entity_t e1 = entities[row_1]; + ecs_entity_t e2 = entities[row_2]; + + ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); + ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); + + ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Keep track of whether entity is watched */ + uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); + uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); + + /* Swap entities & records */ + entities[row_1] = e2; + entities[row_2] = e1; + record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); + record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); + + flecs_table_swap_bitset_columns(table, row_1, row_2); + + ecs_column_t *columns = table->data.columns; + if (!columns) { + flecs_table_check_sanity(table); + return; + } + + /* Find the maximum size of column elements + * and allocate a temporary buffer for swapping */ + int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->column_count; + for (i = 0; i < column_count; i++) { + temp_buffer_size = ECS_MAX(temp_buffer_size, columns[i].size); + } + + void* tmp = ecs_os_alloca(temp_buffer_size); + + /* Swap columns */ + for (i = 0; i < column_count; i ++) { + int32_t size = columns[i].size; + ecs_column_t *column = &columns[i]; + void *ptr = column->data.array; + const ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + void *el_1 = ECS_ELEM(ptr, size, row_1); + void *el_2 = ECS_ELEM(ptr, size, row_2); + + ecs_move_t move = ti->hooks.move; + if (!move) { + ecs_os_memcpy(tmp, el_1, size); + ecs_os_memcpy(el_1, el_2, size); + ecs_os_memcpy(el_2, tmp, size); + } else { + ecs_move_t move_ctor = ti->hooks.move_ctor; + ecs_move_t move_dtor = ti->hooks.move_dtor; + ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(move_dtor != NULL, ECS_INTERNAL_ERROR, NULL); + move_ctor(tmp, el_1, 1, ti); + move(el_1, el_2, 1, ti); + move_dtor(el_2, tmp, 1, ti); + } + } + + flecs_table_check_sanity(table); +} + +static +void flecs_table_merge_vec( + ecs_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_column_t *dst, + ecs_column_t *src, + int32_t column_size) +{ + ecs_size_t size = dst->size; + int32_t dst_count = dst->data.count; + + if (!dst_count) { + ecs_vec_fini(&world->allocator, &dst->data, size); + *dst = *src; + src->data.array = NULL; + src->data.count = 0; + src->data.size = 0; + + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = src->data.count; + + flecs_table_grow_column(world, dst, src_count, column_size, false); + void *dst_ptr = ECS_ELEM(dst->data.array, size, dst_count); + void *src_ptr = src->data.array; + + /* Move values into column */ + ecs_type_info_t *ti = dst->ti; + 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, size * src_count); + } + + ecs_vec_fini(&world->allocator, &src->data, size); + } +} + +/* 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 src_count, + int32_t dst_count, + ecs_data_t *src_data, + ecs_data_t *dst_data) +{ + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; + ecs_column_t *src_columns = src_data->columns; + ecs_column_t *dst_columns = dst_data->columns; + + ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); + + if (!src_count) { + return; + } + + /* Merge entities */ + flecs_table_merge_vec(world, &dst_data->entities, &src_data->entities, + ECS_SIZEOF(ecs_entity_t), 0); + ecs_assert(dst_data->entities.count == src_count + dst_count, + ECS_INTERNAL_ERROR, NULL); + int32_t column_size = dst_data->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 = dst_column->id; + ecs_id_t src_id = src_column->id; + + if (dst_id == src_id) { + flecs_table_merge_column(world, 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_size_t size = dst_column->size; + ecs_vec_set_size(a, &dst_column->data, size, column_size); + ecs_vec_set_count(a, &dst_column->data, size, src_count + dst_count); + 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_column->data, src_column->size); + 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 size = column->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + ecs_vec_set_size(a, &column->data, size, column_size); + ecs_vec_set_count(a, &column->data, size, src_count + dst_count); + 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]; + flecs_table_invoke_dtor(column, 0, src_count); + ecs_vec_fini(a, &column->data, column->size); + } + + /* Mark entity column as dirty */ + flecs_table_mark_table_dirty(world, dst_table, 0); +} + +/* 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(src_table); + flecs_table_check_sanity(dst_table); + + ecs_data_t *dst_data = &dst_table->data; + ecs_data_t *src_data = &src_table->data; + + ecs_entity_t *src_entities = src_data->entities.array; + int32_t src_count = src_data->entities.count; + int32_t dst_count = dst_data->entities.count; + + /* 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, src_count, dst_count, + src_data, dst_data); + + if (src_count) { + if (!dst_count) { + flecs_table_set_empty(world, dst_table); + } + flecs_table_set_empty(world, src_table); + + flecs_table_traversable_add(dst_table, src_table->_->traversable_count); + flecs_table_traversable_add(src_table, -src_table->_->traversable_count); + ecs_assert(src_table->_->traversable_count == 0, ECS_INTERNAL_ERROR, NULL); + } + + flecs_table_check_sanity(src_table); + flecs_table_check_sanity(dst_table); +} + +/* Internal mechanism for propagating information to tables */ +void flecs_table_notify( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_event_t *event) +{ + if (world->flags & EcsWorldFini) { + return; + } + + switch(event->kind) { + case EcsTableTriggersForId: + flecs_table_add_trigger_flags(world, table, event->event); + break; + case EcsTableNoTriggersForId: + break; + } +} + +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]; +} + + +/* -- 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); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + 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); + + 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); + int32_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.array; + if (offset) { + result = ECS_ELEM(result, column->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].size); +error: + return 0; +} + +int32_t ecs_table_count( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return flecs_table_data_count(&table->data); +} + +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->size; + + ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, + ECS_INVALID_PARAMETER, NULL); + + return ecs_vec_get(&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->empty_tables.count -= !!elem->empty; + cache->tables.count -= !elem->empty; + + if (cache->empty_tables.first == elem) { + cache->empty_tables.first = next; + } else if (cache->tables.first == elem) { + cache->tables.first = next; + } + if (cache->empty_tables.last == elem) { + cache->empty_tables.last = prev; + } + if (cache->tables.last == elem) { + cache->tables.last = prev; + } +} + +static +void flecs_table_cache_list_insert( + ecs_table_cache_t *cache, + ecs_table_cache_hdr_t *elem) +{ + ecs_table_cache_hdr_t *last; + if (elem->empty) { + last = cache->empty_tables.last; + cache->empty_tables.last = elem; + if ((++ cache->empty_tables.count) == 1) { + cache->empty_tables.first = elem; + } + } else { + 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; + } +} + +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); +} + +bool ecs_table_cache_is_empty( + const ecs_table_cache_t *cache) +{ + return ecs_map_count(&cache->index) == 0; +} + +void ecs_table_cache_insert_w_empty( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *result, + bool empty) +{ + 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); + result->empty = empty; + + flecs_table_cache_list_insert(cache, result); + + if (table) { + ecs_map_insert_ptr(&cache->index, table->id, result); + } + + ecs_assert(empty || cache->tables.first != NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!empty || cache->empty_tables.first != NULL, + ECS_INTERNAL_ERROR, NULL); +} + +void ecs_table_cache_insert( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *result) +{ + bool empty; + if (!table) { + empty = false; + } else { + empty = ecs_table_count(table) == 0; + } + + ecs_table_cache_insert_w_empty(cache, table, result, empty); +} + +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->empty_tables.first == old) { + cache->empty_tables.first = elem; + } + if (cache->empty_tables.last == old) { + cache->empty_tables.last = 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 ecs_table_cache_set_empty( + ecs_table_cache_t *cache, + const ecs_table_t *table, + bool empty) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_cache_hdr_t *elem = ecs_map_get_deref(&cache->index, + ecs_table_cache_hdr_t, table->id); + if (!elem) { + return false; + } + + if (elem->empty == empty) { + return false; + } + + flecs_table_cache_list_remove(cache, elem); + elem->empty = empty; + flecs_table_cache_list_insert(cache, elem); + + return true; +} + +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->next_list = NULL; + out->cur = NULL; + 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->empty_tables.first; + out->next_list = NULL; + out->cur = NULL; + 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->empty_tables.first; + out->next_list = cache->tables.first; + out->cur = NULL; + return out->next != NULL || out->next_list != NULL; +} + +ecs_table_cache_hdr_t* flecs_table_cache_next_( + ecs_table_cache_iter_t *it) +{ + ecs_table_cache_hdr_t *next = it->next; + if (!next) { + next = it->next_list; + it->next_list = NULL; + if (!next) { + return NULL; + } + } + + it->cur = next; + it->next = next->next; + 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); +} + +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); +} + +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 }; +} + +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); +} + +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); +} + +bool flecs_table_records_update_empty( + ecs_table_t *table) +{ + bool result = false; + bool is_empty = ecs_table_count(table) == 0; + + int32_t i, count = table->_->record_count; + for (i = 0; i < count; i ++) { + ecs_table_record_t *tr = &table->_->records[i]; + ecs_table_cache_t *cache = tr->hdr.cache; + result |= ecs_table_cache_set_empty(cache, table, is_empty); + } + + return result; +} + +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_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.empty_table_count ++; + world->info.table_create_total ++; + + ecs_log_pop_2(); + + 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); + 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; + 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; + + added_count += added; + removed_count += removed; + + i_node += id_node <= id_next; + i_next += id_next <= id_node; + } + + added_count += next_count - i_next; + removed_count += node_count - i_node; + + trivial_edge &= (added_count + removed_count) <= 1 && + !ecs_id_is_wildcard(id); + + 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); + + 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_INTERNAL_ERROR, NULL); + 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; + } 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_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 { + 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); + +#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 +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); + 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); + 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); + 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; + } + + 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); + 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, "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)); + + /* Entity had existing components that weren't in the serialized data */ + if (table != 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_legacy.c + * @brief Deserialize JSON strings into (component) values. + */ + +#include + +#ifdef FLECS_JSON + +typedef struct { + ecs_allocator_t *a; + ecs_vec_t records; + ecs_vec_t result_ids; + ecs_vec_t columns_set; + ecs_map_t anonymous_ids; + ecs_map_t missing_reflection; +} ecs_from_json_ctx_legacy_t; + +static +void flecs_from_json_ctx_init_legacy( + ecs_allocator_t *a, + ecs_from_json_ctx_legacy_t *ctx) +{ + ctx->a = a; + ecs_vec_init_t(a, &ctx->records, ecs_record_t*, 0); + ecs_vec_init_t(a, &ctx->result_ids, ecs_id_t, 0); + ecs_vec_init_t(a, &ctx->columns_set, 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_legacy( + ecs_from_json_ctx_legacy_t *ctx) +{ + ecs_vec_fini_t(ctx->a, &ctx->records, ecs_record_t*); + ecs_vec_fini_t(ctx->a, &ctx->result_ids, ecs_record_t*); + ecs_vec_fini_t(ctx->a, &ctx->columns_set, ecs_id_t); + ecs_map_fini(&ctx->anonymous_ids); + ecs_map_fini(&ctx->missing_reflection); +} + +static +const char* flecs_json_parse_path_legacy( + const ecs_world_t *world, + const char *json, + char *token, + ecs_entity_t *out, + const ecs_from_json_desc_t *desc) +{ + char *path = NULL; + json = flecs_json_expect_string(json, token, &path, desc); + if (!json) { + goto error; + } + + ecs_entity_t result = ecs_lookup(world, path); + if (!result) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "unresolved identifier '%s'", path); + goto error; + } + + *out = result; + + if (path != token) { + ecs_os_free(path); + } + + return json; +error: + if (path != token) { + ecs_os_free(path); + } + + return NULL; +} + +const char* ecs_entity_from_json_legacy( + ecs_world_t *world, + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc_param) +{ + ecs_json_token_t token_kind = 0; + char token[ECS_MAX_TOKEN_SIZE]; + + ecs_from_json_desc_t desc = {0}; + + const char *name = NULL, *expr = json, *ids = NULL, *values = NULL, *lah; + if (desc_param) { + desc = *desc_param; + } + + 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) { + return lah; + } + + json = flecs_json_expect_member(json, token, &desc); + if (!json) { + return NULL; + } + + if (!ecs_os_strcmp(token, "path")) { + char *path = NULL; + json = flecs_json_expect_string(json, token, &path, &desc); + if (!json) { + goto error; + } + + ecs_add_fullpath(world, e, path); + + if (path != token) { + ecs_os_free(path); + } + + json = flecs_json_parse(json, &token_kind, token); + if (!json) { + goto error; + } + + if (token_kind == JsonObjectClose) { + return json; + } else if (token_kind != JsonComma) { + ecs_parser_error(name, expr, json - expr, "unexpected character"); + goto error; + } + + json = flecs_json_expect_member_name(json, token, "ids", &desc); + if (!json) { + goto error; + } + } else if (ecs_os_strcmp(token, "ids")) { + ecs_parser_error(name, expr, json - expr, "expected member 'ids'"); + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, &desc); + if (!json) { + goto error; + } + + ids = json; + + json = flecs_json_skip_array(json, token, &desc); + if (!json) { + return NULL; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonObjectClose) { + if (token_kind != JsonComma) { + ecs_parser_error(name, expr, json - expr, "expected ','"); + goto error; + } + + json = flecs_json_expect_member_name(json, token, "values", &desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, &desc); + if (!json) { + goto error; + } + + values = json; + } + + do { + ecs_entity_t first = 0, second = 0, type_id = 0; + ecs_id_t id; + + ids = flecs_json_parse(ids, &token_kind, token); + if (!ids) { + goto error; + } + + if (token_kind == JsonArrayClose) { + if (values) { + if (values[0] != ']') { + ecs_parser_error(name, expr, values - expr, "expected ']'"); + goto error; + } + json = flecs_parse_ws_eol(values + 1); + } else { + json = ids; + } + + break; + } else if (token_kind == JsonArrayOpen) { + ids = flecs_json_parse_path_legacy(world, ids, token, &first, &desc); + if (!ids) { + goto error; + } + + ids = flecs_json_parse(ids, &token_kind, token); + if (!ids) { + goto error; + } + + if (token_kind == JsonComma) { + /* Id is a pair*/ + ids = flecs_json_parse_path_legacy(world, ids, token, &second, &desc); + if (!ids) { + goto error; + } + + ids = flecs_json_expect(ids, JsonArrayClose, token, &desc); + if (!ids) { + goto error; + } + } else if (token_kind != JsonArrayClose) { + ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'"); + goto error; + } + + lah = flecs_json_parse(ids, &token_kind, token); + if (!lah) { + goto error; + } + + if (token_kind == JsonComma) { + ids = lah; + } else if (token_kind != JsonArrayClose) { + ecs_parser_error(name, expr, lah - expr, "expected ',' or ']'"); + goto error; + } + } else { + ecs_parser_error(name, expr, lah - expr, "expected '[' or ']'"); + goto error; + } + + if (second) { + id = ecs_pair(first, second); + type_id = ecs_get_typeid(world, id); + if (!type_id) { + ecs_parser_error(name, expr, ids - expr, "id is not a type"); + goto error; + } + } else { + id = first; + type_id = first; + } + + /* Get mutable pointer */ + void *comp_ptr = ecs_ensure_id(world, e, id); + if (!comp_ptr) { + char *idstr = ecs_id_str(world, id); + ecs_parser_error(name, expr, json - expr, + "id '%s' is not a valid component", idstr); + ecs_os_free(idstr); + goto error; + } + + if (values) { + ecs_from_json_desc_t parse_desc = { + .name = name, + .expr = expr, + }; + + values = ecs_ptr_from_json( + world, type_id, comp_ptr, values, &parse_desc); + if (!values) { + goto error; + } + + lah = flecs_json_parse(values, &token_kind, token); + if (!lah) { + goto error; + } + + if (token_kind == JsonComma) { + values = lah; + } else if (token_kind != JsonArrayClose) { + ecs_parser_error(name, expr, json - expr, + "expected ',' or ']'"); + goto error; + } else { + values = flecs_parse_ws_eol(values); + } + + ecs_modified_id(world, e, id); + } + } while(ids[0]); + + return flecs_json_expect(json, JsonObjectClose, token, &desc); +error: + return NULL; +} + +static +ecs_entity_t flecs_json_new_id_legacy( + 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 +ecs_entity_t flecs_json_lookup_legacy( + 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_legacy( + 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 +bool flecs_json_name_is_anonymous_legacy( + const char *name) +{ + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + if (isdigit(name[0])) { + const char *ptr; + for (ptr = name + 1; *ptr; ptr ++) { + if (!isdigit(*ptr)) { + break; + } + } + if (!(*ptr)) { + return true; + } + } + return false; +} + +static +ecs_entity_t flecs_json_ensure_entity_legacy( + ecs_world_t *world, + const char *name, + ecs_map_t *anonymous_ids) +{ + ecs_entity_t e = 0; + + if (flecs_json_name_is_anonymous_legacy(name)) { + /* Anonymous entity, find or create mapping to new id */ + ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(name)); + 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_legacy(world, ser_id); + + /* Mark new id as reserved */ + flecs_json_mark_reserved_legacy(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_legacy(world, ser_id); + + /* Mark new id as reserved */ + flecs_json_mark_reserved_legacy(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_legacy(anonymous_ids, e); + } + } + + return e; +} + +static +ecs_table_t* flecs_json_parse_table_legacy( + ecs_world_t *world, + const char *json, + char *token, + ecs_from_json_ctx_legacy_t *ctx, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t token_kind = 0; + + ecs_vec_clear(&ctx->result_ids); + + do { + ecs_id_t id = 0; + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } + + char *first_name = NULL; + json = flecs_json_expect_string(json, token, &first_name, desc); + if (!json) { + goto error; + } + + ecs_entity_t first = flecs_json_lookup_legacy(world, 0, first_name, desc); + if (first_name != token) { + ecs_os_free(first_name); + } + + if (!first) { + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonComma) { + char *second_name = NULL; + json = flecs_json_expect_string(json, token, &second_name, desc); + if (!json) { + goto error; + } + + ecs_entity_t second = flecs_json_lookup_legacy(world, 0, second_name, desc); + if (second_name != token) { + ecs_os_free(second_name); + } + + if (!second) { + goto error; + } + + id = ecs_pair(first, second); + + json = flecs_json_expect(json, JsonArrayClose, token, desc); + if (!json) { + goto error; + } + } else if (token_kind == JsonArrayClose) { + id = first; + } else { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or ']"); + goto error; + } + + ecs_vec_append_t(ctx->a, &ctx->result_ids, ecs_id_t)[0] = id; + + const char *lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonComma) { + json = lah; + } else if (token_kind == JsonArrayClose) { + break; + } else { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or ']'"); + goto error; + } + } while (json[0]); + + /* Make a copy of the ids array because we need the original order for + * deserializing the component values, and the sorted order for finding or + * creating the table. */ + ecs_vec_t id_copy = ecs_vec_copy_t(ctx->a, &ctx->result_ids, ecs_id_t); + ecs_type_t type = { + .array = ecs_vec_first(&id_copy), + .count = ecs_vec_count(&id_copy) + }; + + qsort(type.array, flecs_itosize(type.count), sizeof(ecs_id_t), + flecs_id_qsort_cmp); + + ecs_table_t *table = flecs_table_find_or_create(world, &type); + if (!table) { + goto error; + } + + ecs_vec_fini_t(ctx->a, &id_copy, ecs_id_t); + + return table; +error: + return NULL; +} + +static +void flecs_json_zeromem_table_legacy( + ecs_table_t *table) +{ + int32_t count = ecs_vec_count(&table->data.entities); + int32_t i, column_count = table->column_count; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &table->data.columns[i]; + ecs_type_info_t *ti = column->ti; + if (!ti->hooks.ctor) { + void *ptr = ecs_vec_first(&column->data); + ecs_os_memset(ptr, 0, ti->size * count); + } + } +} + +static +int flecs_json_parse_entities_legacy( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t parent, + const char *json, + char *token, + ecs_from_json_ctx_legacy_t *ctx, + const ecs_from_json_desc_t *desc) +{ + char name_token[ECS_MAX_TOKEN_SIZE]; + ecs_json_token_t token_kind = 0; + ecs_vec_clear(&ctx->records); + + do { + char *name = NULL; + json = flecs_json_parse(json, &token_kind, name_token); + if (!json) { + goto error; + } + + if (token_kind == JsonLargeString) { + ecs_strbuf_t large_token = ECS_STRBUF_INIT; + json = flecs_json_parse_large_string(json, &large_token); + if (!json) { + break; + } + + name = ecs_strbuf_get(&large_token); + token_kind = JsonString; + } else { + name = name_token; + } + + if ((token_kind != JsonNumber) && (token_kind != JsonString)) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected number or string"); + goto error; + } + + ecs_entity_t e = flecs_json_lookup_legacy(world, parent, name, desc); + ecs_record_t *r = flecs_entities_try(world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + + if (r->table != table) { + bool cleared = false; + if (r->table) { + ecs_commit(world, e, r, r->table, NULL, &r->table->type); + cleared = true; + } + + ecs_commit(world, e, r, table, &table->type, NULL); + if (cleared) { + char *entity_name = strrchr(name, '.'); + if (entity_name) { + entity_name ++; + } else { + entity_name = name; + } + if (!flecs_json_name_is_anonymous_legacy(entity_name)) { + ecs_set_name(world, e, entity_name); + } + } + } + + if (name != name_token) { + ecs_os_free(name); + } + + if (table != r->table) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "invalid entity identifier"); + goto error; + } else { + ecs_assert(table == r->table, ECS_INTERNAL_ERROR, NULL); + ecs_record_t** elem = ecs_vec_append_t( + ctx->a, &ctx->records, ecs_record_t*); + *elem = r; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + break; + } else if (token_kind != JsonComma) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or ']'"); + goto error; + } + } while(json[0]); + + return 0; +error: + return -1; +} + +static +const char* flecs_json_missing_reflection_legacy( + ecs_world_t *world, + ecs_id_t id, + const char *json, + char *token, + ecs_from_json_ctx_legacy_t *ctx, + const ecs_from_json_desc_t *desc) +{ + if (!desc->strict && ecs_map_get(&ctx->missing_reflection, id)) { + json = flecs_json_skip_array(json, token, desc); + goto done; + } + + /* 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); + +done: + if (desc->strict) { + return NULL; + } else { + return flecs_json_skip_array(json, token, desc); + } +} + +static +const char* flecs_json_parse_column_legacy( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + const char *json, + char *token, + ecs_from_json_ctx_legacy_t *ctx, + const ecs_from_json_desc_t *desc) +{ + /* If deserializing id caused trouble before, don't bother trying again */ + if (!desc->strict && ecs_map_get(&ctx->missing_reflection, id)) { + return flecs_json_skip_array(json, token, desc); + } + + ecs_table_record_t *tr = flecs_table_record_get(world, table, id); + /* Table was created with this id, it should exist */ + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + + /* If id is not a component, reflection data is missing */ + int32_t column_index = tr->column; + if (column_index == -1) { + return flecs_json_missing_reflection_legacy(world, id, json, token, ctx, desc); + } + + ecs_json_token_t token_kind = 0; + ecs_column_t *column = &table->data.columns[column_index]; + ecs_type_info_t *ti = column->ti; + ecs_size_t size = ti->size; + ecs_entity_t type = ti->component; + + ecs_record_t **record_array = ecs_vec_first_t(&ctx->records, ecs_record_t*); + int32_t entity = 0; + bool values_set = false; + const char *values_start = json; + + do { + ecs_record_t *r = record_array[entity]; + int32_t row = ECS_RECORD_TO_ROW(r->row); + + void *ptr = ecs_vec_get(&column->data, size, row); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + json = ecs_ptr_from_json(world, type, ptr, json, desc); + if (!json) { + if (desc->strict) { + break; + } else { + return flecs_json_missing_reflection_legacy( + world, id, values_start, token, ctx, desc); + } + } else { + values_set = true; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + break; + } else if (token_kind != JsonComma) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or ']'"); + } + + entity ++; + } while (json[0]); + + if (values_set) { + ecs_id_t *id_set = ecs_vec_append_t( + ctx->a, &ctx->columns_set, ecs_id_t); + *id_set = id; + } + + return json; +} + +static +const char* flecs_json_parse_values_legacy( + ecs_world_t *world, + ecs_table_t *table, + const char *json, + char *token, + ecs_from_json_ctx_legacy_t *ctx, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t token_kind = 0; + int32_t value = 0; + + ecs_vec_clear(&ctx->columns_set); + + do { + if (value >= table->type.count) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "more elements in values array than expected for table"); + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (!json) { + goto error; + } + + if (token_kind == JsonArrayClose) { + break; + } else if (token_kind == JsonArrayOpen) { + ecs_id_t *idptr = ecs_vec_get_t(&ctx->result_ids, ecs_id_t, value); + ecs_assert(idptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id = idptr[0]; + + json = flecs_json_parse_column_legacy(world, table, id, + json, token, ctx, desc); + if (!json) { + goto error; + } + } else if (token_kind == JsonNumber) { + if (!ecs_os_strcmp(token, "0")) { + /* no data */ + } else { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "unexpected number"); + goto error; + } + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + break; + } else if (token_kind != JsonComma) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or ']'"); + goto error; + } + + value ++; + } while (json[0]); + + /* Send OnSet notifications */ + ecs_defer_begin(world); + ecs_type_t type = { + .array = ctx->columns_set.array, + .count = ctx->columns_set.count }; + + int32_t table_count = ecs_table_count(table); + int32_t i, record_count = ecs_vec_count(&ctx->records); + + /* If the entire table was inserted, send bulk notification */ + if (table_count == ecs_vec_count(&ctx->records)) { + flecs_notify_on_set( + world, table, 0, ecs_table_count(table), &type, true); + } else { + ecs_record_t **rvec = ecs_vec_first_t(&ctx->records, ecs_record_t*); + for (i = 0; i < record_count; i ++) { + ecs_record_t *r = rvec[i]; + int32_t row = ECS_RECORD_TO_ROW(r->row); + flecs_notify_on_set(world, table, row, 1, &type, true); + } + } + + ecs_defer_end(world); + + return json; +error: + return NULL; +} + +static +const char* flecs_json_parse_result_legacy( + ecs_world_t *world, + const char *json, + char *token, + ecs_from_json_ctx_legacy_t *ctx, + const ecs_from_json_desc_t *desc) +{ + ecs_json_token_t token_kind = 0; + const char *ids = NULL, *values = NULL, *entities = NULL; + + json = flecs_json_expect(json, JsonObjectOpen, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect_member_name(json, token, "ids", desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } + + ids = json; /* store start of ids array */ + + json = flecs_json_skip_array(json, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonComma, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } + + ecs_entity_t parent = 0; + if (!ecs_os_strcmp(token, "parent")) { + char *parent_name = NULL; + json = flecs_json_expect_string(json, token, &parent_name, desc); + if (!json) { + goto error; + } + + parent = flecs_json_lookup_legacy(world, 0, parent_name, desc); + if (parent_name != token) { + ecs_os_free(parent_name); + } + + json = flecs_json_expect(json, JsonComma, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } + } + + if (ecs_os_strcmp(token, "entities")) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected 'entities'"); + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } + + entities = json; /* store start of entity id array */ + + json = flecs_json_skip_array(json, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonComma) { + json = flecs_json_expect_member_name(json, token, "values", desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; + } + + values = json; /* store start of entities array */ + } else if (token_kind != JsonObjectClose) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected ',' or '}'"); + goto error; + } + + /* Find table from ids */ + ecs_table_t *table = flecs_json_parse_table_legacy(world, ids, token, ctx, desc); + if (!table) { + goto error; + } + + /* Add entities to table */ + if (flecs_json_parse_entities_legacy(world, table, parent, + entities, token, ctx, desc)) + { + goto error; + } + + /* If not parsing in strict mode, initialize component arrays to 0. This + * ensures that even if components can't be deserialized (because of + * incomplete reflection data) component values aren't left uninitialized */ + if (!desc->strict) { + flecs_json_zeromem_table_legacy(table); + } + + /* Parse values */ + if (values) { + json = flecs_json_parse_values_legacy(world, table, values, token, ctx, desc); + if (!json) { + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonComma) { + json = flecs_json_expect_member_name(json, token, "alerts", desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonBoolean, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_expect(json, JsonObjectClose, token, desc); + if (!json) { + goto error; + } + } else if (token_kind != JsonObjectClose) { + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "expected '}'"); + goto error; + } + } + + return json; +error: + return NULL; +} + +const char* ecs_world_from_json_legacy( + 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}; + ecs_allocator_t *a = &world->allocator; + ecs_from_json_ctx_legacy_t ctx; + flecs_from_json_ctx_init_legacy(a, &ctx); + + const char *name = NULL, *expr = json, *lah; + if (desc_arg) { + desc = *desc_arg; + } + + if (!desc.lookup_action) { + desc.lookup_action = (ecs_entity_t(*)( + const ecs_world_t*, const char*, void*))flecs_json_ensure_entity_legacy; + 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_json_parse_result_legacy(world, json, token, &ctx, &desc); + if (!json) { + goto error; + } + + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + break; + } else if (token_kind != JsonComma) { + ecs_parser_error(name, expr, json - expr, + "expected ',' or ']'"); + goto error; + } + } while(json && json[0]); + +end: + flecs_from_json_ctx_fini_legacy(&ctx); + + json = flecs_json_expect(json, JsonObjectClose, token, &desc); + if (!json) { + goto error; + } + + return json; +error: + flecs_from_json_ctx_fini_legacy(&ctx); + return NULL; +} + +const char* ecs_world_from_json_file_legacy( + 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_legacy(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, desc->expr, 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); + 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_appendint(buf, (uint32_t)e); + } +} + +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); + ecs_strbuf_appendch(buf, '"'); + ecs_strbuf_appendch(buf, ','); + ecs_strbuf_appendch(buf, '"'); + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); + ecs_strbuf_appendch(buf, '"'); + } else { + ecs_strbuf_appendch(buf, '"'); + ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); + 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); + ecs_strbuf_appendch(buf, ','); + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); + ecs_strbuf_appendch(buf, ')'); + } else { + ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); + } +} + +void flecs_json_id_member( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id, + bool fullpath) +{ + 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); + +#endif + +#endif + + +#ifdef FLECS_JSON + +int ecs_entity_to_json_buf( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_strbuf_t *buf, + const ecs_entity_to_json_desc_t *desc) +{ + 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 = &flecs_table_entities_array(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_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 : false, + .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); + ecs_strbuf_appendch(buf, ','); + ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); + ecs_strbuf_appendch(buf, ')'); + } else { + ecs_get_path_w_sep_buf( + world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); + } + 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); + + flecs_json_memberl(buf, "query_plan"); + + bool prev_color = ecs_log_enable_colors(true); + char *plan = ecs_query_plan(q); + flecs_json_string_escape(buf, plan); + ecs_os_free(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); + qit.flags |= EcsIterIsInstanced; + + while (ecs_iter_next(&qit)) { + result_count ++; + entity_count += qit.count; + + int32_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); + + /* Use instancing for improved performance */ + ECS_BIT_SET(it->flags, EcsIterIsInstanced); + + /* 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 : false); + } + + 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); + + int32_t i, count = ecs_table_count(table); + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + 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; + int32_t i, count = ecs_table_count(table); + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + 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 : false); + 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, "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 = &flecs_table_entities_array(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); + + int32_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); + uint16_t field_mask = flecs_ito(uint16_t, (1 << 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_flags16_t field_bit = flecs_ito(uint16_t, 1 << 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_flags16_t field_bit = flecs_ito(uint16_t, 1 << 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) +{ + int32_t f, field_count = it->field_count; + if (!field_count) { + return 0; + } + + ecs_strbuf_appendlit(buf, "\"values\":"); + flecs_json_array_push(buf); + + for (f = 0; f < field_count; f ++) { + ecs_flags16_t field_bit = flecs_ito(uint16_t, 1 << f); + if (!(it->set_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; + } + + if (!it->ptrs[f]) { + ecs_strbuf_list_appendlit(buf, "0"); + continue; + } + + void *ptr = it->ptrs[f]; + if (!it->sources[f]) { + ptr = ECS_ELEM(ptr, it->sizes[f], 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 + +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) +{ + ecs_column_t *columns = table->data.columns; + int32_t i, column_count = table->column_count; + + flecs_json_memberl(buf, "type_info"); + flecs_json_object_push(buf); + + if (!column_count) { + flecs_json_object_pop(buf); + return false; + } + + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + flecs_json_next(buf); + ecs_strbuf_appendlit(buf, "\""); + flecs_json_id_member(buf, world, column->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) +{ + int32_t f, type_count = table->type.count; + ecs_id_t *ids = table->type.array; + int32_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 (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 (!tag_count) { + flecs_json_memberl(buf, "tags"); + flecs_json_array_push(buf); + } + + flecs_json_next(buf); + flecs_json_path_or_label(buf, world, id, desc->serialize_full_paths); + + 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) +{ + int32_t f, type_count = table->type.count; + ecs_id_t *ids = table->type.array; + int32_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 (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; + } + } + + 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->serialize_full_paths); + 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 = flecs_table_entities_array(table)[row]; + second = ecs_get_target(world, e, first, 0); + } + + flecs_json_path_or_label(buf, world, second, desc->serialize_full_paths); + + pair_count ++; + } + + if (same_first) { + flecs_json_array_pop(buf); + } + + if (pair_count) { + flecs_json_object_pop(buf); + } + + return pair_count != 0; +} + +static +bool flecs_json_serialize_table_inherited_type_components( + const ecs_world_t *world, + ecs_record_t *r, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc) +{ + ecs_table_t *table = r->table; + int32_t row = ECS_RECORD_TO_ROW(r->row); + + bool result = false; + int32_t i, count = table->column_count, component_count = 0; + const ecs_table_record_t *trs = table->_->records; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &table->data.columns[i]; + int32_t type_index = table->column_map[table->type.count + i]; + ecs_assert(type_index != -1, ECS_INTERNAL_ERROR, NULL); + + const ecs_table_record_t *tr = &trs[type_index]; + ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + if (!(idr->flags & EcsIdOnInstantiateInherit)) { + continue; + } + + const ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + const EcsTypeSerializer *ts = NULL; + if (!desc || desc->serialize_values) { + ts = ecs_get(world, ti->component, EcsTypeSerializer); + } + + if (!component_count) { + flecs_json_memberl(buf, "components"); + flecs_json_object_push(buf); + } + + ecs_strbuf_list_next(buf); + ecs_strbuf_appendlit(buf, "\""); + flecs_json_id_member(buf, world, column->id, + desc ? desc->serialize_full_paths : false); + ecs_strbuf_appendlit(buf, "\":"); + + if (ts) { + void *ptr = ecs_vec_get(&column->data, column->size, row); + if (flecs_json_ser_type(world, &ts->ops, ptr, buf) != 0) { + return -1; + } + } else { + ecs_strbuf_appendlit(buf, "null"); + } + + component_count ++; + } + + if (component_count) { + flecs_json_object_pop(buf); + } + + return result; +} + +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); + + flecs_json_serialize_table_inherited_type_components( + world, base_record, buf, desc); + + 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; +} + +static +int flecs_json_serialize_table_components( + const ecs_world_t *world, + ecs_table_t *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 column_count) +{ + int32_t f, component_count = 0; + for (f = 0; f < column_count; f ++) { + ecs_column_t *column = &table->data.columns[f]; + ecs_json_value_ser_ctx_t *value_ctx = &values_ctx[f]; + bool has_reflection = flecs_json_serialize_get_value_ctx( + world, column->id, value_ctx, desc); + + if (!component_count) { + flecs_json_memberl(buf, "components"); + flecs_json_object_push(buf); + } + + flecs_json_member(buf, value_ctx->id_label); + + if (has_reflection && (!desc || desc->serialize_values)) { + void *ptr = ecs_vec_get(&column->data, column->size, row); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value_ctx->id_label != NULL, ECS_INTERNAL_ERROR, NULL); + if (flecs_json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { + return -1; + } + } else { + ecs_strbuf_appendlit(buf, "null"); + } + + component_count ++; + } + + if (component_count) { + flecs_json_object_pop(buf); + } + + return 0; +} + +int flecs_json_serialize_iter_result_table( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + 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[256] = {{0}}; + int32_t column_count = table->column_count; + if (column_count > 256) { + column_count = 256; + } + + 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; + } + + if (flecs_json_serialize_table_components( + world, table, buf, values_ctx, desc, i, column_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 < column_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, 0 == (term->flags_ & EcsTermNoData)); + + 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); + } + 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_add(world, t, EcsEnum); + + 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) { + ecs_add_id(world, c, EcsConstant); + } else { + ecs_set_pair_second(world, c, EcsConstant, ecs_i32_t, + {m_desc->value}); + } + } + + 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, + {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 + }); + + 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 meta_parse_ctx_t { + const char *name; + const char *desc; +} meta_parse_ctx_t; + +typedef struct meta_type_t { + ecs_meta_token_t type; + ecs_meta_token_t params; + bool is_const; + bool is_ptr; +} meta_type_t; + +typedef struct meta_member_t { + meta_type_t type; + ecs_meta_token_t name; + int64_t count; + bool is_partial; +} meta_member_t; + +typedef struct meta_constant_t { + ecs_meta_token_t name; + int64_t value; + bool is_value_set; +} meta_constant_t; + +typedef struct meta_params_t { + meta_type_t key_type; + meta_type_t type; + int64_t count; + bool is_key_value; + bool is_fixed_size; +} meta_params_t; + +static +const char* skip_scope(const char *ptr, meta_parse_ctx_t *ctx) { + /* Keep track of which characters were used to open the scope */ + char stack[256]; + int32_t sp = 0; + char ch; + + 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, + meta_parse_ctx_t *ctx) +{ + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + char *bptr = buff, ch; + + if (params) { + params[0] = '\0'; + } + + /* 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 * meta_open_scope( + const char *ptr, + meta_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* meta_parse_constant( + const char *ptr, + meta_constant_t *token, + meta_parse_ctx_t *ctx) +{ + ptr = meta_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } + + token->is_value_set = false; + + /* 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* meta_parse_type( + const char *ptr, + meta_type_t *token, + meta_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* meta_parse_member( + const char *ptr, + meta_member_t *token, + meta_parse_ctx_t *ctx) +{ + ptr = meta_open_scope(ptr, ctx); + if (!ptr) { + return NULL; + } + + token->count = 1; + token->is_partial = false; + + /* Parse member type */ + ptr = meta_parse_type(ptr, &token->type, ctx); + if (!ptr) { + token->is_partial = true; + goto error; + } + + if (!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 meta_parse_desc( + const char *ptr, + meta_params_t *token, + meta_parse_ctx_t *ctx) +{ + token->is_key_value = false; + token->is_fixed_size = false; + + 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 = meta_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 = meta_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 meta_lookup( + ecs_world_t *world, + meta_type_t *token, + const char *ptr, + int64_t count, + meta_parse_ctx_t *ctx); + +static +ecs_entity_t meta_lookup_array( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) +{ + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; + + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } + if (!params.is_fixed_size) { + ecs_meta_error(ctx, params_decl, "missing size for array"); + goto error; + } + + 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 meta_lookup_vector( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) +{ + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; + + meta_params_t params; + if (meta_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 = meta_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 meta_lookup_bitmask( + ecs_world_t *world, + ecs_entity_t e, + const char *params_decl, + meta_parse_ctx_t *ctx) +{ + (void)e; + + meta_parse_ctx_t param_ctx = { + .name = ctx->name, + .desc = params_decl + }; + + meta_params_t params; + if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { + goto error; + } + + 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 = meta_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 meta_lookup( + ecs_world_t *world, + meta_type_t *token, + const char *ptr, + int64_t count, + meta_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 = meta_lookup_array(world, 0, token->params, ctx); + + } else if (!ecs_os_strcmp(typename, "ecs_vector") || + !ecs_os_strcmp(typename, "flecs::vector")) + { + type = meta_lookup_vector(world, 0, token->params, ctx); + + } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { + type = meta_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 meta_parse_struct( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) +{ + const char *ptr = desc; + const char *name = ecs_get_name(world, t); + + meta_member_t token; + meta_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; + + ecs_entity_t old_scope = ecs_set_scope(world, t); + + while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) { + ecs_entity_t m = ecs_entity(world, { + .name = token.name + }); + + ecs_entity_t type = meta_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 meta_parse_constants( + ecs_world_t *world, + ecs_entity_t t, + const char *desc, + bool is_bitmask) +{ + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + + 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; + + meta_parse_ctx_t ctx = { + .name = name, + .desc = ptr + }; + + meta_constant_t token; + int64_t last_value = 0; + + ecs_entity_t old_scope = ecs_set_scope(world, t); + + while ((ptr = meta_parse_constant(ptr, &token, &ctx))) { + if (token.is_value_set) { + last_value = token.value; + } else if (is_bitmask) { + ecs_meta_error(&ctx, ptr, + "bitmask requires explicit value assignment"); + goto error; + } + + 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 meta_parse_enum( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) +{ + ecs_add(world, t, EcsEnum); + return meta_parse_constants(world, t, desc, false); +} + +static +int meta_parse_bitmask( + ecs_world_t *world, + ecs_entity_t t, + const char *desc) +{ + ecs_add(world, t, EcsBitmask); + return meta_parse_constants(world, t, desc, true); +} + +int ecs_meta_from_desc( + ecs_world_t *world, + ecs_entity_t component, + ecs_type_kind_t kind, + const char *desc) +{ + switch(kind) { + case EcsStructType: + if (meta_parse_struct(world, component, desc)) { + goto error; + } + break; + case EcsEnumType: + if (meta_parse_enum(world, component, desc)) { + goto error; + } + break; + case EcsBitmaskType: + if (meta_parse_bitmask(world, component, desc)) { + goto error; + } + break; + case EcsPrimitiveType: + case EcsArrayType: + case EcsVectorType: + case EcsOpaqueType: + break; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); + } + + return 0; +error: + return -1; +} + +#endif + +/** + * @file addons/meta/cursor.c + * @brief API for assigning values of runtime types with reflection. + */ + +#include + +#ifdef FLECS_META +#ifdef FLECS_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)", scope->elem_cur); + return -1; + } + return 0; + } + + scope->op_cur += op->op_count; + + if (scope->op_cur >= scope->op_count) { + ecs_err("out of bounds"); + return -1; + } + + 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) { + if ((op->kind > EcsOpScope) && (op->count <= 1)) { + cursor->is_primitive_scope = true; + 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) +{ + 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 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: + case EcsOpString: + 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 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 EcsOpString: + 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 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: + case EcsOpString: { + 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 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: + case EcsOpString: + 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 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: + case EcsOpString: + 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); + + 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 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: + case EcsOpString: + 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 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 EcsOpString: + 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); +} + +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 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 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); +} + +/* 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) } + } + }); + + 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, { + .type = { + .name = "flecs.core.addon_vec_t", + .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 = ecs_id(ecs_string_t) }, + { .name = "addons", .type = addon_vec }, + { .name = "version", .type = ecs_id(ecs_string_t) }, + { .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) } + } + }); + + 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(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); +}) + +static ECS_MOVE(EcsEnum, dst, src, { + flecs_constants_dtor(&dst->constants); + dst->constants = src->constants; + 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) { + meta_type->existing = ecs_has(world, type, EcsComponent); + + /* Ensure that component has a default constructor, to prevent crashing + * serializers on uninitialized values. */ + ecs_type_info_t *ti = flecs_type_info_ensure(world, type); + if (!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 = false; + if (m->offset) { + explicit_offset = true; + } + + /* 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); + + /* 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 */ + int32_t value = 0; + bool value_set = false; + if (ecs_id_is_pair(constant_id)) { + if (ecs_pair_second(world, constant_id) != ecs_id(ecs_i32_t)) { + char *path = ecs_get_path(world, e); + ecs_err("expected i32 type for enum constant '%s'", path); + ecs_os_free(path); + return -1; + } + + const int32_t *value_ptr = ecs_get_pair_second( + world, e, EcsConstant, ecs_i32_t); + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + value = *value_ptr; + 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 (value_set) { + if (c->value == value) { + char *path = ecs_get_path(world, e); + ecs_err("conflicting constant value %d for '%s' (other is '%s')", + value, path, c->name); + ecs_os_free(path); + return -1; + } + } else { + if (c->value >= value) { + value = c->value + 1; + } + } + } + + ecs_map_init_if(&ptr->constants, &world->allocator); + ecs_enum_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, + ecs_enum_constant_t, (ecs_map_key_t)value); + c->name = ecs_os_strdup(ecs_get_name(world, e)); + c->value = value; + c->constant = e; + + ecs_i32_t *cptr = ecs_ensure_pair_second( + world, e, EcsConstant, ecs_i32_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 +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; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + + if (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { + continue; + } + + 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)); + 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 ecs_meta_type_init_default_ctor(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsType *type = ecs_field(it, EcsType, 0); + + int i; + for (i = 0; i < it->count; i ++) { + /* If a component is defined from reflection data, configure it with the + * default constructor. This ensures that a new component value does not + * contain uninitialized memory, which could cause serializers to crash + * when for example inspecting string fields. */ + if (!type->existing) { + ecs_entity_t e = it->entities[i]; + const ecs_type_info_t *ti = ecs_get_type_info(world, e); + if (!ti || !ti->hooks.ctor) { + ecs_set_hooks_id(world, e, + &(ecs_type_hooks_t){ + .ctor = flecs_default_ctor + }); + } + } + } +} + +static +void flecs_member_on_set(ecs_iter_t *it) { + EcsMember *mbr = it->ptrs[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" + }), + .type.size = sizeof(EcsType), + .type.alignment = ECS_ALIGNOF(EcsType) + }); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsPrimitive), + .name = "primitive", .symbol = "EcsPrimitive" + }), + .type.size = sizeof(EcsPrimitive), + .type.alignment = ECS_ALIGNOF(EcsPrimitive) + }); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = EcsConstant, + .name = "constant", .symbol = "EcsConstant" + }) + }); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsEnum), + .name = "enum", .symbol = "EcsEnum" + }), + .type.size = sizeof(EcsEnum), + .type.alignment = ECS_ALIGNOF(EcsEnum) + }); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsBitmask), + .name = "bitmask", .symbol = "EcsBitmask" + }), + .type.size = sizeof(EcsBitmask), + .type.alignment = ECS_ALIGNOF(EcsBitmask) + }); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsMember), + .name = "member", .symbol = "EcsMember" + }), + .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" + }), + .type.size = sizeof(EcsMemberRanges), + .type.alignment = ECS_ALIGNOF(EcsMemberRanges) + }); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsStruct), + .name = "struct", .symbol = "EcsStruct" + }), + .type.size = sizeof(EcsStruct), + .type.alignment = ECS_ALIGNOF(EcsStruct) + }); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsArray), + .name = "array", .symbol = "EcsArray" + }), + .type.size = sizeof(EcsArray), + .type.alignment = ECS_ALIGNOF(EcsArray) + }); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsVector), + .name = "vector", .symbol = "EcsVector" + }), + .type.size = sizeof(EcsVector), + .type.alignment = ECS_ALIGNOF(EcsVector) + }); + + ecs_component(world, { + .entity = ecs_entity(world, { .id = ecs_id(EcsOpaque), + .name = "opaque", .symbol = "EcsOpaque" + }), + .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), .src.id = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_primitive + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsMember), .src.id = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_member + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsMemberRanges), .src.id = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_member_ranges + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsEnum), .src.id = EcsSelf }, + .events = {EcsOnAdd}, + .callback = flecs_add_enum + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsBitmask), .src.id = EcsSelf }, + .events = {EcsOnAdd}, + .callback = flecs_add_bitmask + }); + + ecs_observer(world, { + .query.terms[0] = { .id = EcsConstant, .src.id = EcsSelf }, + .events = {EcsOnAdd}, + .callback = flecs_add_constant + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.id = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_add_constant + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsArray), .src.id = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_array + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsVector), .src.id = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_vector + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsOpaque), .src.id = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_custom_type + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsUnit), .src.id = EcsSelf }, + .events = {EcsOnSet}, + .callback = flecs_set_unit + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsType), .src.id = EcsSelf }, + .events = {EcsOnSet}, + .callback = ecs_meta_type_serialized_init + }); + + ecs_observer(world, { + .query.terms[0] = { .id = ecs_id(EcsType), .src.id = EcsSelf }, + .events = {EcsOnSet}, + .callback = ecs_meta_type_init_default_ctor + }); + + 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 */ + 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/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. */ + 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); + + 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_strdup(parser, str)\ + (str ? flecs_strdup(&parser->script->allocator, str) : NULL) +#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(a, size); + 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); + 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); + return result; +} + +ecs_script_entity_t* flecs_script_insert_entity( + 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_entity_t *result = flecs_ast_new( + parser, ecs_script_entity_t, EcsAstEntity); + + if (name && !ecs_os_strcmp(name, "_")) { + name = NULL; + } + + result->name = name; + + 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; +} + +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; +} + +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; + + 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; +} + +#endif + +/** + * @file addons/script/expr.c + * @brief Evaluate script expressions. + */ + + +#ifdef FLECS_SCRIPT +#include +#include + +/* String deserializer for script expressions */ + +/* Order in enumeration is important, as it is used for precedence */ +typedef enum ecs_expr_oper_t { + EcsExprOperUnknown, + EcsLeftParen, + EcsCondAnd, + EcsCondOr, + EcsCondEq, + EcsCondNeq, + EcsCondGt, + EcsCondGtEq, + EcsCondLt, + EcsCondLtEq, + EcsShiftLeft, + EcsShiftRight, + EcsAdd, + EcsSub, + EcsMul, + EcsDiv, + EcsMin +} ecs_expr_oper_t; + +/* Used to track temporary values */ +#define FLECS_EXPR_MAX_STACK_SIZE (256) + +typedef struct ecs_expr_value_t { + const ecs_type_info_t *ti; + void *ptr; +} ecs_expr_value_t; + +typedef struct ecs_value_stack_t { + ecs_expr_value_t values[FLECS_EXPR_MAX_STACK_SIZE]; + ecs_stack_cursor_t *cursor; + ecs_stack_t *stack; + ecs_stage_t *stage; + int32_t count; +} ecs_value_stack_t; + +static +const char* flecs_script_expr_run( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *ptr, + ecs_value_t *value, + ecs_expr_oper_t op, + const ecs_script_expr_run_desc_t *desc); + +#define TOK_VARIABLE '$' + +/* -- Private functions -- */ + +static +bool flecs_isident( + char ch) +{ + return isalpha(ch) || (ch == '_'); +} + +static +bool flecs_valid_identifier_start_char( + char ch) +{ + if (ch && (flecs_isident(ch) || (ch == '*') || + (ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) + { + return true; + } + + return false; +} + +static +bool flecs_valid_token_start_char( + char ch) +{ + if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') + || (ch == '[') || (ch == ']') || (ch == '`') || + flecs_valid_identifier_start_char(ch)) + { + return true; + } + + return false; +} + +static +bool flecs_valid_token_char( + char ch) +{ + if (ch && (flecs_isident(ch) || isdigit(ch) || ch == '.' || ch == '"')) { + return true; + } + + return false; +} + +static +const char* flecs_parse_ws( + const char *ptr) +{ + while ((*ptr != '\n') && isspace(*ptr)) { + ptr ++; + } + + return ptr; +} + +static +const char* flecs_parse_expr_token( + const char *name, + const char *expr, + const char *ptr, + char *token_out, + char delim) +{ + int64_t column = ptr - expr; + + ptr = flecs_parse_ws(ptr); + char *tptr = token_out, ch = ptr[0]; + + if (!flecs_valid_token_start_char(ch)) { + if (ch == '\0' || ch == '\n') { + ecs_parser_error(name, expr, column, + "unexpected end of expression"); + } else { + ecs_parser_error(name, expr, column, + "invalid start of token '%s'", ptr); + } + return NULL; + } + + tptr[0] = ch; + tptr ++; + ptr ++; + + if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',' || ch == '`') { + tptr[0] = 0; + return ptr; + } + + int tmpl_nesting = 0; + bool in_str = ch == '"'; + + for (; (ch = *ptr); ptr ++) { + if (ch == '<') { + tmpl_nesting ++; + } else if (ch == '>') { + if (!tmpl_nesting) { + break; + } + tmpl_nesting --; + } else if (ch == '"') { + in_str = !in_str; + } else if (ch == '\\') { + ptr ++; + tptr[0] = ptr[0]; + tptr ++; + continue; + } else if (!flecs_valid_token_char(ch) && !in_str) { + break; + } + + if (delim && (ch == delim)) { + break; + } + + tptr[0] = ch; + tptr ++; + } + + tptr[0] = '\0'; + + if (tmpl_nesting != 0) { + ecs_parser_error(name, expr, column, + "identifier '%s' has mismatching < > pairs", ptr); + return NULL; + } + + const char *next_ptr = flecs_parse_ws(ptr); + if (next_ptr[0] == ':' && next_ptr != ptr) { + /* Whitespace between token and : is significant */ + ptr = next_ptr - 1; + } else { + ptr = next_ptr; + } + + return ptr; +} + +static +void* flecs_expr_value_new( + ecs_value_stack_t *stack, + ecs_entity_t type) +{ + ecs_stage_t *stage = stack->stage; + ecs_world_t *world = stage->world; + ecs_id_record_t *idr = flecs_id_record_get(world, type); + if (!idr) { + return NULL; + } + + const ecs_type_info_t *ti = idr->type_info; + if (!ti) { + return NULL; + } + + ecs_assert(ti->size != 0, ECS_INTERNAL_ERROR, NULL); + void *result = flecs_stack_alloc(stack->stack, ti->size, ti->alignment); + if (ti->hooks.ctor) { + ti->hooks.ctor(result, 1, ti); + } else { + ecs_os_memset(result, 0, ti->size); + } + if (ti->hooks.dtor) { + /* Track values that have destructors */ + stack->values[stack->count].ti = ti; + stack->values[stack->count].ptr = result; + stack->count ++; + } + + return result; +} + +static +const char* flecs_str_to_expr_oper( + const char *str, + ecs_expr_oper_t *op) +{ + if (!ecs_os_strncmp(str, "+", 1)) { + *op = EcsAdd; + return str + 1; + } else if (!ecs_os_strncmp(str, "-", 1)) { + *op = EcsSub; + return str + 1; + } else if (!ecs_os_strncmp(str, "*", 1)) { + *op = EcsMul; + return str + 1; + } else if (!ecs_os_strncmp(str, "/", 1)) { + *op = EcsDiv; + return str + 1; + } else if (!ecs_os_strncmp(str, "&&", 2)) { + *op = EcsCondAnd; + return str + 2; + } else if (!ecs_os_strncmp(str, "||", 2)) { + *op = EcsCondOr; + return str + 2; + } else if (!ecs_os_strncmp(str, "==", 2)) { + *op = EcsCondEq; + return str + 2; + } else if (!ecs_os_strncmp(str, "!=", 2)) { + *op = EcsCondNeq; + return str + 2; + } else if (!ecs_os_strncmp(str, ">=", 2)) { + *op = EcsCondGtEq; + return str + 2; + } else if (!ecs_os_strncmp(str, "<=", 2)) { + *op = EcsCondLtEq; + return str + 2; + } else if (!ecs_os_strncmp(str, ">>", 2)) { + *op = EcsShiftRight; + return str + 2; + } else if (!ecs_os_strncmp(str, "<<", 2)) { + *op = EcsShiftLeft; + return str + 2; + } else if (!ecs_os_strncmp(str, ">", 1)) { + *op = EcsCondGt; + return str + 1; + } else if (!ecs_os_strncmp(str, "<", 1)) { + *op = EcsCondLt; + return str + 1; + } + + *op = EcsExprOperUnknown; + return NULL; +} + +static +const char *flecs_script_expr_parse_token( + const char *name, + const char *expr, + const char *ptr, + char *token) +{ + char *token_ptr = token; + + if (ptr[0] == '/') { + char ch; + if (ptr[1] == '/') { + // Single line comment + for (ptr = &ptr[2]; (ch = ptr[0]) && (ch != '\n'); ptr ++) {} + token[0] = 0; + return flecs_parse_ws_eol(ptr); + } else if (ptr[1] == '*') { + // Multi line comment + for (ptr = &ptr[2]; (ch = ptr[0]); ptr ++) { + if (ch == '*' && ptr[1] == '/') { + token[0] = 0; + return flecs_parse_ws_eol(ptr + 2); + } + } + + ecs_parser_error(name, expr, ptr - expr, + "missing */ for multiline comment"); + return NULL; + } + } + + ecs_expr_oper_t op; + if (ptr[0] == '(') { + token[0] = '('; + token[1] = 0; + return ptr + 1; + } else if (ptr[0] != '-') { + const char *tptr = flecs_str_to_expr_oper(ptr, &op); + if (tptr) { + ecs_os_strncpy(token, ptr, tptr - ptr); + return tptr; + } + } + + while ((ptr = flecs_parse_expr_token(name, expr, ptr, token_ptr, 0))) { + if (ptr[0] == '|' && ptr[1] != '|') { + token_ptr = &token_ptr[ecs_os_strlen(token_ptr)]; + token_ptr[0] = '|'; + token_ptr[1] = '\0'; + token_ptr ++; + ptr ++; + } else { + break; + } + } + + return ptr; +} + +static +const char* flecs_parse_multiline_string( + ecs_meta_cursor_t *cur, + const char *name, + const char *expr, + const char *ptr) +{ + /* Multiline string */ + ecs_strbuf_t str = ECS_STRBUF_INIT; + char ch; + while ((ch = ptr[0]) && (ch != '`')) { + if (ch == '\\' && ptr[1] == '`') { + ch = '`'; + ptr ++; + } + ecs_strbuf_appendch(&str, ch); + ptr ++; + } + + if (ch != '`') { + ecs_parser_error(name, expr, ptr - expr, + "missing '`' to close multiline string"); + goto error; + } + char *strval = ecs_strbuf_get(&str); + if (ecs_meta_set_string(cur, strval) != 0) { + goto error; + } + ecs_os_free(strval); + + return ptr + 1; +error: + return NULL; +} + +static +bool flecs_parse_is_float( + const char *ptr) +{ + ecs_assert(isdigit(ptr[0]), ECS_INTERNAL_ERROR, NULL); + char ch; + while ((ch = (++ptr)[0])) { + if (ch == '.' || ch == 'e') { + return true; + } + if (!isdigit(ch)) { + return false; + } + } + return false; +} + +/* Attempt to resolve variable dotexpression to value (foo.bar) */ +static +ecs_value_t flecs_dotresolve_var( + ecs_world_t *world, + ecs_script_vars_t *vars, + char *token) +{ + char *dot = strchr(token, '.'); + if (!dot) { + return (ecs_value_t){ .type = ecs_id(ecs_entity_t) }; + } + + dot[0] = '\0'; + + const ecs_script_var_t *var = ecs_script_vars_lookup(vars, token); + if (!var) { + return (ecs_value_t){0}; + } + + ecs_meta_cursor_t cur = ecs_meta_cursor( + world, var->value.type, var->value.ptr); + ecs_meta_push(&cur); + if (ecs_meta_dotmember(&cur, dot + 1) != 0) { + return (ecs_value_t){0}; + } + + return (ecs_value_t){ + .ptr = ecs_meta_get_ptr(&cur), + .type = ecs_meta_get_type(&cur) + }; +} + +static +int flecs_meta_call( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + ecs_meta_cursor_t *cur, + const char *function) +{ + ecs_entity_t type = ecs_meta_get_type(cur); + void *value_ptr = ecs_meta_get_ptr(cur); + + if (!ecs_os_strcmp(function, "parent")) { + if (type != ecs_id(ecs_entity_t)) { + ecs_parser_error(name, expr, ptr - expr, + "parent() can only be called on entity"); + return -1; + } + + *(ecs_entity_t*)value_ptr = ecs_get_parent( + world, *(ecs_entity_t*)value_ptr); + } else if (!ecs_os_strcmp(function, "name")) { + if (type != ecs_id(ecs_entity_t)) { + ecs_parser_error(name, expr, ptr - expr, + "name() can only be called on entity"); + return -1; + } + + char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); + *result = ecs_os_strdup(ecs_get_name(world, *(ecs_entity_t*)value_ptr)); + *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); + } else if (!ecs_os_strcmp(function, "doc_name")) { +#ifdef FLECS_DOC + if (type != ecs_id(ecs_entity_t)) { + ecs_parser_error(name, expr, ptr - expr, + "name() can only be called on entity"); + return -1; + } + + char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); + *result = ecs_os_strdup(ecs_doc_get_name(world, *(ecs_entity_t*)value_ptr)); + *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); +#else + ecs_parser_error(name, expr, ptr - expr, + "doc_name() is not available without FLECS_DOC addon"); + return -1; +#endif + } else { + ecs_parser_error(name, expr, ptr - expr, + "unknown function '%s'", function); + return -1; + } + + return 0; +} + +/* Determine the type of an expression from the first character(s). This allows + * us to initialize a storage for a type if none was provided. */ +static +ecs_entity_t flecs_parse_discover_type( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_entity_t input_type, + const ecs_script_expr_run_desc_t *desc) +{ + /* String literal */ + if (ptr[0] == '"' || ptr[0] == '`') { + if (input_type == ecs_id(ecs_char_t)) { + return input_type; + } + return ecs_id(ecs_string_t); + } + + /* Negative number literal */ + if (ptr[0] == '-') { + if (!isdigit(ptr[1])) { + ecs_parser_error(name, expr, ptr - expr, "invalid literal"); + return 0; + } + if (flecs_parse_is_float(ptr + 1)) { + return ecs_id(ecs_f64_t); + } else { + return ecs_id(ecs_i64_t); + } + } + + /* Positive number literal */ + if (isdigit(ptr[0])) { + if (flecs_parse_is_float(ptr)) { + return ecs_id(ecs_f64_t); + } else { + return ecs_id(ecs_u64_t); + } + } + + /* Variable */ + if (ptr[0] == '$') { + if (!desc || !desc->vars) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable (no variable scope)"); + return 0; + } + + char token[ECS_MAX_TOKEN_SIZE]; + if (flecs_script_expr_parse_token(name, expr, &ptr[1], token) == NULL) { + return 0; + } + + const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, token); + if (!var) { + ecs_size_t len = ecs_os_strlen(token); + if (ptr[len + 1] == '(') { + while ((len > 0) && (token[len] != '.')) { + len --; + } + if (token[len] == '.') { + token[len] = '\0'; + } + + return ecs_id(ecs_entity_t); + } + + ecs_value_t v = flecs_dotresolve_var(world, desc->vars, token); + if (v.type) { + return v.type; + } + + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable '%s'", token); + return 0; + } + return var->value.type; + } + + /* Boolean */ + if (ptr[0] == 't' && !ecs_os_strncmp(ptr, "true", 4)) { + if (!isalpha(ptr[4]) && ptr[4] != '_') { + return ecs_id(ecs_bool_t); + } + } + + if (ptr[0] == 'f' && !ecs_os_strncmp(ptr, "false", 5)) { + if (!isalpha(ptr[5]) && ptr[5] != '_') { + return ecs_id(ecs_bool_t); + } + } + + /* Entity identifier */ + if (isalpha(ptr[0])) { + if (!input_type) { /* Identifier could also be enum/bitmask constant */ + char token[ECS_MAX_TOKEN_SIZE]; + const char *tptr = flecs_script_expr_parse_token( + name, expr, ptr, token); + if (!tptr) { + return 0; + } + + if (tptr[0] != '[') { + return ecs_id(ecs_entity_t); + } + + tptr = flecs_script_expr_parse_token( + name, expr, tptr + 1, token); + if (!tptr) { + return 0; + } + + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t type = desc->lookup_action( + world, token, desc->lookup_ctx); + if (!type) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved type '%s'", token); + return 0; + } + + if (tptr[0] != ']') { + ecs_parser_error(name, expr, ptr - expr, + "missing ']' after '%s'", token); + return 0; + } + + if (tptr[1] != '.') { + return type; + } + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL); + ecs_meta_push(&cur); + + tptr = flecs_script_expr_parse_token( + name, expr, tptr + 2, token); + if (!tptr) { + return 0; + } + + if (ecs_meta_dotmember(&cur, token) != 0) { + ecs_parser_error(name, expr, ptr - expr, + "failed to assign member '%s'", token); + return 0; + } + + return ecs_meta_get_type(&cur); + } + } + + /* If no default type was provided we can't automatically deduce the type of + * composite/collection expressions. */ + if (!input_type) { + if (ptr[0] == '{') { + ecs_parser_error(name, expr, ptr - expr, + "unknown type for composite literal"); + return 0; + } + + if (ptr[0] == '[') { + ecs_parser_error(name, expr, ptr - expr, + "unknown type for collection literal"); + return 0; + } + + ecs_parser_error(name, expr, ptr - expr, "invalid expression"); + } + + return input_type; +} + +/* Normalize types to their largest representation. + * Rather than taking the original type of a value, use the largest + * representation of the type so we don't have to worry about overflowing the + * original type in the operation. */ +static +ecs_entity_t flecs_largest_type( + const EcsPrimitive *type) +{ + switch(type->kind) { + case EcsBool: return ecs_id(ecs_bool_t); + case EcsChar: return ecs_id(ecs_char_t); + case EcsByte: return ecs_id(ecs_u8_t); + case EcsU8: return ecs_id(ecs_u64_t); + case EcsU16: return ecs_id(ecs_u64_t); + case EcsU32: return ecs_id(ecs_u64_t); + case EcsU64: return ecs_id(ecs_u64_t); + case EcsI8: return ecs_id(ecs_i64_t); + case EcsI16: return ecs_id(ecs_i64_t); + case EcsI32: return ecs_id(ecs_i64_t); + case EcsI64: return ecs_id(ecs_i64_t); + case EcsF32: return ecs_id(ecs_f64_t); + case EcsF64: return ecs_id(ecs_f64_t); + case EcsUPtr: return ecs_id(ecs_u64_t); + case EcsIPtr: return ecs_id(ecs_i64_t); + case EcsString: return ecs_id(ecs_string_t); + case EcsEntity: return ecs_id(ecs_entity_t); + case EcsId: return ecs_id(ecs_id_t); + default: ecs_throw(ECS_INTERNAL_ERROR, NULL); + } +error: + return 0; +} + +/** Test if a normalized type can promote to another type in an expression */ +static +bool flecs_is_type_number( + ecs_entity_t type) +{ + if (type == ecs_id(ecs_bool_t)) return false; + else if (type == ecs_id(ecs_char_t)) return false; + else if (type == ecs_id(ecs_u8_t)) return false; + else if (type == ecs_id(ecs_u64_t)) return true; + else if (type == ecs_id(ecs_i64_t)) return true; + else if (type == ecs_id(ecs_f64_t)) return true; + else if (type == ecs_id(ecs_string_t)) return false; + else if (type == ecs_id(ecs_entity_t)) return false; + else return false; +} + +static +bool flecs_oper_valid_for_type( + ecs_entity_t type, + ecs_expr_oper_t op) +{ + switch(op) { + case EcsAdd: + case EcsSub: + case EcsMul: + case EcsDiv: + return flecs_is_type_number(type); + case EcsCondEq: + case EcsCondNeq: + case EcsCondAnd: + case EcsCondOr: + case EcsCondGt: + case EcsCondGtEq: + case EcsCondLt: + case EcsCondLtEq: + return flecs_is_type_number(type) || + (type == ecs_id(ecs_bool_t)) || + (type == ecs_id(ecs_char_t)) || + (type == ecs_id(ecs_entity_t)); + case EcsShiftLeft: + case EcsShiftRight: + return (type == ecs_id(ecs_u64_t)); + case EcsExprOperUnknown: + case EcsLeftParen: + case EcsMin: + return false; + default: + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } +} + +/** Promote type to most expressive (f64 > i64 > u64) */ +static +ecs_entity_t flecs_promote_type( + ecs_entity_t type, + ecs_entity_t promote_to) +{ + if (type == ecs_id(ecs_u64_t)) { + return promote_to; + } + if (promote_to == ecs_id(ecs_u64_t)) { + return type; + } + if (type == ecs_id(ecs_f64_t)) { + return type; + } + if (promote_to == ecs_id(ecs_f64_t)) { + return promote_to; + } + return ecs_id(ecs_i64_t); +} + +static +int flecs_oper_precedence( + ecs_expr_oper_t left, + ecs_expr_oper_t right) +{ + return (left > right) - (left < right); +} + +static +void flecs_value_cast( + ecs_world_t *world, + ecs_value_stack_t *stack, + ecs_value_t *value, + ecs_entity_t type) +{ + if (value->type == type) { + return; + } + + ecs_value_t result; + result.type = type; + result.ptr = flecs_expr_value_new(stack, type); + + if (value->ptr) { + ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, result.ptr); + ecs_meta_set_value(&cur, value); + } + + *value = result; +} + +static +bool flecs_expr_op_is_equality( + ecs_expr_oper_t op) +{ + switch(op) { + case EcsCondEq: + case EcsCondNeq: + case EcsCondGt: + case EcsCondGtEq: + case EcsCondLt: + case EcsCondLtEq: + return true; + case EcsCondAnd: + case EcsCondOr: + case EcsShiftLeft: + case EcsShiftRight: + case EcsAdd: + case EcsSub: + case EcsMul: + case EcsDiv: + case EcsExprOperUnknown: + case EcsLeftParen: + case EcsMin: + return false; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); + } +error: + return false; +} + +static +ecs_entity_t flecs_binary_expr_type( + ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + ecs_value_t *lvalue, + ecs_value_t *rvalue, + ecs_expr_oper_t op, + ecs_entity_t *operand_type_out) +{ + ecs_entity_t result_type = 0, operand_type = 0; + + switch(op) { + case EcsDiv: + /* Result type of a division is always a float */ + *operand_type_out = ecs_id(ecs_f64_t); + return ecs_id(ecs_f64_t); + case EcsCondAnd: + case EcsCondOr: + /* Result type of a condition operator is always a bool */ + *operand_type_out = ecs_id(ecs_bool_t); + return ecs_id(ecs_bool_t); + case EcsCondEq: + case EcsCondNeq: + case EcsCondGt: + case EcsCondGtEq: + case EcsCondLt: + case EcsCondLtEq: + /* 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 EcsShiftLeft: + case EcsShiftRight: + case EcsAdd: + case EcsSub: + case EcsMul: + case EcsExprOperUnknown: + case EcsLeftParen: + case EcsMin: + break; + default: + ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); + } + + /* Result type for arithmetic operators is determined by operands */ + const EcsPrimitive *ltype_ptr = ecs_get(world, lvalue->type, EcsPrimitive); + const EcsPrimitive *rtype_ptr = ecs_get(world, rvalue->type, EcsPrimitive); + if (!ltype_ptr || !rtype_ptr) { + char *lname = ecs_get_path(world, lvalue->type); + char *rname = ecs_get_path(world, rvalue->type); + ecs_parser_error(name, expr, ptr - expr, + "invalid non-primitive type in binary expression (%s, %s)", + lname, rname); + ecs_os_free(lname); + ecs_os_free(rname); + return 0; + } + + ecs_entity_t ltype = flecs_largest_type(ltype_ptr); + ecs_entity_t rtype = flecs_largest_type(rtype_ptr); + if (ltype == rtype) { + operand_type = ltype; + goto done; + } + + if (flecs_expr_op_is_equality(op)) { + char *lstr = ecs_id_str(world, ltype); + char *rstr = ecs_id_str(world, rtype); + ecs_parser_error(name, expr, ptr - expr, + "mismatching types in equality expression (%s vs %s)", + lstr, rstr); + ecs_os_free(rstr); + ecs_os_free(lstr); + return 0; + } + + if (!flecs_is_type_number(ltype) || !flecs_is_type_number(rtype)) { + ecs_parser_error(name, expr, ptr - expr, + "incompatible types in binary expression"); + return 0; + } + + operand_type = flecs_promote_type(ltype, rtype); + +done: + if (op == EcsSub && 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; + } + + *operand_type_out = operand_type; + return result_type; +error: + return 0; +} + +/* Macro's to let the compiler do the operations & conversion work for us */ + +#define ECS_VALUE_GET(value, T) (*(T*)value->ptr) + +#define ECS_BINARY_OP_T(left, right, result, op, R, T)\ + ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) + +#define ECS_BINARY_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ecs_parser_error(name, expr, ptr - expr, "unsupported operator for floating point");\ + return -1;\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_COND_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ + } else if (left->type == ecs_id(ecs_i64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ + } else if (left->type == ecs_id(ecs_f64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ + } else if (left->type == ecs_id(ecs_u8_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ + } else if (left->type == ecs_id(ecs_char_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ + } else if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_BOOL_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_bool_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +#define ECS_BINARY_UINT_OP(left, right, result, op)\ + if (left->type == ecs_id(ecs_u64_t)) { \ + ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ + } else {\ + ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ + } + +static +int flecs_binary_expr_do( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + ecs_value_t *lvalue, + ecs_value_t *rvalue, + ecs_value_t *result, + ecs_expr_oper_t op) +{ + /* Find expression type */ + ecs_entity_t operand_type, type = flecs_binary_expr_type( + world, name, expr, ptr, lvalue, rvalue, op, &operand_type); + if (!type) { + goto error; + } + + if (!flecs_oper_valid_for_type(type, op)) { + ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); + goto error; + } + + flecs_value_cast(world, stack, lvalue, operand_type); + flecs_value_cast(world, stack, rvalue, operand_type); + + ecs_value_t *storage = result; + ecs_value_t tmp_storage = {0}; + if (result->type != type) { + storage = &tmp_storage; + storage->type = type; + storage->ptr = flecs_expr_value_new(stack, type); + } + + switch(op) { + case EcsAdd: + ECS_BINARY_OP(lvalue, rvalue, storage, +); + break; + case EcsSub: + ECS_BINARY_OP(lvalue, rvalue, storage, -); + break; + case EcsMul: + ECS_BINARY_OP(lvalue, rvalue, storage, *); + break; + case EcsDiv: + ECS_BINARY_OP(lvalue, rvalue, storage, /); + break; + case EcsCondEq: + ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, ==); + break; + case EcsCondNeq: + ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, !=); + break; + case EcsCondGt: + ECS_BINARY_COND_OP(lvalue, rvalue, storage, >); + break; + case EcsCondGtEq: + ECS_BINARY_COND_OP(lvalue, rvalue, storage, >=); + break; + case EcsCondLt: + ECS_BINARY_COND_OP(lvalue, rvalue, storage, <); + break; + case EcsCondLtEq: + ECS_BINARY_COND_OP(lvalue, rvalue, storage, <=); + break; + case EcsCondAnd: + ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, &&); + break; + case EcsCondOr: + ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, ||); + break; + case EcsShiftLeft: + ECS_BINARY_UINT_OP(lvalue, rvalue, storage, <<); + break; + case EcsShiftRight: + ECS_BINARY_UINT_OP(lvalue, rvalue, storage, >>); + break; + case EcsExprOperUnknown: + case EcsLeftParen: + case EcsMin: + ecs_parser_error(name, expr, ptr - expr, "unsupported operator"); + goto error; + default: + ecs_throw(ECS_INTERNAL_ERROR, NULL); + } + + if (storage->ptr != result->ptr) { + if (!result->ptr) { + *result = *storage; + } else { + ecs_meta_cursor_t cur = ecs_meta_cursor(world, + result->type, result->ptr); + ecs_meta_set_value(&cur, storage); + } + } + + return 0; +error: + return -1; +} + +static +const char* flecs_binary_expr_parse( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + ecs_value_t *lvalue, + ecs_value_t *result, + ecs_expr_oper_t left_op, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_entity_t result_type = result->type; + do { + ecs_expr_oper_t op; + + ptr = flecs_str_to_expr_oper(ptr, &op); + if (!ptr) { + ecs_parser_error(name, expr, ptr - expr, "invalid operator"); + return NULL; + } + + ptr = flecs_parse_ws_eol(ptr); + + ecs_value_t rvalue = {0}; + const char *rptr = flecs_script_expr_run(world, stack, ptr, &rvalue, op, desc); + if (!rptr) { + return NULL; + } + + if (flecs_binary_expr_do(world, stack, name, expr, ptr, + lvalue, &rvalue, result, op)) + { + return NULL; + } + + ptr = rptr; + + ecs_expr_oper_t right_op; + flecs_str_to_expr_oper(rptr, &right_op); + if (right_op > left_op) { + if (result_type) { + /* If result was initialized, preserve its value */ + lvalue->type = result->type; + lvalue->ptr = flecs_expr_value_new(stack, lvalue->type); + ecs_value_copy(world, lvalue->type, lvalue->ptr, result->ptr); + continue; + } else { + /* Otherwise move result to lvalue */ + *lvalue = *result; + ecs_os_zeromem(result); + continue; + } + } + + break; + } while (true); + + return ptr; +} + +static +const char* flecs_funccall_parse( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *name, + const char *expr, + const char *ptr, + char *token, + ecs_meta_cursor_t *cur, + ecs_value_t *value, + bool isvar, + const ecs_script_expr_run_desc_t *desc) +{ + char *sep = strrchr(token, '.'); + if (!sep) { + ecs_parser_error(name, expr, ptr - expr, + "missing object for function call '%s'", token); + return NULL; + } + + sep[0] = '\0'; + const char *function = sep + 1; + + if (!isvar) { + if (ecs_meta_set_string(cur, token) != 0) { + goto error; + } + } else { + const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, token); + ecs_meta_set_value(cur, &var->value); + } + + do { + if (!function[0]) { + ecs_parser_error(name, expr, ptr - expr, + "missing function name for function call '%s'", token); + return NULL; + } + + if (flecs_meta_call(world, stack, name, expr, ptr, cur, + function) != 0) + { + goto error; + } + + ecs_entity_t type = ecs_meta_get_type(cur); + value->ptr = ecs_meta_get_ptr(cur); + value->type = type; + + ptr += 2; + if (ptr[0] != '.') { + break; + } + + ptr ++; + char *paren = strchr(ptr, '('); + if (!paren) { + break; + } + + ecs_size_t len = flecs_ito(int32_t, paren - ptr); + if (len >= ECS_MAX_TOKEN_SIZE) { + ecs_parser_error(name, expr, ptr - expr, + "token exceeds maximum token size"); + goto error; + } + + ecs_os_strncpy(token, ptr, len); + token[len] = '\0'; + function = token; + + ptr = paren; + } while (true); + + return ptr; +error: + return NULL; +} + +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_expr_run( + ecs_world_t *world, + ecs_value_stack_t *stack, + const char *ptr, + ecs_value_t *value, + ecs_expr_oper_t left_op, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); + char token[ECS_MAX_TOKEN_SIZE]; + int depth = 0; + ecs_value_t result = {0}; + ecs_meta_cursor_t cur = {0}; + const char *name = desc ? desc->name : NULL; + const char *expr = desc ? desc->expr : NULL; + token[0] = '\0'; + expr = expr ? expr : ptr; + + ptr = flecs_parse_ws_eol(ptr); + + /* Check for postfix operators */ + ecs_expr_oper_t unary_op = EcsExprOperUnknown; + if (ptr[0] == '-' && !isdigit(ptr[1])) { + unary_op = EcsMin; + ptr = flecs_parse_ws_eol(ptr + 1); + } + + /* Initialize storage and cursor. If expression starts with a '(' storage + * will be initialized by a nested expression */ + if (ptr[0] != '(') { + ecs_entity_t type = flecs_parse_discover_type( + world, name, expr, ptr, value->type, desc); + if (!type) { + return NULL; + } + + result.type = type; + if (type != value->type) { + result.ptr = flecs_expr_value_new(stack, type); + } else { + result.ptr = value->ptr; + } + + cur = ecs_meta_cursor(world, result.type, result.ptr); + if (!cur.valid) { + return NULL; + } + + cur.lookup_action = desc ? desc->lookup_action : NULL; + cur.lookup_ctx = desc ? desc->lookup_ctx : NULL; + } + + /* Loop that parses all values in a value scope */ + while ((ptr = flecs_script_expr_parse_token(name, expr, ptr, token))) { + /* Used to track of the result of the parsed token can be used as the + * lvalue for a binary expression */ + bool is_lvalue = false; + bool newline = false; + + if (!token[0]) { + /* Comment */ + continue; + } + + if (!ecs_os_strcmp(token, "(")) { + ecs_value_t temp_result, *out; + if (!depth) { + out = &result; + } else { + temp_result.type = ecs_meta_get_type(&cur); + temp_result.ptr = ecs_meta_get_ptr(&cur); + out = &temp_result; + } + + /* Parenthesis, parse nested expression */ + ptr = flecs_script_expr_run( + world, stack, ptr, out, EcsLeftParen, desc); + if (ptr[0] != ')') { + ecs_parser_error(name, expr, ptr - expr, + "missing closing parenthesis"); + return NULL; + } + ptr = flecs_parse_ws(ptr + 1); + is_lvalue = true; + + } else if (!ecs_os_strcmp(token, "{")) { + /* Parse nested value scope */ + ecs_entity_t scope_type = ecs_meta_get_type(&cur); + + depth ++; /* Keep track of depth so we know when parsing is done */ + if (ecs_meta_push(&cur) != 0) { + goto error; + } + + if (ecs_meta_is_collection(&cur)) { + char *path = ecs_get_path(world, scope_type); + ecs_parser_error(name, expr, ptr - expr, + "expected '[' for collection type '%s'", path); + ecs_os_free(path); + return NULL; + } + } + + else if (!ecs_os_strcmp(token, "}")) { + depth --; + + if (ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected ']'"); + return NULL; + } + + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } + + else if (!ecs_os_strcmp(token, "[")) { + /* Open collection value scope */ + depth ++; + if (ecs_meta_push(&cur) != 0) { + goto error; + } + + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "unexpected '['"); + return NULL; + } + } + + else if (!ecs_os_strcmp(token, "]")) { + depth --; + + if (!ecs_meta_is_collection(&cur)) { + ecs_parser_error(name, expr, ptr - expr, "expected '}'"); + return NULL; + } + + if (ecs_meta_pop(&cur) != 0) { + goto error; + } + } + + else if (!ecs_os_strcmp(token, "-")) { + if (unary_op != EcsExprOperUnknown) { + ecs_parser_error(name, expr, ptr - expr, + "unexpected unary operator"); + return NULL; + } + unary_op = EcsMin; + } + + else if (!ecs_os_strcmp(token, ",")) { + /* Move to next field */ + if (ecs_meta_next(&cur) != 0) { + goto error; + } + } + + else if (!ecs_os_strcmp(token, "null")) { + if (ecs_meta_set_null(&cur) != 0) { + goto error; + } + + is_lvalue = true; + } + + else if (token[0] == '\"') { + /* Regular string */ + if (ecs_meta_set_string_literal(&cur, token) != 0) { + goto error; + } + + is_lvalue = true; + } + + else if (!ecs_os_strcmp(token, "`")) { + /* Multiline string */ + if (!(ptr = flecs_parse_multiline_string(&cur, name, expr, ptr))) { + goto error; + } + + is_lvalue = true; + + } else if (token[0] == '$') { + /* Variable */ + if (!desc || !desc->vars) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable '%s' (no variable scope)", token); + return NULL; + } + + if (!token[1]) { + /* Empty name means default to assigned member */ + const char *member = ecs_meta_get_member(&cur); + if (!member) { + ecs_parser_error(name, expr, ptr - expr, + "invalid default variable outside member assignment"); + return NULL; + } + ecs_os_strcpy(&token[1], member); + } + + const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, &token[1]); + if (!var) { + if (ptr[0] == '(') { + /* Function */ + ptr = flecs_funccall_parse(world, stack, name, expr, ptr, + &token[1], &cur, &result, true, desc); + if (!ptr) { + goto error; + } + } else { + ecs_value_t v = flecs_dotresolve_var(world, desc->vars, &token[1]); + if (!v.ptr) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved variable '%s'", token); + return NULL; + } else { + ecs_meta_set_value(&cur, &v); + } + } + } else { + ecs_meta_set_value(&cur, &var->value); + } + + is_lvalue = true; + } else { + const char *tptr = flecs_parse_ws(ptr); + for (; ptr != tptr; ptr ++) { + if (ptr[0] == '\n') { + newline = true; + } + } + + if (ptr[0] == ':') { + /* Member assignment */ + ptr ++; + if (ecs_meta_dotmember(&cur, token) != 0) { + goto error; + } + } else if (ptr[0] == '(') { + /* Function */ + ptr = flecs_funccall_parse(world, stack, name, expr, ptr, + token, &cur, &result, false, desc); + if (!ptr) { + goto error; + } + } else { + if (ptr[0] != '[') { + /* Entity id expression */ + if (ecs_meta_set_string(&cur, token) != 0) { + goto error; + } + } else { + /* Component expression */ + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t e = desc->lookup_action( + world, token, desc->lookup_ctx); + if (!e) { + ecs_parser_error(name, expr, ptr - expr, + "entity '%s' not found", token); + goto error; + } + + ptr = flecs_script_expr_parse_token( + name, expr, ptr + 1, token); + if (!ptr) { + goto error; + } + + ecs_entity_t component = desc->lookup_action( + world, token, desc->lookup_ctx); + if (!component) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved component '%s'", token); + goto error; + } + + ecs_entity_t type = ecs_get_typeid(world, component); + if (!type) { + ecs_parser_error(name, expr, ptr - expr, + "entity '%s' is not a component", token); + goto error; + } + + result.type = type; + result.ptr = ECS_CONST_CAST(void*, + ecs_get_id(world, e, component)); + if (!result.ptr) { + char *entitystr = ecs_id_str(world, e); + char *idstr = ecs_id_str(world, component); + ecs_parser_error(name, expr, ptr - expr, + "entity '%s' does not have component '%s'", + entitystr, idstr); + ecs_os_free(idstr); + ecs_os_free(entitystr); + goto error; + } + + if (ptr[0] != ']') { + ecs_parser_error(name, expr, ptr - expr, + "missing ] for component operator"); + goto error; + } + + ptr ++; + + if (ptr[0] == '.') { + ecs_meta_cursor_t member_cur = ecs_meta_cursor( + world, result.type, result.ptr); + + ptr = flecs_script_expr_parse_token( + name, expr, ptr + 1, token); + if (!ptr) { + goto error; + } + + ecs_meta_push(&member_cur); + if (ecs_meta_dotmember(&member_cur, token) != 0) { + ecs_parser_error(name, expr, ptr - expr, + "failed to assign member '%s'", token); + goto error; + } + + result.type = ecs_meta_get_type(&member_cur); + result.ptr = ecs_meta_get_ptr(&member_cur); + } + } + } + + is_lvalue = true; + } + + /* If lvalue was parsed, apply operators. Expressions cannot start + * directly after a newline character. */ + if (is_lvalue && !newline) { + if (unary_op != EcsExprOperUnknown) { + if (unary_op == EcsMin) { + int64_t v = -1; + ecs_value_t lvalue = {.type = ecs_id(ecs_i64_t), .ptr = &v}; + ecs_value_t *out, rvalue, temp_out = {0}; + + if (!depth) { + rvalue = result; + ecs_os_zeromem(&result); + out = &result; + } else { + ecs_entity_t cur_type = ecs_meta_get_type(&cur); + void *cur_ptr = ecs_meta_get_ptr(&cur); + rvalue.type = cur_type; + rvalue.ptr = cur_ptr; + temp_out.type = cur_type; + temp_out.ptr = cur_ptr; + out = &temp_out; + } + + flecs_binary_expr_do(world, stack, name, expr, ptr, &lvalue, + &rvalue, out, EcsMul); + } + unary_op = 0; + } + + ecs_expr_oper_t right_op; + flecs_str_to_expr_oper(ptr, &right_op); + if (right_op) { + /* This is a binary expression, test precedence to determine if + * it should be evaluated here */ + if (flecs_oper_precedence(left_op, right_op) < 0) { + ecs_value_t lvalue; + ecs_value_t *op_result = &result; + ecs_value_t temp_storage; + if (!depth) { + /* Root level value, move result to lvalue storage */ + lvalue = result; + ecs_os_zeromem(&result); + } else { + /* Not a root level value. Move the parsed lvalue to a + * temporary storage, and initialize the result value + * for the binary operation with the current cursor */ + ecs_entity_t cur_type = ecs_meta_get_type(&cur); + void *cur_ptr = ecs_meta_get_ptr(&cur); + lvalue.type = cur_type; + lvalue.ptr = flecs_expr_value_new(stack, cur_type); + ecs_value_copy(world, cur_type, lvalue.ptr, cur_ptr); + temp_storage.type = cur_type; + temp_storage.ptr = cur_ptr; + op_result = &temp_storage; + } + + /* Do the binary expression */ + ptr = flecs_binary_expr_parse(world, stack, name, expr, ptr, + &lvalue, op_result, left_op, desc); + if (!ptr) { + return NULL; + } + } + } + } + + if (!depth) { + /* Reached the end of the root scope */ + break; + } + + ptr = flecs_parse_ws_eol(ptr); + } + + if (!value->ptr) { + value->type = result.type; + value->ptr = flecs_expr_value_new(stack, result.type); + } + + if (value->ptr != result.ptr) { + cur = ecs_meta_cursor(world, value->type, value->ptr); + ecs_meta_set_value(&cur, &result); + } + + ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value->ptr != NULL, ECS_INTERNAL_ERROR, NULL); + + return ptr; +error: + return NULL; +} + +const char* ecs_script_expr_run( + ecs_world_t *world, + const char *ptr, + ecs_value_t *value, + const ecs_script_expr_run_desc_t *desc) +{ + ecs_script_expr_run_desc_t priv_desc = {0}; + if (desc) { + priv_desc = *desc; + } + + if (!priv_desc.lookup_action) { + priv_desc.lookup_action = flecs_script_default_lookup; + } + + /* Prepare storage for temporary values */ + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_value_stack_t stack; + stack.count = 0; + stack.stage = stage; + stack.stack = &stage->allocators.deser_stack; + stack.cursor = flecs_stack_get_cursor(stack.stack); + + /* Parse expression */ + bool storage_provided = value->ptr != NULL; + ptr = flecs_script_expr_run( + world, &stack, ptr, value, EcsExprOperUnknown, &priv_desc); + + /* If no result value was provided, allocate one as we can't return a + * pointer to a temporary storage */ + if (!storage_provided && value->ptr) { + ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); + void *temp_storage = value->ptr; + value->ptr = ecs_value_new(world, value->type); + ecs_value_move_ctor(world, value->type, value->ptr, temp_storage); + } + + /* Cleanup temporary values */ + int i; + for (i = 0; i < stack.count; i ++) { + const ecs_type_info_t *ti = stack.values[i].ti; + ecs_assert(ti->hooks.dtor != NULL, ECS_INTERNAL_ERROR, NULL); + ti->hooks.dtor(stack.values[i].ptr, 1, ti); + } + flecs_stack_restore_cursor(stack.stack, stack.cursor); + + return ptr; +} + +#endif + +/** + * @file addons/script/interpolate.c + * @brief String interpolation. + */ + + +#ifdef FLECS_SCRIPT +#include + +static +const char* flecs_parse_var_name( + const char *ptr, + char *token_out) +{ + char ch, *bptr = token_out; + + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } + + if (isalpha(ch) || isdigit(ch) || ch == '_') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + break; + } + } + + if (bptr == token_out) { + goto error; + } + + *bptr = '\0'; + + return ptr; +error: + return NULL; +} + +static +const char* flecs_parse_interpolated_str( + const char *ptr, + char *token_out) +{ + char ch, *bptr = token_out; + + while ((ch = *ptr)) { + if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { + goto error; + } + + if (ch == '\\') { + if (ptr[1] == '}') { + *bptr = '}'; + bptr ++; + ptr += 2; + continue; + } + } + + if (ch != '}') { + *bptr = ch; + bptr ++; + ptr ++; + } else { + ptr ++; + break; + } + } + + if (bptr == token_out) { + goto error; + } + + *bptr = '\0'; + + return ptr; +error: + return NULL; +} + +char* ecs_script_string_interpolate( + ecs_world_t *world, + const char *str, + const ecs_script_vars_t *vars) +{ + char token[ECS_MAX_TOKEN_SIZE]; + ecs_strbuf_t result = ECS_STRBUF_INIT; + const char *ptr; + char ch; + + for(ptr = str; (ch = *ptr); ptr++) { + if (ch == '\\') { + ptr ++; + if (ptr[0] == '$') { + ecs_strbuf_appendch(&result, '$'); + continue; + } + if (ptr[0] == '\\') { + ecs_strbuf_appendch(&result, '\\'); + continue; + } + if (ptr[0] == '{') { + ecs_strbuf_appendch(&result, '{'); + continue; + } + if (ptr[0] == '}') { + ecs_strbuf_appendch(&result, '}'); + continue; + } + ptr --; + } + + if (ch == '$') { + ptr = flecs_parse_var_name(ptr + 1, token); + if (!ptr) { + ecs_parser_error(NULL, str, ptr - str, + "invalid variable name '%s'", ptr); + goto error; + } + + ecs_script_var_t *var = ecs_script_vars_lookup(vars, token); + if (!var) { + ecs_parser_error(NULL, str, ptr - str, + "unresolved variable '%s'", token); + goto error; + } + + if (ecs_ptr_to_str_buf( + world, var->value.type, var->value.ptr, &result)) + { + goto error; + } + + ptr --; + } else if (ch == '{') { + ptr = flecs_parse_interpolated_str(ptr + 1, token); + if (!ptr) { + ecs_parser_error(NULL, str, ptr - str, + "invalid interpolated expression"); + goto error; + } + + ecs_script_expr_run_desc_t expr_desc = { + .vars = ECS_CONST_CAST(ecs_script_vars_t*, vars) + }; + + ecs_value_t expr_result = {0}; + if (!ecs_script_expr_run(world, token, &expr_result, &expr_desc)) { + goto error; + } + + if (ecs_ptr_to_str_buf( + world, expr_result.type, expr_result.ptr, &result)) + { + goto error; + } + + ecs_value_free(world, expr_result.type, expr_result.ptr); + + ptr --; + } else { + ecs_strbuf_appendch(&result, ch); + } + } + + return ecs_strbuf_get(&result); +error: + return NULL; +} + +#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_tokens_t token_stack = {0};\ + ecs_script_token_t *tokens = token_stack.tokens;\ + (void)tokens + +#define ParserEnd\ + Error("unexpected end of rule (parser error)");\ + error:\ + return NULL + +/* Get token */ +#define Token(n) (tokens[n].value) + +/* Error */ +#define Error(...)\ + ecs_parser_error(parser->script->pub.name, parser->script->pub.code,\ + (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ + goto error + +/* Parse expression */ +#define Expr(until, ...)\ + {\ + ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &token_stack.tokens[token_stack.count ++];\ + if (!(pos = flecs_script_expr(parser, pos, t, until))) {\ + goto error;\ + }\ + if (!t->value[0] && (until == '\n' || until == '{')) {\ + pos ++;\ + Error("empty expression");\ + }\ + }\ + Parse_1(until, __VA_ARGS__) + +/* Parse token until character */ +#define Until(until, ...)\ + {\ + ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &token_stack.tokens[token_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(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_script_token_t *t = &token_stack.tokens[token_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))) {\ + token_stack.tokens[token_stack.count ++] = lookahead_token;\ + switch(lookahead_token.kind) {\ + __VA_ARGS__\ + default:\ + token_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__\ + }\ + )\ + 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__\ + }\ + )\ + 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 = token_stack.count;\ + do {\ + token_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)); + } + + 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 ) + Expr(')', + ecs_script_component_t *component = + flecs_script_insert_component(parser, Token(0)); + component->node.kind = EcsAstWithComponent; + component->expr = Token(2); + 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 ) + Expr(')', + ecs_script_component_t *component = + flecs_script_insert_pair_component(parser, + Token(1), Token(3)); + component->node.kind = EcsAstWithComponent; + component->expr = Token(6); + 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; + + Expr(')', + entity->kind_w_expr = true; + + Scope(entity->scope, + ecs_script_component_t *component = + flecs_script_insert_component(parser, kind); + component->expr = Token(0); + ) + + 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_stmt( + ecs_script_parser_t *parser, + const char *pos) +{ + ParserBegin; + + Parse( + case EcsTokIdentifier: goto identifier; + case '{': return flecs_script_scope(parser, + flecs_script_insert_scope(parser), pos); + 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; + EcsTokEndOfStatement: EndOfRule; + ); + +identifier: { + // enterprise } (end of scope) + LookAhead_1('}', + goto insert_tag; + ) + + Parse( + // enterprise { + case '{': { + return flecs_script_scope(parser, + flecs_script_insert_entity( + parser, Token(0))->scope, pos); + } + + // Red, + case ',': { + flecs_script_insert_entity(parser, Token(0)); + pos = flecs_script_comma_expr(parser, pos, false); + EndOfRule; + } + + // Npc\n + EcsTokEndOfStatement: { + // Npc\n{ + LookAhead_1('{', + pos = lookahead; + return flecs_script_scope(parser, + flecs_script_insert_entity( + parser, Token(0))->scope, pos); + ) + + goto insert_tag; + } + + // auto_override | + case '|': { + goto identifier_flag; + } + + // Position: + case ':': { + goto identifier_colon; + } + + // x = + case '=': { + goto identifier_assign; + } + + // SpaceShip( + case '(': { + goto identifier_paren; + } + + // Spaceship enterprise + case EcsTokIdentifier: { + goto identifier_identifier; + } + ) +} + +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", tokens[0].value); + } + } else { + if (!flecs_script_insert_tag(parser, Token(0))) { + Error( + "invalid context for tag '%s': must be part of entity", + 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_2(EcsTokIdentifier, '\n', + flecs_script_insert_using(parser, Token(1)); + 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: + Parse_4(EcsTokIdentifier, '=', EcsTokIdentifier, ':', + ecs_script_var_node_t *var = flecs_script_insert_var( + parser, Token(1)); + var->node.kind = EcsAstProp; + var->type = Token(3); + + // prop color = Color : { + LookAhead_1('{', + // prop color = Color: {expr} + pos = lookahead; + Expr('}', + var->expr = Token(6); + EndOfRule; + ) + ) + + // prop color = Color : expr\n + Expr('\n', + var->expr = Token(5); + EndOfRule; + ) + ) +} + +// const +const_var: { + // const color + Parse_1(EcsTokIdentifier, + ecs_script_var_node_t *var = flecs_script_insert_var( + parser, Token(1)); + var->node.kind = 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; + Expr('}', + var->expr = Token(6); + EndOfRule; + ) + ) + + // const color = Color: expr\n + Expr('\n', + var->expr = Token(5); + EndOfRule; + ) + ) + + // const PI = expr\n + Expr('\n', + var->expr = Token(3); + EndOfRule; + ) + } + ) + ) +} + +// if +if_stmt: { + // if expr { + Expr('{', + ecs_script_if_t *stmt = flecs_script_insert_if(parser); + stmt->expr = Token(1); + pos = flecs_script_scope(parser, stmt->if_true, pos); + if (!pos) { + goto error; + } + + // if expr { } else + LookAhead_1(EcsTokKeywordElse, + pos = lookahead; + + // if expr { } else { + Parse_1('{', + return flecs_script_scope(parser, stmt->if_false, pos); + ) + ) + + EndOfRule; + ) +} + +// ( +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 ':': { + // (Eats, Apples): { + Parse_1('{', + // (Eats, Apples): { expr } + Expr('}', + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = Token(7); + 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 = Token(9); + 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 = Token(5); + EndOfRule; + }) + ) + } + ) + } + ) +} + +// Position: +identifier_colon: { + { + // Position: { + LookAhead_1('{', + pos = lookahead; + goto component_expr_scope; + ) + } + + { + // Position: [ + LookAhead_1('[', + pos = lookahead; + goto component_expr_collection; + ) + } + + // enterprise : SpaceShip + Parse_1(EcsTokIdentifier, { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, Token(0)); + + Scope(entity->scope, + flecs_script_insert_pair_tag(parser, "IsA", Token(2)); + + LookAhead_1(',', { + pos = lookahead; + pos = flecs_script_comma_expr(parser, pos, true); + }) + ) + + Parse( + // enterprise : SpaceShip\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)); + + // x = Position: + LookAhead_2(EcsTokIdentifier, ':', + pos = lookahead; + + // 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 = Token(5); + ) + + // x = Position: {expr}\n + Parse( + EcsTokEndOfStatement: + EndOfRule; + ) + ) + }) + ) + + // x = f32\n + Expr('\n', + Scope(entity->scope, + ecs_script_default_component_t *comp = + flecs_script_insert_default_component(parser); + comp->expr = Token(2); + ) + + EndOfRule; + ) +} + +// Spaceship enterprise +identifier_identifier: { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, Token(1)); + entity->kind = Token(0); + + // Spaceship enterprise : + LookAhead_1(':', + pos = lookahead; + + Parse_1(EcsTokIdentifier, { + Scope(entity->scope, + flecs_script_insert_pair_tag(parser, "IsA", Token(3)); + + LookAhead_1(',', { + pos = lookahead; + pos = flecs_script_comma_expr(parser, pos, true); + }) + ) + + goto identifier_identifier_x; + }) + ) + +identifier_identifier_x: + Parse( + // Spaceship enterprise\n + EcsTokEndOfStatement: { + EndOfRule; + } + + // Spaceship enterprise { + case '{': { + return flecs_script_scope(parser, entity->scope, pos); + } + + // Spaceship enterprise( + case '(': { + return flecs_script_paren_expr(parser, Token(0), entity, pos); + } + ) +} + +// SpaceShip( +identifier_paren: { + // SpaceShip() + Expr(')', + Parse( + // SpaceShip(expr)\n + EcsTokEndOfStatement: { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, NULL); + + Scope(entity->scope, + ecs_script_component_t *comp = + flecs_script_insert_component(parser, Token(0)); + comp->expr = Token(2); + if (!ecs_os_strcmp(comp->expr, "{}")) { + comp->expr = NULL; + } + ) + + EndOfRule; + } + + // SpaceShip(expr) { + case '{': { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, NULL); + + Scope(entity->scope, + ecs_script_component_t *comp = + flecs_script_insert_component(parser, Token(0)); + comp->expr = Token(2); + ) + + 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 = Token(3); + EndOfRule; + }) +} + +// Points: [ +component_expr_collection: { + // Position: [expr] + Expr(']', { + ecs_script_component_t *comp = flecs_script_insert_component( + parser, Token(0)); + comp->expr = Token(3); + comp->is_collection = true; + EndOfRule; + }) +} + + ParserEnd; +} + +/* Parse script */ +ecs_script_t* ecs_script_parse( + ecs_world_t *world, + const char *name, + const char *code) +{ + 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( + &impl->allocator, impl->token_buffer_size); + 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); + + 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 + }; + + 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 (!ecs_id_is_valid(world, term.id)) { + ecs_parser_error(name, expr, (result - expr), + "invalid term for add expression"); + 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); + +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) +{ + ecs_delete_with(world, flecs_script_tag(script, instance)); +} + +int ecs_script_run( + ecs_world_t *world, + const char *name, + const char *code) +{ + ecs_script_t *script = ecs_script_parse(world, name, code); + if (!script) { + goto error; + } + + ecs_entity_t prev_scope = ecs_set_scope(world, 0); + + if (ecs_script_eval(script)) { + 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_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); + 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)) { + 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; +} + +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); + 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) + }); + + 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); +} + +#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) +{ + 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 + +/* 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); + } + } +} + +/* 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); + const EcsStruct *st = ecs_record_get(world, r, EcsStruct); + + void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); + + ecs_script_eval_visitor_t v; + flecs_script_eval_visit_init(flecs_script_impl(script->script), &v); + ecs_vec_t prev_using = v.using; + v.using = template->using_; + + 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 < it->count; i ++) { + v.parent = it->entities[i]; + instance_node.eval = it->entities[i]; + + /* Create variables to hold template properties */ + ecs_script_vars_t *vars = flecs_script_vars_push( + NULL, &v.stack, v.allocator); + vars->parent = template->vars; /* Include hoisted variables */ + + /* 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 */ + ecs_script_var_t *var = ecs_script_vars_declare(vars, member->name); + var->value.type = member->type; + var->value.ptr = ECS_OFFSET(data, member->offset); + } + } + + /* Populate $this variable with instance entity */ + ecs_entity_t instance = it->entities[i]; + ecs_script_var_t *var = ecs_script_vars_declare(vars, "this"); + var->value.type = ecs_id(ecs_entity_t); + var->value.ptr = &instance; + + 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, template_entity, instance); + + /* Run template code */ + v.vars = vars; + ecs_script_visit_scope(&v, scope); + + if (is_defer) { + flecs_resume_readonly(real_world, &srs); + } + + /* Pop variable scope */ + ecs_script_vars_pop(vars); + + data = ECS_OFFSET(data, ti->size); + } + + v.using = prev_using; + flecs_script_eval_visit_fini(&v); +} + +static +int flecs_script_eval_prop( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node) +{ + ecs_script_template_t *template = v->template; + 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; + } + + if (node->type) { + ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); + if (!type) { + flecs_script_eval_error(v, node, + "unresolved type '%s' for const variable '%s'", + node->type, node->name); + return -1; + } + + const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, type); + if (!ti) { + return -1; + } + + var->value.type = type; + var->value.ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); + var->type_info = ti; + + if (flecs_script_eval_expr(v, node->expr, &var->value)) { + return -1; + } + + 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(&v->base.script->allocator, ti->size); + 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 EcsAstProp: + return flecs_script_eval_prop(v, (ecs_script_var_node_t*)node); + case EcsAstScope: + case EcsAstTag: + case EcsAstComponent: + case EcsAstVarComponent: + case EcsAstDefaultComponent: + case EcsAstWithVar: + case EcsAstWithTag: + case EcsAstWithComponent: + case EcsAstWith: + case EcsAstUsing: + case EcsAstModule: + case EcsAstAnnotation: + case EcsAstTemplate: + case EcsAstConst: + case EcsAstEntity: + case EcsAstPairScope: + case EcsAstIf: + break; + } + + return flecs_script_eval_node(v, node); +} + +static +int flecs_script_template_preprocess( + ecs_script_eval_visitor_t *v, + ecs_script_template_t *template) +{ + v->template = template; + v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; + v->vars = flecs_script_vars_push(v->vars, &v->stack, v->allocator); + int result = ecs_script_visit_scope(v, template->node->scope); + v->vars = ecs_script_vars_pop(v->vars); + v->template = NULL; + return result; +} + +static +int flecs_script_template_hoist_using( + ecs_script_eval_visitor_t *v, + ecs_script_template_t *template) +{ + if (v->module) { + ecs_vec_append_t( + v->allocator, &template->using_, ecs_entity_t)[0] = v->module; + } + + int i, count = ecs_vec_count(&v->using); + for (i = 0; i < count; i ++) { + ecs_vec_append_t(v->allocator, &template->using_, ecs_entity_t)[0] = + ecs_vec_get_t(&v->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) +{ + if (vars->parent) { + flecs_script_template_hoist_vars(v, template, 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]; + ecs_script_var_t *dst = ecs_script_vars_define_id( + template->vars, src->name, src->value.type); + ecs_value_copy(v->world, + src->value.type, dst->value.ptr, src->value.ptr); + } + + 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; + + 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; + } + + /* 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; +} + +#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); + +#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 EcsTokMul: + case EcsTokAssign: + case EcsTokBitwiseOr: + case EcsTokNot: + case EcsTokOptional: + case EcsTokEq: + case EcsTokNeq: + case EcsTokMatch: + case EcsTokOr: + return ""; + case EcsTokKeywordWith: + case EcsTokKeywordUsing: + case EcsTokKeywordTemplate: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordModule: + return "keyword "; + case EcsTokIdentifier: + return "identifier "; + case EcsTokString: + return "string "; + case EcsTokNumber: + return "number "; + case EcsTokNewline: + return "newline"; + 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 == '#'); +} + +static +const char* flecs_script_identifier( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_t *out) +{ + out->kind = EcsTokIdentifier; + out->value = parser->token_cur; + + ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); + char *outpos = parser->token_cur; + do { + char c = pos[0]; + bool is_ident = flecs_script_is_identifier(c) || + isdigit(c) || (c == '.') || (c == '*'); + + /* Retain \. for name lookup operation */ + if (!is_ident && c == '\\' && 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; + } + + *outpos = c; + outpos ++; + pos ++; + + if (!indent) { + break; + } + } while (true); + + *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 { + *outpos = '\0'; + parser->token_cur = outpos + 1; + return pos; + } + } + + *outpos = *pos; + outpos ++; + pos ++; + } while (true); +} + +// Number token static +static +bool flecs_script_is_number( + char c) +{ + return isdigit(c) || (c == '-'); +} + +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; + + ecs_assert(flecs_script_is_number(pos[0]), ECS_INTERNAL_ERROR, NULL); + char *outpos = parser->token_cur; + do { + char c = pos[0]; + if (!isdigit(c)) { + *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; +} + +const char* flecs_script_expr( + ecs_script_parser_t *parser, + const char *pos, + ecs_script_token_t *out, + char until) +{ + parser->pos = pos; + + int32_t scope_depth = until == '}' ? 1 : 0; + int32_t paren_depth = until == ')' ? 1 : 0; + + const char *start = pos = flecs_scan_whitespace(parser, pos); + char ch; + + for (; (ch = pos[0]); pos ++) { + if (ch == '{') { + if (ch == until) { + break; + } + scope_depth ++; + } else + if (ch == '}') { + scope_depth --; + if (!scope_depth && until == '}') { + break; + } + if (scope_depth < 0) { + ecs_parser_error(parser->script->pub.name, parser->script->pub.code, + pos - parser->script->pub.code, "mismatching { }"); + return NULL; + } + } else + if (ch == '(') { + paren_depth ++; + } else + if (ch == ')') { + paren_depth --; + if (!paren_depth && until == ')') { + break; + } + if (paren_depth < 0) { + ecs_parser_error(parser->script->pub.name, parser->script->pub.code, + pos - parser->script->pub.code, "mismatching ( )"); + return NULL; + } + } else + if (ch == '"') { + pos = flecs_script_skip_string(parser, pos + 1, '"'); + if (!pos) { + return NULL; + } + } else + if (ch == '`') { + pos = flecs_script_skip_string(parser, pos + 1, '`'); + if (!pos) { + return NULL; + } + } else + 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; + } + } + + if (scope_depth) { + ecs_parser_error(parser->script->pub.name, parser->script->pub.code, + pos - parser->script->pub.code, "mismatching { }"); + return NULL; + } + if (paren_depth) { + ecs_parser_error(parser->script->pub.name, parser->script->pub.code, + pos - parser->script->pub.code, "mismatching ( )"); + return NULL; + } + + if (until != ']') { + parser->token_cur[0] = '{'; + } else { + parser->token_cur[0] = '['; + } + + int32_t len = flecs_ito(int32_t, pos - start); + ecs_os_memcpy(parser->token_cur + 1, start, len); + out->value = parser->token_cur; + parser->token_cur += len + 1; + + while (isspace(parser->token_cur[-1])) { + parser->token_cur --; + } + + if (until != ']') { + parser->token_cur[0] = '}'; + } else { + parser->token_cur[0] = ']'; + } + + parser->token_cur ++; + + parser->token_cur[0] = '\0'; + parser->token_cur ++; + + return pos; +} + +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; + + Operator (":", EcsTokColon) + Operator ("{", EcsTokScopeOpen) + Operator ("}", EcsTokScopeClose) + Operator ("(", EcsTokParenOpen) + Operator (")", EcsTokParenClose) + Operator ("[", EcsTokBracketOpen) + Operator ("]", EcsTokBracketClose) + Operator ("@", EcsTokAnnotation) + Operator (",", EcsTokComma) + Operator (";", EcsTokSemiColon) + Operator ("*", EcsTokMul) + Operator ("?", EcsTokOptional) + + OperatorMultiChar ("==", EcsTokEq) + OperatorMultiChar ("!=", EcsTokNeq) + OperatorMultiChar ("~=", EcsTokMatch) + OperatorMultiChar ("||", EcsTokOr) + + OperatorMultiChar ("!", EcsTokNot) + OperatorMultiChar ("=", EcsTokAssign) + OperatorMultiChar ("|", EcsTokBitwiseOr) + + Keyword ("with", EcsTokKeywordWith) + Keyword ("using", EcsTokKeywordUsing) + Keyword ("template", EcsTokKeywordTemplate) + Keyword ("prop", EcsTokKeywordProp) + Keyword ("const", EcsTokKeywordConst) + Keyword ("if", EcsTokKeywordIf) + Keyword ("else", EcsTokKeywordElse) + Keyword ("module", EcsTokKeywordModule) + + } else if (pos[0] == '"') { + return flecs_script_string(parser, pos, out); + + } else if (flecs_script_is_number(pos[0])) { + return flecs_script_number(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->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 (!ecs_vec_count(&vars->vars)) { + flecs_name_index_init(&vars->var_index, vars->allocator); + } else { + if (flecs_name_index_find(&vars->var_index, name, 0, 0) != 0) { + goto error; + } + } + + 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; + + flecs_name_index_ensure(&vars->var_index, + flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); + + return var; +error: + return NULL; +} + +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) +{ + uint64_t var_id = 0; + if (ecs_vec_count(&vars->vars)) { + 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)); +} + +/* 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); + } + var->value.ptr = &it->entities[offset]; + } + + /* Set variables for fields */ + { + int32_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 = it->ptrs[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 ++) { + 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) { + ecs_entity_t *entities = range->table->data.entities.array; + 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); + } + var->value.ptr = 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_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) +{ + if (ecs_vec_count(&v->with)) { + ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->type == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->ptr == NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_vec_remove_last(&v->with); + } + + ecs_vec_append_t(a, &v->with, ecs_value_t); + ecs_value_t *last = ecs_vec_append_t(a, &v->with, ecs_value_t); + ecs_os_memset_t(last, 0, ecs_value_t); + return ecs_vec_get_t(&v->with, ecs_value_t, ecs_vec_count(&v->with) - 2); +} + +static +void flecs_script_with_set_count( + ecs_allocator_t *a, + ecs_script_eval_visitor_t *v, + int32_t count) +{ + if (count) { + ecs_value_t *last = ecs_vec_get_t(&v->with, ecs_value_t, count); + ecs_os_memset_t(last, 0, ecs_value_t); + ecs_vec_set_count_t(a, &v->with, ecs_value_t, count + 1); + } else { + ecs_vec_set_count_t(a, &v->with, ecs_value_t, 0); + } +} + +static +ecs_value_t* flecs_script_with_last( + ecs_script_eval_visitor_t *v) +{ + int32_t count = ecs_vec_count(&v->with); + if (count) { + return ecs_vec_get_t(&v->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->with)) { + ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->type == 0, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->ptr == NULL, + ECS_INTERNAL_ERROR, NULL); + return ecs_vec_count(&v->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); + ecs_os_free(idstr); + } + return NULL; +} + +ecs_entity_t flecs_script_find_entity( + ecs_script_eval_visitor_t *v, + ecs_entity_t from, + const char *path) +{ + if (!path) { + return 0; + } + + if (path[0] == '$') { + const ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, &path[1]); + if (!var) { + return 0; + } + + 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); + return 0; + } + + if (var->value.ptr == NULL) { + flecs_script_eval_error(v, NULL, + "variable '%s' is not initialized", path); + return 0; + } + + 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); + return 0; + } + + return result; + } + + if (from) { + return ecs_lookup_path_w_sep(v->world, from, path, NULL, NULL, false); + } else { + int32_t i, using_count = ecs_vec_count(&v->using); + if (using_count) { + ecs_entity_t *using = ecs_vec_first(&v->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) { + return e; + } + } + } + + return ecs_lookup_path_w_sep( + v->world, v->parent, path, NULL, NULL, true); + } +} + +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->with, ecs_value_t); + } + + if (name || with) { + ecs_entity_desc_t desc = {0}; + desc.name = name; + desc.parent = v->parent; + desc.set = with; + return ecs_entity_init(v->world, &desc); + } else if (v->parent) { + return ecs_new_w_pair(v->world, EcsChildOf, v->parent); + } else { + return ecs_new(v->world); + } +} + +static +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; + return flecs_script_find_entity(v, 0, path); +} + +static +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->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] == '$') { + return 0; + } + if (id->second && id->second[0] == '$') { + return 0; + } + } + + ecs_entity_t first = flecs_script_find_entity(v, 0, id->first); + if (!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 = flecs_script_find_entity( + v, second_from, id->second); + if (!second) { + if (id->second[0] == '$') { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", id->second); + return -1; + } + + /* Tags/components must be created in advance for templates */ + if (v->template) { + flecs_script_eval_error(v, node, + "'%s' must be defined outside of template scope", + id->second); + 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, + const char *expr, + ecs_value_t *value) +{ + if (!value->type && expr[0] == '{') { + expr ++; + } + + ecs_script_expr_run_desc_t desc = { + .name = v->base.script->pub.name, + .expr = expr, + .lookup_action = flecs_script_find_entity_action, + .lookup_ctx = v, + .vars = v->vars + }; + + if (!ecs_script_expr_run(v->world, expr, value, &desc)) { + return -1; + } + + return 0; +} + +static +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->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->allocator; + v->vars = flecs_script_vars_push(v->vars, &v->stack, a); + + if (scope_parent && (scope_parent->kind == EcsAstEntity)) { + v->parent = ecs_script_node(entity, scope_parent)->eval; + } + + int result = ecs_script_visit_scope(v, node); + + ecs_vec_set_count_t(a, &v->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; + } + } + + if (v->template) { + return 0; + } + + node->eval = flecs_script_create_entity(v, node->name); + node->parent = v->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->annot); + if (count) { + ecs_script_annot_t **annots = ecs_vec_first(&v->annot); + for (i = 0; i < count ; i ++) { + flecs_script_apply_annot(v, node->eval, annots[i]); + } + ecs_vec_clear(&v->annot); + } + + if (ecs_script_visit_node(v, node->scope)) { + return -1; + } + + 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->template) { + return 0; + } + + 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->template) { + return 0; + } + + 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; + } + + ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); + + if (node->expr && node->expr[0]) { + 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_value_t value = { + .ptr = ecs_ensure_id(v->world, src, node->id.eval), + .type = ti->component + }; + + if (ecs_os_strcmp(node->expr, "{}")) { + 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 = ecs_script_vars_lookup(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "unresolved variable '%s'", node->name); + 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_node_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; + } + + ecs_allocator_t *a = v->allocator; + ecs_value_t *value = flecs_script_with_append(a, v); + *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->allocator; + ecs_value_t *value = flecs_script_with_append(a, v); + 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->allocator; + ecs_value_t *value = flecs_script_with_append(a, v); + value->type = node->id.eval; + value->ptr = NULL; + + if (node->expr && node->expr[0]) { + const ecs_type_info_t *ti = flecs_script_get_type_info( + v, node, node->id.eval); + if (!ti) { + return -1; + } + + value->ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); + value->type = ti->component; // Expression parser needs actual type + + 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->allocator; + int32_t prev_with_count = flecs_script_with_count(v); + ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->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; + } + } + + if (ecs_script_visit_scope(v, node->scope)) { + result = -1; + goto error; + } + +error: + flecs_script_with_set_count(a, v, prev_with_count); + flecs_stack_restore_cursor(&v->stack, prev_stack_cursor); + return result; +} + +static +int flecs_script_eval_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node) +{ + ecs_allocator_t *a = v->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->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->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; +} + +static +int flecs_script_eval_const( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node) +{ + 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; + } + + if (node->type) { + ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); + if (!type) { + flecs_script_eval_error(v, node, + "unresolved type '%s' for const variable '%s'", + node->type, node->name); + return -1; + } + + const ecs_type_info_t *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; + } + + var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); + var->value.type = type; + var->type_info = 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); + const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); + var->value.type = value.type; + var->type_info = 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); + } + + 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 = flecs_script_find_entity(v, 0, node->id.first); + if (!first) { + first = flecs_script_create_entity(v, node->id.first); + if (!first) { + return -1; + } + } + + ecs_entity_t second = flecs_script_create_entity(v, node->id.second); + if (!second) { + return -1; + } + + ecs_allocator_t *a = v->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); + 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->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->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_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; + } + + ecs_allocator_t *a = v->allocator; + ecs_vec_append_t(a, &v->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) +{ + 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_node_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); + } + + ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); +} + +void flecs_script_eval_visit_init( + ecs_script_impl_t *script, + ecs_script_eval_visitor_t *v) +{ + *v = (ecs_script_eval_visitor_t){ + .base = { + .script = script, + .visit = (ecs_visit_action_t)flecs_script_eval_node + }, + .world = script->pub.world, + .allocator = &script->allocator + }; + + flecs_stack_init(&v->stack); + ecs_vec_init_t(v->allocator, &v->using, ecs_entity_t, 0); + ecs_vec_init_t(v->allocator, &v->with, ecs_value_t, 0); + ecs_vec_init_t(v->allocator, &v->annot, ecs_script_annot_t*, 0); + + /* Always include flecs.meta */ + ecs_vec_append_t(v->allocator, &v->using, ecs_entity_t)[0] = + ecs_lookup(v->world, "flecs.meta"); +} + +void flecs_script_eval_visit_fini( + ecs_script_eval_visitor_t *v) +{ + ecs_vec_fini_t(v->allocator, &v->annot, ecs_script_annot_t*); + ecs_vec_fini_t(v->allocator, &v->with, ecs_value_t); + ecs_vec_fini_t(v->allocator, &v->using, ecs_entity_t); + flecs_stack_fini(&v->stack); +} + +int ecs_script_eval( + ecs_script_t *script) +{ + ecs_script_eval_visitor_t v; + ecs_script_impl_t *impl = flecs_script_impl(script); + flecs_script_eval_visit_init(impl, &v); + int result = ecs_script_visit(impl, &v, flecs_script_eval_node); + flecs_script_eval_visit_fini(&v); + 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*); + 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); +} + +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); +} + +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 EcsAstTag: + flecs_free_t(a, ecs_script_tag_t, node); + break; + case EcsAstComponent: + flecs_free_t(a, ecs_script_component_t, node); + break; + case EcsAstDefaultComponent: + 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 EcsAstWithComponent: + flecs_free_t(a, ecs_script_component_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_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; +} 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_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_script_expr_to_str( + ecs_script_str_visitor_t *v, + const char *expr) +{ + if (expr) { + flecs_scriptbuf_append(v, "%s%s%s", ECS_GREEN, expr, ECS_NORMAL); + } 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"; + } + return "???"; +} + +static +void flecs_scriptbuf_node( + ecs_script_str_visitor_t *v, + ecs_script_node_t *node) +{ + flecs_scriptbuf_append(v, "%s%s%s: ", + ECS_BLUE, flecs_script_node_to_str(node), 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_script_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_script_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_scriptbuf_append(v, "%sexpressions%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->expressions); + flecs_scriptbuf_append(v, "%sscope%s: ", ECS_CYAN, 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_scriptbuf_append(v, "%s = %s\"%s\"%s", node->name, + ECS_GREEN, node->expr, 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_script_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_script_expr_to_str(v, node->expr); + + flecs_scriptbuf_appendstr(v, " {\n"); + v->depth ++; + flecs_scriptbuf_append(v, "%strue%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->if_true); + flecs_scriptbuf_append(v, "%sfalse%s: ", ECS_CYAN, ECS_NORMAL); + flecs_script_scope_to_str(v, node->if_false); + 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; + } + + return 0; +} + +int ecs_script_ast_to_buf( + ecs_script_t *script, + ecs_strbuf_t *buf) +{ + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_script_str_visitor_t v = { .buf = buf }; + 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) +{ + ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + if (ecs_script_ast_to_buf(script, &buf)) { + 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; + } 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_sparse_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_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_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 UpdateWorldSummary(ecs_iter_t *it) { + EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); + + const ecs_world_info_t *info = ecs_get_world_info(it->world); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + summary[i].target_fps = (double)info->target_fps; + summary[i].time_scale = (double)info->time_scale; + + summary[i].frame_time_last = (double)info->frame_time_total - summary[i].frame_time_total; + summary[i].system_time_last = (double)info->system_time_total - summary[i].system_time_total; + summary[i].merge_time_last = (double)info->merge_time_total - summary[i].merge_time_total; + + summary[i].frame_time_total = (double)info->frame_time_total; + summary[i].system_time_total = (double)info->system_time_total; + summary[i].merge_time_total = (double)info->merge_time_total; + + summary[i].frame_count ++; + summary[i].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; + + summary[i].build_info = *ecs_get_build_info(); + } +} + +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 + }); +} + +#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) +{ + 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; + } + } + + 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]; + } + + /* 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_data->query->term_count) { + 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); + + 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); + } + + 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 +void 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) || + 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; + if (!tick_source) { + tick_source = entity; + } + 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 + } +} + +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; + + flecs_system_init_timer(world, entity, desc); + + 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 (desc->run) { + system->run = desc->run; + } + + if (desc->callback) { + system->action = desc->callback; + } + + 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); + } + } + + if (system->run_ctx_free) { + if (system->run_ctx && system->run_ctx != desc->run_ctx) { + system->run_ctx_free(system->run_ctx); + } + } + + 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->query.flags & EcsQueryIsInstanced) { + ECS_BIT_SET(system->query->flags, EcsQueryIsInstanced); + } + + if (desc->multi_threaded) { + system->multi_threaded = desc->multi_threaded; + } + + if (desc->immediate) { + system->immediate = desc->immediate; + } + + flecs_system_init_timer(world, entity, desc); + } + + 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 (!(term->flags_ & EcsTermNoData)) { + /* 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 (term->flags_ & EcsTermIsSparse) { + /* If this is a sparse $this term entities have to be returned + * one by one. */ + flecs_query_add_var(query, EcsThisName, vars, EcsVarEntity); + + /* Track if query contains $this sparse terms. Queries with + * sparse $this fields need to return results one by one. */ + if ((ECS_TERM_REF_ID(&term->src) == EcsThis) && + ((term->src.id & (EcsSelf|EcsIsVariable)) == (EcsSelf|EcsIsVariable))) + { + query->pub.flags |= EcsQueryHasSparseThis; + } + } + } + + 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; + } + + if (ECS_TERM_REF_ID(first) == EcsThis || ECS_TERM_REF_ID(second) == EcsThis) { + 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 = &query->vars_cache.var; + 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(int16_t, var_count); + ECS_BIT_COND(query->pub.flags, EcsQueryHasTableThisVar, + !entity_before_table_this); + query->var_size = var_count + anonymous_count; + + char **var_names = ECS_ELEM(query_vars, ECS_SIZEOF(ecs_query_var_t), + var_count + anonymous_count); + query->pub.vars = (char**)var_names; + + 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[0] = ECS_CONST_CAST(char*, query_vars[0].name); + 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 ++) { + var_names[i] = ECS_CONST_CAST(char*, query_vars[i].name); + } + } + + /* Hide anonymous table variables from application */ + query->pub.var_count = + flecs_ito(int16_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_flags64_t *populated, + 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_data_terms = 0; + int32_t trivial_terms = 0; + bool populate = true; + + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->flags_ & (EcsTermIsToggle|EcsTermIsMember|EcsTermIsSparse)) { + /* If query returns individual entities, let dedicated populate + * instruction handle populating data fields */ + populate = false; + break; + } + } + + 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; + } + + trivial_set |= (1llu << i); + + if (ecs_id_is_wildcard(term->id)) { + trivial_wildcard_terms ++; + } + + if (populate) { + if (q->data_fields & (1llu << term->field_index)) { + trivial_data_terms ++; + } + } + + 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 (populate) { + *populated |= (1ull << terms[i].field_index); + } + } + } + + /* 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 = EcsQueryTrivWildcard; + } else { + if (trivial_data_terms) { + trivial.kind = EcsQueryTrivData; + } + if (!trivial.kind) { + 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_flags64_t *populated, + ecs_query_compile_ctx_t *ctx) +{ + if (!query->cache) { + return; + } + + ecs_query_t *q = &query->pub; + bool populate = true; + + int32_t populate_count = 0; + if (q->cache_kind == EcsQueryCacheAll) { + /* If all terms are cacheable, make sure no other terms are compiled */ + *compiled = 0xFFFFFFFFFFFFFFFF; + + /* If query has a sparse $this field, fields can't be populated by the cache + * instruction. The reason for this is that sparse $this fields have to be + * returned one at a time. This means that the same needs to happen for + * cached fields, and the cache instruction only returns entire arrays. */ + if (!(query->pub.flags & EcsQueryHasSparseThis)) { + *populated = 0xFFFFFFFFFFFFFFFF; + populate_count = query->pub.field_count; + } + } 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 ++) { + ecs_term_t *term = &terms[i]; + if (term->flags_ & (EcsTermIsToggle|EcsTermIsMember|EcsTermIsSparse)) { + /* If query returns individual entities, let dedicated populate + * instruction handle populating data fields */ + populate = false; + break; + } + } + + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + int16_t field = term->field_index; + if ((*compiled) & (1ull << i)) { + continue; + } + + if (!(term->flags_ & EcsTermIsCacheable)) { + continue; + } + + *compiled |= (1ull << i); + + if (populate) { + *populated |= (1ull << field); + populate_count ++; + } + } + } + + /* Insert the operation for cache traversal */ + ecs_query_op_t op = {0}; + if (!populate_count || (q->flags & EcsQueryNoData)) { + if (q->flags & EcsQueryIsCacheable) { + op.kind = EcsQueryIsCache; + } else { + op.kind = EcsQueryCache; + } + } else { + if (q->flags & EcsQueryIsCacheable) { + op.kind = EcsQueryIsCacheData; + } else { + op.kind = EcsQueryCacheData; + } + } + + 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); +} + +/* Insert instruction to populate data fields. */ +void flecs_query_insert_populate( + ecs_query_impl_t *query, + ecs_query_compile_ctx_t *ctx, + ecs_flags64_t populated) +{ + ecs_query_t *q = &query->pub; + int32_t i, term_count = q->term_count; + + if (populated && !(q->flags & EcsQueryNoData)) { + ecs_flags64_t sparse_fields = 0; + int32_t populate_count = 0; + int32_t self_count = 0; + + /* Figure out which populate instruction to use */ + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &q->terms[i]; + int16_t field = term->field_index; + ecs_flags64_t field_bit = (1ull << field); + + if (!(populated & field_bit)) { + /* Only check fields that need to be populated */ + continue; + } + + populate_count ++; + + /* Callee is asking us to populate a term without data */ + if (ecs_term_match_this(term) && !(term->src.id & EcsUp)) { + self_count ++; + } + + /* Track sparse fields as they require a separate instruction */ + if (term->flags_ & EcsTermIsSparse) { + sparse_fields |= field_bit; + } + } + + ecs_assert(populate_count != 0, ECS_INTERNAL_ERROR, NULL); + + /* Mask out the sparse fields as they are populated separately. */ + populated &= ~sparse_fields; + + if (populated) { + ecs_query_op_kind_t kind = EcsQueryPopulate; + if (populate_count == self_count) { + kind = EcsQueryPopulateSelf; + } + + ecs_query_op_t op = {0}; + op.kind = flecs_ito(uint8_t, kind); + op.src.entity = populated; /* Abuse for bitset w/fields to populate */ + flecs_query_op_insert(&op, ctx); + } + + if (sparse_fields) { + ecs_query_op_t op = {0}; + op.kind = EcsQueryPopulateSparse; + op.src.entity = sparse_fields; /* Abuse for bitset w/fields to populate */ + flecs_query_op_insert(&op, ctx); + } + } +} + +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_flags64_t *populated_out, + 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; + ecs_flags64_t populated = 0; + + 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, populated_out, ctx)) { + return -1; + } + + *compiled |= (1llu << i); + + /* If this is a data field, track it. This will let us insert an + * instruction specifically for populating data fields of terms with + * fixed source (see below). */ + if (q->data_fields & (1llu << term->field_index)) { + populated |= (1llu << term->field_index); + } + } + } + + if (populated) { + /* If data fields with a fixed source were evaluated, insert a populate + * instruction that just populates those fields. The advantage of doing + * this before the rest of the query is evaluated, is that we only + * populate data for static fields once vs. for each returned result of + * the query, which would be wasteful since this data doesn't change. */ + flecs_query_insert_populate(impl, ctx, populated); + } + + *populated_out |= populated; + + return 0; +} + +int flecs_query_compile( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_query_impl_t *query) +{ + 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; + ecs_flags64_t populated = 0; + + /* Always evaluate terms with fixed source before other terms */ + flecs_query_insert_fixed_src_terms( + world, query, &compiled, &populated, &ctx); + + /* Compile cacheable terms */ + flecs_query_insert_cache_search( + query, &compiled, &populated, &ctx); + + /* Insert trivial term search if query allows for it */ + flecs_query_insert_trivial_search( + query, &compiled, &populated, &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, &populated, &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 query has a $this term with a sparse component value and so far + * we've only accessed $this as table, we need to insert an each + * instruction. This is because we can't return multiple sparse + * component instances in a single query result. */ + if ((query->pub.flags & EcsQueryHasSparseThis) && + !(ctx.written & (1ull << this_id))) + { + flecs_query_insert_each(0, this_id, &ctx, false); + } + + /* 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 instruction to populate remaining data fields */ + ecs_flags64_t remaining = q->data_fields & ~populated; + flecs_query_insert_populate(query, &ctx, remaining); + + /* 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 { + 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; + + /* 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) +{ + 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 +bool flecs_query_select_all( + 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); + 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_t *q, + ecs_query_op_t *op, + ecs_term_t *term, + bool src_is_var, + bool member_term) +{ + /* 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; + } + } + + /* If term has fixed id, insert simpler instruction that skips dealing with + * wildcard terms and variables */ + if (flecs_term_is_fixed_id(q, term) && !member_term) { + if (op->kind == EcsQueryAnd) { + op->kind = EcsQueryAndId; + } else if (op->kind == EcsQuerySelfUp) { + op->kind = EcsQuerySelfUpId; + } else if (op->kind == EcsQueryUp) { + op->kind = EcsQueryUpId; + } + } +} + +int flecs_query_compile_term( + ecs_world_t *world, + ecs_query_impl_t *query, + ecs_term_t *term, + ecs_flags64_t *populated, + 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) { + (*populated) |= (1llu << term->field_index); + 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(q, &op, term, src_is_var, member_term); + + 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(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) { + 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; + } + + op.other = flecs_itolbl(table_flags); + } + } + + /* 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) +{ + ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); + return cache->cache.tables.count; +} + +int32_t flecs_query_cache_empty_table_count( + ecs_query_cache_t *cache) +{ + ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); + return cache->cache.empty_tables.count; +} + +int32_t flecs_query_cache_entity_count( + const ecs_query_cache_t *cache) +{ + ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); + + 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; + 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 +void flecs_query_cache_add_ref( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_cache_table_match_t *qm, + ecs_entity_t component, + ecs_entity_t entity, + ecs_size_t size) +{ + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + ecs_ref_t *ref = ecs_vec_append_t(&world->allocator, &qm->refs, ecs_ref_t); + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + + if (size) { + *ref = ecs_ref_init_id(world, entity, component); + } else { + *ref = (ecs_ref_t){ + .entity = entity, + .id = 0 + }; + } + + impl->pub.flags |= EcsQueryHasRefs; +} + +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->columns = flecs_balloc(&cache->allocators.columns); + qm->storage_columns = flecs_balloc(&cache->allocators.columns); + qm->ids = flecs_balloc(&cache->allocators.ids); + qm->sources = flecs_balloc(&cache->allocators.sources); + + /* Insert match to iteration list if table is not empty */ + if (!table || ecs_table_count(table) != 0 || + (cache->query->flags & EcsQueryCacheYieldEmptyTables)) + { + flecs_query_cache_insert_table_node(cache, qm); + } + + return qm; +} + +static +void flecs_query_cache_set_table_match( + ecs_world_t *world, + ecs_query_impl_t *impl, + ecs_query_cache_t *cache, + ecs_query_cache_table_match_t *qm, + ecs_table_t *table, + ecs_iter_t *it) +{ + ecs_allocator_t *a = &world->allocator; + ecs_query_t *query = cache->query; + int32_t i, term_count = query->term_count; + int32_t field_count = query->field_count; + ecs_term_t *terms = query->terms; + + /* Reset resources in case this is an existing record */ + ecs_vec_reset_t(a, &qm->refs, ecs_ref_t); + ecs_os_memcpy_n(qm->columns, it->columns, int32_t, field_count); + ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); + ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); + qm->set_fields = it->set_fields; + qm->up_fields = it->up_fields; + + if (table) { + /* Initialize storage columns for faster access to component storage */ + for (i = 0; i < field_count; i ++) { + int32_t column = it->columns[i]; + if (!ecs_field_is_set(it, i) || terms[i].inout == EcsInOutNone) { + qm->storage_columns[i] = -1; + continue; + } + + ecs_entity_t src = it->sources[i]; + if (!src) { + qm->storage_columns[i] = ecs_table_type_to_column_index( + table, column); + } else { + /* Shared field (not from table) */ + qm->storage_columns[i] = -2; + } + } + } + + /* Add references for substituted terms */ + for (i = 0; i < term_count; i ++) { + int32_t field = terms[i].field_index; + ecs_entity_t src = it->sources[field]; + ecs_size_t size = 0; + if (it->sizes) { + size = it->sizes[field]; + } + if (src) { + ecs_id_t id = it->ids[field]; + ecs_assert(ecs_is_valid(world, src), ECS_INTERNAL_ERROR, NULL); + + if (id) { + flecs_query_cache_add_ref( + world, impl, qm, id, src, size); + + /* Use column index to bind term and ref */ + if (qm->set_fields & (1llu << field)) { + qm->columns[field] = ecs_vec_count(&qm->refs) - 1; + } + } + } + } +} + +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; + } + + if (cache->query->flags & EcsQueryCacheYieldEmptyTables) { + ecs_table_cache_insert_w_empty(&cache->cache, table, &qt->hdr, false); + } else { + 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_impl_t *impl, + 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, EcsIterIsInstanced); + 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_query_cache_table_match_t *qm = + flecs_query_cache_add_table_match(cache, qt, table); + flecs_query_cache_set_table_match(world, impl, cache, qm, table, &it); + } +} + +static +bool flecs_query_cache_match_table( + ecs_world_t *world, + ecs_query_impl_t *impl, + 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 |= EcsIterIsInstanced; + 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(world, impl, cache, qm, table, &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; +} + +/** When a table becomes empty remove it from the query list, or vice versa. */ +static +void flecs_query_cache_update_table( + ecs_query_cache_t *cache, + ecs_table_t *table, + bool empty) +{ + int32_t prev_count = flecs_query_cache_table_count(cache); + ecs_table_cache_set_empty(&cache->cache, table, empty); + int32_t cur_count = flecs_query_cache_table_count(cache); + + if (prev_count != cur_count) { + ecs_query_cache_table_t *qt = ecs_table_cache_get(&cache->cache, table); + ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_cache_table_match_t *cur, *next; + + for (cur = qt->first; cur != NULL; cur = next) { + next = cur->next_match; + + if (empty) { + ecs_assert(ecs_table_count(table) == 0, + ECS_INTERNAL_ERROR, NULL); + + flecs_query_cache_remove_table_node(cache, cur); + } else { + ecs_assert(ecs_table_count(table) != 0, + ECS_INTERNAL_ERROR, NULL); + + flecs_query_cache_insert_table_node(cache, cur); + } + } + } + + ecs_assert(cur_count || cache->list.first == NULL, + ECS_INTERNAL_ERROR, NULL); +} + +/* Remove table */ +static +void flecs_query_cache_table_match_free( + ecs_query_cache_t *cache, + ecs_query_cache_table_t *elem, + 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.columns, cur->columns); + flecs_bfree(&cache->allocators.columns, cur->storage_columns); + flecs_bfree(&cache->allocators.ids, cur->ids); + flecs_bfree(&cache->allocators.sources, cur->sources); + + if (cur->monitor) { + flecs_bfree(&cache->allocators.monitors, cur->monitor); + } + if (!elem->hdr.empty) { + flecs_query_cache_remove_table_node(cache, cur); + } + + ecs_vec_fini_t(&world->allocator, &cur->refs, ecs_ref_t); + + 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, 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) +{ + 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; + } + + cache->monitor_generation = world->monitor_generation; + + it = ecs_query_iter(world, cache->query); + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + 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, qt, 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(world, impl, cache, qm, table, &it); + + if (table && ecs_table_count(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, qt, 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); + } +} + +/* -- 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, impl, 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, impl, 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 == EcsOnTableEmpty) { + flecs_query_cache_update_table(cache, table, true); + } else + if (event == EcsOnTableFill) { + flecs_query_cache_update_table(cache, table, false); + } else 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.columns, + field_count * ECS_SIZEOF(int32_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.columns); + 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); + 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; + ecs_stage_t *stage = impl->stage; + 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 }; + + ecs_flags32_t query_flags = const_desc->flags | world->default_query_flags; + desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly; + 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); + + if (q->term_count) { + observer_desc.run = flecs_query_cache_on_event; + observer_desc.ctx = impl; + + int32_t event_index = 0; + if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { + observer_desc.events[event_index ++] = EcsOnTableEmpty; + observer_desc.events[event_index ++] = EcsOnTableFill; + } + + observer_desc.events[event_index ++] = EcsOnTableCreate; + observer_desc.events[event_index ++] = EcsOnTableDelete; + observer_desc.query.flags |= EcsQueryNoData|EcsQueryIsInstanced; + 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; + } + + /* Ensure that while initially populating the query with tables, they are + * in the right empty/non-empty list. This ensures the query won't miss + * empty/non-empty events for tables that are currently out of sync, but + * change back to being in sync before processing pending events. */ + ecs_run_aperiodic(world, EcsAperiodicEmptyTables); + ecs_table_cache_init(world, &result->cache); + flecs_query_cache_match_tables(world, impl, 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, NULL); + + 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 +ecs_query_cache_table_match_t* flecs_query_next( + const ecs_query_run_ctx_t *ctx) +{ + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; + 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; + it->instance_count = 0; + qit->node = node->next; + qit->prev = node; + return node; + } + + return NULL; +} + +static +ecs_query_cache_table_match_t* flecs_query_test( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + bool first) +{ + ecs_iter_t *it = ctx->it; + if (first) { + 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( + impl->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_next(ctx); +} + +static +void flecs_query_populate_ptrs( + ecs_iter_t *it, + ecs_table_t *table, + int32_t offset, + ecs_query_cache_table_match_t *node) +{ + int32_t i, field_count = it->field_count; + ecs_data_t *data = &table->data; + for (i = 0; i < field_count; i ++) { + ECS_TERMSET_CLEAR(it->shared_fields, 1u << i); + + int32_t storage_column = node->storage_columns[i]; + ecs_size_t size = it->sizes[i]; + if (storage_column < 0 || !size) { + /* Tag / no data */ + it->ptrs[i] = NULL; + continue; + } + + it->ptrs[i] = ecs_vec_get(&data->columns[storage_column].data, + it->sizes[i], offset); + } +} + +static +void flecs_query_populate_ptrs_w_field_map( + ecs_iter_t *it, + ecs_table_t *table, + int32_t offset, + ecs_query_cache_table_match_t *node, + int8_t *field_map, + int32_t field_count) +{ + int32_t i; + ecs_data_t *data = &table->data; + for (i = 0; i < field_count; i ++) { + int32_t storage_column = node->storage_columns[i]; + int8_t field_index = field_map[i]; + ecs_size_t size = it->sizes[field_index]; + if (storage_column < 0 || !size) { + /* Tag / no data */ + it->ptrs[field_index] = NULL; + continue; + } + + it->ptrs[field_index] = ecs_vec_get(&data->columns[storage_column].data, + size, offset); + } +} + +static +void flecs_query_populate_ptrs_w_shared( + ecs_iter_t *it, + ecs_table_t *table, + int32_t offset, + ecs_query_cache_table_match_t *node, + int8_t *field_map, + int8_t field_count) +{ + ecs_ref_t *refs = ecs_vec_first(&node->refs); + int8_t i; + for (i = 0; i < field_count; i ++) { + int32_t column = node->columns[i]; + int8_t field_index = i; + if (field_map) { + field_index = field_map[i]; + } + + ecs_size_t size = it->sizes[field_index]; + if (!ecs_field_is_set(it, i) || !size) { + /* Tag / no data */ + it->ptrs[field_index] = NULL; + continue; + } + + ecs_entity_t src = node->sources[i]; + if (src != 0) { + ecs_ref_t *ref = &refs[column]; + if (ref->id) { + it->ptrs[field_index] = (void*)ecs_ref_get_id( + it->real_world, ref, ref->id); + ECS_TERMSET_SET(it->shared_fields, 1u << i); + } else { + it->ptrs[field_index] = NULL; + } + } else { + int32_t storage_column = node->storage_columns[i]; + if (storage_column < 0) { + it->ptrs[field_index] = NULL; + continue; + } + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + it->ptrs[field_index] = ecs_vec_get( + &table->data.columns[storage_column].data, size, offset); + ECS_TERMSET_CLEAR(it->shared_fields, 1u << i); + } + } +} + +/* Populate for query that is partially cached. + * This requires a mapping from cached fields to query fields. */ +static +void flecs_query_cache_data_populate( + const ecs_query_impl_t *impl, + ecs_iter_t *it, + const ecs_query_run_ctx_t *ctx, + ecs_query_cache_table_match_t *node) +{ + ecs_table_t *table = node->table; + ecs_var_t *var = &ctx->vars[0]; + int32_t count = var->range.count, offset = var->range.offset; + + if (!count) { + count = ecs_table_count(table); + } + + /* If NoData flag is set on iterator, don't populate fields */ + if (ECS_BIT_IS_SET(it->flags, EcsIterNoData) || !count) { + return; + } + + ecs_query_t *cache_query = impl->cache->query; + if (!ecs_vec_count(&node->refs)) { + flecs_query_populate_ptrs_w_field_map( + it, table, offset, node, impl->field_map, cache_query->field_count); + } else { + flecs_query_populate_ptrs_w_shared( + it, table, offset, node, impl->field_map, cache_query->field_count); + } +} + +/* Populate for query that is entirely cached. + * Cached fields the same as query fields. */ +static +void flecs_query_is_cache_data_populate( + ecs_iter_t *it, + const ecs_query_run_ctx_t *ctx, + ecs_query_cache_table_match_t *node) +{ + ecs_table_t *table = node->table; + ecs_var_t *var = &ctx->vars[0]; + int32_t count = var->range.count, offset = var->range.offset; + + if (!count) { + count = ecs_table_count(table); + } + + /* If NoData flag is set on iterator, don't populate fields */ + if (ECS_BIT_IS_SET(it->flags, EcsIterNoData) || !count) { + return; + } + + if (!ecs_vec_count(&node->refs)) { + flecs_query_populate_ptrs(it, table, offset, node); + } else { + flecs_query_populate_ptrs_w_shared(it, table, offset, node, NULL, + flecs_ito(int8_t, it->field_count)); + } +} + +static +void flecs_query_cache_init_mapped_fields( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + ecs_query_cache_table_match_t *node) +{ + ecs_iter_t *it = ctx->it; + ecs_query_cache_t *cache = impl->cache; + int32_t i, field_count = cache->query->field_count; + int8_t *field_map = impl->field_map; + + for (i = 0; i < field_count; i ++) { + int8_t field_index = field_map[i]; + it->columns[field_index] = node->columns[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_impl_t *impl, + const ecs_query_run_ctx_t *ctx) +{ + ecs_query_cache_table_match_t *node = flecs_query_next(ctx); + if (!node) { + return false; + } + + flecs_query_cache_init_mapped_fields(impl, ctx, node); + + ctx->vars[0].range.count = node->count; + ctx->vars[0].range.offset = node->offset; + return true; +} + +/* Iterate cache for query that's entirely cached */ +bool flecs_query_is_cache_search( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx) +{ + (void)impl; + + ecs_query_cache_table_match_t *node = flecs_query_next(ctx); + if (!node) { + return false; + } + + ecs_iter_t *it = ctx->it; + it->columns = node->columns; + it->ids = node->ids; + it->sources = node->sources; + it->set_fields = node->set_fields; + it->up_fields = node->up_fields; + + ctx->vars[0].range.count = node->count; + ctx->vars[0].range.offset = node->offset; + return true; +} + +/* Iterate cache for query that's partially cached with data */ +bool flecs_query_cache_data_search( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx) +{ + if (!flecs_query_cache_search(impl, ctx)) { + return false; + } + + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; + flecs_query_cache_data_populate(impl, it, ctx, qit->prev); + return true; +} + +/* Iterate cache for query that's entirely cached with data */ +bool flecs_query_is_cache_data_search( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx) +{ + if (!flecs_query_is_cache_search(impl, ctx)) { + return false; + } + + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; + flecs_query_is_cache_data_populate(it, ctx, qit->prev); + return true; +} + +/* Test if query that is entirely cached matches constrained $this */ +bool flecs_query_cache_test( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + bool first) +{ + ecs_query_cache_table_match_t *node = flecs_query_test(impl, ctx, first); + if (!node) { + return false; + } + + flecs_query_cache_init_mapped_fields(impl, ctx, node); + + return true; +} + +/* Test if query that is entirely cached matches constrained $this */ +bool flecs_query_is_cache_test( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + bool first) +{ + ecs_query_cache_table_match_t *node = flecs_query_test(impl, ctx, first); + if (!node) { + return false; + } + + ecs_iter_t *it = ctx->it; + it->columns = node->columns; + it->ids = node->ids; + it->sources = node->sources; + + return true; +} + +/* Test if query that is partially cached matches constrained $this */ +bool flecs_query_cache_data_test( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + bool first) +{ + if (!flecs_query_cache_test(impl, ctx, first)) { + return false; + } + + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; + flecs_query_cache_data_populate(impl, it, ctx, qit->prev); + return true; +} + +/* Test if query that is entirely cached matches constrained $this with data */ +bool flecs_query_is_cache_data_test( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + bool first) +{ + if (!flecs_query_is_cache_test(impl, ctx, first)) { + return false; + } + + ecs_iter_t *it = ctx->it; + ecs_query_iter_t *qit = &it->priv_.iter.query; + flecs_query_is_cache_data_populate(it, ctx, qit->prev); + 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) +{ + ecs_data_t *data = &table->data; + if (!ecs_vec_count(&data->entities)) { + /* Nothing to sort */ + return; + } + + int32_t count = flecs_table_data_count(data); + if (count < 2) { + return; + } + + ecs_entity_t *entities = ecs_vec_first(&data->entities); + + void *ptr = NULL; + int32_t size = 0; + if (column_index != -1) { + ecs_column_t *column = &data->columns[column_index]; + ecs_type_info_t *ti = column->ti; + size = ti->size; + ptr = ecs_vec_first(&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; + 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; + ecs_data_t *data = &table->data; + + 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; + int32_t column = cur->columns[field]; + ecs_size_t size = cache->query->sizes[field]; + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t src = cur->sources[field]; + if (src == 0) { + column = table->column_map[column]; + ecs_vec_t *vec = &data->columns[column].data; + helper[to_sort].ptr = ecs_vec_first(vec); + 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 = ecs_vec_first(&data->entities); + helper[to_sort].row = 0; + helper[to_sort].count = ecs_table_count(table); + to_sort ++; + } + + ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); + + bool proceed; + do { + int32_t j, min = 0; + proceed = true; + + ecs_entity_t e1; + while (!(e1 = e_from_helper(&helper[min]))) { + min ++; + if (min == to_sort) { + proceed = false; + break; + } + } + + 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->columns != cur_helper->match->columns) { + 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]; + } + + nodes[0].prev = NULL; + nodes[i - 1].next = NULL; + + 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_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; + } + + 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); + + ecs_entity_t src = match->sources[field]; + ecs_table_t *table = NULL; + int32_t column = -1; + + if (!src) { + table = match->table; + column = match->storage_columns[field]; + if (column == -2) { + /* Shared field */ + column = -1; + } + } else { + ecs_record_t *r = flecs_entities_get(q->world, src); + table = r->table; + + int32_t ref_index = match->columns[field]; + ecs_ref_t *ref = ecs_vec_get_t(&match->refs, ecs_ref_t, ref_index); + if (ref->id != 0) { + ecs_ref_update(q->world, ref); + column = ref->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; + } + + ecs_assert(match->columns != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t column = match->columns[field]; + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + (void)column; + + 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->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_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; + } + } + + bool is_this = false; + const ecs_query_t *query = cache->query; + ecs_world_t *world = query->world; + int32_t i, j, field_count = query->field_count; + int32_t *storage_columns = match->storage_columns; + ecs_entity_t *sources = match->sources; + int32_t *columns = it ? it->columns : match->columns; + ecs_flags64_t set_fields = it ? it->set_fields : match->set_fields; + + ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_vec_t *refs = &match->refs; + 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 = storage_columns[i]; + 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 */ + } + } + + column = columns[i]; + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + + /* Find term index from field index, which differ when using || */ + int32_t term_index = i; + if (query->terms[i].field_index != i) { + for (j = i; j < query->term_count; j ++) { + if (query->terms[j].field_index == i) { + term_index = j; + break; + } + } + } + + is_this = ecs_term_match_this(&query->terms[term_index]); + + /* Flattened fields are encoded by adding field_count to the column + * index of the parent component. */ + ecs_assert(column <= field_count, ECS_INTERNAL_ERROR, NULL); + + if (is_this) { + /* Component reached through traversal from this */ + int32_t ref_index = column; + ecs_ref_t *ref = ecs_vec_get_t(refs, ecs_ref_t, ref_index); + if (ref->id != 0) { + ecs_ref_update(world, ref); + ecs_table_record_t *tr = ref->tr; + ecs_table_t *src_table = tr->hdr.table; + column = tr->index; + column = ecs_table_type_to_column_index(src_table, column); + int32_t *src_dirty_state = flecs_table_get_dirty_state( + world, src_table); + if (mon != src_dirty_state[column + 1]) { + return true; + } + } + } else { + /* Component from static 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); + column = ecs_table_type_to_column_index(src_table, column); + 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) { + return; + } + + ecs_world_t *world = q->world; + int16_t i, field_count = q->field_count; + for (i = 0; i < field_count; i ++) { + if (!(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_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->columns[i]; + 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->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->columns[i] >= 0, ECS_INTERNAL_ERROR, NULL); + int32_t column = table->column_map[it->columns[i]]; + 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); + + 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 we're checking the cache, make sure that tables are in the correct + * empty/non-empty lists. */ + flecs_process_pending_tables(q->world); + + 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); + + ecs_query_iter_t *qit = &it->priv_.iter.query; + if (it->instance_count > it->count) { + qit->skip_count ++; + if (qit->skip_count == it->instance_count) { + /* For non-instanced queries, make sure all entities are skipped */ + it->flags |= EcsIterSkip; + } + } else { + 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)); +} + +static +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; + const 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); + } 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); + } +} + +static +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_column(it, field, tr->index); + return true; +} + +static +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_column(it, field, tr->index); + return true; +} + +static +bool flecs_query_and_id( + 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_id(op, redo, ctx); + } else { + return flecs_query_select_id(op, redo, ctx, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); + } +} + +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_world_t *world = ctx->world; + ecs_iter_t *it = ctx->it; + bool redo_select = redo; + const ecs_query_t *q = &ctx->query->pub; + bool self = trav_kind == FlecsQueryUpSelectSelfUp; + + /* Early out if traversal relationship doesn't exist */ + 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 (!self) { + 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) { + op_ctx->with = flecs_query_op_get_id(op, ctx); + op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); + if (!op_ctx->idr_with) { + return false; + } + + op_ctx->down = NULL; + op_ctx->cache_elem = 0; + } + + ecs_trav_down_t *down = op_ctx->down; + + do { + while (!down) { + ecs_table_t *table = op_ctx->table; + if (!table) { + ecs_table_range_t range; + it->sources[op->field_index] = 0; + do { + bool result; + if (kind == FlecsQueryUpSelectId) { + result = flecs_query_select_id(op, redo_select, ctx, 0); + } else if (kind == FlecsQueryUpSelectDefault) { + result = flecs_query_select_w_id(op, redo_select, ctx, + op_ctx->with, 0); + } else if (kind == FlecsQueryUpSelectUnion) { + result = flecs_query_union_select(op, redo_select, ctx); + } else { + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + if (!result) { + return false; + } + + redo_select = true; + + range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); + } while (!self && range.table->_->traversable_count == 0); + + if (!range.count) { + range.count = ecs_table_count(range.table); + } + + 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]; + + 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 { + op_ctx->row ++; + } + + if (table->_->traversable_count == 0) { + op_ctx->table = NULL; + continue; + } else { + int32_t row; + ecs_entity_t entity = 0; + ecs_entity_t *entities = flecs_table_entities_array(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) { + it->sources[op->field_index] = entity; + break; + } + } + + if (row == op_ctx->end) { + op_ctx->table = NULL; + continue; + } + + op_ctx->row = row; + + bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; + down = 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; + } + } + +next_elem: + if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { + down = NULL; + continue; + } + + 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))) + { + goto next_elem; + } + + break; + } while (true); + + flecs_set_source_set_flag(it, op->field_index); + + return true; +} + +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; + + /* Early out if traversal relationship doesn't exist */ + 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_all_count(&op_ctx->idr_trav->cache)){ + 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 (!op_ctx->idr_with) { + return false; + } + + ecs_table_range_t range = flecs_query_get_range( + op, &op->src, EcsQuerySrc, ctx); + if (!range.table) { + return false; + } + + 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) { + return false; + } + + it->sources[op->field_index] = flecs_entities_get_alive( + ctx->world, up->src); + it->columns[op->field_index] = up->column; + 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 { + return false; + } +} + +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) { + 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) { + 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) { + ecs_iter_t *it = ctx->it; + it->sources[op->field_index] = 0; + } + return true; + } + + 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) { + return flecs_query_with(op, redo, ctx); + } + } + + return false; +} + +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_up_id( + 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, FlecsQueryUpSelectId); + } +} + +static +bool flecs_query_self_up_id( + 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, true); + } else { + return flecs_query_up_select(op, redo, ctx, + FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectId); + } +} + +static +bool flecs_query_select_any( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + return flecs_query_select_w_id(op, redo, ctx, EcsAny, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); +} + +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; + } + + if (!redo) { + 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); + } + + 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_any(op, redo, ctx); + } +} + +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->query, ctx, !redo, termset); + } else { + return flecs_query_trivial_search_nodata( + ctx->query, ctx, op_ctx, !redo, termset); + } +} + +static +bool flecs_query_triv_data( + 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->query, ctx, !redo, termset); + } else { + return flecs_query_trivial_search( + ctx->query, ctx, op_ctx, !redo, termset); + } +} + +static +bool flecs_query_triv_wildcard( + 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_w_wildcards( + ctx->query, ctx, !redo, termset); + } else { + return flecs_query_trivial_search_w_wildcards( + ctx->query, 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->query, ctx, !redo); + } else { + return flecs_query_cache_search(ctx->query, ctx); + } +} + +static +bool flecs_query_cache_data( + 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_data_test(ctx->query, ctx, !redo); + } else { + return flecs_query_cache_data_search(ctx->query, 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->query, ctx, !redo); + } else { + return flecs_query_is_cache_search(ctx->query, ctx); + } +} + +static +bool flecs_query_is_cache_data( + 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_data_test(ctx->query, ctx, !redo); + } else { + return flecs_query_is_cache_data_search(ctx->query, 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; + int32_t i; + + if (!redo) { + /* Find entity that acts as the template from which we match the ids */ + ecs_id_t id = flecs_query_op_get_id(op, ctx); + ecs_assert(ecs_is_alive(world, id), ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, 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 = 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; + } + } + return true; + } + + 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) { + return true; /* Single match is enough */ + } + } + } + + if (i == type->count) { + if (oper == EcsAndFrom || oper == EcsNotFrom) { + break; /* All ids matched */ + } + } + } + } while (true); + + 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->columns[op->field_index] = -1; /* 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) { + row = op_ctx->row = range.offset; + } else { + int32_t end = range.count; + if (end) { + end += range.offset; + } else { + end = table->data.entities.count; + } + row = ++ op_ctx->row; + if (op_ctx->row >= end) { + return false; + } + } + + ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *entities = table->data.entities.array; + 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; + } + + /* 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]; + } + } + + 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; + } + + 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; + uint8_t field_index = flecs_ito(uint8_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 EcsQueryAndId: return flecs_query_and_id(op, redo, ctx); + case EcsQueryAndAny: return flecs_query_and_any(op, redo, ctx); + case EcsQueryTriv: return flecs_query_triv(op, redo, ctx); + case EcsQueryTrivData: return flecs_query_triv_data(op, redo, ctx); + case EcsQueryTrivWildcard: return flecs_query_triv_wildcard(op, redo, ctx); + case EcsQueryCache: return flecs_query_cache(op, redo, ctx); + case EcsQueryIsCache: return flecs_query_is_cache(op, redo, ctx); + case EcsQueryCacheData: return flecs_query_cache_data(op, redo, ctx); + case EcsQueryIsCacheData: return flecs_query_is_cache_data(op, redo, ctx); + case EcsQueryOnlyAny: return flecs_query_only_any(op, redo, ctx); + case EcsQueryUp: return flecs_query_up(op, redo, ctx); + case EcsQueryUpId: return flecs_query_up_id(op, redo, ctx); + case EcsQuerySelfUp: return flecs_query_self_up(op, redo, ctx); + case EcsQuerySelfUpId: return flecs_query_self_up_id(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 EcsQueryPopulate: return flecs_query_populate(op, redo, ctx); + case EcsQueryPopulateSelf: return flecs_query_populate_self(op, redo, ctx); + case EcsQueryPopulateSparse: return flecs_query_populate_sparse(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_init( + ecs_query_run_ctx_t *ctx) +{ + ecs_assert(ctx->written != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_iter_t *it = ctx->it; + + 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 */ + } + } + + ecs_flags32_t flags = q->flags; + ecs_query_cache_t *cache = query->cache; + if (flags & EcsQueryIsTrivial && !cache) { + if ((flags & EcsQueryMatchOnlySelf)) { + if (it_written) { + flecs_query_set_iter_this(it, ctx); + + if (flags & EcsQueryMatchWildcards) { + it->flags |= EcsIterTrivialTestWildcard; + flecs_query_setids(&query->ops[0], false, ctx); + } else { + it->flags |= EcsIterTrivialTest; + flecs_query_setids(&query->ops[0], false, ctx); + } + } else { + if (flags & EcsQueryMatchWildcards) { + it->flags |= EcsIterTrivialSearchWildcard; + flecs_query_setids(&query->ops[0], false, ctx); + } else if (flags & EcsQueryNoData) { + it->flags |= EcsIterTrivialSearchNoData; + flecs_query_setids(&query->ops[0], false, ctx); + } else { + it->flags |= EcsIterTrivialSearch; + flecs_query_setids(&query->ops[0], false, ctx); + } + } + } + } + + if (cache) { + cache->prev_match_count = cache->match_count; + } + + flecs_iter_validate(it); +} + +bool flecs_query_next_instanced( + 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; + 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; + const ecs_query_op_t *ops = qit->ops; + + bool redo = true; + if (!(it->flags & EcsIterIsValid)) { + ecs_assert(impl != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_query_iter_init(&ctx); + redo = false; + } else { + it->frame_offset += it->count; + + if (!(it->flags & EcsIterSkip)) { + flecs_query_mark_fields_dirty(impl, it); + if (qit->prev) { + if (ctx.query->pub.flags & EcsQueryHasMonitor) { + flecs_query_sync_match_monitor(impl, qit->prev); + } + } + } + + it->flags &= ~EcsIterSkip; + } + + /* Specialized iterator modes for trivial queries */ + if (it->flags & EcsIterTrivialSearch) { + ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; + int32_t fields = ctx.query->pub.term_count; + ecs_flags64_t mask = (2llu << (fields - 1)) - 1; + if (!flecs_query_trivial_search(ctx.query, &ctx, op_ctx, !redo, mask)) { + goto done; + } + it->table = ctx.vars[0].range.table; + it->count = ecs_table_count(it->table); + it->entities = flecs_table_entities_array(it->table); + return true; + } else if (it->flags & EcsIterTrivialSearchNoData) { + ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; + int32_t fields = ctx.query->pub.term_count; + ecs_flags64_t mask = (2llu << (fields - 1)) - 1; + if (!flecs_query_trivial_search_nodata(ctx.query, &ctx, op_ctx, !redo, mask)) { + goto done; + } + it->table = ctx.vars[0].range.table; + it->count = ecs_table_count(it->table); + it->entities = flecs_table_entities_array(it->table); + return true; + } else if (it->flags & EcsIterTrivialSearchWildcard) { + ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; + int32_t fields = ctx.query->pub.term_count; + ecs_flags64_t mask = (2llu << (fields - 1)) - 1; + if (!flecs_query_trivial_search_w_wildcards(ctx.query, &ctx, op_ctx, !redo, mask)) { + goto done; + } + it->table = ctx.vars[0].range.table; + it->count = ecs_table_count(it->table); + it->entities = flecs_table_entities_array(it->table); + return true; + } 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.query, &ctx, !redo, mask)) { + goto done; + } + return true; + } else if (it->flags & EcsIterTrivialTestWildcard) { + int32_t fields = ctx.query->pub.term_count; + ecs_flags64_t mask = (2llu << (fields - 1)) - 1; + if (!flecs_query_trivial_test_w_wildcards(ctx.query, &ctx, !redo, mask)) { + goto done; + } + return true; + } + + /* Default iterator mode */ + 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); + return true; + } + +done: + 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; +} + +bool ecs_query_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); + + if (flecs_iter_next_row(it)) { + return true; + } + + return flecs_iter_next_instanced(it, flecs_query_next_instanced(it)); +error: + return false; +} + +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 EcsQueryUpId: + case EcsQuerySelfUpId: + 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, op_count = impl->op_count; + 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); + 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.up_fields = 0; + flecs_query_apply_iter_flags(&it, q); + + flecs_iter_init(it.world, &it, + flecs_iter_cache_ids | + flecs_iter_cache_columns | + 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); + qit->node = ecs_vec_first(&cache->table_slices); + qit->last = ecs_vec_last_t( + &cache->table_slices, ecs_query_cache_table_match_t); + } + } + + 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; + +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); + + if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { + ecs_run_aperiodic(q->real_world, EcsAperiodicEmptyTables); + } + + /* Ok, only for stats */ + 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 (!(world->flags & 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. */ + void *old_data = it->ptrs[field_index]; + flecs_query_populate_field_from_range( + it, &range, field_index, it->columns[field_index]); + data = op_ctx->data = it->ptrs[field_index]; + + /* Ensure we only write ptrs when we match data */ + it->ptrs[field_index] = old_data; + + /* Member fields are of type ecs_entity_t */ + #ifdef FLECS_META + it->ids[field_index] = ecs_id(ecs_entity_t); + #else + it->ids[field_index] = 0; + #endif + } 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); + ecs_entity_t *entities = table->data.entities.array; + 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); + } + + it->ptrs[field_index] = val; + 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_populate.c + * @brief Populate data fields. + */ + + +void flecs_query_populate_field_from_range( + ecs_iter_t *it, + ecs_table_range_t *range, + int8_t field_index, + int32_t index) +{ + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(range->table != NULL, ECS_INTERNAL_ERROR, NULL); + if (range->count && range->table->column_map) { + int32_t column = range->table->column_map[index]; + if (column != -1) { + it->ptrs[field_index] = ECS_ELEM( + range->table->data.columns[column].data.array, + it->sizes[field_index], + range->offset); + } + } +} + +static +void flecs_query_populate_field( + ecs_iter_t *it, + ecs_table_range_t *range, + int8_t field_index, + ecs_query_run_ctx_t *ctx) +{ + int32_t index = it->columns[field_index]; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_field_is_set(it, field_index), ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t src = it->sources[field_index]; + if (!src) { + flecs_query_populate_field_from_range(it, range, field_index, index); + ECS_TERMSET_CLEAR(it->shared_fields, 1u << field_index); + } else { + ecs_record_t *r = flecs_entities_get(ctx->world, src); + ecs_table_t *src_table = r->table; + if (src_table->column_map) { + ecs_assert(index < src_table->type.count, ECS_INTERNAL_ERROR, NULL); + int32_t column = src_table->column_map[index]; + if (column != -1) { + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + it->ptrs[field_index] = ecs_vec_get( + &src_table->data.columns[column].data, + it->sizes[field_index], + ECS_RECORD_TO_ROW(r->row)); + + ECS_TERMSET_SET(it->shared_fields, 1u << field_index); + } + } + } +} + +bool flecs_query_populate( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + (void)op; + if (!redo) { + ecs_iter_t *it = ctx->it; + if (it->flags & EcsIterNoData) { + return true; + } + + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + int8_t i, field_count = q->field_count; + ecs_flags64_t data_fields = op->src.entity; /* Bitset with fields to set */ + ecs_table_range_t *range = &ctx->vars[0].range; + ecs_table_t *table = range->table; + if (table && !range->count) { + range->count = ecs_table_count(table); + } + + /* Only populate fields that are set */ + data_fields &= it->set_fields; + for (i = 0; i < field_count; i ++) { + if (!(data_fields & (1llu << i))) { + continue; + } + + flecs_query_populate_field(it, range, i, ctx); + } + + return true; + } else { + return false; + } +} + +bool flecs_query_populate_self( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + (void)op; + if (!redo) { + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + int32_t i, field_count = q->field_count; + ecs_flags64_t data_fields = op->src.entity; /* Bitset with fields to set */ + ecs_iter_t *it = ctx->it; + + ecs_table_range_t *range = &ctx->vars[0].range; + ecs_table_t *table = range->table; + if (!table->column_map) { + return true; + } + + if (!ecs_table_count(table)) { + return true; + } + + /* Only populate fields that can be set */ + data_fields &= it->set_fields; + for (i = 0; i < field_count; i ++) { + if (!(data_fields & (1llu << i))) { + continue; + } + + int32_t index = it->columns[i]; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); + + int32_t column = table->column_map[index]; + if (column != -1) { + it->ptrs[i] = ECS_ELEM( + table->data.columns[column].data.array, + it->sizes[i], + range->offset); + } + } + + return true; + } else { + return false; + } +} + +bool flecs_query_populate_sparse( + const ecs_query_op_t *op, + bool redo, + ecs_query_run_ctx_t *ctx) +{ + (void)op; + if (!redo) { + ecs_iter_t *it = ctx->it; + if (it->flags & EcsIterNoData) { + return true; + } + + ecs_world_t *world = ctx->world; + const ecs_query_impl_t *query = ctx->query; + const ecs_query_t *q = &query->pub; + int8_t i, field_count = q->field_count; + ecs_flags64_t data_fields = op->src.entity; /* Bitset with fields to set */ + + /* Only populate fields that are set */ + data_fields &= it->set_fields; + for (i = 0; i < field_count; i ++) { + if (!(data_fields & (1llu << i))) { + continue; + } + + ecs_entity_t src = it->sources[i]; + if (!src) { + src = ctx->vars[0].entity; + } + + ecs_id_record_t *idr = flecs_id_record_get(world, it->ids[i]); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + it->ptrs[i] = flecs_sparse_get_any(idr->sparse, 0, src); + } + + return true; + } else { + return false; + } +} + +/** + * @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_vec_get_t(&r.table->data.entities, + ecs_entity_t, r.offset)[0]; + } + 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.array; + 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); + ecs_entity_t *entities = table->data.entities.array; + 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, -1, 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, -1, 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 */ + int32_t column = ecs_search_relation(ctx->world, table, 0, + ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, NULL); + if (column == -1) { + 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, column, 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_vec_get_t( + &range->table->data.entities, ecs_entity_t, trav_ctx->index)[0]; + flecs_query_set_trav_match(op, -1, 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->column, 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 = flecs_table_entities_array(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 = flecs_table_entities_array(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_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_ELEM_T( + table->data.entities.array, ecs_entity_t, 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; + ecs_entity_t *entities = table->data.entities.array; + 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 = flecs_table_entities_array(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_column( + ecs_iter_t *it, + int32_t field_index, + int32_t column) +{ + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); + it->columns[field_index] = column; +} + +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); + flecs_query_it_set_column(it, field_index, column); + ecs_id_t matched = flecs_query_it_set_id(it, table, field_index, column); + flecs_query_set_vars(op, matched, ctx); +} + +void flecs_query_set_trav_match( + const ecs_query_op_t *op, + int32_t column, + 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; + if (column != -1) { + flecs_query_it_set_column(it, op->field_index, column); + } + 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); + ecs_entity_t *entities = table->data.entities.array; + 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->column = root_column; + 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_entity_t entity, + 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); + + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + 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, entity, 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; + } + + ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + 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, e, + idr_trav, 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_entity_t entity, + 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); + + flecs_trav_entity_down_isa( + world, a, cache, dst, trav, entity, idr_with, self, empty); + + 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); + flecs_trav_entity_down( + world, a, cache, result, trav, e, 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]; + return up->column = tr->index; + } + + return -1; +} + +static +int32_t flecs_trav_type_offset_search( + ecs_trav_up_t *up, + 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; + return up->column = 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->column != -1) { + up->src = up_parent->src; + up->column = up_parent->column; + up->id = up_parent->id; + goto found; + } + + r_column = flecs_trav_type_offset_search( + &up_pair, r_column + 1, rel, &type); + } + + if (!is_a) { + 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->column != -1) { + up->src = up_parent->src; + up->column = up_parent->column; + up->id = up_parent->id; + goto found; + } + + r_column = flecs_trav_type_offset_search( + &up_pair, r_column + 1, rel, &type); + } + } + } + +not_found: + up->column = -1; +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 first, + ecs_flags64_t term_set) +{ + if (first) { + /* Find first trivial term*/ + int32_t t; + for (t = 0; t < query->term_count; t ++) { + if (term_set & (1llu << t)) { + break; + } + } + + ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); + + const ecs_term_t *term = &query->terms[t]; + ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); + 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 < 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_impl_t *query, + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + bool first, + ecs_flags64_t term_set) +{ + 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, first, 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; + } + + int32_t first_term = op_ctx->first_to_eval; + for (t = first_term; t < term_count; t ++) { + ecs_flags64_t term_bit = (1llu << t); + if (!(term_set & term_bit)) { + continue; + } + + const ecs_term_t *term = &terms[t]; + ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); + if (!idr) { + return false; + } + + const ecs_table_record_t *tr_with = flecs_id_record_get_table( + idr, table); + if (!tr_with) { + break; + } + + it->columns[term->field_index] = tr_with->index; + if (!(term->flags_ & EcsTermNoData)) { + if (tr_with->column != -1) { + it->ptrs[term->field_index] = ecs_vec_first( + &table->data.columns[tr_with->column].data); + } + } + } + + if (t == term_count) { + ctx->vars[0].range.table = table; + ctx->vars[0].range.count = 0; + ctx->vars[0].range.offset = 0; + it->columns[first_term] = tr->index; + if (!(terms[first_term].flags_ & EcsTermNoData)) { + if (tr->column != -1) { + it->ptrs[first_term] = ecs_vec_first( + &table->data.columns[tr->column].data); + } + } + break; + } + } while (true); + + return true; +} + +bool flecs_query_trivial_search_w_wildcards( + const ecs_query_impl_t *query, + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + bool first, + ecs_flags64_t term_set) +{ + bool result = flecs_query_trivial_search( + query, ctx, op_ctx, first, term_set); + if (result) { + ecs_iter_t *it = ctx->it; + ecs_table_t *table = ctx->vars[0].range.table; + int32_t t, term_count = query->pub.term_count; + for (t = 0; t < term_count; t ++) { + if (term_set & (1llu << t)) { + int32_t column = it->columns[t]; + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + it->ids[t] = table->type.array[column]; + } + } + } + + return result; +} + +bool flecs_query_trivial_search_nodata( + const ecs_query_impl_t *query, + const ecs_query_run_ctx_t *ctx, + ecs_query_trivial_ctx_t *op_ctx, + bool first, + ecs_flags64_t term_set) +{ + 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, first, 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->columns[term->field_index] = tr_with->index; + } + + if (t == term_count) { + ctx->vars[0].range.table = table; + ctx->vars[0].range.count = 0; + ctx->vars[0].range.offset = 0; + it->columns[0] = tr->index; + break; + } + } while (true); + + return true; +} + +bool flecs_query_trivial_test( + const ecs_query_impl_t *impl, + const ecs_query_run_ctx_t *ctx, + bool first, + ecs_flags64_t term_set) +{ + if (first) { + 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->columns[term->field_index] = tr->index; + if (it->count && tr->column != -1) { + it->ptrs[term->field_index] = ecs_vec_get( + &table->data.columns[tr->column].data, + it->sizes[term->field_index], + it->offset); + } + } + + it->entities = flecs_table_entities_array(table); + if (it->entities) { + it->entities = &it->entities[it->offset]; + } + + return true; + } else { + return false; + } +} + +bool flecs_query_trivial_test_w_wildcards( + const ecs_query_impl_t *query, + const ecs_query_run_ctx_t *ctx, + bool first, + ecs_flags64_t term_set) +{ + int32_t t, term_count = query->pub.term_count; + bool result = flecs_query_trivial_test( + query, ctx, first, term_set); + if (result) { + ecs_iter_t *it = ctx->it; + ecs_table_t *table = ctx->vars[0].range.table; + for (t = 0; t < term_count; t ++) { + if (term_set & (1llu << t)) { + int32_t column = it->columns[t]; + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + it->ids[t] = table->type.array[column]; + } + } + } + + return result; +} + diff --git a/addons/glecs/cpp/include/flecs/flecs.h b/addons/glecs/cpp/include/flecs/flecs.h new file mode 100644 index 0000000..9708f6e --- /dev/null +++ b/addons/glecs/cpp/include/flecs/flecs.h @@ -0,0 +1,32452 @@ +// 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 0 /**< 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 + +/* 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 + +/** @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 + +#ifndef FLECS_CUSTOM_BUILD +// #define FLECS_C /**< C API convenience macros, always enabled */ +#define FLECS_CPP /**< C++ API */ +#define FLECS_MODULE /**< Module support */ +#define FLECS_SCRIPT /**< ECS data definition format */ +#define FLECS_STATS /**< Track runtime statistics */ +#define FLECS_METRICS /**< Expose component data as statistics */ +#define FLECS_ALERTS /**< Monitor conditions for errors */ +#define FLECS_SYSTEM /**< System support */ +#define FLECS_PIPELINE /**< Pipeline support */ +#define FLECS_TIMER /**< Timer support */ +#define FLECS_META /**< Reflection support */ +#define FLECS_UNITS /**< Builtin standard units */ +#define FLECS_JSON /**< Parsing JSON to/from component values */ +#define FLECS_DOC /**< Document entities & components */ +#define FLECS_LOG /**< When enabled ECS provides more detailed logs */ +#define FLECS_APP /**< Application addon */ +#define FLECS_OS_API_IMPL /**< Default implementation for OS API */ +#define FLECS_HTTP /**< Tiny HTTP server for connecting to remote UI */ +#define FLECS_REST /**< REST API for querying application data */ +// #define FLECS_JOURNAL /**< Journaling addon (disabled by default) */ +#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 (6) +#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 (12) +#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 be set higher than 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 */ +#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 + +/** @} */ + +/** + * @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) + + +//////////////////////////////////////////////////////////////////////////////// +//// 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 EcsIdHasOnAdd (1u << 16) /* Same values as table flags */ +#define EcsIdHasOnRemove (1u << 17) +#define EcsIdHasOnSet (1u << 18) +#define EcsIdHasOnTableFill (1u << 20) +#define EcsIdHasOnTableEmpty (1u << 21) +#define EcsIdHasOnTableCreate (1u << 22) +#define EcsIdHasOnTableDelete (1u << 23) +#define EcsIdIsSparse (1u << 24) +#define EcsIdIsUnion (1u << 25) +#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 EcsIterIsInstanced (1u << 2u) /* Is iterator instanced */ +#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 EcsIterTrivialSearchNoData (1u << 9u) /* Trivial iterator w/no data */ +#define EcsIterTrivialTest (1u << 10u) /* Trivial test mode (constrained $this) */ +#define EcsIterTrivialTestWildcard (1u << 11u) /* Trivial test w/wildcards */ +#define EcsIterTrivialSearchWildcard (1u << 12u) /* Trivial search with wildcard ids */ +#define EcsIterCacheSearch (1u << 13u) /* Cache search */ +#define EcsIterFixedInChangeComputed (1u << 14u) /* Change detection for fixed in terms is done */ +#define EcsIterFixedInChanged (1u << 15u) /* Fixed in terms changed */ +#define EcsIterSkip (1u << 16u) /* Result was skipped for change detection */ +#define EcsIterCppEach (1u << 17u) /* Uses C++ 'each' iterator */ + +/* Same as event flags */ +#define EcsIterTableOnly (1u << 18u) /* Result only populates table */ + + +//////////////////////////////////////////////////////////////////////////////// +//// Event flags (used by ecs_event_decs_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsEventTableOnly (1u << 18u) /* 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 EcsQueryHasCondSet (1u << 15u) /* Query has conditionally set fields */ +#define EcsQueryHasPred (1u << 16u) /* Query has equality predicates */ +#define EcsQueryHasScopes (1u << 17u) /* Query has query scopes */ +#define EcsQueryHasRefs (1u << 18u) /* Query has terms with static source */ +#define EcsQueryHasOutTerms (1u << 19u) /* Query has [out] terms */ +#define EcsQueryHasNonThisOutTerms (1u << 20u) /* Query has [out] terms with no $this source */ +#define EcsQueryHasMonitor (1u << 21u) /* Query has monitor for change detection */ +#define EcsQueryIsTrivial (1u << 22u) /* Query can use trivial evaluation function */ +#define EcsQueryHasCacheable (1u << 23u) /* Query has cacheable terms */ +#define EcsQueryIsCacheable (1u << 24u) /* All terms of query are cacheable */ +#define EcsQueryHasTableThisVar (1u << 25u) /* Does query have $this table var */ +#define EcsQueryHasSparseThis (1u << 26u) /* Does query have $this sparse fields */ +#define EcsQueryCacheYieldEmptyTables (1u << 27u) /* Does query cache empty tables */ + +//////////////////////////////////////////////////////////////////////////////// +//// 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 EcsTermNoData (1u << 6) +#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) + +//////////////////////////////////////////////////////////////////////////////// +//// 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 << 20u) +#define EcsTableHasOnTableEmpty (1u << 21u) +#define EcsTableHasOnTableCreate (1u << 22u) +#define EcsTableHasOnTableDelete (1u << 23u) +#define EcsTableHasSparse (1u << 24u) +#define EcsTableHasUnion (1u << 25u) + +#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) + + +//////////////////////////////////////////////////////////////////////////////// +//// Aperiodic action flags (used by ecs_run_aperiodic) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsAperiodicEmptyTables (1u << 1u) /* Process pending empty table events */ +#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 + +/* 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" +#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" +/* Enum reflection relies on testing constant values that may not be valid for + * the enumeration. */ +#pragma clang diagnostic ignored "-Wenum-constexpr-conversion" +/* 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 + +#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" +#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) +#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 = &((type*)_it->ptrs[0])[i];\ + (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; +#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); + +#define ecs_vec_init_t(allocator, vec, T, elem_count) \ + ecs_vec_init(allocator, vec, ECS_SIZEOF(T), elem_count) + +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_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; + int32_t alloc_count; +} 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_bcalloc( + ecs_block_allocator_t *allocator); + +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_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; + +typedef 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; +} ecs_map_t; + +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_t(a, T) flecs_alloc(a, ECS_SIZEOF(T)) +#define flecs_alloc_n(a, T, count) flecs_alloc(a, ECS_SIZEOF(T) * (count)) + +#define flecs_calloc(a, size) flecs_bcalloc(flecs_allocator_get(a, size)) +#define flecs_calloc_t(a, T) flecs_calloc(a, ECS_SIZEOF(T)) +#define flecs_calloc_n(a, T, count) flecs_calloc(a, ECS_SIZEOF(T) * (count)) + +#define flecs_free(a, size, ptr)\ + flecs_bfree(flecs_allocator_get(a, size), ptr) +#define flecs_free_t(a, T, ptr)\ + flecs_bfree_w_dbg_info(flecs_allocator_get(a, ECS_SIZEOF(T)), ptr, #T) +#define flecs_free_n(a, T, count, ptr)\ + flecs_bfree_w_dbg_info(flecs_allocator_get(a, ECS_SIZEOF(T) * (count))\ + , 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_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); + +/* 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. */ + + 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) + +#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 + +#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); + +/** 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; + +/** Information about where in a table a specific (component) id is stored. */ +typedef struct ecs_table_record_t ecs_table_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; + +/** @} */ + +/** + * @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 */ + + int16_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 */ + int16_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 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 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 + */ +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; + + /** 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 */ + + ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_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 */ +}; + +/** 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 */ +}; + +/** 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; + struct ecs_table_cache_hdr_t *next_list; +} 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; + void *ptrs; +} 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_columns (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); + +/* 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; + int32_t defer_count; + ecs_entity_t scope; + ecs_entity_t with; + ecs_vec_t commands; + ecs_stack_t defer_stack; + ecs_stage_t *stage; +} ecs_suspend_readonly_state_t; + +FLECS_API +ecs_world_t* flecs_suspend_readonly( + const ecs_world_t *world, + ecs_suspend_readonly_state_t *state); + +FLECS_API +void flecs_resume_readonly( + ecs_world_t *world, + ecs_suspend_readonly_state_t *state); + +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); + +#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))) + +FLECS_API +bool flecs_query_next_instanced( + ecs_iter_t *it); + +/** 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 */ + ecs_world_t *real_world; /**< Actual world. This differs from world when in readonly mode */ + + /* Matched data */ + ecs_entity_t *entities; /**< Entity identifiers */ + void **ptrs; /**< Pointers to components. Array if from this, pointer if not. */ + 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) */ + int32_t *columns; /**< Query term to table column mapping */ + 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 */ + int32_t field_count; /**< Number of fields in iterator */ + ecs_termset_t set_fields; /**< Fields that are set */ + ecs_termset_t shared_fields; /**< Bitset with shared fields */ + 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 */ + const ecs_query_t *query; /**< Query being evaluated */ + int32_t term_index; /**< Index of term that emitted an event. + * This field will be set to the 'index' field + * of an observer term. */ + int32_t variable_count; /**< Number of variables for query */ + char **variable_names; /**< Names of variables (if any) */ + + /* Context */ + void *param; /**< Param passed to ecs_run */ + void *ctx; /**< System context */ + void *binding_ctx; /**< 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 */ + int32_t instance_count; /**< Number of entities to iterate before next table */ + + /* 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 won't provide component data. + * Can be combined with other query flags on the ecs_query_desc_t::flags field. + * \ingroup queries + */ +#define EcsQueryNoData (1u << 4u) + +/** Query iteration is always instanced. + * Can be combined with other query flags on the ecs_query_desc_t::flags field. + * \ingroup queries + */ +#define EcsQueryIsInstanced (1u << 5u) + +/** 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 */ + int32_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 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 */ + int32_t empty_table_count; /**< Number of tables without entities */ + + /* -- 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; + +/** 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; + +/** Event that triggers when a table becomes empty (doesn't emit on creation). */ +FLECS_API extern const ecs_entity_t EcsOnTableEmpty; + +/** Event that triggers when a table becomes non-empty. */ +FLECS_API extern const ecs_entity_t EcsOnTableFill; + +/** 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); + +/** @} */ + +/** + * @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: + * + * - `EcsQueryIsInstanced` + * - `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); + +/** 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 id Optional component filter for the tables to evaluate. + * @param clear_generation Free table data when generation > clear_generation. + * @param delete_generation Delete table when generation > delete_generation. + * @param min_id_count Minimum number of component ids the table should have. + * @param time_budget_seconds Amount of time operation is allowed to spend. + * @return Number of deleted tables. + */ +FLECS_API +int32_t ecs_delete_empty_tables( + ecs_world_t *world, + ecs_id_t id, + uint16_t clear_generation, + uint16_t delete_generation, + int32_t min_id_count, + double time_budget_seconds); + +/** 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); + +/** 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 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 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); + +/** @} */ + +/** + * @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) + * - The iterator must be instanced + * + * @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); + +/** 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); + +/** @} */ + +/** + * @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); + +/** Obtain data for a query 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. + * + * The provided size must be either 0 or must match the size of the datatype + * of the returned array. If the size does not match, the operation may assert. + * The size can be dynamically obtained with ecs_field_size(). + * + * @param it The iterator. + * @param size The type size of the requested data. + * @param index The index of the field in the iterator. + * @return A pointer to the data of the field. + */ +FLECS_API +void* ecs_field_w_size( + const ecs_iter_t *it, + size_t size, + int32_t index); + +/** 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, + int32_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, + int32_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, + int32_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, + int32_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, + int32_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, + int32_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, + int32_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, + int32_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 records in the table. + * This operation returns the number of records that have been populated through + * the regular (entity) API as well as the number of records that have been + * inserted using the direct access API. + * + * @param table The table. + * @return The number of records in a table. + */ +FLECS_API +int32_t ecs_table_count( + 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); + +/** @} */ + +/** + * @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(const 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) + +#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))) + +/** @} */ + +/** + * @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_STATS +#undef FLECS_STATS +#endif +#ifdef FLECS_NO_SYSTEM +#undef FLECS_SYSTEM +#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); + + +//////////////////////////////////////////////////////////////////////////////// +//// 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) + +#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, + 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; + + /** 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_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. */ +#define ECS_ENTITY_TO_JSON_INIT (ecs_entity_to_json_desc_t){\ + .serialize_doc = false, \ + .serialize_full_paths = false, \ + .serialize_inherited = false, \ + .serialize_values = true, \ + .serialize_type_info = false, \ + .serialize_alerts = false, \ + .serialize_refs = 0, \ + .serialize_matches = false, \ +} + +/** 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_doc; /**< Serialize doc attributes */ + bool serialize_var_labels; /**< Serialize doc names of matched variables */ + 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. */ +#define ECS_ITER_TO_JSON_INIT (ecs_iter_to_json_desc_t){\ + .serialize_entity_ids = false, \ + .serialize_values = true, \ + .serialize_doc = false, \ + .serialize_full_paths = false, \ + .serialize_fields = true, \ + .serialize_inherited = false, \ + .serialize_table = false, \ + .serialize_type_info = false, \ + .serialize_field_info = false, \ + .serialize_query_info = false, \ + .serialize_query_plan = false, \ + .serialize_query_profile = false, \ + .dont_serialize_results = false, \ + .serialize_alerts = false, \ + .serialize_refs = false, \ + .serialize_matches = false, \ +} + +/** Serialize 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); + + + +/* Legacy deserializer functions. These can be used to load a v3 JSON string and + * save it to the new format. These functions will be removed in the next + * release. */ + +FLECS_API +const char* ecs_entity_from_json_legacy( + ecs_world_t *world, + ecs_entity_t entity, + const char *json, + const ecs_from_json_desc_t *desc); + +FLECS_API +const char* ecs_world_from_json_legacy( + ecs_world_t *world, + const char *json, + const ecs_from_json_desc_t *desc); + +FLECS_API +const char* ecs_world_from_json_file_legacy( + ecs_world_t *world, + const char *filename, + const ecs_from_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_DECLARE(EcsUnitPrefixes); /**< Parent scope for prefixes. */ + +FLECS_API extern ECS_DECLARE(EcsYocto); /**< Yocto unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsZepto); /**< Zepto unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsAtto); /**< Atto unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsFemto); /**< Femto unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsPico); /**< Pico unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsNano); /**< Nano unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsMicro); /**< Micro unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsMilli); /**< Milli unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsCenti); /**< Centi unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsDeci); /**< Deci unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsDeca); /**< Deca unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsHecto); /**< Hecto unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsKilo); /**< Kilo unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsMega); /**< Mega unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsGiga); /**< Giga unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsTera); /**< Tera unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsPeta); /**< Peta unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsExa); /**< Exa unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsZetta); /**< Zetta unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsYotta); /**< Yotta unit prefix. */ + +FLECS_API extern ECS_DECLARE(EcsKibi); /**< Kibi unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsMebi); /**< Mebi unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsGibi); /**< Gibi unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsTebi); /**< Tebi unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsPebi); /**< Pebi unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsExbi); /**< Exbi unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsZebi); /**< Zebi unit prefix. */ +FLECS_API extern ECS_DECLARE(EcsYobi); /**< Yobi unit prefix. */ + +/** @} */ + +/** + * @defgroup c_addons_units_duration Duration + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsDuration); /**< Duration quantity. */ +FLECS_API extern ECS_DECLARE(EcsPicoSeconds); /**< PicoSeconds duration unit. */ +FLECS_API extern ECS_DECLARE(EcsNanoSeconds); /**< NanoSeconds duration unit. */ +FLECS_API extern ECS_DECLARE(EcsMicroSeconds); /**< MicroSeconds duration unit. */ +FLECS_API extern ECS_DECLARE(EcsMilliSeconds); /**< MilliSeconds duration unit. */ +FLECS_API extern ECS_DECLARE(EcsSeconds); /**< Seconds duration unit. */ +FLECS_API extern ECS_DECLARE(EcsMinutes); /**< Minutes duration unit. */ +FLECS_API extern ECS_DECLARE(EcsHours); /**< Hours duration unit. */ +FLECS_API extern ECS_DECLARE(EcsDays); /**< Days duration unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_time Time + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsTime); /**< Time quantity. */ +FLECS_API extern ECS_DECLARE(EcsDate); /**< Date unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_mass Mass + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsMass); /**< Mass quantity. */ +FLECS_API extern ECS_DECLARE(EcsGrams); /**< Grams unit. */ +FLECS_API extern ECS_DECLARE(EcsKiloGrams); /**< KiloGrams unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_electric_Current Electric Current + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsElectricCurrent); /**< ElectricCurrent quantity. */ +FLECS_API extern ECS_DECLARE(EcsAmpere); /**< Ampere unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_amount Amount + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsAmount); /**< Amount quantity. */ +FLECS_API extern ECS_DECLARE(EcsMole); /**< Mole unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_luminous_intensity Luminous Intensity + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsLuminousIntensity); /**< LuminousIntensity quantity. */ +FLECS_API extern ECS_DECLARE(EcsCandela); /**< Candela unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_force Force + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsForce); /**< Force quantity. */ +FLECS_API extern ECS_DECLARE(EcsNewton); /**< Newton unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_length Length + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsLength); /**< Length quantity. */ +FLECS_API extern ECS_DECLARE(EcsMeters); /**< Meters unit. */ +FLECS_API extern ECS_DECLARE(EcsPicoMeters); /**< PicoMeters unit. */ +FLECS_API extern ECS_DECLARE(EcsNanoMeters); /**< NanoMeters unit. */ +FLECS_API extern ECS_DECLARE(EcsMicroMeters); /**< MicroMeters unit. */ +FLECS_API extern ECS_DECLARE(EcsMilliMeters); /**< MilliMeters unit. */ +FLECS_API extern ECS_DECLARE(EcsCentiMeters); /**< CentiMeters unit. */ +FLECS_API extern ECS_DECLARE(EcsKiloMeters); /**< KiloMeters unit. */ +FLECS_API extern ECS_DECLARE(EcsMiles); /**< Miles unit. */ +FLECS_API extern ECS_DECLARE(EcsPixels); /**< Pixels unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_pressure Pressure + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsPressure); /**< Pressure quantity. */ +FLECS_API extern ECS_DECLARE(EcsPascal); /**< Pascal unit. */ +FLECS_API extern ECS_DECLARE(EcsBar); /**< Bar unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_speed Speed + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsSpeed); /**< Speed quantity. */ +FLECS_API extern ECS_DECLARE(EcsMetersPerSecond); /**< MetersPerSecond unit. */ +FLECS_API extern ECS_DECLARE(EcsKiloMetersPerSecond); /**< KiloMetersPerSecond unit. */ +FLECS_API extern ECS_DECLARE(EcsKiloMetersPerHour); /**< KiloMetersPerHour unit. */ +FLECS_API extern ECS_DECLARE(EcsMilesPerHour); /**< MilesPerHour unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_temperature Temperature + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsTemperature); /**< Temperature quantity. */ +FLECS_API extern ECS_DECLARE(EcsKelvin); /**< Kelvin unit. */ +FLECS_API extern ECS_DECLARE(EcsCelsius); /**< Celsius unit. */ +FLECS_API extern ECS_DECLARE(EcsFahrenheit); /**< Fahrenheit unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_data Data + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsData); /**< Data quantity. */ +FLECS_API extern ECS_DECLARE(EcsBits); /**< Bits unit. */ +FLECS_API extern ECS_DECLARE(EcsKiloBits); /**< KiloBits unit. */ +FLECS_API extern ECS_DECLARE(EcsMegaBits); /**< MegaBits unit. */ +FLECS_API extern ECS_DECLARE(EcsGigaBits); /**< GigaBits unit. */ +FLECS_API extern ECS_DECLARE(EcsBytes); /**< Bytes unit. */ +FLECS_API extern ECS_DECLARE(EcsKiloBytes); /**< KiloBytes unit. */ +FLECS_API extern ECS_DECLARE(EcsMegaBytes); /**< MegaBytes unit. */ +FLECS_API extern ECS_DECLARE(EcsGigaBytes); /**< GigaBytes unit. */ +FLECS_API extern ECS_DECLARE(EcsKibiBytes); /**< KibiBytes unit. */ +FLECS_API extern ECS_DECLARE(EcsMebiBytes); /**< MebiBytes unit. */ +FLECS_API extern ECS_DECLARE(EcsGibiBytes); /**< GibiBytes unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_datarate Data Rate + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsDataRate); /**< DataRate quantity. */ +FLECS_API extern ECS_DECLARE(EcsBitsPerSecond); /**< BitsPerSecond unit. */ +FLECS_API extern ECS_DECLARE(EcsKiloBitsPerSecond); /**< KiloBitsPerSecond unit. */ +FLECS_API extern ECS_DECLARE(EcsMegaBitsPerSecond); /**< MegaBitsPerSecond unit. */ +FLECS_API extern ECS_DECLARE(EcsGigaBitsPerSecond); /**< GigaBitsPerSecond unit. */ +FLECS_API extern ECS_DECLARE(EcsBytesPerSecond); /**< BytesPerSecond unit. */ +FLECS_API extern ECS_DECLARE(EcsKiloBytesPerSecond); /**< KiloBytesPerSecond unit. */ +FLECS_API extern ECS_DECLARE(EcsMegaBytesPerSecond); /**< MegaBytesPerSecond unit. */ +FLECS_API extern ECS_DECLARE(EcsGigaBytesPerSecond); /**< GigaBytesPerSecond unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_duration Duration + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsAngle); /**< Angle quantity. */ +FLECS_API extern ECS_DECLARE(EcsRadians); /**< Radians unit. */ +FLECS_API extern ECS_DECLARE(EcsDegrees); /**< Degrees unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_angle Angle + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsFrequency); /**< Frequency quantity. */ +FLECS_API extern ECS_DECLARE(EcsHertz); /**< Hertz unit. */ +FLECS_API extern ECS_DECLARE(EcsKiloHertz); /**< KiloHertz unit. */ +FLECS_API extern ECS_DECLARE(EcsMegaHertz); /**< MegaHertz unit. */ +FLECS_API extern ECS_DECLARE(EcsGigaHertz); /**< GigaHertz unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_uri Uri + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsUri); /**< URI quantity. */ +FLECS_API extern ECS_DECLARE(EcsUriHyperlink); /**< UriHyperlink unit. */ +FLECS_API extern ECS_DECLARE(EcsUriImage); /**< UriImage unit. */ +FLECS_API extern ECS_DECLARE(EcsUriFile); /**< UriFile unit. */ + +/** @} */ + +/** + * @defgroup c_addons_units_color Color + * @ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsColor); /**< Color quantity. */ +FLECS_API extern ECS_DECLARE(EcsColorRgb); /**< ColorRgb unit. */ +FLECS_API extern ECS_DECLARE(EcsColorHsl); /**< ColorHsl unit. */ +FLECS_API extern ECS_DECLARE(EcsColorCss); /**< ColorCss unit. */ + +/** @} */ + + +FLECS_API extern ECS_DECLARE(EcsAcceleration); /**< Acceleration unit. */ +FLECS_API extern ECS_DECLARE(EcsPercentage); /**< Percentage unit. */ +FLECS_API extern ECS_DECLARE(EcsBel); /**< Bel unit. */ +FLECS_API extern ECS_DECLARE(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 +#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 + +FLECS_API +extern ECS_COMPONENT_DECLARE(EcsScript); + +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; +} ecs_script_var_t; + +/** Script variable scope. */ +typedef struct ecs_script_vars_t { + struct ecs_script_vars_t *parent; + 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; + +/** 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; + + +/* Parsing & running scripts */ + +/** Parse script. + * This operation parses a script and returns a script object upon success. To + * run the script, call ecs_script_eval(). + * + * @param world The world. + * @param name Name of the script (typically a file/module name). + * @param code The script code. + * @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); + +/** Evaluate script. + * This operation evaluates (runs) a parsed script. + * + * @param script The script. + * @return Zero if success, non-zero if failed. +*/ +FLECS_API +int ecs_script_eval( + ecs_script_t *script); + +/** 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); + +/** 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); + +/** 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); + + +/* 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); + +/** 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_script_expr_run(). */ +typedef struct ecs_script_expr_run_desc_t { + const char *name; + const char *expr; + ecs_entity_t (*lookup_action)( + const ecs_world_t*, + const char *value, + void *ctx); + void *lookup_ctx; + ecs_script_vars_t *vars; +} ecs_script_expr_run_desc_t; + +/** Parse standalone expression into value. + * This operation parses a flecs expression into the provided pointer. The + * memory pointed to must be large enough to contain a value of the used type. + * + * If no type and pointer are provided for the value argument, the operation + * will discover the type from the expression and allocate storage for the + * value. The allocated value must be freed with ecs_value_free(). + * + * @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 deserializer. + * @return Pointer to the character after the last one read, or NULL if failed. + */ +FLECS_API +const char* ecs_script_expr_run( + ecs_world_t *world, + const char *ptr, + ecs_value_t *value, + const ecs_script_expr_run_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); + + +/* 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); + +/** 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 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 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 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. */ +} 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; + + /** 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 */ + int32_t value; + + /** 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 { + /** 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_flags32_t value; + + /** 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_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 +void ecs_cpp_component_validate( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + const char *symbol, + size_t size, + size_t alignment, + bool implicit_name); + +FLECS_API +ecs_entity_t ecs_cpp_component_register( + ecs_world_t *world, + ecs_entity_t id, + const char *name, + const char *symbol, + ecs_size_t size, + ecs_size_t alignment, + bool implicit_name, + bool *existing_out); + +FLECS_API +ecs_entity_t ecs_cpp_component_register_explicit( + ecs_world_t *world, + ecs_entity_t s_id, + ecs_entity_t id, + const char *name, + const char *type_name, + const char *symbol, + size_t size, + size_t alignment, + bool is_component, + bool *existing_out); + +FLECS_API +void ecs_cpp_enum_init( + ecs_world_t *world, + ecs_entity_t id); + +FLECS_API +ecs_entity_t ecs_cpp_enum_constant_register( + ecs_world_t *world, + ecs_entity_t parent, + ecs_entity_t id, + const char *name, + int value); + +FLECS_API +int32_t ecs_cpp_reset_count_get(void); + +FLECS_API +int32_t ecs_cpp_reset_count_inc(void); + +#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; + +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(Ty* _ptr) { + if (_ptr) { + destruct_obj(_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 + +#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 + +namespace flecs { + +/** Int to enum */ +namespace _ { +template Value> +struct to_constant { +#if defined(__clang__) && __clang_major__ >= 16 + // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 + static constexpr E value = __builtin_bit_cast(E, Value); +#else + static constexpr E value = static_cast(Value); +#endif +}; + +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 + +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 { + flecs::entity_t 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 class Handler> +struct enum_reflection { + template + static constexpr underlying_type_t to_int() { + return static_cast>(Value); + } + + template Value> + static constexpr E from_int() { + return to_constant::value; + } + + template + static constexpr underlying_type_t is_not_0() { + return static_cast>(Value != from_int<0>()); + } + + /** + * @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 underlying_type_t The result of the iteration. + */ + template + static constexpr underlying_type_t each_enum_range(underlying_type_t last_value, Args... args) { + return to_int() - to_int() <= 1 + ? to_int() == to_int() + ? Handler::template handle_constant(last_value, args...) + : Handler::template handle_constant(Handler::template handle_constant(last_value, args...), args...) + : each_enum_range()+to_int()) / 2 + 1>(), High>( + each_enum_range()+to_int()) / 2>()>(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 underlying_type_t The result of the iteration. + */ + template + static constexpr underlying_type_t each_mask_range(underlying_type_t 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 (to_int() & to_int()) || (to_int() <= to_int() && to_int() != 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 underlying_type_t The result of the iteration. + */ + template + static constexpr underlying_type_t each_enum(Args... args) { + return each_mask_range()>(each_enum_range(), Value>(0, args...), args...); + } + + static const underlying_type_t high_bit = static_cast>(1) << (sizeof(underlying_type_t) * 8 - 1); +}; + +/** Enumeration type data */ +template +struct enum_data_impl { +private: + /** + * @brief Handler struct for generating compile-time count of enum constants. + */ + template + struct reflection_count { + template () > = 0> + static constexpr underlying_type_t handle_constant(underlying_type_t last_value) { + return last_value; + } + + template () > = 0> + static constexpr underlying_type_t handle_constant(underlying_type_t last_value) { + return 1 + last_value; + } + }; +public: + flecs::entity_t id; + 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 + underlying_type_t contiguous_until; + // Compile-time generated count of enum constants. + static constexpr unsigned int constants_size = enum_reflection::template each_enum< 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: + /** + * @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. + * + * @tparam Enum The enum type. + */ + template + struct reflection_init { + template () > = 0> + static underlying_type_t handle_constant(underlying_type_t last_value, flecs::world_t*) { + // Search for constant failed. Pass last valid value through. + return last_value; + } + + template () > = 0> + static underlying_type_t handle_constant(underlying_type_t last_value, flecs::world_t *world) { + // Constant is valid, so fill reflection data. + auto v = enum_reflection::template to_int(); + 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; + enum_type::data.constants[enum_type::data.max].id = ecs_cpp_enum_constant_register( + world, enum_type::data.id, 0, name, static_cast(v)); + 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); + data.id = id; + + // Generate reflection data + enum_reflection::template each_enum< 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 { + 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(underlying_type_t value) { + int index = index_by_value(value); + if (index < 0) { + return false; + } + return impl_.constants[index].id != 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(underlying_type_t value) const { + if (!impl_.max) { + return -1; + } + // Check if value is in contiguous lookup section + if (impl_.has_contiguous && value < impl_.contiguous_until && value >= 0) { + return static_cast(value); + } + underlying_type_t 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(underlying_type_t 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::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.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 _ +{ + +inline void ecs_ctor_illegal(void *, int32_t, const ecs_type_info_t *ti) { + ecs_abort(ECS_INVALID_OPERATION, "invalid constructor for %s", ti->name); +} + +inline void ecs_dtor_illegal(void *, int32_t, const ecs_type_info_t *ti) { + ecs_abort(ECS_INVALID_OPERATION, "invalid destructor for %s", ti->name); +} + +inline void ecs_copy_illegal( + void *, const void *, int32_t, const ecs_type_info_t *ti) +{ + ecs_abort(ECS_INVALID_OPERATION, "invalid copy assignment for %s", ti->name); +} + +inline void ecs_move_illegal(void *, void *, int32_t, const ecs_type_info_t *ti) { + ecs_abort(ECS_INVALID_OPERATION, "invalid move assignment for %s", ti->name); +} + +inline void ecs_copy_ctor_illegal( + void *, const void *, int32_t, const ecs_type_info_t *ti) +{ + ecs_abort(ECS_INVALID_OPERATION, "invalid copy construct for %s", ti->name); +} + +inline void ecs_move_ctor_illegal( + void *, void *, int32_t, const ecs_type_info_t *ti) +{ + ecs_abort(ECS_INVALID_OPERATION, "invalid move construct for %s", ti->name); +} + + +// 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() { + return nullptr; +} + +// Not constructible by flecs +template ::value > = 0> +ecs_xtor_t ctor() { + return ecs_ctor_illegal; +} + +// Default constructible +template ::value && + std::is_default_constructible::value > = 0> +ecs_xtor_t ctor() { + return ctor_impl; +} + +// No dtor +template ::value > = 0> +ecs_xtor_t dtor() { + return nullptr; +} + +// Dtor +template ::value && + ! std::is_trivially_destructible::value > = 0> +ecs_xtor_t dtor() { + return dtor_impl; +} + +// Assert when the type cannot be destructed +template ::value > = 0> +ecs_xtor_t dtor() { + flecs_static_assert(always_false::value, + "component type must be destructible"); + return ecs_dtor_illegal; +} + +// Trivially copyable +template ::value > = 0> +ecs_copy_t copy() { + return nullptr; +} + +// Not copyable +template ::value && + ! std::is_copy_assignable::value > = 0> +ecs_copy_t copy() { + return ecs_copy_illegal; +} + +// Copy assignment +template ::value && + ! std::is_trivially_copyable::value > = 0> +ecs_copy_t copy() { + return copy_impl; +} + +// Trivially move assignable +template ::value > = 0> +ecs_move_t move() { + return nullptr; +} + +// Component types must be move assignable +template ::value > = 0> +ecs_move_t move() { + return ecs_move_illegal; +} + +// Move assignment +template ::value && + ! std::is_trivially_move_assignable::value > = 0> +ecs_move_t move() { + return move_impl; +} + +// Trivially copy constructible +template ::value > = 0> +ecs_copy_t copy_ctor() { + return nullptr; +} + +// No copy ctor +template ::value > = 0> +ecs_copy_t copy_ctor() { + return ecs_copy_ctor_illegal; +} + +// Copy ctor +template ::value && + ! std::is_trivially_copy_constructible::value > = 0> +ecs_copy_t copy_ctor() { + return copy_ctor_impl; +} + +// Trivially move constructible +template ::value > = 0> +ecs_move_t move_ctor() { + return nullptr; +} + +// Component types must be move constructible +template ::value > = 0> +ecs_move_t move_ctor() { + return ecs_move_ctor_illegal; +} + +// Move ctor +template ::value && + ! std::is_trivially_move_constructible::value > = 0> +ecs_move_t move_ctor() { + return move_ctor_impl; +} + +// Trivial merge (move assign + dtor) +template ::value && + std::is_trivially_destructible::value > = 0> +ecs_move_t ctor_move_dtor() { + return nullptr; +} + +// Component types must be move constructible and destructible +template ::value || + ! std::is_destructible::value > = 0> +ecs_move_t ctor_move_dtor() { + return ecs_move_ctor_illegal; +} + +// Merge ctor + dtor +template ::value && + std::is_trivially_destructible::value) && + std::is_move_constructible::value && + std::is_destructible::value > = 0> +ecs_move_t ctor_move_dtor() { + return ctor_move_dtor_impl; +} + +// Trivial merge (move assign + dtor) +template ::value && + std::is_trivially_destructible::value > = 0> +ecs_move_t move_dtor() { + return nullptr; +} + +// Component types must be move constructible and destructible +template ::value || + ! std::is_destructible::value > = 0> +ecs_move_t move_dtor() { + return ecs_move_ctor_illegal; +} + +// Merge assign + dtor +template ::value && + std::is_trivially_destructible::value) && + std::is_move_assignable::value && + std::is_destructible::value > = 0> +ecs_move_t move_dtor() { + return move_dtor_impl; +} + +} // _ +} // flecs + +/** + * @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); + } + + 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(); + } + + flecs::entity entity() const; + +private: + world_t *world_; + flecs::ref_t ref_; +}; + +/** @} */ + +} + +/** + * @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 { + 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 { + world_ = obj.world_; + obj.world_ = nullptr; + return *this; + } + + ~world() { + if (world_) { + if (!flecs_poly_release(world_)) { + if (ecs_stage_get_id(world_) == -1) { + ecs_stage_free(world_); + } else { + ecs_fini(world_); + } + } + } + } + + /* 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; + + /* 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_, 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_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(int32_t index) const { + return ecs_field_is_self(iter_, index); + } + + /** Returns whether field is set. + * + * @param index The field index. + */ + bool is_set(int32_t index) const { + return ecs_field_is_set(iter_, index); + } + + /** Returns whether field is readonly. + * + * @param index The field index. + */ + bool is_readonly(int32_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(int32_t index) const { + return ecs_field_size(iter_, index); + } + + /** Obtain field source (0 if This). + * + * @param index The field index. + */ + flecs::entity src(int32_t index) const; + + /** Obtain id matched for field. + * + * @param index The field index. + */ + flecs::id id(int32_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(int32_t index) const; + + /** Obtain column index for field. + * + * @param index The field index. + */ + int32_t column_index(int32_t index) const { + return ecs_field_column(iter_, index); + } + + /** Obtain term that triggered an observer + */ + int32_t term_index() const { + return iter_->term_index + 1; // in iter_t, the term index is zero-based, so add 1. + } + + /** 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. + * + * @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(int32_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. + * + * @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(int32_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. + * + * @param index The field index. + */ + flecs::untyped_field field(int32_t index) const { + ecs_assert(!(iter_->flags & EcsIterCppEach), 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. */ + void* field_at(int32_t index, size_t row) const { + return get_unchecked_field(index)[row]; + } + + /** Get reference to field at row. */ + template , + typename std::enable_if::value, void>::type* = nullptr> + const A& field_at(int32_t index, size_t row) const { + return get_field(index)[row]; + } + + /** Get reference to field at row. */ + template , + typename std::enable_if< + std::is_const::value == false, void>::type* = nullptr> + A& field_at(int32_t index, size_t row) const { + ecs_assert(!ecs_field_is_readonly(iter_, index), + ECS_ACCESS_VIOLATION, NULL); + 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_); + } + + /** 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(int32_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); + } + + flecs::untyped_field get_unchecked_field(int32_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::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 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_, ecs_pair(comp_id, 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(_::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 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_, ecs_pair(comp_id, 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(_::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_); +} + +# 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(), 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()); + } + + /** 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()); + } + + /** 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(), 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()); + } + + 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_); + 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_); + 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 { + flecs::emplace(this->world_, this->id_, + ecs_pair(_::type::id(this->world_), second), + FLECS_FWD(args)...); + return to_base(); + } + + template + const Self& emplace_second(flecs::entity_t first, Args&&... args) const { + flecs::emplace(this->world_, this->id_, + ecs_pair(first, _::type::id(this->world_)), + 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 *link) const { + ecs_doc_set_color(world_, id_, link); + 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 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_, ecs_pair(comp_id, 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(_::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 { + this->modified(_::type::id(world_)); + } + + /** 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 { + return ref(world_, id_, + ecs_pair(_::type::id(world_), second)); + } + + template + ref get_ref_second(flecs::entity_t first) const { + return ref(world_, id_, + ecs_pair(first, _::type::id(world_))); + } + + /** 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 term_ptr { + void *ptr; + bool is_ref; +}; + +template +struct term_ptrs { + using array = flecs::array<_::term_ptr, sizeof...(Components)>; + + bool populate(const ecs_iter_t *iter) { + return populate(iter, 0, static_cast< + remove_reference_t< + remove_pointer_t> + *>(nullptr)...); + } + + array terms_; + +private: + /* Populate terms array without checking for references */ + bool populate(const ecs_iter_t*, size_t) { return false; } + + template + bool populate(const ecs_iter_t *iter, size_t index, T, Targs... comps) { + terms_[index].ptr = iter->ptrs[index]; + bool is_ref = iter->sources && iter->sources[index] != 0; + terms_[index].is_ref = is_ref; + is_ref |= populate(iter, index + 1, comps ...); + return is_ref; + } +}; + +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_column { }; + +// Base class +struct each_column_base { + each_column_base(const _::term_ptr& term, size_t row) + : term_(term), row_(row) { } + +protected: + const _::term_ptr& term_; + size_t row_; +}; + +// If type is not a pointer, return a reference to the type (default case) +template +struct each_column::value && + !is_empty>::value && is_actual::value > > + : each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T& get_row() { + return static_cast(this->term_.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_column::value && + !is_empty>::value && !is_actual::value> > + : each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T get_row() { + return static_cast*>(this->term_.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_column>::value && + !is_pointer::value > > + : each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + T get_row() { + return actual_type_t(); + } +}; + + +// If type is a pointer (indicating an optional value) return the type as is +template +struct each_column::value && + !is_empty>::value > > + : each_column_base +{ + each_column(const _::term_ptr& term, size_t row) + : each_column_base(term, row) { } + + actual_type_t get_row() { + if (this->term_.ptr) { + return &static_cast>(this->term_.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_column : public each_column { + each_ref_column(const _::term_ptr& term, size_t row) + : each_column(term, row) { + + if (term.is_ref) { + // If this is a reference, set the row to 0 as a ref always is a + // single value, not an array. This prevents the application from + // having to do an if-check on whether the column is owned. + // + // This check only happens when the current table being iterated + // over caused the query to match a reference. The check is + // performed once per iterated table. + this->row_ = 0; + } + } +}; + +template +struct each_delegate : public delegate { + using Terms = typename term_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 { + term_ptrs terms; + + iter->flags |= EcsIterCppEach; + + if (terms.populate(iter)) { + invoke_unpack< each_ref_column >(iter, func_, 0, terms.terms_); + } else { + invoke_unpack< each_column >(iter, func_, 0, terms.terms_); + } + } + + // 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(static_cast(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); + } + + // Each delegates always use instanced iterators + static bool instanced() { + return true; + } + +private: + // func(flecs::entity, Components...) + template class ColumnType, + typename... Args, + typename Fn = Func, + decltype(std::declval()( + std::declval(), + std::declval > >().get_row()...), 0) = 0> + static void invoke_callback( + ecs_iter_t *iter, const Func& func, size_t i, Args... comps) + { + ecs_assert(iter->count > 0, ECS_INVALID_OPERATION, + "no entities returned, use each() without flecs::entity argument"); + + func(flecs::entity(iter->world, iter->entities[i]), + (ColumnType< remove_reference_t >(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 >(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*, const Func& func, size_t i, Args... comps) + { + func((ColumnType< remove_reference_t >(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 term_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 { + term_ptrs terms; + + if (terms.populate(iter)) { + return invoke_callback< each_ref_column >(iter, func_, 0, terms.terms_); + } else { + return invoke_callback< each_column >(iter, func_, 0, terms.terms_); + } + } + + // Find delegates always use instanced iterators + static bool instanced() { + return true; + } + +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; + + ecs_assert(count > 0, ECS_INVALID_OPERATION, + "no entities returned, use find() without flecs::entity argument"); + + for (size_t i = 0; i < count; i ++) { + if (func(flecs::entity(world, iter->entities[i]), + (ColumnType< remove_reference_t >(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 >(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 >(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); + return ecs_cpp_get_type_name(result, ECS_FUNC_NAME, len, front_len); +} +#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] = {}; + return ecs_cpp_get_symbol_name(result, type_name(), len); +} + +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.dtor = dtor(); + + cl.copy = copy(); + cl.copy_ctor = copy_ctor(); + cl.move = move(); + cl.move_ctor = move_ctor(); + + cl.ctor_move_dtor = ctor_move_dtor(); + cl.move_dtor = move_dtor(); + + ecs_set_hooks_id( world, component, &cl); + + if (cl.move == ecs_move_illegal || cl.move_ctor == ecs_move_ctor_illegal) { + ecs_add_id(world, component, flecs::Sparse); + } +} + +// Class that manages component ids across worlds & binaries. +// The type class stores the component id for a C++ type in a static global +// variable that is shared between worlds. Whenever a component is used this +// class will check if it already has been registered (has the global id been +// set), and if not, register the component with the world. +// +// If the id has been set, the class will ensure it is known by the world. If it +// is not known the component has been registered by another world and will be +// registered with the world using the same id. If the id does exist, the class +// will register it as a component, and verify whether the input is consistent. +template +struct type_impl { + static_assert(is_pointer::value == false, + "pointer types are not allowed for components"); + + // Initialize component identifier + static void init( + entity_t entity, + bool allow_tag = true) + { + if (s_reset_count != ecs_cpp_reset_count_get()) { + reset(); + } + + // If an identifier was already set, check for consistency + if (s_id) { + ecs_assert(s_id == entity, ECS_INCONSISTENT_COMPONENT_ID, + type_name()); + ecs_assert(allow_tag == s_allow_tag, ECS_INVALID_PARAMETER, NULL); + + // Component was already registered and data is consistent with new + // identifier, so nothing else to be done. + return; + } + + // Component wasn't registered yet, set the values. Register component + // name as the fully qualified flecs path. + s_id = entity; + 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; + } + + s_reset_count = ecs_cpp_reset_count_get(); + } + + // Obtain a component identifier for explicit component registration. + static entity_t id_explicit(world_t *world = nullptr, + const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0, + bool is_component = true, bool *existing = nullptr) + { + if (!s_id) { + // If no world was provided the component cannot be registered + ecs_assert(world != nullptr, ECS_COMPONENT_NOT_REGISTERED, name); + } else { + ecs_assert(!id || s_id == id, ECS_INCONSISTENT_COMPONENT_ID, NULL); + } + + // If no id has been registered yet for the component (indicating the + // component has not yet been registered, or the component is used + // across more than one binary), or if the id does not exists in the + // world (indicating a multi-world application), register it. + if (!s_id || (world && !ecs_exists(world, s_id))) { + init(s_id ? s_id : id, allow_tag); + + ecs_assert(!id || s_id == id, ECS_INTERNAL_ERROR, NULL); + + const char *symbol = nullptr; + if (id) { + symbol = ecs_get_symbol(world, id); + } + if (!symbol) { + symbol = symbol_name(); + } + + entity_t entity = ecs_cpp_component_register_explicit( + world, s_id, id, name, type_name(), symbol, + s_size, s_alignment, is_component, existing); + + s_id = entity; + + // If component is enum type, register constants + #if FLECS_CPP_ENUM_REFLECTION_SUPPORT + _::init_enum(world, entity); + #endif + } + + // By now the identifier must be valid and known with the world. + ecs_assert(s_id != 0 && ecs_exists(world, s_id), + ECS_INTERNAL_ERROR, NULL); + + return s_id; + } + + // Obtain a component identifier for implicit component registration. This + // is almost the same as id_explicit, except that this operation + // automatically registers lifecycle callbacks. + // Additionally, implicit registration temporarily resets the scope & with + // state of the world, so that the component is not implicitly created with + // the scope/with of the code it happens to be first used by. + static id_t id(world_t *world = nullptr, const char *name = nullptr, + bool allow_tag = true) + { + // If no id has been registered yet, do it now. +#ifndef FLECS_CPP_NO_AUTO_REGISTRATION + if (!registered(world)) { + ecs_entity_t prev_scope = 0; + ecs_id_t prev_with = 0; + + if (world) { + prev_scope = ecs_set_scope(world, 0); + prev_with = ecs_set_with(world, 0); + } + + // This will register a component id, but will not register + // lifecycle callbacks. + bool existing; + id_explicit(world, name, allow_tag, 0, true, &existing); + + // 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, s_id); + } + + if (prev_with) { + ecs_set_with(world, prev_with); + } + if (prev_scope) { + ecs_set_scope(world, prev_scope); + } + } +#else + (void)world; + (void)name; + (void)allow_tag; + + ecs_assert(registered(world), ECS_INVALID_OPERATION, + "component '%s' was not registered before use", + type_name()); +#endif + + // By now we should have a valid identifier + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + + return s_id; + } + + // Return the size of a component. + static size_t size() { + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + return s_size; + } + + // Return the alignment of a component. + static size_t alignment() { + ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); + return s_alignment; + } + + // Was the component already registered. + static bool registered(flecs::world_t *world) { + if (s_reset_count != ecs_cpp_reset_count_get()) { + reset(); + } + if (s_id == 0) { + return false; + } + if (world && !ecs_exists(world, s_id)) { + 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_id = 0; + s_size = 0; + s_alignment = 0; + s_allow_tag = true; + } + + static entity_t s_id; + static size_t s_size; + static size_t s_alignment; + static bool s_allow_tag; + static int32_t s_reset_count; +}; + +// Global templated variables that hold component identifier and other info +template entity_t type_impl::s_id; +template size_t type_impl::s_size; +template size_t type_impl::s_alignment; +template bool type_impl::s_allow_tag( true ); +template int32_t type_impl::s_reset_count; + +// 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 + * + * @{ + */ + +/** Add member with unit. */ +untyped_component& member(flecs::entity_t type_id, flecs::entity_t unit, const char *name, int32_t count = 0, size_t offset = 0) { + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.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); + e.set(m); + + return *this; +} + +/** Add member. */ +untyped_component& member(flecs::entity_t type_id, const char* name, int32_t count = 0, size_t offset = 0) { + return member(type_id, 0, name, count, offset); +} + +/** Add member. */ +template +untyped_component& member(const char *name, int32_t count = 0, size_t offset = 0) { + 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, size_t offset = 0) { + 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, size_t offset = 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, 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 (_::type::registered(world)) { + /* Obtain component id. Because the component is already registered, + * this operation does nothing besides returning the existing id */ + id = _::type::id_explicit(world, name, allow_tag, id); + + ecs_cpp_component_validate(world, id, n, _::symbol_name(), + _::type::size(), + _::type::alignment(), + implicit_name); + } else { + /* If component is registered from an existing scope, ignore the + * namespace in the name of the component. */ + if (implicit_name && (ecs_get_scope(world) != 0)) { + /* 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; + } + } + + /* Find or register component */ + bool existing; + id = ecs_cpp_component_register(world, id, n, _::symbol_name(), + ECS_SIZEOF(T), ECS_ALIGNOF(T), implicit_name, &existing); + + /* Initialize static component data */ + id = _::type::id_explicit(world, name, allow_tag, id); + + /* Initialize lifecycle actions (ctor, dtor, copy, move) */ + if (_::type::size() && !existing) { + _::register_lifecycle_actions(world, id); + } + } + + world_ = world; + id_ = id; + } + + /** 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 = reinterpret_cast( + _::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 = reinterpret_cast( + _::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 = reinterpret_cast( + _::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) { + int32_t v = static_cast(value); + untyped_component::constant(name, v); + 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 = reinterpret_cast( + _::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 {}; + } + } +}; + +/** Get id currently assigned to component. If no world has registered the + * component yet, this operation will return 0. */ +template +flecs::entity_t type_id() { + if (_::type::s_reset_count == ecs_cpp_reset_count_get()) { + return _::type::s_id; + } else { + return 0; + } +} + +/** Reset static component ids. + * When components are registered their component ids are stored in a static + * type specific variable. This stored id is passed into component registration + * functions to ensure consistent ids across worlds. + * + * In some cases this can be undesirable, like when a process repeatedly creates + * worlds with different components. A typical example where this can happen is + * when running multiple tests in a single process, where each test registers + * its own set of components. + * + * This operation can be used to prevent reusing of component ids and force + * generating a new ids upon registration. + * + * Note that this operation should *never* be called while there are still + * alive worlds in a process. Doing so results in undefined behavior. + * + * Also note that this operation does not actually change the static component + * variables. It only ensures that the next time a component id is requested, a + * new id will be generated. + * + * @ingroup cpp_components + */ +inline void reset() { + ecs_cpp_reset_count_inc(); +} + +} + +/** @} */ + +/** + * @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]); + } + + flecs::id_t* begin() const { + return type_->array; + } + + flecs::id_t* end() const { + return &type_->array[type_->count]; + } + + /** Implicit conversion to type_t */ + operator const type_t*() const { + return type_; + } +private: + world_t *world_; + const type_t *type_; +}; + +/** #} */ + +} + +/** + * @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& ...) + * + * Each iterators are automatically instanced. + */ + template + void each(Func&& func) const { + ecs_iter_t it = this->get_iter(nullptr); + ECS_BIT_SET(it.flags, EcsIterIsInstanced); + ecs_iter_next_action_t next = this->next_each_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_BIT_SET(it.flags, EcsIterIsInstanced); + ecs_iter_next_action_t next = this->next_each_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) { + return this->iter().set_var(var_id, value); + } + + iter_iterable set_var(const char *name, flecs::entity_t value) { + return this->iter().set_var(name, value); + } + + iter_iterable set_var(const char *name, flecs::table_t *value) { + return this->iter().set_var(name, value); + } + + iter_iterable set_var(const char *name, ecs_table_range_t value) { + return this->iter().set_var(name, value); + } + + iter_iterable set_var(const char *name, flecs::table_range value) { + 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) { + return this->iter().set_group(group_id); + } + + // Limit results to tables with specified group id (grouped queries only) + template + iter_iterable set_group() { + 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; + virtual ecs_iter_next_action_t next_each_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_; + } + + ecs_iter_next_action_t next_each_action() const override { + return next_each_; + } + +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; + } + + ecs_iter_next_action_t next_each_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; + } + + ecs_iter_next_action_t next_each_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) { + // Get constant value from constant entity + const T* v = static_cast(ecs_get_id(world_, c, r)); + ecs_assert(v != NULL, ECS_INTERNAL_ERROR, + "missing enum constant value"); + return v; + } else { + // If there is no matching pair for (r, *), try just r + return static_cast(ecs_get_id(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::id_explicit(world_, name, true, 0, false) ); +} + +template +inline flecs::entity world::prefab(Args &&... args) const { + flecs::entity result = flecs::entity(world_, FLECS_FWD(args)...); + result.add(flecs::Prefab); + return result; +} + +template +inline flecs::entity world::prefab(const char *name) const { + flecs::entity result = 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& instanced() { + desc_->flags |= EcsQueryIsInstanced; + return *this; + } + + 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; + } + + 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; + +protected: + query_t *query_ = nullptr; +}; + +template +struct query : query_base, iterable { +private: + using Terms = typename _::term_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; + } + + ecs_iter_next_action_t next_each_action() const override { + return flecs_query_next_instanced; + } +}; + +// 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()); + + 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) + , instanced_(false) + { + 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 = reinterpret_cast< + ecs_ctx_free_t>(_::free_obj); + return T(world_, &desc_, false); + } + + 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 = reinterpret_cast< + ecs_ctx_free_t>(_::free_obj); + return each(FLECS_FWD(each_func)); + } + + template + T each(Func&& func) { + using Delegate = typename _::each_delegate< + typename std::decay::type, Components...>; + instanced_ = true; + + auto ctx = FLECS_NEW(Delegate)(FLECS_FWD(func)); + desc_.callback = Delegate::run; + desc_.callback_ctx = ctx; + desc_.callback_ctx_free = reinterpret_cast< + ecs_ctx_free_t>(_::free_obj); + return T(world_, &desc_, true); + } + +protected: + flecs::world_t* world_v() override { return world_; } + TDesc desc_; + flecs::world_t *world_; + bool instanced_; +}; + +#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 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, bool instanced) + { + if (!(desc->query.flags & EcsQueryIsInstanced)) { + ECS_BIT_COND(desc->query.flags, EcsQueryIsInstanced, instanced); + } + + 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, + reinterpret_cast(_::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, + reinterpret_cast(_::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, + reinterpret_cast(_::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 { + const E* ptr = this->get(); + ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, "entity is not a constant"); + return ptr[0]; +} + +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); + ecs_add_id(world, c_, EcsModule); + + ecs_set_scope(world, c_); + world.emplace(world); + ecs_set_scope(world, scope); + + // 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(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::id_t result = _::type::id(world_, nullptr, false); + if (name) { + ecs_add_path_w_sep(world_, result, 0, name, "::", "::"); + } + ecs_set_scope(world_, result); + return flecs::entity(world_, 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, bool instanced) + { + if (!(desc->query.flags & EcsQueryIsInstanced)) { + ECS_BIT_COND(desc->query.flags, EcsQueryIsInstanced, instanced); + } + + 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 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 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(flecs::Iptr, true); + ecs_assert(flecs::type_id() == flecs::Iptr, + ECS_INTERNAL_ERROR, NULL); + // 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(flecs::Uptr, true); + ecs_assert(flecs::type_id() == flecs::Uptr, + ECS_INTERNAL_ERROR, NULL); + // 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(int32_t index) const { + ecs_assert(!(iter_->flags & EcsIterCppEach), ECS_INVALID_OPERATION, + "cannot .field from .each, use .field_at(%d, row) instead", + _::type_name(), index); + return get_field(index); +} + +template ::value == false, void>::type*> +inline flecs::field iter::field(int32_t index) const { + ecs_assert(!(iter_->flags & EcsIterCppEach), 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)); +} + +} // 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_, impl_.id); +} + +template +inline flecs::entity enum_data::entity(underlying_type_t value) const { + int index = index_by_value(value); + if (index >= 0) { + return flecs::entity(world_, impl_.constants[index].id); + } +#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/addons/glecs/cpp/include/godot-cpp b/addons/glecs/cpp/include/godot-cpp new file mode 160000 index 0000000..fbbf9ec --- /dev/null +++ b/addons/glecs/cpp/include/godot-cpp @@ -0,0 +1 @@ +Subproject commit fbbf9ec4efd8f1055d00edb8d926eef8ba4c2cce diff --git a/addons/glecs/cpp/src/component.cpp b/addons/glecs/cpp/src/component.cpp new file mode 100644 index 0000000..12baf44 --- /dev/null +++ b/addons/glecs/cpp/src/component.cpp @@ -0,0 +1,335 @@ + +#include "component.h" +#include "component_builder.h" +#include "entity.h" +#include "godot_cpp/classes/wrapped.hpp" +#include "godot_cpp/variant/array.hpp" +#include "godot_cpp/variant/dictionary.hpp" +#include "godot_cpp/variant/packed_int64_array.hpp" +#include "godot_cpp/variant/variant.hpp" +#include "registerable_entity.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include + +using namespace godot; + +GFComponent::GFComponent() { +} +GFComponent::~GFComponent() { +} + +Ref GFComponent::spawn(GFWorld* world_) { + ERR(NULL, + "Could not instantiate ", get_class_static(), "\n", + "Use ", get_class_static(), ".from or ", + get_class_static(), ".from_id instead." + ); +} +Ref GFComponent::from(Variant comp, Variant entity, GFWorld* world) { + return from_id(world->coerce_id(comp), world->coerce_id(entity), world); +} +Ref GFComponent::from_id(ecs_entity_t comp, ecs_entity_t entity, GFWorld* world) { + if (!ecs_has_id(world->raw(), comp, ecs_id(EcsComponent))) { + ERR(nullptr, + "Could not instantiate ", get_class_static(), "\n", + "ID is not a component" + ); + } + Ref component = from_id_template(comp, world); + component->set_source_id(entity); + return component; +} + +void GFComponent::_register_internal() { + // Build component + Ref b = get_world()->component_builder(); + if (GDVIRTUAL_IS_OVERRIDDEN(_build)) { + b->set_entity(get_id()); + GDVIRTUAL_CALL(_build, b); + if (!b->is_built()) { + b->build(); + } + } + + // Call super method + GFRegisterableEntity::_register_internal(); +} + +void GFComponent::setm(String member, Variant value) { + ecs_world_t* raw = get_world()->raw(); + + // Get member data + const EcsMember* member_data = get_member_data(member); + if (member_data == nullptr) { + // Member data is null. This should never happen. + ERR(/**/, + "Member metadata is null" + ); + } + void* member_ptr = get_member_ptr_mut_at(member_data->offset); + if (member_ptr == nullptr) { + ERR(/**/, + "Member pointer is null" + ); + } + + // Return member + Utils::set_type_from_variant(value, member_data->type, raw, member_ptr); + ecs_modified_id(get_world()->raw(), get_source_id(), get_id()); +} + +Variant GFComponent::getm(String member) { + ecs_world_t* raw = get_world()->raw(); + + // Get member data + const EcsMember* member_data = get_member_data(member); + if (member_data == nullptr) { + // Member data is null. This should never happen. + ERR(nullptr, + "Member metadata is null" + ); + } + void* member_value_ptr = get_member_ptr_mut_at(member_data->offset); + if (member_value_ptr == nullptr) { + ERR(nullptr, + "Member value is null" + ); + } + + return member_value_as_type(member_value_ptr, member_data->type); +} + +void* GFComponent::get_member_ptr_mut_at(int offset) { + ecs_world_t* raw = get_world()->raw(); + int8_t* bytes = static_cast( + ecs_get_mut_id(raw, get_source_id(), get_id()) + ); + return static_cast(&bytes[offset]); +} + +const EcsMember* GFComponent::get_member_data(String member) { + ecs_world_t* raw = get_world()->raw(); + const char* c_str = member.utf8().get_data(); + + // Get member ID + ecs_entity_t member_id = ecs_lookup_child(raw, get_id(), c_str); + + if (member_id == 0) { + ERR(nullptr, + "No member named \"", + member, + "\" found in component \"", + ecs_get_name(raw, get_id()), + "\"" + ); + } + + // Get member data + const EcsMember* member_data = ecs_get(raw, member_id, EcsMember); + + return member_data; +} + +Variant GFComponent::member_value_as_primitive( + void* ptr, + ecs_primitive_kind_t primitive +) { + switch (primitive) { + case ecs_primitive_kind_t::EcsBool: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsChar: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsByte: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsU8: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsU16: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsU32: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsU64: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsI8: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsI16: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsI32: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsI64: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsF32: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsF64: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsUPtr: ERR(nullptr, "Can't get primitive\nCan't handle uptr"); + case ecs_primitive_kind_t::EcsIPtr: ERR(nullptr, "Can't get primitive\nCan't handle iptr"); + case ecs_primitive_kind_t::EcsString: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsEntity: return *static_cast(ptr); + case ecs_primitive_kind_t::EcsId: return *static_cast(ptr); + default: ERR(nullptr, "Can't get primitive\nUnknown primitive type"); + } +} + +Variant GFComponent::member_value_as_type( + void* ptr, + ecs_entity_t type +) { + ecs_world_t* raw = get_world()->raw(); + Variant::Type vari_type = get_world()->id_to_variant_type(type); + + switch (vari_type) { + case(Variant::Type::NIL): { + // Member is not a Godot type. Try to get from Flecs primitive + if (ecs_has_id(raw, type, ecs_id(EcsPrimitive))) { + return member_value_as_primitive( + ptr, + ecs_get(raw, type, EcsPrimitive)->kind + ); + } + + ERR(nullptr, + "Can't convert type ", ecs_get_name(raw, type), " to Variant" + ); + } + case(Variant::Type::BOOL): return Variant( *static_cast(ptr) ); + case(Variant::Type::INT): return Variant( *static_cast(ptr) ); + case(Variant::Type::FLOAT): return Variant( *static_cast(ptr) ); + case(Variant::Type::STRING): return Variant( *static_cast(ptr) ); + case(Variant::Type::VECTOR2): return Variant( *static_cast(ptr) ); + case(Variant::Type::VECTOR2I): return Variant( *static_cast(ptr) ); + case(Variant::Type::RECT2): return Variant( *static_cast(ptr) ); + case(Variant::Type::RECT2I): return Variant( *static_cast(ptr) ); + case(Variant::Type::VECTOR3): return Variant( *static_cast(ptr) ); + case(Variant::Type::VECTOR3I): return Variant( *static_cast(ptr) ); + case(Variant::Type::TRANSFORM2D): return Variant( *static_cast(ptr) ); + case(Variant::Type::VECTOR4): return Variant( *static_cast(ptr) ); + case(Variant::Type::VECTOR4I): return Variant( *static_cast(ptr) ); + case(Variant::Type::PLANE): return Variant( *static_cast(ptr) ); + case(Variant::Type::QUATERNION): return Variant( *static_cast(ptr) ); + case(Variant::Type::AABB): return Variant( *static_cast(ptr) ); + case(Variant::Type::BASIS): return Variant( *static_cast(ptr) ); + case(Variant::Type::TRANSFORM3D): return Variant( *static_cast(ptr) ); + case(Variant::Type::PROJECTION): return Variant( *static_cast(ptr) ); + case(Variant::Type::COLOR): return Variant( *static_cast(ptr) ); + case(Variant::Type::STRING_NAME): return Variant( *static_cast(ptr) ); + case(Variant::Type::NODE_PATH): return Variant( *static_cast(ptr) ); + case(Variant::Type::RID): return Variant( *static_cast(ptr) ); + case(Variant::Type::OBJECT): return Variant( *static_cast(ptr) ); + case(Variant::Type::CALLABLE): return Variant( *static_cast(ptr) ); + case(Variant::Type::SIGNAL): return Variant( *static_cast(ptr) ); + case(Variant::Type::DICTIONARY): return *static_cast(ptr); + case(Variant::Type::ARRAY): return *static_cast(ptr); + case(Variant::Type::PACKED_BYTE_ARRAY): return Variant( *static_cast(ptr) ); + case(Variant::Type::PACKED_INT32_ARRAY): return Variant( *static_cast(ptr) ); + case(Variant::Type::PACKED_INT64_ARRAY): return Variant( *static_cast(ptr) ); + case(Variant::Type::PACKED_FLOAT32_ARRAY): return Variant( *static_cast(ptr) ); + case(Variant::Type::PACKED_FLOAT64_ARRAY): return Variant( *static_cast(ptr) ); + case(Variant::Type::PACKED_STRING_ARRAY): return Variant( *static_cast(ptr) ); + case(Variant::Type::PACKED_VECTOR2_ARRAY): return Variant( *static_cast(ptr) ); + case(Variant::Type::PACKED_VECTOR3_ARRAY): return Variant( *static_cast(ptr) ); + case(Variant::Type::PACKED_COLOR_ARRAY): return Variant( *static_cast(ptr) ); + case(Variant::Type::PACKED_VECTOR4_ARRAY): return Variant( *static_cast(ptr) ); + case(Variant::Type::VARIANT_MAX): throw "Can't get type VARIANT_MAX"; + } + + throw "Unreachable"; +} + +Ref GFComponent::get_source_entity() { + return GFEntity::from(get_source_id(), get_world()); +} + +ecs_entity_t GFComponent::get_source_id() { + return source_entity_id; +} + +int GFComponent::get_data_size() { + return ecs_get(get_world()->raw(), get_id(), EcsComponent)->size; +} +int GFComponent::get_data_alignment() { + return ecs_get(get_world()->raw(), get_id(), EcsComponent)->alignment; +} + +bool GFComponent::is_alive() { + GFEntity* entity = this; + return entity->is_alive() + && ecs_has_id(get_world()->raw(), get_source_id(), get_id()); +} + +void GFComponent::build_data_from_variant( + Variant vari, + void* output +) { + ecs_world_t* raw = get_world()->raw(); + + const EcsStruct* struct_data = ecs_get(raw, get_id(), EcsStruct); + if (struct_data == nullptr) { + ERR(/**/, + "Could not build data from Variant\n", + "Component is not a struct." + ); + } + + switch (vari.get_type()) { + case Variant::DICTIONARY: { + Dictionary dict = vari; + Array keys = dict.keys(); + for (int i=0; i != keys.size(); i++) { + String member_name = keys[i]; + Variant value = dict[member_name]; + const EcsMember* member_data = get_member_data(member_name); + if (member_data == nullptr) { + // No member with that name exists, skip + continue; + } + void* member_ptr = static_cast( + static_cast(output) + member_data->offset + ); + // Set member from dictionary + Utils::set_type_from_variant(value, member_data->type, raw, member_ptr); + } + break; + } + case Variant::ARRAY: { + Array arr = vari; + for (int i=0; i != arr.size() && i != ecs_vec_size(&struct_data->members); i++) { + // Iterate the combined sizes of the passed array and the members vector + Variant value = arr[i]; + + ecs_member_t* member_data = ecs_vec_get_t(&struct_data->members, ecs_member_t, i); + void* member_ptr = static_cast( + static_cast(output) + member_data->offset + ); + // Set member from array + Utils::set_type_from_variant(value, member_data->type, raw, member_ptr); + } + break; + } + default: ERR(/**/, + "Could not build data from Variant\n", + "Variant type ", Variant::get_type_name(vari.get_type()), + " can't be built into component data." + ); + } +} + +void GFComponent::set_source_id(ecs_entity_t id) { + source_entity_id = id; +} + +Ref GFComponent::new_internal() { + return Ref(memnew(GFComponent)); +} + +void GFComponent::_bind_methods() { + GDVIRTUAL_BIND(_build, "b"); + godot::ClassDB::bind_method(D_METHOD("_register_internal"), &GFComponent::_register_internal); + + godot::ClassDB::bind_static_method(GFComponent::get_class_static(), D_METHOD("spawn", "world"), &GFComponent::spawn, nullptr); + godot::ClassDB::bind_static_method(GFComponent::get_class_static(), D_METHOD("from", "component", "world"), &GFComponent::from, nullptr); + godot::ClassDB::bind_static_method(GFComponent::get_class_static(), D_METHOD("from_id", "id", "world"), &GFComponent::from_id, nullptr); + + godot::ClassDB::bind_method(D_METHOD("getm", "member"), &GFComponent::getm); + godot::ClassDB::bind_method(D_METHOD("setm", "member", "value"), &GFComponent::setm); + + godot::ClassDB::bind_method(D_METHOD("get_source_entity"), &GFComponent::get_source_entity); + godot::ClassDB::bind_method(D_METHOD("get_source_id"), &GFComponent::get_source_id); + godot::ClassDB::bind_method(D_METHOD("get_data_size"), &GFComponent::get_data_size); + godot::ClassDB::bind_method(D_METHOD("get_data_alignment"), &GFComponent::get_data_alignment); + godot::ClassDB::bind_method(D_METHOD("is_alive"), &GFComponent::is_alive); + + godot::ClassDB::bind_static_method(get_class_static(), D_METHOD("_new_internal"), &GFComponent::new_internal); +} diff --git a/addons/glecs/cpp/src/component.h b/addons/glecs/cpp/src/component.h new file mode 100644 index 0000000..893f60a --- /dev/null +++ b/addons/glecs/cpp/src/component.h @@ -0,0 +1,75 @@ + +#ifndef COMPONENT_H +#define COMPONENT_H + +#include "component_builder.h" +#include "entity.h" +#include "godot_cpp/core/gdvirtual.gen.inc" +#include "registerable_entity.h" + +#include +#include +#include + +namespace godot { + + class GFComponent : public GFRegisterableEntity { + GDCLASS(GFComponent, GFRegisterableEntity) + + public: + GFComponent(); + GFComponent(ecs_entity_t entity, ecs_entity_t component, GFWorld* world): + source_entity_id(entity), + GFRegisterableEntity(component, world) {} + ~GFComponent(); + + // -------------------------------------- + // --- Exposed + // -------------------------------------- + + GDVIRTUAL1(_build, Ref) + + static Ref spawn(GFWorld*); + static Ref from(Variant c, Variant e, GFWorld*); + static Ref from_id(ecs_entity_t c, ecs_entity_t e, GFWorld*); + + Variant getm(String); + void setm(String, Variant); + + Ref get_source_entity(); + ecs_entity_t get_source_id(); + int get_data_size(); + int get_data_alignment(); + + bool is_alive(); + + // -------------------------------------- + // --- Unexposed + // -------------------------------------- + + void _register_internal(); + + void build_data_from_variant(Variant, void* output); + void set_source_id(ecs_entity_t id); + + static Ref new_internal(); + + protected: + static void _bind_methods(); + + void* get_member_ptr_mut_at(int offset); + const EcsMember* get_member_data(String); + + Variant member_value_as_primitive(void*, ecs_primitive_kind_t); + Variant member_value_as_type(void*, ecs_entity_t); + void set_member_value_as_primitive(void*, Variant, ecs_primitive_kind_t); + void set_member_value_as_type(void*, Variant, ecs_entity_t); + + + private: + ecs_entity_t source_entity_id {0}; + }; + +} + +#endif diff --git a/addons/glecs/cpp/src/component_builder.cpp b/addons/glecs/cpp/src/component_builder.cpp new file mode 100644 index 0000000..6b873a5 --- /dev/null +++ b/addons/glecs/cpp/src/component_builder.cpp @@ -0,0 +1,203 @@ + + +#include "component_builder.h" +#include "world.h" +#include "utils.h" + +#include +#include + +using namespace godot; + +GFComponentBuilder::GFComponentBuilder() { + component_desc = {0}; + struct_desc = {0}; + member_names = Array(); + world = {0}; + built = {0}; +} +GFComponentBuilder::~GFComponentBuilder() { +} + +Ref GFComponentBuilder::add_member( + String member, + Variant::Type type +) { + const char* ERR_ADD_COMPONENT = "Failed to add member to component builder\n"; + + if (get_member_count() == ECS_MEMBER_DESC_CACHE_SIZE) { + ERR(Ref(this), + ERR_ADD_COMPONENT, + "Max member count reached" + ); + } + + EntityResult ecs_type_result = GFWorld::variant_type_to_id(type); + if (!ecs_type_result.is_ok()) { + ERR(Ref(this), + ERR_ADD_COMPONENT, + ecs_type_result.unwrap_err() + ); + } + ecs_entity_t ecs_type = ecs_type_result.unwrap(); + + struct_desc.members[get_member_count()] = { + .type = ecs_type + }; + member_names.append(member); + + return Ref(this); +} + +int GFComponentBuilder::get_member_count() { + return member_names.size(); +} + +GFWorld* GFComponentBuilder::get_world() { + return world; +} + +bool GFComponentBuilder::is_built() { + return built; +} + +Ref GFComponentBuilder::set_entity(Variant entity) { + component_desc.entity = get_world()->coerce_id(entity); + return Ref(this); +} + +Ref GFComponentBuilder::set_name( + String name_ +) { + name = name_; + return Ref(this); +} + +void GFComponentBuilder::build() { + const char* FAILED_TO_BUILD = "Failed to build component\n"; + if (built) { + ERR(/**/, + FAILED_TO_BUILD, + "Component builder was already built" + ); + } + built = true; + + // Set names to temporary pointers + CharString name_utf8 = name.utf8(); + CharString member_names_utf8[ECS_MEMBER_DESC_CACHE_SIZE] = {0}; + component_desc.type.name = name_utf8.ptr(); + for (int i=0; i != get_member_count(); i++) { + member_names_utf8[i] = String(member_names[i]).utf8(); + struct_desc.members[i].name = member_names_utf8[i].ptr(); + } + + ecs_world_t* raw = world->raw(); + + // Create component entity + ecs_entity_t component_id = 0; + if (component_desc.entity != 0) { + component_id = component_desc.entity; + } else { + component_id = ecs_new(raw); + } + component_desc.entity = component_id; + struct_desc.entity = component_id; + + ecs_struct_init(raw, &struct_desc); + + assert(ecs_has_id(raw, component_id, ecs_id(Component))); + + ecs_type_hooks_t hooks = { + .ctor = GFComponentBuilder::ctor, + .dtor = GFComponentBuilder::dtor, + .copy = GFComponentBuilder::copy, + .move = GFComponentBuilder::move, + .binding_ctx = new HooksBindingContext(world), + .binding_ctx_free = [](void* ptr) { + HooksBindingContext* ctx = static_cast(ptr); + delete ctx; + } + }; ecs_set_hooks_id(raw, component_id, &hooks); + + ecs_add_path(raw, component_id, 0, component_desc.type.name); +} + +void GFComponentBuilder::set_world(GFWorld* world_) { + world = world_; +} + +// ********************************************** +// *** PROTECTED *** +// ********************************************** + +void GFComponentBuilder::_bind_methods() { + godot::ClassDB::bind_method(D_METHOD("add_member", "member", "type"), &GFComponentBuilder::add_member); + godot::ClassDB::bind_method(D_METHOD("is_built"), &GFComponentBuilder::is_built); + godot::ClassDB::bind_method(D_METHOD("set_name", "name"), &GFComponentBuilder::set_name); + godot::ClassDB::bind_method(D_METHOD("build"), &GFComponentBuilder::build); + +} + +// ********************************************** +// *** PRIVATE *** +// ********************************************** + +void GFComponentBuilder::ctor(void* ptr, int32_t count, const ecs_type_info_t* type_info) { + uint8_t* list = static_cast(ptr); + HooksBindingContext* ctx = static_cast(type_info->hooks.binding_ctx); + + for (int i=0; i != count; i++) { + uint8_t* item = &list[i*type_info->size]; + ctx->world->init_component_ptr(static_cast(item), type_info->component, Variant()); + } +} +void GFComponentBuilder::dtor(void* ptr, int32_t count, const ecs_type_info_t* type_info) { + uint8_t* list = static_cast(ptr); + HooksBindingContext* ctx = static_cast(type_info->hooks.binding_ctx); + + for (int i=0; i != count; i++) { + uint8_t* item = &list[i*type_info->size]; + ctx->world->deinit_component_ptr(static_cast(item), type_info->component); + } +} +void GFComponentBuilder::copy( + void* dst_ptr, + const void* src_ptr, + int32_t count, + const ecs_type_info_t* type_info +) { + const uint8_t* src_list = static_cast(src_ptr); + uint8_t* dst_list = static_cast(dst_ptr); + HooksBindingContext* ctx = static_cast(type_info->hooks.binding_ctx); + + for (int i=0; i != count; i++) { + const uint8_t* src = &src_list[i*type_info->size]; + uint8_t* dst = &dst_list[i*type_info->size]; + + ctx->world->copy_component_ptr(static_cast(src), static_cast(dst), type_info->component); + } +} +void GFComponentBuilder::move( + void* dst_ptr, + void* src_ptr, + int32_t count, + const ecs_type_info_t* type_info +) { + uint8_t* src_list = static_cast(src_ptr); + uint8_t* dst_list = static_cast(dst_ptr); + HooksBindingContext* ctx = static_cast(type_info->hooks.binding_ctx); + + for (int i=0; i != count; i++) { + uint8_t* src = &src_list[i*type_info->size]; + uint8_t* dst = &dst_list[i*type_info->size]; + + ctx->world->copy_component_ptr(static_cast(src), static_cast(dst), type_info->component); + } +} + +HooksBindingContext::HooksBindingContext(GFWorld* world_) { + world = world_; +} +HooksBindingContext::~HooksBindingContext() { +} diff --git a/addons/glecs/cpp/src/component_builder.h b/addons/glecs/cpp/src/component_builder.h new file mode 100644 index 0000000..c6df5d1 --- /dev/null +++ b/addons/glecs/cpp/src/component_builder.h @@ -0,0 +1,71 @@ + +#ifndef COMPONENT_Builder_H +#define COMPONENT_Builder_H + +#include +#include +#include + +namespace godot { + + // Predefine instead of include to avoid cyclic dependencies + class GFWorld; + + class GFComponentBuilder : public RefCounted { + GDCLASS(GFComponentBuilder, RefCounted) + + public: + GFComponentBuilder(); + ~GFComponentBuilder(); + + // ************************************** + // *** Exposed *** + // ************************************** + + Ref add_member(String, Variant::Type); + int get_member_count(); + GFWorld* get_world(); + bool is_built(); + Ref set_entity(Variant); + Ref set_name(String); + void build(); + + // ************************************** + // *** Unexposed *** + // ************************************** + + void set_world(GFWorld*); + + protected: + static void _bind_methods(); + + private: + ecs_component_desc_t component_desc; + ecs_struct_desc_t struct_desc; + /// @brief A list of the allocated names of members + /// Also used to find the number of added of members + Array member_names; + /// The name of this compoennt + String name; + GFWorld* world; + /// Is true if this builder has already been built + bool built; + + static void ctor(void*, int32_t, const ecs_type_info_t*); + static void dtor(void*, int32_t, const ecs_type_info_t*); + static void copy(void*, const void*, int32_t, const ecs_type_info_t*); + static void move(void*, void*, int32_t, const ecs_type_info_t*); + + }; + + class HooksBindingContext { + public: + GFWorld* world; + + HooksBindingContext(GFWorld* world); + ~HooksBindingContext(); + }; + +} + +#endif diff --git a/addons/glecs/cpp/src/doc_classes/GFComponent.xml b/addons/glecs/cpp/src/doc_classes/GFComponent.xml new file mode 100644 index 0000000..40a62a6 --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFComponent.xml @@ -0,0 +1,84 @@ + + + + A reference to a Glecs component from a [GFWorld] that is attatached to an entity. + + + + + https://www.flecs.dev/flecs/md_docs_2EntitiesComponents.html#components-1 + + + + + + + Override this method to build a component associated with a [Script]. + The example below shows how to build a component associated with a custom Velocity [Script]: + [codeblock] + class_name Velocity2 extends GFComponent + + func _build(b:GFComponentBuilder) -> void: + b.add_member("value", TYPE_VECTOR2) + [/codeblock] + + + + + + Returns a [GFEntity] reference to the entity this component's data is from. + [codeblock] + var Vector2C = GFEntity.from("glecs/meta/Vector2") + var entity = GFEntity.spawn() + entity.add_component(Vector2C) + var vec2 = entity.get_component(Vector2C) + + vec2.get_source_entity().get_id() == Vector2C.get_id() # True + [/codeblock] + + + + + + Returns the ID of the entity that this component's data is from. + [codeblock] + var Vector2C = GFEntity.from("glecs/meta/Vector2") + var entity = GFEntity.spawn() + entity.add_component(Vector2C) + var vec2 = entity.get_component(Vector2C) + + vec2.get_source_id() == Vector2C.get_id() # True + [/codeblock] + + + + + + + Returns the value of a component's member + [codeblock] + var entity = GFEntity.spawn() + entity.add_component("glecs/meta/Vector2") + var vec2 = entity.get_component("glecs/meta/Vector2") + + vec2.getm("x") + [/codeblock] + + + + + + + + Sets the value of a component's member + [codeblock] + var entity = GFEntity.spawn() + entity.add_component("glecs/meta/Vector2") + var vec2 = entity.get_component("glecs/meta/Vector2") + + vec2.setm("x", 3.14) + [/codeblock] + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFComponentBuilder.xml b/addons/glecs/cpp/src/doc_classes/GFComponentBuilder.xml new file mode 100644 index 0000000..1a04210 --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFComponentBuilder.xml @@ -0,0 +1,48 @@ + + + + Builder for a new component type. + + + This example shows the creation of a [i]Jump[/i] component: + [codeblock] + var world:= GFWorld.new() + world.component_builder() \ + .set_name("Jump") \ + .add_member("jump_velocity", TYPE_FLOAT) \ + .build() + [/codeblock] + + + https://www.flecs.dev/flecs/md_docs_2EntitiesComponents.html#components-1 + + + + + + + + Adds a member to the new component. + + + + + + Creates the new component from the settings of the builder. + + + + + + Returns [code]true[/code] if this builder has already been built. + + + + + + + Sets the name of the new component. + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFEntity.xml b/addons/glecs/cpp/src/doc_classes/GFEntity.xml new file mode 100644 index 0000000..0942b6f --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFEntity.xml @@ -0,0 +1,166 @@ + + + + A reference to an entity from a [GFWorld]. + + + + + https://www.flecs.dev/flecs/md_docs_2EntitiesComponents.html + + + + + + + Adds component data of type [param component] to this entity. + The component's type ID is coerced from a [Variant]. To learn more about [Variant] coercion see [method GFWorld.coerce_id]. + [codeblock] + var entity:= GFEntity.spawn() + entity.add_component("glecs/meta/Array") + entity.get_component("glecs/meta/Array") != null # true + [/codeblock] + + + + + + + + Returns an entity from an ID coerced from a [Variant]. + If no world is specified, a default world is used. + To learn more about [Variant] coercion see [method GFWorld.coerce_id]. + [codeblock] + var entity:= GFEntity.from("flecs/core/OnAdd") + [/codeblock] + + + + + + + + Returns an entity from an ID. + If no world is specified, a default world is used. + [codeblock] + var entity:= GFEntity.from_id(255) + [/codeblock] + + + + + + + Returns a reference to this entity's component's data of type [param component]. + Returns [code]null[/code] if the entity does not have the [param component] attached or if [param component] is not a component. The component is identified by an ID coerced from a [Variant]. To learn more about [Variant] coercion see [method GFWorld.coerce_id]. + [codeblock] + var entity:= GFEntity.spawn() + entity.add_component("glecs/meta/Array") + entity.get_component("glecs/meta/Array") != null # true + [/codeblock] + + + + + + Returns the ID of this entity. + [codeblock] + var entity:= GFEntity.from_id(42) + entity.get_id() == 42 # true + [/codeblock] + + + + + + Returns the name of this entity. + [codeblock] + var entity:= GFEntity.spawn() + entity.set_name("Entity") + entity.get_name() == "Entity" # true + [/codeblock] + + + + + + Returns the world this entity is from. + [codeblock] + var world:= GFWorld.new() + var entity:= GFEntity.spawn(world) + entity.get_world() == world # true + [/codeblock] + + + + + + Returns [code]true[/code] if this entity is still alive. + [codeblock] + var entity:= GFEntity.spawn() + entity.is_alive() # true + entity.delete() + entity.is_alive() # false + [/codeblock] + + + + + + Returns [code]true[/code] if the ID of this entity is a pair. + [codeblock] + var world:= GFWorld.new() + var entity:= GFEntity.from_id(world.pair_ids(1, 2), world) + entity.is_pair() # true + [/codeblock] + + + + + + + Creates a [GFPair] from this entity and [param second]. + [codeblock] + var Eats:= GFEntity.spawn() + var Grass:= GFEntity.spawn() + var EatsGrass:GFPair = Eats.pair(Grass) + [/codeblock] + + + + + + + Creates a pair ID from this entity and [param second_id]. + [codeblock] + var Eats:= GFEntity.spawn() + var Grass:= GFEntity.spawn() + var EatsGrass_id:int = Eats.pair(Grass.get_id()) + [/codeblock] + + + + + + + Sets the name of this entity. + [codeblock] + var entity:= GFEntity.spawn() + entity.set_name("Entity") + entity.get_name() == "Entity" # true + [/codeblock] + + + + + + + Creates a new entity ID and returns it. + If no world is specified, a default world is used. + [codeblock] + var entity:= GFEntity.spawn() + [/codeblock] + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFObserverBuilder.xml b/addons/glecs/cpp/src/doc_classes/GFObserverBuilder.xml new file mode 100644 index 0000000..8c4fa31 --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFObserverBuilder.xml @@ -0,0 +1,54 @@ + + + + A builder for observers. + + + This example shows the creation of an observer that is run whenever either the [i]Run[/i] or [i]Jump[/i] component is added to an entity and that entity has both the [i]Run[/i] and [i]Jump[/i] components: + [codeblock] + var world:= GFWorld.new() + world.observer_builder("flecs/core/OnAdd") \ + .with("Run") \ + .with("Jump") \ + .for_each(func(run:GFComponent, jump:GFComponent): + pass + ) + [/codeblock] + This example shows the creation of an observer that is run when the [i]Run[/i] component is added to an entity and that has the [i]Jump[/i] component: + [codeblock] + var world:= GFWorld.new() + world.observer_builder("flecs/core/OnAdd") \ + .with("Run") \ + .with("Jump").access_filter() \ + .for_each(func(run:GFComponent, jump:GFComponent): + pass + ) + [/codeblock] + + + https://www.flecs.dev/flecs/md_docs_2ObserversManual + + + + + + + Builds the observer with a callback for iterating over each result. + + + + + + + + Sets an event this observer listens for at the specified index. + + + + + + Sets all events this observer listens for. + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFPair.xml b/addons/glecs/cpp/src/doc_classes/GFPair.xml new file mode 100644 index 0000000..b7f1c30 --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFPair.xml @@ -0,0 +1,102 @@ + + + + A reference to a pair of entity IDs. + + + + + https://www.flecs.dev/flecs/md_docs_2Relationships + + + + + + Returns the first part of this pair as an entity. + [codeblock] + var world:= GFWorld.new() + var IsAPrefab:= world.pair("flecs/core/IsA", "flecs/core/Prefab") + IsAPrefab.first().get_id() == world.coerce_id("flecs/core/IsA") # true + [/codeblock] + + + + + + Returns the first part of this pair as an ID. + [codeblock] + var world:= GFWorld.new() + var IsAPrefab:= world.pair("flecs/core/IsA", "flecs/core/Prefab") + IsAPrefab.first_id() == world.coerce_id("flecs/core/IsA") # true + [/codeblock] + + + + + + + + + Returns a pair from two entity parts. + [codeblock] + var IsAPrefab = GFPair.from("flecs/core/IsA", "flecs/core/Prefab") + [/codeblock] + + + + + + + + Returns a pair from a pair ID. + [codeblock] + var first_id:= GFGlobalWorld.coerce_id("flecs/core/IsA") + var second_id:= GFGlobalWorld.coerce_id("flecs/core/Prefab") + var IsAPrefab = GFPair.from_id(GFGlobalWorld.pair_ids(first_id, second_id)) + [/codeblock] + + + + + + + + + Returns a pair from two ID parts. + [codeblock] + var first_id:= GFGlobalWorld.coerce_id("flecs/core/IsA") + var second_id:= GFGlobalWorld.coerce_id("flecs/core/Prefab") + var IsAPrefab = GFPair.from_ids(first_id, second_id) + [/codeblock] + + + + + + Returns the second part of this pair as an entity. + [codeblock] + var world:= GFWorld.new() + var IsAPrefab:= world.pair("flecs/core/IsA", "flecs/core/Prefab") + IsAPrefab.second().get_id() == world.coerce_id("flecs/core/Prefab") # true + [/codeblock] + + + + + + Returns the second part of this pair as an ID. + [codeblock] + var world:= GFWorld.new() + var IsAPrefab:= world.pair("flecs/core/IsA", "flecs/core/Prefab") + IsAPrefab.second_id() == world.coerce_id("flecs/core/Prefab") # true + [/codeblock] + + + + + + Do not call, pairs can not be instantiated. Use [method GFPair.from] instead. Overrides [method GFEntity.spawn]. + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFQuery.xml b/addons/glecs/cpp/src/doc_classes/GFQuery.xml new file mode 100644 index 0000000..17bc4a2 --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFQuery.xml @@ -0,0 +1,36 @@ + + + + The results of a query. + + + See [GFQueryBuilder] for how to create a query. + + + https://www.flecs.dev/flecs/md_docs_2Queries + + + + + + Returns the world queried from. + + + + + + Returns an iterator over the queried entities. + The example below shows iteration of a query over entities with the Vector2 component: + [codeblock] + var query:GFQuery = GFGlobalWorld.query_builder() \ + .with("glecs/meta/Vector2") \ + .build() + + for terms:Array[GFComponent] in query.iterate(): + var vec2_c:= terms[0] + print("X: %s, Y %s" % [vec2_c.getm("x"), vec2_c.getm("y")]) + [/codeblock] + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFQueryBuilder.xml b/addons/glecs/cpp/src/doc_classes/GFQueryBuilder.xml new file mode 100644 index 0000000..e88dd38 --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFQueryBuilder.xml @@ -0,0 +1,27 @@ + + + + A builder for queries. + + + Queries are the main way entities are accessed from a [GFWorld]. Under the hood, systems and observers both use queries. + The example below shows the creation of a query over entities with the [i]Jump[/i] component: + [codeblock] + var world:= GFWorld.new() + var query:GFQuery = world.query_builder() \ + .with("Jump") \ + .build() + [/codeblock] + + + https://www.flecs.dev/flecs/md_docs_2Queries.html + + + + + + Builds and returns the new query. + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFQueryIterator.xml b/addons/glecs/cpp/src/doc_classes/GFQueryIterator.xml new file mode 100644 index 0000000..faea4c1 --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFQueryIterator.xml @@ -0,0 +1,41 @@ + + + + An iterator over queried entities. + + + The example below shows iteration of a query over entities with the Vector2 component: + [codeblock] + var query:GFQuery = GFGlobalWorld.query_builder() \ + .with("glecs/meta/Vector2") \ + .build() + + for terms:Array[GFComponent] in query.iterate(): + var vec2_c:= terms[0] + print("X: %s, Y %s" % [vec2_c.getm("x"), vec2_c.getm("y")]) + [/codeblock] + + + https://www.flecs.dev/flecs/md_docs_2Queries + + + + + + Returns the world queried from. + + + + + + Returns [code]true[/code] if this iterator has been exhausted. + + + + + + Returns [code]true[/code] if this iterator has started iterating. + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFQuerylikeBuilder.xml b/addons/glecs/cpp/src/doc_classes/GFQuerylikeBuilder.xml new file mode 100644 index 0000000..ca33a7c --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFQuerylikeBuilder.xml @@ -0,0 +1,51 @@ + + + + The abstract base class for different query-like builders. + + + + + https://www.flecs.dev/flecs/md_docs_2Queries + + + + + + Returns [code]true[/code] if this builder has been built. + + + + + + + Adds an optional term to the query. + The term added is identified by a [Variant] coerced to an ID. To learn more about [Variant] coercion see [method GFWorld.coerce_id]. + + + + + + + Adds a term to the query that matches this or one of the previous terms in an [i]or[/i] chain. + The term added is identified by a [Variant] coerced to an ID. To learn more about [Variant] coercion see [method GFWorld.coerce_id]. + + + + + + + Adds a term to the query. + The term added is identified by a [Variant] coerced to an ID. To learn more about [Variant] coercion see [method GFWorld.coerce_id]. + + + + + + + Adds a negative term to the query. + The term added is identified by a [Variant] coerced to an ID. To learn more about [Variant] coercion see [method GFWorld.coerce_id]. + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFRegisterableEntity.xml b/addons/glecs/cpp/src/doc_classes/GFRegisterableEntity.xml new file mode 100644 index 0000000..046fc72 --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFRegisterableEntity.xml @@ -0,0 +1,25 @@ + + + + An abstract base class for types of entities that can be registered to a world via a [Script]. + + + + + + + + + + + Override this method to add customize logic that runs when this [Script] is registered with a world. + [codeblock] + class_name EnemyTag extends GFRegisterableEntity + + func _register(world:GFWorld) -> void: + set_name("EnemyTag") + [/codeblock] + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFSystemBuilder.xml b/addons/glecs/cpp/src/doc_classes/GFSystemBuilder.xml new file mode 100644 index 0000000..c9be490 --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFSystemBuilder.xml @@ -0,0 +1,30 @@ + + + + A builder for systems. + + + This example shows the creation of a system that runs over entities with the [i]Run[/i] and [i]Jump[/i] components: + [codeblock] + var world:= GFWorld.new() + world.observer_builder() \ + .with("Run") \ + .with("Jump") \ + .for_each(func(run:GFComponent, jump:GFComponent): + pass + ) + [/codeblock] + + + https://www.flecs.dev/flecs/md_docs_2Systems + + + + + + + Builds the system with a callback for iterating over each result. + + + + diff --git a/addons/glecs/cpp/src/doc_classes/GFWorld.xml b/addons/glecs/cpp/src/doc_classes/GFWorld.xml new file mode 100644 index 0000000..67a93c9 --- /dev/null +++ b/addons/glecs/cpp/src/doc_classes/GFWorld.xml @@ -0,0 +1,206 @@ + + + + An entity component system world. + + + + + + + + + + + Coerces a [Variant] to an ID. + How Variants are coerced to IDs goes as follows: [br]- [int] : Used as ID directly. [br]- [String] : Treated as a path and looks up the entity at that path. [br]- [StringName] : Treated as a path and looks up the entity at that path. [br]- [NodePath] : Treated as a path and looks up the entity at that path. [br]- [Vector2i] : Used directly as a relationship pair. [br]- [GFEntity] (and subtypes) : Returns the ID of the entity being referenced.[br]- [Script] : If the script extends [GFRegisterableEntity] and is registered with the [GFWorld], then returns the associated ID.[br]- Everything else : Returns [code]0[/code] and pushes an error. + This example shows a coercion from an [int]: + [codeblock] + var world:= GFWorld.new() + world.coerce_id(25) == 25 # true + [/codeblock] + This example shows a coercion from a [String] as a path: + [codeblock] + var world:= GFWorld.new() + world.coerce_id("flecs/core/Component") == 1 # true + [/codeblock] + This example shows a coercion from a [GFEntity]: + [codeblock] + var world:= GFWorld.new() + var entity:= GFEntity.from(255, world) + world.coerce_id(entity) == 255 # true + [/codeblock] + This example shows a coercion from a [Vector2i]: + [codeblock] + var world:= GFWorld.new() + var Eats:= GFEntity.spawn(world) + var Carrots:= GFEntity.spawn(world) + var pair:= Vector2i(Eats.get_id(), Carrots.get_id()) + world.coerce_id(pair) == world.pair(Eats, Carrots) # true + [/codeblock] + This example shows a coercion from a registered [Script]: + [codeblock] + class Health extends GFComponent: + func _build(b:GFComponentBuilder) -> void: + b.add_member("health", TYPE_FLOAT) + + var world:= GFWorld.new() + func _init() -> void: + world.register_script(Health) + world.coerce_id(Health) # The ID of the Health component + [/codeblock] + + + + + + Returns a new component builder. + This example shows the creation of a [i]Jump[/i] component: + [codeblock] + var world:= GFWorld.new() + world.component_builder() \ + .set_name("Jump") \ + .add_member("jump_velocity", TYPE_FLOAT) \ + .build() + [/codeblock] + + + + + + Returns a new observer builder. + Arguments to this method specify the events (coerced from IDs) that this observer listens for. To learn more about [Variant] coercion see [method GFWorld.coerce_id]. + This example shows the creation of an observer that is run whenever either the [i]Run[/i] or [i]Jump[/i] component is added to an entity and that entity has both the [i]Run[/i] and [i]Jump[/i] components: + [codeblock] + var world:= GFWorld.new() + world.observer_builder("flecs/core/OnAdd") \ + .with("Run") \ + .with("Jump") \ + .for_each(func(run:GFComponent, jump:GFComponent): + pass + ) + [/codeblock] + This example shows the creation of an observer that is run when the [i]Run[/i] component is added to an entity and that has the [i]Jump[/i] component: + [codeblock] + var world:= GFWorld.new() + world.observer_builder("flecs/core/OnAdd") \ + .with("Run") \ + .with("Jump").access_filter() \ + .for_each(func(run:GFComponent, jump:GFComponent): + pass + ) + [/codeblock] + + + + + + + + Creates and returns a pair from two [Variant]s coerced to IDs. + To learn more about [Variant] coercion see [method GFWorld.coerce_id]. + [codeblock] + var world:= GFWorld.new() + var Attacks_tag:= GFEntity.spawn() + var Players_tag:= GFEntity.spawn() + world.pair(Attacks_tag, Players_tag) + [/codeblock] + + + + + + + + Creates and returns a pair ID from two IDs. + [codeblock] + var world:= GFWorld.new() + var Attacks_tag:= GFEntity.spawn() + var Player_tag:= GFEntity.spawn() + world.pair_ids(Attacks_tag.get_id(), Player_tag.get_id()) + [/codeblock] + + + + + + + Progresses the world by the supplied amount of time. + [codeblock] + extends Node + + var world:= GFWorld.new() + + func _process(delta:float) -> void: + world.progress(delta) + [/codeblock] + + + + + + Returns a new query builder. + This example shows the creation of a query over entities with the [i]Jump[/i] component: + [codeblock] + var world:= GFWorld.new() + var query:GFQuery = world.query_builder() \ + .with("Jump") \ + .build() + [/codeblock] + + + + + + + Registers a [Script] that derives from [GFRegisterableEntity]. + A [Script], once registered, gets an entity ID created and associated with it. This allows the [Script] to be coerced to the associated ID. To learn more about [Variant] coercion see [method GFWorld.coerce_id]. + This example shows registering the [i]Health[/i] [Script] as a component. + [codeblock] + class Health extends GFComponent: + func _build(b:GFComponentBuilder) -> void: + b.add_member("health", TYPE_FLOAT) + + var world:= GFWorld.new() + func _init() -> void: + world.register_script(Health) + world.coerce_id(Health) # The ID of the Health component + [/codeblock] + + + + + + Starts the REST API server. + The REST API can be used to interface the world from other running processes or other devices. It also exposes the world to the Flecs inspector. This example shows the minimum setup to expose your world to the Flecs inspector: + [codeblock] + extends Node + + var world:= GFWorld.new() + + func _ready() -> void: + world.start_rest_api() + + func _process(delta:float) -> void: + world.progress(delta) + [/codeblock] + + + + + + Returns a new system builder. + This example shows the creation of a system that runs over entities with the [i]Run[/i] and [i]Jump[/i] components: + [codeblock] + var world:= GFWorld.new() + world.observer_builder() \ + .with("Run") \ + .with("Jump") \ + .for_each(func(run:GFComponent, jump:GFComponent): + pass + ) + [/codeblock] + + + + diff --git a/addons/glecs/cpp/src/entity.cpp b/addons/glecs/cpp/src/entity.cpp new file mode 100644 index 0000000..e38007e --- /dev/null +++ b/addons/glecs/cpp/src/entity.cpp @@ -0,0 +1,294 @@ + +#include "entity.h" +#include "godot_cpp/classes/script.hpp" +#include "utils.h" +// needed here because entity.h does not include +// component.h, but uses forward declaration instead +#include "component.h" +#include "pair.h" + +#include +#include +#include +#include +#include "godot_cpp/variant/variant.hpp" + +using namespace godot; + +GFEntity::GFEntity() { +} +GFEntity::~GFEntity() { +} + +Ref GFEntity::spawn(GFWorld* world_) { + GFWorld* world = GFWorld::world_or_singleton(world_); + return from_id(ecs_new(world->raw()), world); +} +Ref GFEntity::from(Variant entity, GFWorld* world) { + return from_id(world->coerce_id(entity), world); +} +Ref GFEntity::from_id(ecs_entity_t id, GFWorld* world) { + return from_id_template(id, world); +} + +Ref GFEntity::add_component(Variant component, Variant data) { + GFWorld* w = get_world(); + + ecs_entity_t c_id = w->coerce_id(component); + + if (ECS_IS_PAIR(c_id)) { + add_pair( + ECS_PAIR_FIRST(c_id), + ECS_PAIR_SECOND(c_id), + data + ); + return Ref(this); + } + + if (!ecs_has_id(w->raw(), c_id, ecs_id(EcsComponent))) { + ERR(Ref(this), + "Failed to add component to entity\n", + "ID coerced from ", component, " is not a component" + ); + } + + ecs_add_id(w->raw(), get_id(), c_id); + + if (data != Variant()) { + set_component(c_id, data); + } + + return Ref(this); +} + +Ref GFEntity::add_entity(Variant entity, Variant data) { + GFWorld* w = get_world(); + + ecs_add_id(w->raw(), get_id(), w->coerce_id(entity)); + if (data != Variant()) { + set_component(entity, data); + } + + return Ref(this); +} + +Ref GFEntity::add_pair(Variant first, Variant second, Variant data) { + GFWorld* w = get_world(); + + ecs_entity_t first_id = w->coerce_id(first); + ecs_entity_t second_id = w->coerce_id(second); + ecs_entity_t pair_id = w->pair_ids(first_id, second_id); + add_entity(pair_id, data); + + return Ref(this); +} + +Ref GFEntity::add_tag(Variant tag) { + GFWorld* w = get_world(); + + ecs_entity_t tag_id = w->coerce_id(tag); + if (ecs_has(w->raw(), tag_id, EcsComponent)) { + ERR(Ref(this), + "Failed to add tag to entity\n", + "ID, ", tag_id, "is a component, not a tag\n" + "(Tags are any non-component entity)" + ); + } + + ecs_add_id(w->raw(), get_id(), tag_id); + + return Ref(this); +} + +Ref GFEntity::get_component(Variant component) { + Ref c = GFComponent::from_id( + get_world()->coerce_id(component), + get_id(), + get_world() + ); + + if (c == nullptr) { + ERR(nullptr, + "Component ID ", c->get_id(), " is not alive." + ); + } + if (!ecs_has_id(get_world()->raw(), id, c->get_id())) { + ERR(nullptr, + "Could not find attached component ID ", c->get_id(), " on entity" + ); + } + + return c; +} + +Ref GFEntity::set_component(Variant component, Variant data) { + GFWorld* world = get_world(); + + ecs_entity_t id = world->coerce_id(component); + + if (!ECS_IS_PAIR(id)) { + if (!ecs_has(world->raw(), id, EcsComponent)) { + ERR(Ref(this), + "Failed to set data in component\n", + "ID, ", id, "is not a component" + ); + } + } else { + ecs_entity_t first_id = ECS_PAIR_FIRST(id); + ecs_entity_t second_id = ECS_PAIR_SECOND(id); + if ( + !ecs_has_id(world->raw(), first_id, ecs_id(EcsComponent)) + && !ecs_has_id(world->raw(), second_id, ecs_id(EcsComponent)) + ) { + // ID is not a component, err + ERR(Ref(this), + "Failed to set data in pair\n", + "Neither ID ", first_id, + " nor ", second_id, "are components" + ); + } + } + + // TODO: change build_data_from_variant to a static method + Ref(memnew(GFComponent(get_id(), id, world))) + ->build_data_from_variant( + data, + ecs_get_mut_id(world->raw(), get_id(), id) + ); + ecs_modified_id(world->raw(), get_id(), id); + + return Ref(this); +} + +void GFEntity::delete_() { + ecs_delete(get_world()->raw(), get_id()); +} + +ecs_entity_t GFEntity::get_id() { return id; } +String GFEntity::get_path() { + return String(ecs_get_path_w_sep( + get_world()->raw(), + 0, + get_id(), + "/", + "/root/" + )); +} +GFWorld* GFEntity::get_world() { return Object::cast_to( + UtilityFunctions::instance_from_id(world_instance_id) +); } + +bool GFEntity::is_alive() { + if (get_world() == nullptr) { + return false; + } + if (!UtilityFunctions::is_instance_id_valid(world_instance_id)) { + return false; + } + if (is_pair()) { + if (!ecs_is_alive(get_world()->raw(), ECS_PAIR_FIRST(get_id()))) { + return false; + } + if (!ecs_is_alive(get_world()->raw(), ECS_PAIR_SECOND(get_id()))) { + return false; + } + } else { + if (!ecs_is_alive(get_world()->raw(), get_id())) { + return false; + } + } + + return true; +} +bool GFEntity::is_pair() { + return ecs_id_is_pair(get_id()); +} + +String GFEntity::get_name() { + return String(ecs_get_name(get_world()->raw(), get_id())); +} +Ref GFEntity::set_name(String name_) { + ecs_entity_t parent = ecs_get_parent( + get_world()->raw(), + get_id() + ); + + if (ecs_lookup_child( + get_world()->raw(), + parent, + name_.utf8() + ) == 0) { + // No name conflicts, set name and return + ecs_set_name(get_world()->raw(), get_id(), name_.utf8()); + return Ref(this); + } + + int trailing_digits = 0; + for (int i=0; i != name_.length(); i++) { + char32_t digit = name_[name_.length()-i-1]; + if (digit >= '0' && digit >= '9') { + trailing_digits++; + } else { + break; + } + } + + String number = name_.substr(name_.length()-1-trailing_digits); + String base_name = name_.substr(0, name_.length()-1-trailing_digits); + String name = name_; + do { + int name_int = number.to_int(); + name_int += 1; + number = String::num_uint64(name_int); + name = base_name + number; + } while (ecs_lookup_child( + get_world()->raw(), + parent, + name.utf8() + )); + + ecs_set_name(get_world()->raw(), get_id(), name.utf8()); + return Ref(this); +} + +Ref GFEntity::pair(Variant second) { + return GFPair::from_id(pair_id(get_world()->coerce_id(second)), get_world()); +} +ecs_entity_t GFEntity::pair_id(ecs_entity_t second) { + return get_world()->pair_ids(get_id(), second); +} + +// ---------------------------------------------- +// --- Unexposed --- +// ---------------------------------------------- + +void GFEntity::set_id(ecs_entity_t value) { id = value; } +void GFEntity::set_world(GFWorld* value) { world_instance_id = value->get_instance_id(); } + +void GFEntity::_bind_methods() { + godot::ClassDB::bind_static_method(GFEntity::get_class_static(), D_METHOD("spawn", "world"), &GFEntity::spawn, nullptr); + godot::ClassDB::bind_static_method(GFEntity::get_class_static(), D_METHOD("from", "entity", "world"), &GFEntity::from, nullptr); + godot::ClassDB::bind_static_method(GFEntity::get_class_static(), D_METHOD("from_id", "id", "world"), &GFEntity::from_id, nullptr); + + godot::ClassDB::bind_method(D_METHOD("add_component", "component", "data"), &GFEntity::add_component, nullptr); + godot::ClassDB::bind_method(D_METHOD("add_entity", "entity", "data"), &GFEntity::add_entity, nullptr); + godot::ClassDB::bind_method(D_METHOD("add_pair", "first", "second", "data"), &GFEntity::add_pair, nullptr); + godot::ClassDB::bind_method(D_METHOD("add_tag", "tag"), &GFEntity::add_tag); + godot::ClassDB::bind_method(D_METHOD("get_component", "component"), &GFEntity::get_component); + godot::ClassDB::bind_method(D_METHOD("set_component", "component", "value"), &GFEntity::set_component); + + godot::ClassDB::bind_method(D_METHOD("delete"), &GFEntity::delete_); + + godot::ClassDB::bind_method(D_METHOD("get_id"), &GFEntity::get_id); + godot::ClassDB::bind_method(D_METHOD("get_path"), &GFEntity::get_path); + godot::ClassDB::bind_method(D_METHOD("get_world"), &GFEntity::get_world); + godot::ClassDB::bind_method(D_METHOD("get_name"), &GFEntity::get_name); + + godot::ClassDB::bind_method(D_METHOD("is_alive"), &GFEntity::is_alive); + godot::ClassDB::bind_method(D_METHOD("is_pair"), &GFEntity::is_pair); + + godot::ClassDB::bind_method(D_METHOD("pair", "second"), &GFEntity::pair); + godot::ClassDB::bind_method(D_METHOD("pair_id", "second_id"), &GFEntity::pair_id); + + godot::ClassDB::bind_method(D_METHOD("set_name", "name"), &GFEntity::set_name); +} diff --git a/addons/glecs/cpp/src/entity.h b/addons/glecs/cpp/src/entity.h new file mode 100644 index 0000000..f80b35b --- /dev/null +++ b/addons/glecs/cpp/src/entity.h @@ -0,0 +1,111 @@ + +#ifndef GL_ENTITY_H +#define GL_ENTITY_H + +#include "godot_cpp/core/class_db.hpp" +#include "godot_cpp/variant/variant.hpp" +#include "utils.h" +#include "world.h" + +#include +#include +#include +#include + +namespace godot { + + // Predefine instead of include to avoid cyclic dependencies + class GFComponent; + class GFPair; + + class GFEntity : public RefCounted { + GDCLASS(GFEntity, RefCounted) + + public: + GFEntity(); + GFEntity(ecs_entity_t id_, GFWorld* world_): id(id_), world_instance_id(world_->get_instance_id()) {} + GFEntity(GFEntity& ett): GFEntity(ett.get_id(), ett.get_world()) {} + ~GFEntity(); + + // -------------------------------------- + // --- Exposed --- + // -------------------------------------- + + static Ref spawn(GFWorld*); + static Ref from(Variant, GFWorld*); + static Ref from_id(ecs_entity_t, GFWorld*); + + Ref add_component(Variant, Variant data); + Ref add_entity(Variant, Variant data); + Ref add_pair(Variant, Variant, Variant data); + Ref add_tag(Variant); + + Ref get_component(Variant); + Ref set_component(Variant, Variant data); + + void delete_(); + + ecs_entity_t get_id(); + String get_path(); + GFWorld* get_world(); + + bool is_alive(); + bool is_pair(); + + Ref set_name(String); + String get_name(); + + Ref pair(Variant second); + ecs_entity_t pair_id(ecs_entity_t second_id); + + // -------------------------------------- + // --- Unexposed --- + // -------------------------------------- + + template + static Ref from_id_template(ecs_entity_t id, GFWorld* world_) { + GFWorld* world = GFWorld::world_or_singleton(world_); + + Ref e; + e = Ref(memnew(T)); + e->set_id(id); + e->set_world(world); + + Ref